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
1210namespace 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}
0 commit comments