Skip to content

Commit 056c505

Browse files
committed
Standardize error emission/handling
1 parent a70cca0 commit 056c505

6 files changed

Lines changed: 375 additions & 244 deletions

File tree

lib/Config.cpp

Lines changed: 82 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,95 @@
1-
//
2-
// Created by W K on 4/24/25.
3-
//
4-
51
#include "Config.h"
2+
#include "EditorError.h"
63
#include <fstream>
74
#include <iostream>
8-
#include <sstream>
95
#include <algorithm>
106
#include <filesystem>
7+
#include <pwd.h>
8+
#include <unistd.h>
119

1210
namespace QEditor {
13-
Config::Config() {
14-
parse();
15-
}
11+
Config::Config() = default;
1612

1713
Config::~Config() = default;
1814

1915
std::string Config::getConfigFilePath() {
20-
const char* homeDir = std::getenv("HOME");
21-
if (!homeDir) {
22-
return "";
16+
const char* home = getenv("HOME");
17+
if (!home) {
18+
// Fallback to getpwuid if HOME is not set
19+
if (const passwd* pw = getpwuid(getuid())) {
20+
home = pw->pw_dir;
21+
} else {
22+
throw ConfigError("Could not determine home directory");
23+
}
2324
}
24-
25-
return std::string(homeDir) + "/" + CONFIG_FILENAME;
25+
return std::string(home) + "/" + CONFIG_FILENAME;
2626
}
2727

2828
void Config::parse() {
29-
std::string configPath = getConfigFilePath();
30-
if (configPath.empty()) {
31-
return;
32-
}
29+
const std::string configPath = getConfigFilePath();
3330

34-
std::ifstream configFile(configPath);
35-
if (!configFile.is_open()) {
36-
// Config file doesn't exist or can't be opened
37-
return;
31+
// Check if config file exists
32+
if (!std::filesystem::exists(configPath)) {
33+
return; // No config file is not an error
3834
}
39-
35+
36+
// Check file permissions
37+
if (access(configPath.c_str(), R_OK) != 0) {
38+
throw FilePermissionError(configPath);
39+
}
40+
41+
std::ifstream file(configPath);
42+
if (!file) {
43+
throw FileOpenError(configPath);
44+
}
45+
4046
std::string line;
41-
while (std::getline(configFile, line)) {
47+
int lineNum = 0;
48+
while (std::getline(file, line)) {
49+
++lineNum;
50+
4251
// Skip empty lines and comments
43-
line = trim(line);
4452
if (line.empty() || line[0] == '#') {
4553
continue;
4654
}
47-
55+
4856
// Parse key=value pair
49-
if (size_t pos = line.find('='); pos != std::string::npos) {
50-
std::string key = trim(line.substr(0, pos));
51-
std::string value = trim(line.substr(pos + 1));
52-
53-
if (!key.empty()) {
54-
// Try to determine the type of the value
55-
if (value == "true" || value == "yes" || value == "1") {
56-
set(key, true);
57-
} else if (value == "false" || value == "no" || value == "0") {
58-
set(key, false);
59-
} else {
60-
// Try to parse as an int
61-
try {
62-
int intValue = std::stoi(value);
63-
set(key, intValue);
64-
} catch (...) {
65-
// Not an int, just store as string
66-
set(key, value);
67-
}
68-
}
57+
size_t pos = line.find('=');
58+
if (pos == std::string::npos) {
59+
throw ConfigParseError("Invalid format at line " + std::to_string(lineNum) +
60+
": missing '=' in '" + line + "'");
61+
}
62+
63+
std::string key = trim(line.substr(0, pos));
64+
std::string value = trim(line.substr(pos + 1));
65+
66+
if (key.empty()) {
67+
throw ConfigParseError("Empty key at line " + std::to_string(lineNum));
68+
}
69+
70+
// Convert and store the value
71+
try {
72+
if (value == "true" || value == "yes" || value == "1") {
73+
set(key, true);
74+
} else if (value == "false" || value == "no" || value == "0") {
75+
set(key, false);
76+
} else if (std::all_of(value.begin(), value.end(), ::isdigit)) {
77+
set(key, std::stoi(value));
78+
} else {
79+
set(key, value);
6980
}
81+
} catch (const std::invalid_argument&) {
82+
throw ConfigValueError(key, "valid value");
83+
} catch (const std::out_of_range&) {
84+
throw ConfigValueError(key, "value within valid range");
7085
}
7186
}
7287
}
7388

7489
void Config::set(const std::string& key, const ConfigValue& value) {
90+
if (key.empty()) {
91+
throw ConfigError("Cannot set empty key");
92+
}
7593
values[key] = value;
7694
}
7795

@@ -80,91 +98,41 @@ namespace QEditor {
8098
}
8199

82100
std::optional<std::string> Config::getString(const std::string& key) const {
83-
const auto it = values.find(key);
84-
if (it == values.end()) {
85-
return std::nullopt;
86-
}
87-
88-
const ConfigValue& value = it->second;
89-
if (std::holds_alternative<std::string>(value)) {
90-
return std::get<std::string>(value);
91-
}
92-
93-
if (std::holds_alternative<int>(value)) {
94-
return std::to_string(std::get<int>(value));
95-
}
96-
97-
if (std::holds_alternative<bool>(value)) {
98-
return std::get<bool>(value) ? "true" : "false";
101+
if (auto it = values.find(key); it != values.end()) {
102+
if (auto* str = std::get_if<std::string>(&it->second)) {
103+
return *str;
104+
}
105+
throw ConfigValueError(key, "string");
99106
}
100-
101107
return std::nullopt;
102108
}
103109

104110
std::optional<int> Config::getInt(const std::string& key) const {
105-
const auto it = values.find(key);
106-
if (it == values.end()) {
107-
return std::nullopt;
108-
}
109-
110-
const ConfigValue& value = it->second;
111-
if (std::holds_alternative<int>(value)) {
112-
return std::get<int>(value);
113-
}
114-
115-
if (std::holds_alternative<std::string>(value)) {
116-
try {
117-
return std::stoi(std::get<std::string>(value));
118-
} catch (...) {
119-
return std::nullopt;
111+
if (auto it = values.find(key); it != values.end()) {
112+
if (auto* num = std::get_if<int>(&it->second)) {
113+
return *num;
120114
}
115+
throw ConfigValueError(key, "integer");
121116
}
122-
123-
if (std::holds_alternative<bool>(value)) {
124-
return std::get<bool>(value) ? 1 : 0;
125-
}
126-
127117
return std::nullopt;
128118
}
129119

130120
std::optional<bool> Config::getBool(const std::string& key) const {
131-
const auto it = values.find(key);
132-
if (it == values.end()) {
133-
return std::nullopt;
134-
}
135-
136-
const ConfigValue& value = it->second;
137-
if (std::holds_alternative<bool>(value)) {
138-
return std::get<bool>(value);
139-
}
140-
141-
if (std::holds_alternative<int>(value)) {
142-
return std::get<int>(value) != 0;
143-
}
144-
145-
if (std::holds_alternative<std::string>(value)) {
146-
const auto& strValue = std::get<std::string>(value);
147-
if (strValue == "true" || strValue == "yes" || strValue == "1") {
148-
return true;
149-
}
150-
151-
if (strValue == "false" || strValue == "no" || strValue == "0") {
152-
return false;
121+
if (auto it = values.find(key); it != values.end()) {
122+
if (auto* b = std::get_if<bool>(&it->second)) {
123+
return *b;
153124
}
125+
throw ConfigValueError(key, "boolean");
154126
}
155-
156127
return std::nullopt;
157128
}
158129

159130
std::string Config::trim(const std::string& str) {
160-
const auto start = std::find_if_not(str.begin(), str.end(), [](const unsigned char c) {
161-
return std::isspace(c);
162-
});
163-
164-
const auto end = std::find_if_not(str.rbegin(), str.rend(), [](const unsigned char c) {
165-
return std::isspace(c);
166-
}).base();
167-
168-
return (start < end) ? std::string(start, end) : std::string();
131+
const auto first = str.find_first_not_of(" \t");
132+
if (first == std::string::npos) {
133+
return "";
134+
}
135+
const auto last = str.find_last_not_of(" \t");
136+
return str.substr(first, (last - first + 1));
169137
}
170138
}

lib/Config.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
//
2-
// Created by W K on 4/24/25.
3-
//
4-
51
#ifndef CONFIG_H
62
#define CONFIG_H
73

lib/EditorError.h

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#pragma once
2+
3+
#include <stdexcept>
4+
#include <string>
5+
6+
namespace QEditor {
7+
8+
// Base exception class for all editor errors
9+
class EditorError : public std::runtime_error {
10+
public:
11+
explicit EditorError(const std::string& message) : std::runtime_error(message) {}
12+
};
13+
14+
// File operation errors
15+
class FileError : public EditorError {
16+
public:
17+
explicit FileError(const std::string& message) : EditorError("File error: " + message) {}
18+
};
19+
20+
class FileOpenError : public FileError {
21+
public:
22+
explicit FileOpenError(const std::string& filename)
23+
: FileError("Could not open file: " + filename) {}
24+
};
25+
26+
class FileSaveError : public FileError {
27+
public:
28+
explicit FileSaveError(const std::string& filename)
29+
: FileError("Could not save file: " + filename) {}
30+
};
31+
32+
class FilePermissionError : public FileError {
33+
public:
34+
explicit FilePermissionError(const std::string& filename)
35+
: FileError("Permission denied: " + filename) {}
36+
};
37+
38+
// Configuration errors
39+
class ConfigError : public EditorError {
40+
public:
41+
explicit ConfigError(const std::string& message) : EditorError("Configuration error: " + message) {}
42+
};
43+
44+
class ConfigParseError : public ConfigError {
45+
public:
46+
explicit ConfigParseError(const std::string& message)
47+
: ConfigError("Failed to parse configuration: " + message) {}
48+
};
49+
50+
class ConfigValueError : public ConfigError {
51+
public:
52+
explicit ConfigValueError(const std::string& key, const std::string& expected_type)
53+
: ConfigError("Invalid value for '" + key + "', expected " + expected_type) {}
54+
};
55+
56+
// Terminal/UI errors
57+
class TerminalError : public EditorError {
58+
public:
59+
explicit TerminalError(const std::string& message) : EditorError("Terminal error: " + message) {}
60+
};
61+
62+
class TerminalSizeError : public TerminalError {
63+
public:
64+
TerminalSizeError(int rows, int cols)
65+
: TerminalError("Invalid terminal size: " + std::to_string(rows) + "x" + std::to_string(cols)) {}
66+
};
67+
68+
// Buffer operation errors
69+
class BufferError : public EditorError {
70+
public:
71+
explicit BufferError(const std::string& message) : EditorError("Buffer error: " + message) {}
72+
};
73+
74+
class BufferBoundsError : public BufferError {
75+
public:
76+
BufferBoundsError(size_t x, size_t y, size_t max_x, size_t max_y)
77+
: BufferError("Cursor position out of bounds: (" +
78+
std::to_string(x) + "," + std::to_string(y) +
79+
") exceeds (" + std::to_string(max_x) + "," +
80+
std::to_string(max_y) + ")") {}
81+
};
82+
83+
// Command errors
84+
class CommandError : public EditorError {
85+
public:
86+
explicit CommandError(const std::string& message) : EditorError("Command error: " + message) {}
87+
};
88+
89+
class InvalidCommandError : public CommandError {
90+
public:
91+
explicit InvalidCommandError(const std::string& command)
92+
: CommandError("Invalid command: " + command) {}
93+
};
94+
95+
} // namespace QEditor

0 commit comments

Comments
 (0)