diff --git a/meson_options.txt b/meson_options.txt
index c186aea..c1024c4 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,2 +1,3 @@
option('base_url', type: 'string', value: 'https://api.originsro.org')
option('database_file', type: 'string', value: 'originsro.db3')
+option('config_file', type: 'string', value: 'orotool.conf')
diff --git a/src/config.hpp.in b/src/config.hpp.in
index 25232a8..5151365 100644
--- a/src/config.hpp.in
+++ b/src/config.hpp.in
@@ -19,7 +19,8 @@
namespace duck {
-constexpr const char g_base_url[] = "@BASE_URL@";
-constexpr const char g_database[] = "@DATABASE@";
+constexpr const char g_base_url[] = @BASE_URL@;
+constexpr const char g_database[] = @DATABASE@;
+constexpr const char g_config_file_path[] = @CONFIG_FILE_PATH@;
} //namespace duck
diff --git a/src/ini_file.cpp b/src/ini_file.cpp
new file mode 100644
index 0000000..46e6abc
--- /dev/null
+++ b/src/ini_file.cpp
@@ -0,0 +1,172 @@
+/* Copyright 2017, Michele Santullo
+ * This file is part of "kamokan".
+ *
+ * "kamokan" is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * "kamokan" is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with "kamokan". If not, see .
+ */
+
+#include "ini_file.hpp"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace kamokan {
+ namespace {
+ typedef std::string_view string_type;
+
+ template
+ struct IniGrammar : boost::spirit::qi::grammar {
+ explicit IniGrammar (const std::string* parString);
+ boost::spirit::qi::rule start;
+ boost::spirit::qi::rule section;
+ boost::spirit::qi::rule key;
+ boost::spirit::qi::rule key_value;
+ boost::spirit::qi::rule key_values;
+ const std::string* m_master_string;
+ Iterator m_begin;
+ };
+
+ template
+ struct IniCommentSkipper : boost::spirit::qi::grammar {
+ IniCommentSkipper() :
+ IniCommentSkipper::base_type(skipping),
+ first_char(true)
+ {
+ namespace px = boost::phoenix;
+ using boost::spirit::qi::blank;
+ using boost::spirit::qi::lit;
+ using boost::spirit::qi::eol;
+ using boost::spirit::qi::char_;
+ using boost::spirit::qi::eps;
+
+ skipping = comment | blank;
+ comment = (eps(px::cref(first_char) == true) | eol) >>
+ *blank >> lit("#")[px::ref(first_char) = false] >>
+ *(!eol >> char_);
+ }
+
+ boost::spirit::qi::rule skipping;
+ boost::spirit::qi::rule comment;
+ bool first_char;
+ };
+
+ template
+ IniGrammar::IniGrammar (const std::string* parString) :
+ IniGrammar::base_type(start),
+ m_master_string(parString),
+ m_begin(m_master_string->cbegin())
+ {
+ assert(m_master_string);
+ namespace px = boost::phoenix;
+ using boost::spirit::qi::_val;
+ using boost::spirit::_1;
+ using boost::spirit::qi::eol;
+ using boost::spirit::qi::raw;
+ using std::string_view;
+ using boost::spirit::qi::hold;
+ using boost::spirit::qi::graph;
+ using boost::spirit::qi::blank;
+ typedef IniFile::KeyValueMapType::value_type refpair;
+
+ section = '[' >> raw[+(graph - ']') >> *(hold[+blank >> +(graph - ']')])]
+ [_val = px::bind(
+ &string_view::substr,
+ px::construct(px::ref(*m_master_string)),
+ px::begin(_1) - px::cref(m_begin), px::size(_1)
+ )] >> ']';
+ key = raw[(graph - '[' - '=') >> *(graph - '=') >> *(hold[+blank >> +(graph - '=')])][_val = px::bind(
+ &string_view::substr,
+ px::construct(px::ref(*m_master_string)),
+ px::begin(_1) - px::cref(m_begin), px::size(_1)
+ )];
+ key_value = key[px::bind(&refpair::first, _val) = _1] >> '=' >>
+ raw[*(!eol >> graph) % +blank][px::bind(&refpair::second, _val) = px::bind(
+ &string_view::substr,
+ px::construct(px::ref(*m_master_string)),
+ px::begin(_1) - px::cref(m_begin), px::size(_1)
+ )];
+ key_values = -(key_value % +eol);
+ start = *eol >> *(section >> +eol >> key_values >> *eol);
+ }
+
+ IniFile::IniMapType parse_ini (const std::string* parIni, bool& parParseOk, int& parParsedCharCount) {
+ using boost::spirit::qi::blank_type;
+ using skipper_type = IniCommentSkipper;
+
+ IniGrammar gramm(parIni);
+ IniFile::IniMapType result;
+
+ parParseOk = false;
+ parParsedCharCount = 0;
+
+ std::string::const_iterator start_it = parIni->cbegin();
+ //TODO: make a skipper that also skips comments eg: blank | lit("//") >> *(char_ - eol)
+ const bool parse_ok = boost::spirit::qi::phrase_parse(
+ start_it,
+ parIni->cend(),
+ gramm,
+ skipper_type(),
+ result
+ );
+
+ parParseOk = parse_ok and (parIni->cend() == start_it);
+ parParsedCharCount = std::distance(parIni->cbegin(), start_it);
+ assert(parParsedCharCount >= 0);
+ return result;
+ }
+ } //unnamed namespace
+
+ IniFile::IniFile (std::istream_iterator parInputFrom, std::istream_iterator parInputEnd) :
+ IniFile(std::string(parInputFrom, parInputEnd))
+ {
+ }
+
+ IniFile::IniFile (std::string&& parIniData) :
+ m_raw_ini(std::move(parIniData)),
+ m_map(parse_ini(&m_raw_ini, m_parse_ok, m_parsed_chars))
+ {
+ }
+
+ IniFile::IniFile (IniFile&& parOther) {
+ auto* const old_data_ptr = parOther.m_raw_ini.data();
+ m_raw_ini = std::move(parOther.m_raw_ini);
+ if (m_raw_ini.data() == old_data_ptr)
+ m_map = std::move(parOther.m_map);
+ else
+ m_map = parse_ini(&m_raw_ini, m_parse_ok, m_parsed_chars);
+ }
+
+ IniFile::~IniFile() noexcept = default;
+} //namespace kamokan
diff --git a/src/ini_file.hpp b/src/ini_file.hpp
new file mode 100644
index 0000000..d80e861
--- /dev/null
+++ b/src/ini_file.hpp
@@ -0,0 +1,57 @@
+/* Copyright 2017, Michele Santullo
+ * This file is part of "kamokan".
+ *
+ * "kamokan" is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * "kamokan" is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with "kamokan". If not, see .
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+namespace kamokan {
+ class IniFile {
+ public:
+ typedef boost::container::flat_map KeyValueMapType;
+ typedef boost::container::flat_map IniMapType;
+
+ IniFile (std::istream_iterator parInputFrom, std::istream_iterator parInputEnd);
+ explicit IniFile (std::string&& parIniData);
+ IniFile (IniFile&& parOther);
+ IniFile (const IniFile& parOther) = delete;
+ ~IniFile() noexcept;
+
+ IniFile& operator== (IniFile&&) = delete;
+ IniFile& operator== (const IniFile&) = delete;
+
+ bool parse_success() const { return m_parse_ok; }
+ int parsed_characters() const { return m_parsed_chars; }
+
+ const IniMapType& parsed() const;
+
+ private:
+ std::string m_raw_ini;
+ IniMapType m_map;
+ int m_parsed_chars;
+ bool m_parse_ok;
+ };
+
+ inline const IniFile::IniMapType& IniFile::parsed() const {
+ assert(parse_success());
+ return m_map;
+ }
+} //namespace kamokan
diff --git a/src/meson.build b/src/meson.build
index aeb3695..550c572 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -12,6 +12,7 @@ sqlitecpp_dep = dependency('sqlitecpp', version: '>=3.0.0',
ev_dep = dependency('libev', version: '>=4.31')
threads_dep = dependency('threads')
+boost_dep = dependency('boost')
base_url = get_option('base_url').strip()
if not base_url.endswith('/')
@@ -19,8 +20,9 @@ if not base_url.endswith('/')
endif
conf = configuration_data()
-conf.set('BASE_URL', base_url)
-conf.set('DATABASE', get_option('database_file'))
+conf.set_quoted('BASE_URL', base_url)
+conf.set_quoted('DATABASE', get_option('database_file'))
+conf.set_quoted('CONFIG_FILE_PATH', get_option('prefix') / get_option('sysconfdir') / get_option('config_file'))
project_config_file = configure_file(
input: 'config.hpp.in',
output: meson.project_name() + '_config.hpp',
@@ -36,10 +38,12 @@ lib_deps = [
sqlitecpp_dep,
ev_dep,
threads_dep,
+ boost_dep,
]
executable(meson.project_name(),
'main.cpp',
+ 'ini_file.cpp',
'oro/datatypes.cpp',
'oro/api.cpp',
'oro/dateconv.cpp',