#ifndef OPTIONS_H #define OPTIONS_H #include #include #include #include #include namespace OptionLib { // TODO There has to be a better way of doing this bool isValidKeyChar(char c) { return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '_' || c == '-'); } const std::string validateKey(const std::string &key) { for (char c : key) { if (!isValidKeyChar(c)) { throw std::invalid_argument("Options key contains invalid characters, must match [a-z0-9\\_\\-]"); } } return key; } class __OptionBase { public: const std::string key; __OptionBase(const std::string &key) : key(validateKey(key)) {} virtual ~__OptionBase() = default; virtual void setFromString(const std::string& value) = 0; virtual void store(ostream& stream) = 0; }; class OptionsBase { private: std::vector<__OptionBase*> options; std::string comment = ""; public: void registerOption(__OptionBase* opt) { options.push_back(opt); } const std::vector<__OptionBase*>& getOptions() const { return options; } void store(const std::string& filename, std::string comment="") { std::ofstream file(filename); if(!comment.empty()) { file << "# " << comment << endl; } for (auto* opt : getOptions()) { file << opt->key << "="; opt->store(file); file << endl; } } void read(const std::string& filename) { std::ifstream file(filename); std::unordered_map kv; std::string key, value, line; while (std::getline(file, line)) { if (line.size() < 1 || line[0] == '#') continue; // skip comment lines TODO make better auto pos = line.find('='); if (pos == std::string::npos) continue; // skip invalid lines std::string key = line.substr(0, pos); std::string value = line.substr(pos + 1); kv[key] = value; } for (auto* opt : getOptions()) { if (kv.count(opt->key)) { opt->setFromString(kv[opt->key]); } } } }; template class Option : public __OptionBase{ private: T value; public: Option(OptionsBase * parent, std::string name) : __OptionBase(name) { parent->registerOption(this); } Option(OptionsBase * parent, std::string name, T defaultValue) : __OptionBase(name) { parent->registerOption(this); set(defaultValue); } const T& get() const {return value;} void set(const T& v) {value = v;} void setFromString(const std::string& str) override { std::istringstream iss(str); iss >> value; } void store(ostream& stream) { stream << value; }; }; } #endif