From 289d396c8d0684e23c2233291164febfec3947bf Mon Sep 17 00:00:00 2001 From: Maxim Mamontov Date: Tue, 7 Jan 2014 19:25:40 +0200 Subject: [PATCH] Implemented XDG-compliant config file reading. --- projects/sgconf/action.h | 1 + projects/sgconf/actions.h | 20 ++++++++ projects/sgconf/config.h | 39 ++++++++++----- projects/sgconf/main.cpp | 48 +++++++++++++++++- projects/sgconf/options.cpp | 65 +++++++++++++++++++++++++ projects/sgconf/options.h | 13 +++++ stglibs/common.lib/common.cpp | 6 +++ stglibs/common.lib/include/stg/common.h | 1 + 8 files changed, 180 insertions(+), 13 deletions(-) diff --git a/projects/sgconf/action.h b/projects/sgconf/action.h index 0ffd4aad..0c1b4f92 100644 --- a/projects/sgconf/action.h +++ b/projects/sgconf/action.h @@ -40,6 +40,7 @@ class ACTION virtual std::string DefaultDescription() const = 0; virtual OPTION_BLOCK & Suboptions() = 0; virtual PARSER_STATE Parse(int argc, char ** argv) = 0; + virtual void ParseValue(const std::string &) {} class ERROR : public std::runtime_error { diff --git a/projects/sgconf/actions.h b/projects/sgconf/actions.h index c88de14d..33a11347 100644 --- a/projects/sgconf/actions.h +++ b/projects/sgconf/actions.h @@ -89,6 +89,7 @@ class PARAM_ACTION : public ACTION virtual std::string DefaultDescription() const; virtual OPTION_BLOCK & Suboptions() { return m_suboptions; } virtual PARSER_STATE Parse(int argc, char ** argv); + virtual void ParseValue(const std::string & value); private: RESETABLE & m_param; @@ -129,6 +130,25 @@ m_param = value; return PARSER_STATE(false, --argc, ++argv); } +template +inline +void PARAM_ACTION::ParseValue(const std::string & stringValue) +{ +if (stringValue.empty()) + throw ERROR("Missing value."); +T value; +if (str2x(stringValue, value)) + throw ERROR(std::string("Bad value: '") + stringValue + "'"); +m_param = value; +} + +template <> +inline +void PARAM_ACTION::ParseValue(const std::string & stringValue) +{ +m_param = stringValue; +} + template <> inline PARSER_STATE PARAM_ACTION::Parse(int argc, char ** argv) diff --git a/projects/sgconf/config.h b/projects/sgconf/config.h index e7984783..9ec9616e 100644 --- a/projects/sgconf/config.h +++ b/projects/sgconf/config.h @@ -38,20 +38,35 @@ struct CONFIG RESETABLE userName; RESETABLE userPass; + CONFIG & operator=(const CONFIG & rhs) + { + if (!rhs.configFile.empty()) + configFile = rhs.configFile; + if (!rhs.server.empty()) + server = rhs.server; + if (!rhs.port.empty()) + port = rhs.port; + if (!rhs.userName.empty()) + userName = rhs.userName; + if (!rhs.userPass.empty()) + userPass = rhs.userPass; + return *this; + } + std::string Serialize() const { - std::string res("{ "); - if (!configFile.empty()) - res += "configFile: '" + configFile.data() + "'"; - if (!server.empty()) - res += ", server: '" + server.data() + "'"; - if (!port.empty()) - res += ", port: " + x2str(port.data()); - if (!userName.empty()) - res += ", userName: '" + userName.data() + "'"; - if (!userPass.empty()) - res += ", userPass: '" + userPass.data() + "'"; - return res + " }"; + std::string res("{ "); + if (!configFile.empty()) + res += "configFile: '" + configFile.data() + "'"; + if (!server.empty()) + res += ", server: '" + server.data() + "'"; + if (!port.empty()) + res += ", port: " + x2str(port.data()); + if (!userName.empty()) + res += ", userName: '" + userName.data() + "'"; + if (!userPass.empty()) + res += ", userPass: '" + userPass.data() + "'"; + return res + " }"; } }; diff --git a/projects/sgconf/main.cpp b/projects/sgconf/main.cpp index 70c55718..a39f57ce 100644 --- a/projects/sgconf/main.cpp +++ b/projects/sgconf/main.cpp @@ -146,6 +146,28 @@ void UsageCorporations(bool full); void Version(); +void ReadUserConfigFile(SGCONF::OPTION_BLOCK & block) +{ +std::vector paths; +const char * configHome = getenv("XDG_CONFIG_HOME"); +if (configHome == NULL) + { + const char * home = getenv("HOME"); + if (home == NULL) + return; + paths.push_back(std::string(home) + "/.config/sgconf/sgconf.conf"); + paths.push_back(std::string(home) + "/.sgconf/sgconf.conf"); + } +else + paths.push_back(std::string(configHome) + "/sgconf/sgconf.conf"); +for (std::vector::const_iterator it = paths.begin(); it != paths.end(); ++it) + if (access(it->c_str(), R_OK) == 0) + { + block.ParseFile(*it); + return; + } +} + } // namespace anonymous namespace SGCONF @@ -1211,7 +1233,7 @@ blocks.Add("General options") .Add("h", "help", SGCONF::MakeFunc0Action(bind0(Method1Adapt(&SGCONF::OPTION_BLOCKS::Help, blocks), 0)), "\t\tshow this help and exit") .Add("help-all", SGCONF::MakeFunc0Action(UsageAll), "\t\tshow full help and exit") .Add("v", "version", SGCONF::MakeFunc0Action(Version), "\t\tshow version information and exit"); -blocks.Add("Connection options") +SGCONF::OPTION_BLOCK & block = blocks.Add("Connection options") .Add("s", "server", SGCONF::MakeParamAction(config.server, std::string("localhost"), "
"), "\t\thost to connect") .Add("p", "port", SGCONF::MakeParamAction(config.port, uint16_t(5555), ""), "\t\tport to connect") .Add("u", "username", SGCONF::MakeParamAction(config.userName, std::string("admin"), ""), "\tadministrative login") @@ -1240,6 +1262,30 @@ if (state.argc > 0) return -1; } +try +{ +SGCONF::CONFIG configOverride(config); + +if (config.configFile.empty()) + { + const char * mainConfigFile = "/etc/sgconf/sgconf.conf"; + if (access(mainConfigFile, R_OK) == 0) + block.ParseFile(mainConfigFile); + ReadUserConfigFile(block); + } +else + { + block.ParseFile(config.configFile.data()); + } + +config = configOverride; +} +catch (const SGCONF::OPTION::ERROR& ex) +{ +std::cerr << ex.what() << "\n"; +return -1; +} + std::cerr << "Config: " << config.Serialize() << std::endl; return 0; diff --git a/projects/sgconf/options.cpp b/projects/sgconf/options.cpp index a079e1b1..6a4eef1b 100644 --- a/projects/sgconf/options.cpp +++ b/projects/sgconf/options.cpp @@ -23,10 +23,47 @@ #include "action.h" #include "parser_state.h" +#include "stg/common.h" + +#include +#include #include #include #include +#include + +namespace +{ + +template +void ReadConfigFile(const std::string & filePath, void (C::* callback)(const std::string&, const std::string&), C * obj) +{ +std::ifstream stream(filePath.c_str()); +std::string line; +size_t num = 0; +while (std::getline(stream, line)) + { + ++num; + line = Trim(line); + std::string::size_type pos = line.find_first_of('#'); + if (pos != std::string::npos) + line = line.substr(0, pos); + if (line.empty()) + continue; + pos = line.find_first_of('='); + if (pos == std::string::npos) + { + std::ostringstream error; + error << "Bad file format, missing '=' in '" << filePath << ":" << num << "'."; + throw std::runtime_error(error.str().c_str()); + } + (obj->*callback)(Trim(line.substr(0, pos)), Trim(line.substr(pos + 1, line.length() - pos - 1))); + } +} + +} // namespace anonymous + using SGCONF::OPTION; using SGCONF::OPTION_BLOCK; using SGCONF::OPTION_BLOCKS; @@ -119,6 +156,20 @@ catch (const ACTION::ERROR & ex) } } +void OPTION::ParseValue(const std::string & value) +{ +if (!m_action) + throw ERROR("Option is not defined."); +try + { + return m_action->ParseValue(value); + } +catch (const ACTION::ERROR & ex) + { + throw ERROR(m_longName + ": " + ex.what()); + } +} + OPTION_BLOCK & OPTION_BLOCK::Add(const std::string & shortName, const std::string & longName, ACTION * action, @@ -161,6 +212,20 @@ while (state.argc > 0 && !state.stop) return state; } +void OPTION_BLOCK::ParseFile(const std::string & filePath) +{ +if (access(filePath.c_str(), R_OK)) + throw ERROR("File '" + filePath + "' does not exists."); +ReadConfigFile(filePath, &OPTION_BLOCK::OptionCallback, this); +} + +void OPTION_BLOCK::OptionCallback(const std::string & key, const std::string & value) +{ +for (std::vector