]> git.stg.codes - stg.git/commitdiff
Implemented XDG-compliant config file reading.
authorMaxim Mamontov <faust.madf@gmail.com>
Tue, 7 Jan 2014 17:25:40 +0000 (19:25 +0200)
committerMaxim Mamontov <faust.madf@gmail.com>
Tue, 7 Jan 2014 17:25:40 +0000 (19:25 +0200)
projects/sgconf/action.h
projects/sgconf/actions.h
projects/sgconf/config.h
projects/sgconf/main.cpp
projects/sgconf/options.cpp
projects/sgconf/options.h
stglibs/common.lib/common.cpp
stglibs/common.lib/include/stg/common.h

index 0ffd4aad46df0daba3140d5e677c9424d34fffc8..0c1b4f92a57aed3e70ad0acf663c22463a25c281 100644 (file)
@@ -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 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
         {
 
         class ERROR : public std::runtime_error
         {
index c88de14dc7f5f306e5b0ee0421485a5945f5724f..33a11347a27fb7a3143380f5dda1a8f3d08435c5 100644 (file)
@@ -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 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<T> & m_param;
 
     private:
         RESETABLE<T> & m_param;
@@ -129,6 +130,25 @@ m_param = value;
 return PARSER_STATE(false, --argc, ++argv);
 }
 
 return PARSER_STATE(false, --argc, ++argv);
 }
 
+template <typename T>
+inline
+void PARAM_ACTION<T>::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<std::string>::ParseValue(const std::string & stringValue)
+{
+m_param = stringValue;
+}
+
 template <>
 inline
 PARSER_STATE PARAM_ACTION<std::string>::Parse(int argc, char ** argv)
 template <>
 inline
 PARSER_STATE PARAM_ACTION<std::string>::Parse(int argc, char ** argv)
index e7984783e05a9bfe7d9a2b492a2b39a43dde23ae..9ec9616eabebfc11ad40f6e5d8d30f0853fcf030 100644 (file)
@@ -38,20 +38,35 @@ struct CONFIG
     RESETABLE<std::string> userName;
     RESETABLE<std::string> userPass;
 
     RESETABLE<std::string> userName;
     RESETABLE<std::string> 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 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 + " }";
     }
 };
 
     }
 };
 
index 70c55718f35ccf887ef18354d5c34740b607eb84..a39f57cee6b7887b3b995101109c67823553018d 100644 (file)
@@ -146,6 +146,28 @@ void UsageCorporations(bool full);
 
 void Version();
 
 
 void Version();
 
+void ReadUserConfigFile(SGCONF::OPTION_BLOCK & block)
+{
+std::vector<std::string> 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<std::string>::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
 } // 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");
       .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"), "<address>"), "\t\thost to connect")
       .Add("p", "port", SGCONF::MakeParamAction(config.port, uint16_t(5555), "<port>"), "\t\tport to connect")
       .Add("u", "username", SGCONF::MakeParamAction(config.userName, std::string("admin"), "<username>"), "\tadministrative login")
       .Add("s", "server", SGCONF::MakeParamAction(config.server, std::string("localhost"), "<address>"), "\t\thost to connect")
       .Add("p", "port", SGCONF::MakeParamAction(config.port, uint16_t(5555), "<port>"), "\t\tport to connect")
       .Add("u", "username", SGCONF::MakeParamAction(config.userName, std::string("admin"), "<username>"), "\tadministrative login")
@@ -1240,6 +1262,30 @@ if (state.argc > 0)
     return -1;
     }
 
     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;
 std::cerr << "Config: " << config.Serialize() << std::endl;
 
 return 0;
index a079e1b18f835467772b6c60e7e3e3dd89aefda2..6a4eef1b6952cb9849945afffc857d59b36ff90d 100644 (file)
 #include "action.h"
 #include "parser_state.h"
 
 #include "action.h"
 #include "parser_state.h"
 
