Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Falls back to section "main" if a give key is not found in a given section #22

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 97 additions & 46 deletions INIReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ inline int ini_parse(const char* filename, ini_handler handler, void* user)
#include <map>
#include <set>
#include <string>
#include <vector>

// 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.)
Expand All @@ -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<std::string>& 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<std::string>& sections() const;

template <typename T>
T get(std::string section, std::string name, T default_value) const;

template <typename T>
std::vector<T> get_array(std::string section, std::string name) const;

protected:
int _error;
std::map<std::string, std::string> _values;
std::set<std::string> _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);
};

Expand All @@ -371,64 +355,131 @@ class INIReader
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <sstream>

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<std::string>& INIReader::Sections() const
inline const std::set<std::string>& 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 = make_key(section, name);
std::string main_key = make_key("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;
}


// 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 <typename T>
std::vector<T> INIReader::get_array(std::string section, std::string name) const
{
std::string key = MakeKey(section, name);
return _values.count(key) ? _values.at(key) : default_value;
std::string valstr = get<std::string>(section, name, "");
std::stringstream ss(valstr);
std::vector<T> 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;
}

inline long INIReader::GetInteger(std::string section, std::string name, long default_value) const
template <>
inline long INIReader::get(std::string section, std::string name, long default_value) const
{
std::string valstr = Get(section, name, "");
std::string valstr = get<std::string>(section, name, "");
const char* value = valstr.c_str();
char* end;
// This parses "1234" (decimal) and also "0x4D2" (hex)
long n = strtol(value, &end, 0);
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<int>(get<long>(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<std::string>(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<std::string>(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<std::string>(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")
Expand All @@ -439,24 +490,24 @@ 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
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
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;
reader->_sections.insert(section);
return 1;
}

#endif // __INIREADER__
#endif // __INIREADER__
39 changes: 28 additions & 11 deletions INIReaderTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,47 @@
#include <sstream>
#include "INIReader.h"

std::string sections(INIReader &reader)
std::string sections(const INIReader &reader)
{
std::stringstream ss;
std::set<std::string> sections = reader.Sections();
std::set<std::string> sections = reader.sections();
for (std::set<std::string>::iterator it = sections.begin(); it != sections.end(); ++it)
ss << *it << ",";
return ss.str();
}

template <typename T>
std::string vector_to_str(const std::vector<T> 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)
<< " 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.get<int>("protocol", "version", -1) << "\n"
<< "name=" << reader.get<std::string>("user", "name", "UNKNOWN") << "\n"
<< "email=" << reader.get<std::string>("user", "email", "UNKNOWN") << "\n"
<< "multi=" << reader.get<std::string>("user", "multi", "UNKNOWN") << "\n"
<< "pi=" << reader.get<double>("user", "pi", -1) << "\n"
<< "active=" << reader.get<bool>("user", "active", true) << "\n"
<< "array=" << vector_to_str(reader.get_array<int>("user", "array")) << "\n"
<< "main:global_value=" << reader.get<std::string>("main", "global_value", "UNKNOWN") << "\n"
<< "user:global_value=" << reader.get<std::string>("user", "global_value", "UNKNOWN") << "\n"
<< "user:overwriten_value=" << reader.get<std::string>("user", "overwriten_value", "UNKNOWN") << "\n"
<< "\n";
return 0;
}
28 changes: 21 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;

}
Expand All @@ -37,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!
```
6 changes: 6 additions & 0 deletions test.ini
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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!