mirror of
https://github.com/matrix-construct/construct
synced 2024-11-15 22:41:12 +01:00
312 lines
7.2 KiB
C++
312 lines
7.2 KiB
C++
/*
|
|
* Copyright (C) 2016 Charybdis Development Team
|
|
* Copyright (C) 2016 Jason Volk <jason@zemos.net>
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice is present in all copies.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <boost/spirit/include/qi.hpp>
|
|
|
|
using namespace ircd;
|
|
|
|
namespace qi = boost::spirit::qi;
|
|
namespace ascii = qi::ascii;
|
|
using qi::lexeme;
|
|
using qi::char_;
|
|
using qi::lit;
|
|
using qi::eol;
|
|
using qi::blank;
|
|
using qi::eps;
|
|
|
|
/*
|
|
registry['A'] = "admin";
|
|
registry['B'] = "blacklist";
|
|
registry['C'] = "connect";
|
|
registry['I'] = "auth";
|
|
registry['O'] = "operator";
|
|
registry['P'] = "listen";
|
|
registry['U'] = "service";
|
|
registry['Y'] = "class";
|
|
registry['a'] = "alias";
|
|
registry['d'] = "exempt";
|
|
registry['g'] = "general";
|
|
registry['l'] = "log";
|
|
*/
|
|
|
|
namespace ircd {
|
|
namespace conf {
|
|
namespace newconf {
|
|
|
|
using str = std::string;
|
|
using strvec = std::vector<str>;
|
|
using strvecvec = std::vector<strvec>;
|
|
using strvecvecvec = std::vector<strvecvec>;
|
|
|
|
template<class iter>
|
|
struct ignores
|
|
:qi::grammar<iter>
|
|
{
|
|
using rule = qi::rule<iter>;
|
|
|
|
rule c_comment;
|
|
rule cc_comment;
|
|
rule sh_comment;
|
|
rule skip;
|
|
|
|
ignores();
|
|
};
|
|
|
|
template<class iter,
|
|
class ignores>
|
|
struct parser
|
|
:qi::grammar<iter, strvecvecvec(), ignores>
|
|
{
|
|
template<class ret> using rule = qi::rule<iter, ret, ignores>;
|
|
|
|
rule<str()> unquoted;
|
|
rule<str()> quoted;
|
|
rule<str()> key;
|
|
rule<strvec()> item;
|
|
rule<strvec()> topitem;
|
|
rule<strvecvec()> block;
|
|
rule<strvecvecvec()> conf;
|
|
|
|
parser();
|
|
};
|
|
|
|
letters registry;
|
|
|
|
} // namespace newconf
|
|
} // namespace conf
|
|
} // namespace ircd
|
|
|
|
template<class iter,
|
|
class ignores>
|
|
conf::newconf::parser<iter, ignores>::parser()
|
|
:parser::base_type // pass reference the topmost level to begin parsing on
|
|
{
|
|
conf
|
|
}
|
|
,unquoted // config values without double-quotes, cannot have ';' because that's the ending
|
|
{
|
|
lexeme[+(char_ - ';')]
|
|
,"unquoted"
|
|
}
|
|
,quoted // config values surrounded by double quotes, which cannot have double quotes in them
|
|
{
|
|
lexeme['"' >> +(char_ - '"') >> '"']
|
|
,"quoted"
|
|
}
|
|
,key // configuration key, must be simple alnum's with underscores
|
|
{
|
|
+char_("[a-zA-Z0-9_]")
|
|
,"key"
|
|
}
|
|
,item // a key-value pair
|
|
{
|
|
+(key) >> '=' >> +(quoted | unquoted) >> ';'
|
|
,"item"
|
|
}
|
|
,topitem // a key-value pair, but at the topconf no '=' is used
|
|
{
|
|
+(key) >> +(quoted) >> ';'
|
|
,"topitem"
|
|
}
|
|
,block // a bracketed conf block, the type-label is like a key, and it can have a unique quoted label
|
|
{
|
|
*(key >> *(quoted)) >> '{' >> *(item) >> '}' >> ';'
|
|
,"block"
|
|
}
|
|
,conf // newconf is comprised of either conf blocks or key-value's at the top level
|
|
{
|
|
*(block | topitem)
|
|
,"conf"
|
|
}
|
|
{
|
|
}
|
|
|
|
template<class iter>
|
|
conf::newconf::ignores<iter>::ignores()
|
|
:ignores::base_type // pass reference to the topmost skip-parser
|
|
{
|
|
skip
|
|
}
|
|
,c_comment // a multiline comment
|
|
{
|
|
lit('/') >> lit('*') >> *(char_ - (lit('*') >> '/')) >> lit("*/")
|
|
}
|
|
,cc_comment // a single line comment, like this one :)
|
|
{
|
|
lit('/') >> lit('/') >> *(char_ - eol) >> eol | blank
|
|
}
|
|
,sh_comment // a single line shell comment
|
|
{
|
|
'#' >> *(char_ - eol) >> eol | blank
|
|
}
|
|
,skip // the skip parser is one and any one of the comment types, or unmatched whitespace
|
|
{
|
|
+(ascii::space | c_comment | cc_comment | sh_comment)
|
|
}
|
|
{
|
|
}
|
|
|
|
conf::newconf::topconf
|
|
conf::newconf::parse_file(const std::string &path)
|
|
{
|
|
std::ifstream file(path);
|
|
return parse(file);
|
|
}
|
|
|
|
conf::newconf::topconf
|
|
conf::newconf::parse(std::ifstream &file)
|
|
{
|
|
const std::istreambuf_iterator<char> bit(file), eit;
|
|
return parse(std::string(bit, eit));
|
|
}
|
|
|
|
conf::newconf::topconf
|
|
conf::newconf::parse(const std::string &str)
|
|
try
|
|
{
|
|
using iter = std::string::const_iterator;
|
|
|
|
strvecvecvec vec;
|
|
const ignores<iter> ign;
|
|
const newconf::parser<iter, ignores<iter>> p;
|
|
if(!phrase_parse(begin(str), end(str), p, ign, vec))
|
|
throw syntax_error("newconf parse failed for unknown reason");
|
|
|
|
// This was my first qi grammar and there's a lot wrong with it.
|
|
// Once it's revisited it should parse directly into the ideal structures
|
|
// returned to the user. Until then it's translated here.
|
|
topconf top;
|
|
for(const auto &v : vec)
|
|
{
|
|
key k; block b;
|
|
for(const auto &vv : v)
|
|
{
|
|
if(vv.empty())
|
|
{
|
|
// For unnamed blocks (without double-quoted names) the "*" serves
|
|
// as a placeholder indicating no-name rather than ""
|
|
b.first = "*";
|
|
continue;
|
|
}
|
|
|
|
item i;
|
|
for(const auto &vvv : vv)
|
|
{
|
|
if(vvv.empty())
|
|
continue;
|
|
else if(k.empty())
|
|
k = vvv;
|
|
else if(b.first.empty())
|
|
b.first = vvv;
|
|
else if(i.first.empty())
|
|
i.first = vvv;
|
|
else
|
|
i.second.emplace_back(vvv);
|
|
}
|
|
|
|
if(!i.first.empty())
|
|
b.second.emplace_back(i);
|
|
}
|
|
|
|
top.emplace_back(k, b);
|
|
}
|
|
|
|
return top;
|
|
}
|
|
catch(const boost::spirit::qi::expectation_failure<std::string::const_iterator> &e)
|
|
{
|
|
const size_t character(e.last - e.first);
|
|
const auto line_num(std::count(begin(str), begin(str)+character, '\n'));
|
|
|
|
size_t char_num(0);
|
|
size_t line_start(character);
|
|
while(line_start && str[--line_start] != '\n')
|
|
char_num++;
|
|
|
|
throw syntax_error("@line %zu +%zu: expecting :%s",
|
|
line_num,
|
|
char_num,
|
|
string(e.what_).c_str());
|
|
}
|
|
catch(const std::exception &e)
|
|
{
|
|
ircd::log::error("Unexpected newconf error during parsing: %s", e.what());
|
|
throw;
|
|
}
|
|
|
|
|
|
std::list<std::string>
|
|
conf::newconf::translate(const topconf &top)
|
|
{
|
|
std::list<std::string> ret;
|
|
translate(top, [&ret](std::string line)
|
|
{
|
|
ret.emplace_back(std::move(line));
|
|
});
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
conf::newconf::translate(const topconf &top,
|
|
const std::function<void (std::string)> &closure)
|
|
{
|
|
for(const auto &pair : top) try
|
|
{
|
|
const auto &type(pair.first);
|
|
const auto &block(pair.second);
|
|
const auto &label(block.first);
|
|
const auto &items(block.second);
|
|
const auto &letter(find_letter(type));
|
|
for(const auto &item : items)
|
|
{
|
|
const auto &key(item.first);
|
|
const auto &vals(item.second);
|
|
std::stringstream buf;
|
|
buf << letter << " "
|
|
<< label << " "
|
|
<< key << " :";
|
|
|
|
for(auto i(0); i < int(vals.size()) - 1; ++i)
|
|
buf << vals.at(i) << " ";
|
|
|
|
if(!vals.empty())
|
|
buf << vals.back();
|
|
|
|
closure(buf.str());
|
|
}
|
|
}
|
|
catch(const error &e)
|
|
{
|
|
log.warning("%s", e.what());
|
|
}
|
|
}
|
|
|
|
uint8_t
|
|
conf::newconf::find_letter(const std::string &name)
|
|
{
|
|
const auto ®istry(newconf::registry);
|
|
const auto it(std::find(begin(registry), end(registry), name));
|
|
if(it == end(registry))
|
|
throw unknown_block("%s is not registered to a letter", name.c_str());
|
|
|
|
return std::distance(begin(registry), it);
|
|
}
|