From b9b9ae07bac1199e8032cb087b479a1943f6553f Mon Sep 17 00:00:00 2001 From: Cristian Maruan Bosin Date: Mon, 31 Aug 2020 18:56:59 -0300 Subject: [PATCH 1/4] If key isnt found, falls back to the main section. --- INIReader.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/INIReader.h b/INIReader.h index 11f3191..9f924c4 100644 --- a/INIReader.h +++ b/INIReader.h @@ -395,7 +395,15 @@ inline const std::set& INIReader::Sections() const inline std::string INIReader::Get(std::string section, std::string name, std::string default_value) const { std::string key = MakeKey(section, name); - return _values.count(key) ? _values.at(key) : default_value; + std::string main_key = MakeKey("main", name); + std::string value = default_value; + + if (_values.count(key) > 0) + value = _values.at(key); + else if (_values.count(main_key) > 0) + value = _values.at(main_key); + + return value; } inline long INIReader::GetInteger(std::string section, std::string name, long default_value) const @@ -459,4 +467,4 @@ inline int INIReader::ValueHandler(void* user, const char* section, const char* return 1; } -#endif // __INIREADER__ +#endif // __INIREADER__ \ No newline at end of file From c8d70b18e5137574154a4a8c7f16b122e1ba6843 Mon Sep 17 00:00:00 2001 From: Cristian Maruan Bosin Date: Mon, 31 Aug 2020 19:07:02 -0300 Subject: [PATCH 2/4] Updated code and readme to reflect new feature --- INIReaderTest.cpp | 19 +++++++++++-------- README.md | 16 ++++++++++------ test.ini | 6 ++++++ 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/INIReaderTest.cpp b/INIReaderTest.cpp index 8472122..a599b7e 100644 --- a/INIReaderTest.cpp +++ b/INIReaderTest.cpp @@ -21,13 +21,16 @@ int main() std::cout << "Can't load 'test.ini'\n"; return 1; } - std::cout << "Config loaded from 'test.ini': found sections=" << sections(reader) - << " version=" - << reader.GetInteger("protocol", "version", -1) << ", name=" - << reader.Get("user", "name", "UNKNOWN") << ", email=" - << reader.Get("user", "email", "UNKNOWN") << ", multi=" - << reader.Get("user", "multi", "UNKNOWN") << ", pi=" - << reader.GetReal("user", "pi", -1) << ", active=" - << reader.GetBoolean("user", "active", true) << "\n"; + std::cout << "Config loaded from 'test.ini': found sections=" << sections(reader) << "\n" + << "version=" << reader.GetInteger("protocol", "version", -1) << "\n" + << "name=" << reader.Get("user", "name", "UNKNOWN") << "\n" + << "email=" << reader.Get("user", "email", "UNKNOWN") << "\n" + << "multi=" << reader.Get("user", "multi", "UNKNOWN") << "\n" + << "pi=" << reader.GetReal("user", "pi", -1) << "\n" + << "active=" << reader.GetBoolean("user", "active", true) << "\n" + << "main:global_value=" << reader.Get("main", "global_value", "UNKNOWN") << "\n" + << "user:global_value=" << reader.Get("user", "global_value", "UNKNOWN") << "\n" + << "user:overwriten_value=" << reader.Get("user", "overwriten_value", "UNKNOWN") << "\n" + << "\n"; return 0; } diff --git a/README.md b/README.md index 6b8f007..9ca0b26 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,16 @@ int main() { std::cout << "Can't load 'test.ini'\n"; return 1; } - std::cout << "Config loaded from 'test.ini': version=" - << reader.GetInteger("protocol", "version", -1) << ", name=" - << reader.Get("user", "name", "UNKNOWN") << ", email=" - << reader.Get("user", "email", "UNKNOWN") << ", pi=" - << reader.GetReal("user", "pi", -1) << ", active=" - << reader.GetBoolean("user", "active", true) << "\n"; + std::cout << "Config loaded from 'test.ini':\n" + << "version=" << reader.GetInteger("protocol", "version", -1) << "\n" + << "name=" << reader.Get("user", "name", "UNKNOWN") << "\n" + << "email=" << reader.Get("user", "email", "UNKNOWN") << "\n" + << "multi=" << reader.Get("user", "multi", "UNKNOWN") << "\n" + << "pi=" << reader.GetReal("user", "pi", -1) << "\n" + << "active=" << reader.GetBoolean("user", "active", true) << "\n" + << "main:global_value=" << reader.Get("main", "global_value", "UNKNOWN") << "\n" + << "user:global_value=" << reader.Get("user", "global_value", "UNKNOWN") << "\n" + << "user:overwriten_value=" << reader.Get("user", "overwriten_value", "UNKNOWN") << "\n"; return 0; } diff --git a/test.ini b/test.ini index 000f58a..bb6ce2a 100644 --- a/test.ini +++ b/test.ini @@ -1,5 +1,9 @@ ; Test config file for ini_example.c and INIReaderTest.cpp +[main] +global_value = I am global +overwriten_value = I should not appear + [protocol] ; Protocol configuration version=6 ; IPv6 @@ -10,3 +14,5 @@ active = true ; Test a boolean pi = 3.14159 ; Test a floating point number multi = this is a ; test multi-line value ; test + +overwriten_value = New value here! \ No newline at end of file From f11fa50d671f2f885035d60c8beecf4d91c9070e Mon Sep 17 00:00:00 2001 From: Cristian Maruan Bosin Date: Mon, 31 Aug 2020 19:08:02 -0300 Subject: [PATCH 3/4] Fix Readme --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ca0b26..d4e2d6a 100644 --- a/README.md +++ b/README.md @@ -41,5 +41,15 @@ To compile and run: ```sh g++ INIReaderTest.cpp -o INIReaderTest.out ./INIReaderTest.out -# Config loaded from 'test.ini': version=6, name=Bob Smith, email=bob@smith.com, pi=3.14159, active=1 +# Config loaded from 'test.ini': +# version=6 +# name=Bob Smith +# email=bob@smith.com +# multi=this is a +# multi-line value +# pi=3.14159 +# active=1 +# main:global_value=I am global +# user:global_value=I am global +# user:overwriten_value=New value here! ``` From 209a115a1b4d1017c075340371308c6746ff4abe Mon Sep 17 00:00:00 2001 From: Cristian Maruan Bosin Date: Mon, 31 Aug 2020 20:12:30 -0300 Subject: [PATCH 4/4] Feature: get and get_array. get is not a template method. get_array returns a vector of comma separated values for a given key. --- INIReader.h | 133 ++++++++++++++++++++++++++++++---------------- INIReaderTest.cpp | 38 ++++++++----- 2 files changed, 114 insertions(+), 57 deletions(-) diff --git a/INIReader.h b/INIReader.h index 9f924c4..2b67f0a 100644 --- a/INIReader.h +++ b/INIReader.h @@ -306,6 +306,7 @@ inline int ini_parse(const char* filename, ini_handler handler, void* user) #include #include #include +#include // Read an INI file into easy-to-access name/value pairs. (Note that I've gone // for simplicity here rather than speed, but it should be pretty decent.) @@ -325,40 +326,23 @@ class INIReader // Return the result of ini_parse(), i.e., 0 on success, line number of // first error on parse error, or -1 on file open error. - int ParseError() const; + int error() const; // Return the list of sections found in ini file - const std::set& Sections() const; - - // Get a string value from INI file, returning default_value if not found. - std::string Get(std::string section, std::string name, - std::string default_value) const; - - // Get an integer (long) value from INI file, returning default_value if - // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). - long GetInteger(std::string section, std::string name, long default_value) const; - - // Get a real (floating point double) value from INI file, returning - // default_value if not found or not a valid floating point value - // according to strtod(). - double GetReal(std::string section, std::string name, double default_value) const; - - // Get a single precision floating point number value from INI file, returning - // default_value if not found or not a valid floating point value - // according to strtof(). - float GetFloat(std::string section, std::string name, float default_value) const; - - // Get a boolean value from INI file, returning default_value if not found or if - // not a valid true/false value. Valid true values are "true", "yes", "on", "1", - // and valid false values are "false", "no", "off", "0" (not case sensitive). - bool GetBoolean(std::string section, std::string name, bool default_value) const; + const std::set& sections() const; + + template + T get(std::string section, std::string name, T default_value) const; + + template + std::vector get_array(std::string section, std::string name) const; protected: int _error; std::map _values; std::set _sections; - static std::string MakeKey(std::string section, std::string name); - static int ValueHandler(void* user, const char* section, const char* name, + static std::string make_key(std::string section, std::string name); + static int value_handler(void* user, const char* section, const char* name, const char* value); }; @@ -371,31 +355,33 @@ class INIReader #include #include #include +#include inline INIReader::INIReader(std::string filename) { - _error = ini_parse(filename.c_str(), ValueHandler, this); + _error = ini_parse(filename.c_str(), value_handler, this); } inline INIReader::INIReader(FILE *file) { - _error = ini_parse_file(file, ValueHandler, this); + _error = ini_parse_file(file, value_handler, this); } -inline int INIReader::ParseError() const +inline int INIReader::error() const { return _error; } -inline const std::set& INIReader::Sections() const +inline const std::set& INIReader::sections() const { return _sections; } -inline std::string INIReader::Get(std::string section, std::string name, std::string default_value) const +template <> +inline std::string INIReader::get(std::string section, std::string name, std::string default_value) const { - std::string key = MakeKey(section, name); - std::string main_key = MakeKey("main", name); + std::string key = make_key(section, name); + std::string main_key = make_key("main", name); std::string value = default_value; if (_values.count(key) > 0) @@ -406,9 +392,57 @@ inline std::string INIReader::Get(std::string section, std::string name, std::st return value; } -inline long INIReader::GetInteger(std::string section, std::string name, long default_value) const + +// trim from start (in place) +static inline void ltrim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { + return !std::isspace(ch); + })); +} + +// trim from end (in place) +static inline void rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { + return !std::isspace(ch); + }).base(), s.end()); +} + +// trim from both ends (in place) +static inline void trim(std::string &s) { + ltrim(s); + rtrim(s); +} + +template +std::vector INIReader::get_array(std::string section, std::string name) const { - std::string valstr = Get(section, name, ""); + std::string valstr = get(section, name, ""); + std::stringstream ss(valstr); + std::vector result; + T value; + + while(ss.good()) { + std::string substr; + std::stringstream _ss; + getline(ss, substr, ','); + substr.erase(substr.begin(), std::find_if(substr.begin(), substr.end(), [](int ch) { + return !std::isspace(ch); + })); + substr.erase(std::find_if(substr.rbegin(), substr.rend(), [](int ch) { + return !std::isspace(ch); + }).base(), substr.end()); + _ss << substr; + _ss >> value; + result.push_back(value); + } + + return result; +} + +template <> +inline long INIReader::get(std::string section, std::string name, long default_value) const +{ + std::string valstr = get(section, name, ""); const char* value = valstr.c_str(); char* end; // This parses "1234" (decimal) and also "0x4D2" (hex) @@ -416,27 +450,36 @@ inline long INIReader::GetInteger(std::string section, std::string name, long de return end > value ? n : default_value; } -inline double INIReader::GetReal(std::string section, std::string name, double default_value) const +template <> +inline int INIReader::get(std::string section, std::string name, int default_value) const +{ + return static_cast(get(section, name, default_value)); +} + +template <> +inline double INIReader::get(std::string section, std::string name, double default_value) const { - std::string valstr = Get(section, name, ""); + std::string valstr = get(section, name, ""); const char* value = valstr.c_str(); char* end; double n = strtod(value, &end); return end > value ? n : default_value; } -inline float INIReader::GetFloat(std::string section, std::string name, float default_value) const +template <> +inline float INIReader::get(std::string section, std::string name, float default_value) const { - std::string valstr = Get(section, name, ""); + std::string valstr = get(section, name, ""); const char* value = valstr.c_str(); char* end; float n = strtof(value, &end); return end > value ? n : default_value; } -inline bool INIReader::GetBoolean(std::string section, std::string name, bool default_value) const +template <> +inline bool INIReader::get(std::string section, std::string name, bool default_value) const { - std::string valstr = Get(section, name, ""); + std::string valstr = get(section, name, ""); // Convert to lower case to make string comparisons case-insensitive std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower); if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1") @@ -447,7 +490,7 @@ inline bool INIReader::GetBoolean(std::string section, std::string name, bool de return default_value; } -inline std::string INIReader::MakeKey(std::string section, std::string name) +inline std::string INIReader::make_key(std::string section, std::string name) { std::string key = section + "=" + name; // Convert to lower case to make section/name lookups case-insensitive @@ -455,11 +498,11 @@ inline std::string INIReader::MakeKey(std::string section, std::string name) return key; } -inline int INIReader::ValueHandler(void* user, const char* section, const char* name, +inline int INIReader::value_handler(void* user, const char* section, const char* name, const char* value) { INIReader* reader = (INIReader*)user; - std::string key = MakeKey(section, name); + std::string key = make_key(section, name); if (reader->_values[key].size() > 0) reader->_values[key] += "\n"; reader->_values[key] += value; diff --git a/INIReaderTest.cpp b/INIReaderTest.cpp index a599b7e..899550b 100644 --- a/INIReaderTest.cpp +++ b/INIReaderTest.cpp @@ -4,33 +4,47 @@ #include #include "INIReader.h" -std::string sections(INIReader &reader) +std::string sections(const INIReader &reader) { std::stringstream ss; - std::set sections = reader.Sections(); + std::set sections = reader.sections(); for (std::set::iterator it = sections.begin(); it != sections.end(); ++it) ss << *it << ","; return ss.str(); } +template +std::string vector_to_str(const std::vector vec) { + std::stringstream ss; + ss << "["; + for (auto it = vec.begin(); it != vec.end(); it++) { + if (it != vec.begin()) + ss << ", "; + ss << *it; + } + ss << "]"; + return ss.str(); +} + int main() { INIReader reader("test.ini"); - if (reader.ParseError() < 0) { + if (reader.error() < 0) { std::cout << "Can't load 'test.ini'\n"; return 1; } std::cout << "Config loaded from 'test.ini': found sections=" << sections(reader) << "\n" - << "version=" << reader.GetInteger("protocol", "version", -1) << "\n" - << "name=" << reader.Get("user", "name", "UNKNOWN") << "\n" - << "email=" << reader.Get("user", "email", "UNKNOWN") << "\n" - << "multi=" << reader.Get("user", "multi", "UNKNOWN") << "\n" - << "pi=" << reader.GetReal("user", "pi", -1) << "\n" - << "active=" << reader.GetBoolean("user", "active", true) << "\n" - << "main:global_value=" << reader.Get("main", "global_value", "UNKNOWN") << "\n" - << "user:global_value=" << reader.Get("user", "global_value", "UNKNOWN") << "\n" - << "user:overwriten_value=" << reader.Get("user", "overwriten_value", "UNKNOWN") << "\n" + << "version=" << reader.get("protocol", "version", -1) << "\n" + << "name=" << reader.get("user", "name", "UNKNOWN") << "\n" + << "email=" << reader.get("user", "email", "UNKNOWN") << "\n" + << "multi=" << reader.get("user", "multi", "UNKNOWN") << "\n" + << "pi=" << reader.get("user", "pi", -1) << "\n" + << "active=" << reader.get("user", "active", true) << "\n" + << "array=" << vector_to_str(reader.get_array("user", "array")) << "\n" + << "main:global_value=" << reader.get("main", "global_value", "UNKNOWN") << "\n" + << "user:global_value=" << reader.get("user", "global_value", "UNKNOWN") << "\n" + << "user:overwriten_value=" << reader.get("user", "overwriten_value", "UNKNOWN") << "\n" << "\n"; return 0; }