redo ini parser

pull/244/head
Jeff Becker 5 years ago
parent c622aa1eb3
commit 465a3d3e43
No known key found for this signature in database
GPG Key ID: F357B3B42F6F9B05

@ -634,6 +634,7 @@ set(TEST_SRC
test/util/test_llarp_util_aligned.cpp
test/util/test_llarp_util_bencode.cpp
test/util/test_llarp_util_encode.cpp
test/util/test_llarp_util_ini.cpp
test/util/test_llarp_util_queue_manager.cpp
test/util/test_llarp_util_queue.cpp
test/util/test_llarp_util_thread_pool.cpp

@ -65,9 +65,8 @@ namespace llarp
HandleSignal(int sig);
private:
void
SetPIDFile(const std::string & fname);
void
SetPIDFile(const std::string &fname);
bool
WritePIDFile() const;

@ -11,37 +11,41 @@
namespace llarp
{
template < typename Config, typename Section >
static const Section &
Section
find_section(Config &c, const std::string &name, const Section &fallback)
{
if(c.sections.find(name) == c.sections.end())
Section ret;
if(c.VisitSection(name.c_str(),
[&ret](const ConfigParser::Section_t &s) -> bool {
for(const auto &item : s)
{
ret.emplace_back(item.first, item.second);
}
return true;
}))
return ret;
else
return fallback;
return c.sections[name].values;
}
bool
Config::Load(const char *fname)
{
std::ifstream f;
f.open(fname);
if(f.is_open())
{
ini::Parser parser(f);
auto &top = parser.top();
router = find_section(top, "router", section_t{});
network = find_section(top, "network", section_t{});
connect = find_section(top, "connect", section_t{});
netdb = find_section(top, "netdb", section_t{});
dns = find_section(top, "dns", section_t{});
iwp_links = find_section(top, "bind", section_t{});
services = find_section(top, "services", section_t{});
system = find_section(top, "system", section_t{});
api = find_section(top, "api", section_t{});
lokid = find_section(top, "lokid", section_t{});
bootstrap = find_section(top, "bootstrap", section_t{});
return true;
}
return false;
ConfigParser parser;
if(!parser.LoadFile(fname))
return false;
router = find_section(parser, "router", section_t{});
network = find_section(parser, "network", section_t{});
connect = find_section(parser, "connect", section_t{});
netdb = find_section(parser, "netdb", section_t{});
dns = find_section(parser, "dns", section_t{});
iwp_links = find_section(parser, "bind", section_t{});
services = find_section(parser, "services", section_t{});
system = find_section(parser, "system", section_t{});
api = find_section(parser, "api", section_t{});
lokid = find_section(parser, "lokid", section_t{});
bootstrap = find_section(parser, "bootstrap", section_t{});
return true;
};
} // namespace llarp
@ -357,20 +361,23 @@ extern "C"
llarp_config_iter(struct llarp_config *conf,
struct llarp_config_iterator *iter)
{
iter->conf = conf;
std::map< std::string, llarp::Config::section_t & > sections = {
{"network", conf->impl.network}, {"connect", conf->impl.connect},
{"bootstrap", conf->impl.bootstrap}, {"system", conf->impl.system},
{"netdb", conf->impl.netdb}, {"api", conf->impl.api},
{"services", conf->impl.services}};
iter->conf = conf;
std::unordered_map< std::string, const llarp::Config::section_t & >
sections = {{"network", conf->impl.network},
{"connect", conf->impl.connect},
{"bootstrap", conf->impl.bootstrap},
{"system", conf->impl.system},
{"netdb", conf->impl.netdb},
{"api", conf->impl.api},
{"services", conf->impl.services}};
for(const auto item : conf->impl.router)
for(const auto &item : conf->impl.router)
iter->visit(iter, "router", item.first.c_str(), item.second.c_str());
for(const auto item : conf->impl.dns)
for(const auto &item : conf->impl.dns)
iter->visit(iter, "dns", item.first.c_str(), item.second.c_str());
for(const auto item : conf->impl.iwp_links)
for(const auto &item : conf->impl.iwp_links)
iter->visit(iter, "bind", item.first.c_str(), item.second.c_str());
for(const auto &section : sections)

@ -88,8 +88,8 @@ namespace llarp
}
}
void
Context::SetPIDFile(const std::string & fname)
void
Context::SetPIDFile(const std::string &fname)
{
pidfile = fname;
}
@ -192,7 +192,7 @@ namespace llarp
// run
if(!router->Run(nodedb))
return 1;
// run net io thread
llarp::LogInfo("running mainloop");
llarp_ev_loop_run_single_process(mainloop, worker, logic);

