diff --git a/src/common/utils/ini.cpp b/src/common/utils/ini.cpp index eb04606..adfd0d3 100644 --- a/src/common/utils/ini.cpp +++ b/src/common/utils/ini.cpp @@ -1,2 +1,150 @@ #include "ini.hpp" +using namespace sosc::ini; +File* File::Open + (const std::string& filename, const std::vector& rules) +{ + auto ini = new File; + ini->filename = filename; + std::ifstream file; + + file.open(filename); + if(!file.is_open()) + throw LoadError(ini, -1, + "Could not open file." + ); + + int line_number = 0; + std::string line, tmp; + File::SectionList::Section* active_section = nullptr; + while(std::getline(file, line)) { + ++line_number; + str::trim(&line); + if(line == "") + continue; + if(line[0] == ';') + continue; + + if(line[0] == '[') { + str::tolower(&line); + if(line[line.length() - 1] != ']' || line.length() == 2) + throw LoadError(ini, line_number, + "Section header must begin with [ and end with ]." + ); + + tmp = line.substr(1, line.length() - 2); + if(ini->section_lists.count(tmp) == 0) + ini->section_lists[tmp] = SectionList(); + + auto sections = &(ini->section_lists[tmp].sections); + sections->push_back(SectionList::Section()); + active_section = &((*sections)[sections->size() - 1]); + } else { + if(active_section == nullptr) + throw LoadError(ini, line_number, + "Key-value pair field must be after section header." + ); + + auto parts = str::split(line, '=', 2); + if(parts.size() < 2) + throw LoadError(ini, line_number, + "Key-value pair field must be separated by '='." + ); + + str::tolower(str::trim(&(parts[0]))); + str::trim(&(parts[1])); + + if(parts[0] == "" || active_section->values.count(parts[0]) == 0) + throw LoadError(ini, line_number, + "Duplicate key-value pair field in section." + ); + + active_section->values[parts[0]] = parts[1]; + } + } + + for(auto& rule : rules) { + if(rule.required && ini->section_lists.count(rule.name) == 0) + throw LoadError(ini, -1, str::join({ + "Required section '", rule.name, "' not found." + })); + + if(!rule.allow_multiple && ini[rule.name].sections.size() > 1) + throw LoadError(ini, -1, str::join({ + "Multiple instances of '", rule.name, "' section " + "when explicitely not allowed." + })); + + for(auto& section : ini[rule.name].sections) { + for(auto &field : rule.required_fields) { + str::tolower(&field.name); + if(section.values.count(field.name) == 0) + throw LoadError(ini, -1, str::join({ + "Required field '", field.name, "' in section '", + rule.name, "' not found." + })); + + if(!field.Test()) + throw LoadError(ini, -1, str::join({ + "Field '", field.name, "' in section '",rule.name, "' " + "cannot be casted to requested type." + })); + } + } + } + + return ini; +} + +std::runtime_error File::LoadError + (File* file, int line, const std::string &error) +{ + delete file; + if(line > 0) + return std::runtime_error(str::join( + {"LOAD ERROR IN '", file->filename, "' L", TOSTR(line), ": ", error} + )); + else + return std::runtime_error(str::join( + {"LOAD ERROR IN '", file->filename, "': ", error} + )); +} + +const File::SectionList& + File::operator[] (std::string name) const +{ + str::tolower(&name); + if(this->section_lists.count(name) > 0) + return this->section_lists[name]; + else + throw std::range_error("Section name not found."); +} + +const std::string& + File::SectionList::operator[] (std::string key) const +{ + return this->sections[0][key]; +} + +const File::SectionList::Section& + File::SectionList::operator[] (int index) const +{ + if(index < sections.size()) + return this->sections[index]; + else + throw std::range_error("Section index out-of-bounds."); +} + +const std::string& + File::SectionList::Section::operator[] (std::string key) const +{ + str::tolower(&key); + if(this->values.count(key) > 0) + return this->values[key]; + else + throw std::range_error("Key not found in section."); +} + +void File::Close() { + delete this; +} \ No newline at end of file diff --git a/src/common/utils/ini.hpp b/src/common/utils/ini.hpp index 4dd2c0c..c6f3215 100644 --- a/src/common/utils/ini.hpp +++ b/src/common/utils/ini.hpp @@ -1,20 +1,73 @@ #ifndef SOSC_UTIL_INI_H #define SOSC_UTIL_INI_H +#include +#include +#include #include #include +#include "string.hpp" + namespace sosc { namespace ini { class File { public: + struct Proxy { + std::string value; + Proxy(const std::string& value) : value(value) {}; + + explicit operator bool() const { + if(this->value == "1" || this->value == "true") + return true; + else if(this->value == "0" || this->value == "false") + return false; + else + throw std::bad_cast(); + } + + template< + typename T, + typename Decayed = typename std::decay::type, + typename = typename std::enable_if< + !std::is_same::value + && !std::is_same, Decayed>::value + && !std::is_same, Decayed>::value + >::type + > explicit operator T() { + std::stringstream ss; + ss << this->value; + + T retval; + if(!(ss >> retval)) + throw std::bad_cast(); + else + return retval; + } + }; + struct Rule { + template + struct Field { + Field(const std::string& name) : name(name) {} + bool Test() { + try { + (T)Proxy(this->name); + return true; + } catch(...) { + return false; + } + } + + std::string name; + }; + Rule() = delete; Rule( const std::string& name, bool required, bool allow_multiple, - const std::vector& required_fields + const std::vector& required_fields ) : name(name), required(required), allow_multiple(allow_multiple), @@ -23,14 +76,43 @@ public: std::string name; bool required; bool allow_multiple; - std::vector required_fields; + std::vector required_fields; }; - class Section { + class SectionList { + public: + class Section { + public: + const std::string& operator[] (std::string key) const; + private: + Section() = default; + std::map values; + friend class File; + }; + + const std::string& operator[] (std::string key) const; + const Section& operator[] (int index) const; + private: + SectionList() = default; + + std::vector
sections; + friend class File; }; + + File(const File&) = delete; + static File* Open + (const std::string& filename, const std::vector& rules = {}); + void Close(); + + const SectionList& operator[] (std::string name) const; private: - std::map + File() = default; + static std::runtime_error LoadError + (File* file, int line, const std::string& error); + + std::string filename; + std::map section_lists; }; }} diff --git a/src/common/utils/string.cpp b/src/common/utils/string.cpp index 20eeff0..434937e 100644 --- a/src/common/utils/string.cpp +++ b/src/common/utils/string.cpp @@ -73,6 +73,12 @@ std::vector sosc::str::split return parts; } +std::string sosc::str::join + (const std::vector& parts, int count) +{ + return join(parts, "", count); +} + std::string sosc::str::join(const std::vector& parts, char delimiter, int count) { diff --git a/src/common/utils/string.hpp b/src/common/utils/string.hpp index 8a279c1..3fba017 100644 --- a/src/common/utils/string.hpp +++ b/src/common/utils/string.hpp @@ -29,7 +29,8 @@ std::vector split (const std::string& str, char delimiter, int count = -1); std::vector split (const std::string& str, std::string delimiter, int count = -1); - + +std::string join(const std::vector& parts, int count = -1); std::string join(const std::vector& parts, char delimiter, int count = -1); std::string join(const std::vector& parts, diff --git a/src/server/main.cpp b/src/server/main.cpp index 6e58795..2fd9716 100644 --- a/src/server/main.cpp +++ b/src/server/main.cpp @@ -3,10 +3,11 @@ #include #include -#include "utils/string.hpp" #include "db/database.hpp" #include "hosts/master.hpp" #include "hosts/slave.hpp" +#include "utils/ini.hpp" +#include "utils/string.hpp" template struct _server_ctx { @@ -42,6 +43,12 @@ int main(int argc, char **argv) { if(argc < 2) return -1; + auto test = ini::File::Open("test.ini", { + ini::File::Rule("test section", true, false, { + ini::File::Rule::Field("test") + }), + }); + if(argv[1][0] == 'm') { if(!db::init_databases(nullptr)) return -1;