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',