@ -9,11 +9,17 @@ namespace llarp
bool
Config::Load(const std::string& fname)
{
ini::Parser parser(fname);
for(const auto& sec : parser.top().ordered_sections)
{
services.push_back({sec->first, sec->second.values});
}
llarp::ConfigParser parser;
if(!parser.LoadFile(fname.c_str()))
return false;
parser.IterAll([&](const llarp::ConfigParser::String_t& name,
const llarp::ConfigParser::Section_t& section) {
llarp::service::Config::section_t values;
values.first = name;
for(const auto& item : section)
values.second.emplace_back(item.first, item.second);
services.emplace_back(values);
});
return services.size() > 0;
}

@ -1 +1,140 @@
#include <util/ini.hpp>
#include <fstream>
#include <list>
#include <iostream>
namespace llarp
{
bool
ConfigParser::LoadFile(const char* fname)
{
{
std::ifstream f(fname);
if(!f.is_open())
return false;
f.seekg(0, std::ios::end);
m_Data.resize(f.tellg());
f.seekg(0, std::ios::beg);
if(m_Data.size() == 0)
return false;
f.read(m_Data.data(), m_Data.size());
}
return Parse();
}
bool
ConfigParser::LoadString(const std::string& str)
{
m_Data.resize(str.size());
std::copy(str.begin(), str.end(), m_Data.begin());
return Parse();
}
void
ConfigParser::Clear()
{
m_Config.clear();
m_Data.clear();
}
bool
ConfigParser::Parse()
{
std::list< String_t > lines;
{
auto itr = m_Data.begin();
// split into lines
while(itr != m_Data.end())
{
auto beg = itr;
while(itr != m_Data.end() && *itr != '\n' && *itr != '\r')
++itr;
lines.emplace_back(std::addressof(*beg), (itr - beg));
if(itr == m_Data.end())
break;
++itr;
}
}
String_t sectName;
for(const auto& line : lines)
{
String_t realLine;
auto comment = line.find_first_of(';');
if(comment == String_t::npos)
comment = line.find_first_of('#');
if(comment == String_t::npos)
realLine = line;
else
realLine = line.substr(0, comment);
// blank or commented line?
if(realLine.size() == 0)
continue;
// find delimiters
auto sectOpenPos = realLine.find_first_of('[');
auto sectClosPos = realLine.find_first_of(']');
auto kvDelim = realLine.find_first_of('=');
if(sectOpenPos != String_t::npos && sectClosPos != String_t::npos
&& kvDelim == String_t::npos)
{
// section header
// clamp whitespaces
++sectOpenPos;
while(std::isspace(realLine[sectOpenPos]) && sectOpenPos != sectClosPos)
++sectOpenPos;
--sectClosPos;
while(std::isspace(realLine[sectClosPos]) && sectClosPos != sectOpenPos)
--sectClosPos;
// set section name
sectName = realLine.substr(sectOpenPos, sectClosPos);
}
else if(kvDelim != String_t::npos)
{
// key value pair
String_t::size_type k_start = 0;
String_t::size_type k_end = kvDelim;
String_t::size_type v_start = kvDelim + 1;
String_t::size_type v_end = realLine.size() - 1;
// clamp whitespaces
while(std::isspace(realLine[k_start]) && k_start != kvDelim)
++k_start;
while(std::isspace(realLine[k_end]) && k_end != k_start)
--k_end;
while(std::isspace(realLine[v_start]) && v_start != v_end)
++v_start;
while(std::isspace(realLine[v_end]))
--v_end;
// sect.k = v
String_t k = realLine.substr(k_start, k_end);
String_t v = realLine.substr(v_start, v_end);
Section_t& sect = m_Config[sectName];
sect.emplace(k, v);
}
else // malformed?
return false;
}
return true;
}
void
ConfigParser::IterAll(
std::function< void(const String_t&, const Section_t&) > visit)
{
for(const auto& item : m_Config)
visit(item.first, item.second);
}
bool
ConfigParser::VisitSection(
const char* name,
std::function< bool(const Section_t& sect) > visit) const
{
auto itr = m_Config.find(name);
if(itr == m_Config.end())
return false;
return visit(itr->second);
}
} // namespace llarp

