mirror of https://github.com/oxen-io/lokinet
redo ini parser
parent
c622aa1eb3
commit
465a3d3e43
@ -1 +1,140 @@
|
|||||||
#include <util/ini.hpp>
|
#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 @@
|
|||||||
/**
|
#ifndef LOKINET_BOOTSERV_CONFIG_HPP
|
||||||
* The MIT License (MIT)
|
#define LOKINET_BOOTSERV_CONFIG_HPP
|
||||||
* Copyright (c) <2015> <carriez.md@gmail.com>
|
#include <unordered_map>
|
||||||
* Copyright (c) <2018> <rtharp@customwebapps.com>
|
#include <util/string_view.hpp>
|
||||||
*
|
#include <functional>
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
#include <memory>
|
||||||
* 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>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace ini
|
namespace llarp
|
||||||
{
|
{
|
||||||
struct Level
|
struct ConfigParser
|
||||||
{
|
{
|
||||||
Level() : parent(nullptr), depth(0)
|
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 >;
|
||||||
Level(Level *p) : parent(p), depth(0)
|
/// clear parser
|
||||||
{
|
void
|
||||||
}
|
Clear();
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
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)
|
/// load from string
|
||||||
{
|
/// return true on success
|
||||||
for(const auto &itr : values)
|
/// return false on error
|
||||||
if(itr.first == name)
|
bool
|
||||||
return itr.second;
|
LoadString(const std::string& str);
|
||||||
return default_value;
|
|
||||||
}
|
|
||||||
Level &
|
|
||||||
operator()(const std::string &name)
|
|
||||||
{
|
|
||||||
return sections[name];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class Parser
|
/// iterate all sections and thier values
|
||||||
{
|
|
||||||
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(), "");
|
|
||||||
}
|
|
||||||
void
|
void
|
||||||
print()
|
IterAll(std::function< void(const String_t&, const Section_t&) > visit);
|
||||||
{
|
|
||||||
dump(std::cout, top(), "");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// visit a section in config read only by name
|
||||||
|
/// return false if no section or value propagated from visitor
|
||||||
bool
|
bool
|
||||||
write(const std::string filename)
|
VisitSection(const char* name,
|
||||||
{
|
std::function< bool(const Section_t&) > visit) const;
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void
|
bool
|
||||||
dump(std::ostream &s, const Level &l, const std::string &sname);
|
Parse();
|
||||||
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);
|
|
||||||
|
|
||||||
private:
|
std::vector< char > m_Data;
|
||||||
Level top_;
|
Config_impl_t m_Config;
|
||||||
std::ifstream f0_;
|
|
||||||
std::istream *f_;
|
|
||||||
std::string line_;
|
|
||||||
size_t ln_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void
|
} // namespace llarp
|
||||||
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
|
|
||||||
|
|
||||||
#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 = [§](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…
Reference in New Issue