+#include "stg/common.h"
+
+#include <fstream>
+#include <sstream>
 #include <iostream>
 #include <functional>
 #include <algorithm>
 
 #include <iostream>
 #include <functional>
 #include <algorithm>
 
+#include <unistd.h>
+
+namespace
+{
+
+template <class C>
+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;
 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,
 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;
 }
 
 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<OPTION>::iterator it = m_options.begin(); it != m_options.end(); ++it)
+    if (it->Name() == key)
+        it->ParseValue(value);
+}
+
 void OPTION_BLOCKS::Help(size_t level) const
 {
 std::list<OPTION_BLOCK>::const_iterator it(m_blocks.begin());
 void OPTION_BLOCKS::Help(size_t level) const
 {
 std::list<OPTION_BLOCK>::const_iterator it(m_blocks.begin());
index 3d4bdd64263355211dcaf41ef5539506c777f40d..6b33ee8c2ba263efaa9bb3d3474586b1feb5fd24 100644 (file)
@@ -24,6 +24,7 @@
 #include <string>
 #include <vector>
 #include <list>
 #include <string>
 #include <vector>
 #include <list>
+#include <utility>
 #include <stdexcept>
 #include <cstddef> // size_t
 
 #include <stdexcept>
 #include <cstddef> // size_t
 
@@ -50,7 +51,9 @@ class OPTION
 
         void Help(size_t level = 0) const;
         PARSER_STATE Parse(int argc, char ** argv);
 
         void Help(size_t level = 0) const;
         PARSER_STATE Parse(int argc, char ** argv);
+        void ParseValue(const std::string & value);
         bool Check(const char * arg) const;
         bool Check(const char * arg) const;
+        const std::string & Name() const { return m_longName; }
 
         class ERROR : public std::runtime_error
         {
 
         class ERROR : public std::runtime_error
         {
@@ -83,10 +86,20 @@ class OPTION_BLOCK
         void Help(size_t level) const;
 
         PARSER_STATE Parse(int argc, char ** argv);
         void Help(size_t level) const;
 
         PARSER_STATE Parse(int argc, char ** argv);
+        void ParseFile(const std::string & filePath);
+
+        class ERROR : public std::runtime_error
+        {
+            public:
+                ERROR(const std::string & message)
+                    : std::runtime_error(message.c_str()) {}
+        };
 
     private:
         std::vector<OPTION> m_options;
         std::string m_description;
 
     private:
         std::vector<OPTION> m_options;
         std::string m_description;
+
+        void OptionCallback(const std::string & key, const std::string & value);
 };
 
 class OPTION_BLOCKS
 };
 
 class OPTION_BLOCKS
index 15547b49122e4fa0ade9d6d98ab000e1c56cda14..7ba78e1d8b311e149ea0b705ee3b0102a3af423f 100644 (file)
@@ -840,6 +840,12 @@ std::string & Trim(std::string & val)
 return TrimR(TrimL(val));
 }
 //---------------------------------------------------------------------------
 return TrimR(TrimL(val));
 }
 //---------------------------------------------------------------------------
+std::string Trim(const std::string & val)
+{
+std::string res(val);
+return TrimR(TrimL(res));
+}
+//---------------------------------------------------------------------------
 std::string ToLower(const std::string & value)
 {
     std::string res;
 std::string ToLower(const std::string & value)
 {
     std::string res;
index b4b53530d512a94cc4021ce954892218eba7c886..f2e4e7e2432ab52aa75ed29efbe684726a7fe5ea 100644 (file)
@@ -95,6 +95,7 @@ void            SwapBytes(int64_t & value);
 std::string &   TrimL(std::string & val);
 std::string &   TrimR(std::string & val);
 std::string &   Trim(std::string & val);
 std::string &   TrimL(std::string & val);
 std::string &   TrimR(std::string & val);
 std::string &   Trim(std::string & val);
+std::string     Trim(const std::string & val);
 
 std::string     ToLower(const std::string & value);
 std::string     ToUpper(const std::string & value);
 
 std::string     ToLower(const std::string & value);
 std::string     ToUpper(const std::string & value);