@ -1,446 +1,52 @@
/**
* The MIT License (MIT)
* Copyright (c) <2015> <carriez.md@gmail.com>
* Copyright (c) <2018> <rtharp@customwebapps.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#ifndef INI_HPP
#define INI_HPP
#include <algorithm>
#include <cassert>
#include <cstring>
#include <fstream>
#include <iostream>
#include <list>
#include <map>
#include <stdexcept>
#include <string>
#ifndef LOKINET_BOOTSERV_CONFIG_HPP
#define LOKINET_BOOTSERV_CONFIG_HPP
#include <unordered_map>
#include <util/string_view.hpp>
#include <functional>
#include <memory>
#include <vector>
namespace ini
namespace llarp
{
struct Level
struct ConfigParser
{
Level() : parent(nullptr), depth(0)
{
}
Level(Level *p) : parent(p), depth(0)
{
}
using value_map_t = std::list< std::pair< std::string, std::string > >;
using section_map_t = std::map< std::string, Level >;
using sections_t = std::list< section_map_t::const_iterator >;
value_map_t values;
section_map_t sections;
sections_t ordered_sections;
Level *parent;
size_t depth;
using String_t = llarp::string_view;
using Section_t = std::unordered_multimap< String_t, String_t >;
using Config_impl_t = std::unordered_map< String_t, Section_t >;
/// clear parser
void
Clear();
static std::string default_value;
/// load config file for bootserv
/// return true on success
/// return false on error
bool
LoadFile(const char* fname);
const std::string &operator[](const std::string &name)
{
for(const auto &itr : values)
if(itr.first == name)
return itr.second;
return default_value;
}
Level &
operator()(const std::string &name)
{
return sections[name];
}
};
/// load from string
/// return true on success
/// return false on error
bool
LoadString(const std::string& str);
class Parser
{
public:
Parser(const std::string &fname) : Parser(fname.c_str())
{
}
Parser(const char *fn);
Parser(std::istream &f) : f_(&f), ln_(0)
{
parse(top_);
}
Level &
top()
{
return top_;
}
void
dump(std::ostream &s)
{
dump(s, top(), "");
}
/// iterate all sections and thier values
void
print()
{
dump(std::cout, top(), "");
}
IterAll(std::function< void(const String_t&, const Section_t&) > visit);
/// visit a section in config read only by name
/// return false if no section or value propagated from visitor
bool
write(const std::string filename)
{
// this->print();
// printf("parser::Write\n");
std::ofstream s;
s.open(filename);
if(!s.is_open())
{
printf("parser::Write - can't open\n");
err("Cant open");
return false;
}
// reset read
// printf("parser::Write - committing\n");
this->commitStream(s);
s.close();
return true;
}
void
commitStream(std::ostream &s)
{
// printf("parser::commitStream - seekd\n");
this->ln_ = 0;
this->f_->clear();
this->f_->seekg(0);
// printf("parser::commitStream - reading top\n");
std::vector< std::string > savedSections;
this->commit(s, top_, savedSections, false);
}
VisitSection(const char* name,
std::function< bool(const Section_t&) > visit) const;
private:
void
dump(std::ostream &s, const Level &l, const std::string &sname);
void
parse(Level &l);
void
commit(std::ostream &s, Level &l, std::vector< std::string > &savedSections,
bool disabledSection);
void
parseSLine(std::string &sname, size_t &depth);
void
err(const char *s);
bool
Parse();
private:
Level top_;
std::ifstream f0_;
std::istream *f_;
std::string line_;
size_t ln_;
std::vector< char > m_Data;
Config_impl_t m_Config;
};
inline void
Parser::err(const char *s)
{
char buf[256];
sprintf(buf, "%s on line #%zu", s, ln_);
}
inline std::string
trim(const std::string &s)
{
char p[] = " \t\r\n";
long sp = 0;
long ep = s.length() - 1;
for(; sp <= ep; ++sp)
if(!strchr(p, s[sp]))
break;
for(; ep >= 0; --ep)
if(!strchr(p, s[ep]))
break;
return s.substr(sp, ep - sp + 1);
}
inline Parser::Parser(const char *fn) : f0_(fn), f_(&f0_), ln_(0)
{
if(f0_)
{
parse(top_);
}
}
inline void
Parser::parseSLine(std::string &sname, size_t &depth)
{
depth = 0;
for(; depth < line_.length(); ++depth)
if(line_[depth] != '[')
break;
sname = line_.substr(depth, line_.length() - 2 * depth);
}
inline void
Parser::parse(Level &l)
{
while(std::getline(*f_, line_))
{
++ln_;
if(line_[0] == '#' || line_[0] == ';')
continue;
line_ = trim(line_);
if(line_.empty())
continue;
if(line_[0] == '[')
{
size_t depth;
std::string sname;
parseSLine(sname, depth);
Level *lp = nullptr;
Level *parent = &l;
if(depth > l.depth + 1)
err("section with wrong depth");
if(l.depth == depth - 1)
lp = &l.sections[sname];
else
{
lp = l.parent;
size_t n = l.depth - depth;
for(size_t i = 0; i < n; ++i)
lp = lp->parent;
parent = lp;
lp = &lp->sections[sname];
}
if(lp->depth != 0)
err("duplicate section name on the same level");
if(!lp->parent)
{
lp->depth = depth;
lp->parent = parent;
}
parent->ordered_sections.push_back(parent->sections.find(sname));
parse(*lp);
}
else
{
size_t n = line_.find('=');
if(n == std::string::npos)
err("no '=' found");
auto p =
std::make_pair(trim(line_.substr(0, n)),
trim(line_.substr(n + 1, line_.length() - n - 1)));
l.values.push_back(p);
}
}
}
inline void
saveValues(std::ostream &s, std::vector< std::string > excludes, Level &l)
{
// printf("checking keys[%lu] against [%lu]\n", l.values.size(),
// excludes.size());
for(auto it = l.values.begin(); it != l.values.end(); ++it)
{
// printf("key[%s]\n", it->first.c_str());
auto check = find(excludes.begin(), excludes.end(), it->first);
if(check == excludes.end())
{
// printf("We didnt write it [%s=%s]\n", it->first.c_str(),
// it->second.c_str());
s << it->first + "=" + it->second << "\n"; // commit to stream
}
}
}
inline void
Parser::commit(std::ostream &s, Level &l,
std::vector< std::string > &savedSections,
bool disabledSection)
{
std::vector< std::string > keys;
bool keysChecked = false;
while(std::getline(*this->f_, line_))
{
++ln_;
if(line_[0] == '#' || line_[0] == ';')
{
s << line_ << "\n"; // commit to stream
continue;
}
std::string tline_ = trim(line_);
if(tline_.empty())
{
s << line_ << "\n"; // commit to stream
continue;
}
if(tline_[0] == '[')
{
bool disableNextSection = false;
size_t depth;
std::string sname;
parseSLine(sname, depth);
s << "[" << sname << "]"
<< "\n"; // commit to stream
auto test = this->top_.sections.find(sname);
if(test == this->top_.sections.end())
{
// could mean we're done with this section
// printf("We dont have section [%s]\n", sname.c_str());
// we'll comment out these keys since we've intentionally dropped them
disableNextSection = true;
}
Level *lp = NULL;
Level *parent = &l;
if(depth > l.depth + 1)
err("section with wrong depth");
// if depth is one level deep
if(l.depth == depth - 1)
{
// make level point to one of our sections
lp = &l.sections[sname];
}
else
{
// find the parent by depth
lp = l.parent;
size_t n = l.depth - depth;
for(size_t i = 0; i < n; ++i)
lp = lp->parent;
parent = lp;
lp = &lp->sections[sname];
}
/*
if(lp->depth != 0)
{
printf("has depth still, found [%s] at [%zu]\n", sname.c_str(),
depth);
}
*/
if(!lp->parent)
{
printf("no parent\n");
lp->depth = depth;
lp->parent = parent;
}
// flush remainder of this section
saveValues(s, keys, l);
keysChecked = true;
// start next section
this->commit(s, *lp, savedSections, disableNextSection);
savedSections.push_back(sname);
}
else
{
size_t n = line_.find('=');
if(n == std::string::npos)
err("no '=' found");
auto key = trim(line_.substr(0, n));
keys.push_back(key);
auto val = std::find_if(
l.values.begin(), l.values.end(),
[&key](const std::pair< std::string, std::string > &element) {
return element.first == key;
});
if(val != l.values.end())
{
if(val->second.c_str()
== trim(line_.substr(n + 1, line_.length() - n - 1)))
{
// copying line
if(disabledSection)
s << "# ";
s << line_ << "\n"; // commit to stream
}
else
{
// update value
if(disabledSection)
s << "# ";
s << line_.substr(0, n) + "=" + val->second
<< "\n"; // commit to stream
}
} /*
else
{
// remove it
//printf("kv found [%s] no current\n", key.c_str());
} */
}
}
// handle last section
if(!keysChecked)
{
saveValues(s, keys, l);
}
// we're at the main level and have the list of sections
if(l.sections.size())
{
// check to make sure we've written out all the sections we need to
// printf("sections old[%lu] run[%lu]\n", savedSections.size(),
// l.sections.size());
for(auto it = l.sections.begin(); it != l.sections.end(); ++it)
{
// printf("sections[%s]\n", it->first.c_str());
auto check =
find(savedSections.begin(), savedSections.end(), it->first);
if(check == savedSections.end())
{
// printf("Adding section [%s]\n", it->first.c_str());
// s << "[" << it->first + "]" << "\n"; // commit to stream
dump(s, l.sections[it->first], it->first);
}
}
}
}
inline void
Parser::dump(std::ostream &s, const Level &l, const std::string &sname)
{
if(!sname.empty())
s << '\n';
for(size_t i = 0; i < l.depth; ++i)
s << '[';
if(!sname.empty())
s << sname;
for(size_t i = 0; i < l.depth; ++i)
s << ']';
if(!sname.empty())
s << std::endl;
for(const auto &itr : l.values)
s << itr.first << '=' << itr.second << std::endl;
for(Level::sections_t::const_iterator it = l.ordered_sections.begin();
it != l.ordered_sections.end(); ++it)
{
assert((*it)->second.depth == l.depth + 1);
dump(s, (*it)->second, (*it)->first);
}
}
} // namespace ini
} // namespace llarp
#endif // INI_HPP
#endif

@ -0,0 +1,51 @@
#include <gtest/gtest.h>
#include <util/ini.hpp>
struct TestINIParser : public ::testing::Test
{
llarp::ConfigParser parser;
void TearDown()
{
parser.Clear();
}
};
TEST_F(TestINIParser, TestParseEmpty)
{
ASSERT_TRUE(parser.LoadString(""));
}
TEST_F(TestINIParser, TestParseOneSection)
{
llarp::ConfigParser::Section_t sect;
auto assertVisit = [&sect](const auto & section) -> bool {
sect = section;
return true;
};
ASSERT_TRUE(parser.LoadString("[test]\nkey=val"));
ASSERT_TRUE(parser.VisitSection("test", assertVisit));
auto itr = sect.find("notfound");
ASSERT_EQ(itr, sect.end());
itr = sect.find("key");
ASSERT_NE(itr, sect.end());
ASSERT_STREQ(itr->second.c_str(), "val");
}
TEST_F(TestINIParser, TestParseSectionDuplicateKeys)
{
ASSERT_TRUE(parser.LoadString("[test]\nkey1=val1\nkey1=val2"));
size_t num = 0;
auto visit =[&num](const auto & section) -> bool {
num = section.count("key1");
return true;
};
ASSERT_TRUE(parser.VisitSection("test", visit));
ASSERT_EQ(num, size_t(2));
}
TEST_F(TestINIParser, TestParseInvalid)
{
ASSERT_FALSE(parser.LoadString("srged5ghe5\nf34wtge5\nw34tgfs4ygsd5yg=4;\n#g4syhgd5\n"));
}
Loading…
Cancel
Save