dncurrency/nano/lib/tomlconfig.cpp
theohax 9bb89ab323
Improvements in const correctness and 'const' positioning (#3510)
* Improve const correctness and adhere to 'const' specifier positioning style

Co-authored-by: Mario Ortiz Manero <marioortizmanero@gmail.com>
2021-10-21 16:20:09 +03:00

377 lines
No EOL
9.4 KiB
C++

#include <nano/boost/asio/ip/address_v6.hpp>
#include <nano/lib/tomlconfig.hpp>
#include <boost/filesystem/convenience.hpp>
nano::tomlconfig::tomlconfig () :
tree (cpptoml::make_table ())
{
error = std::make_shared<nano::error> ();
}
nano::tomlconfig::tomlconfig (std::shared_ptr<cpptoml::table> const & tree_a, std::shared_ptr<nano::error> const & error_a) :
nano::configbase (error_a), tree (tree_a)
{
if (!error)
{
error = std::make_shared<nano::error> ();
}
}
void nano::tomlconfig::doc (std::string const & key, std::string const & doc)
{
tree->document (key, doc);
}
nano::error & nano::tomlconfig::read (boost::filesystem::path const & path_a)
{
std::stringstream stream_override_empty;
stream_override_empty << std::endl;
return read (stream_override_empty, path_a);
}
nano::error & nano::tomlconfig::read (std::istream & stream_overrides, boost::filesystem::path const & path_a)
{
std::fstream stream;
open_or_create (stream, path_a.string ());
if (!stream.fail ())
{
read (stream_overrides, stream);
}
return *error;
}
nano::error & nano::tomlconfig::read (std::istream & stream_a)
{
std::stringstream stream_override_empty;
stream_override_empty << std::endl;
return read (stream_override_empty, stream_a);
}
/** Read from two streams where keys in the first will take precedence over those in the second stream. */
nano::error & nano::tomlconfig::read (std::istream & stream_first_a, std::istream & stream_second_a)
{
try
{
tree = cpptoml::parse_base_and_override_files (stream_first_a, stream_second_a, cpptoml::parser::merge_type::ignore, true);
}
catch (std::runtime_error const & ex)
{
*error = ex;
}
return *error;
}
void nano::tomlconfig::write (boost::filesystem::path const & path_a)
{
std::fstream stream;
open_or_create (stream, path_a.string ());
write (stream);
}
void nano::tomlconfig::write (std::ostream & stream_a) const
{
cpptoml::toml_writer writer{ stream_a, "" };
tree->accept (writer);
}
/** Open configuration file, create if necessary */
void nano::tomlconfig::open_or_create (std::fstream & stream_a, std::string const & path_a)
{
if (!boost::filesystem::exists (path_a))
{
// Create temp stream to first create the file
std::ofstream stream (path_a);
// Set permissions before opening otherwise Windows only has read permissions
nano::set_secure_perm_file (path_a);
}
stream_a.open (path_a);
}
/** Returns the table managed by this instance */
std::shared_ptr<cpptoml::table> nano::tomlconfig::get_tree ()
{
return tree;
}
/** Returns true if the toml table is empty */
bool nano::tomlconfig::empty () const
{
return tree->empty ();
}
boost::optional<nano::tomlconfig> nano::tomlconfig::get_optional_child (std::string const & key_a)
{
boost::optional<tomlconfig> child_config;
if (tree->contains (key_a))
{
return tomlconfig (tree->get_table (key_a), error);
}
return child_config;
}
nano::tomlconfig nano::tomlconfig::get_required_child (std::string const & key_a)
{
if (!tree->contains (key_a))
{
*error = nano::error_config::missing_value;
error->set_message ("Missing configuration node: " + key_a);
return *this;
}
else
{
return tomlconfig (tree->get_table (key_a), error);
}
}
nano::tomlconfig & nano::tomlconfig::put_child (std::string const & key_a, nano::tomlconfig & conf_a)
{
tree->insert (key_a, conf_a.get_tree ());
return *this;
}
nano::tomlconfig & nano::tomlconfig::replace_child (std::string const & key_a, nano::tomlconfig & conf_a)
{
tree->erase (key_a);
put_child (key_a, conf_a);
return *this;
}
/** Returns true if \p key_a is present */
bool nano::tomlconfig::has_key (std::string const & key_a)
{
return tree->contains (key_a);
}
/** Erase the property of given key */
nano::tomlconfig & nano::tomlconfig::erase (std::string const & key_a)
{
tree->erase (key_a);
return *this;
}
std::shared_ptr<cpptoml::array> nano::tomlconfig::create_array (std::string const & key, boost::optional<char const *> documentation_a)
{
if (!has_key (key))
{
auto arr = cpptoml::make_array ();
tree->insert (key, arr);
if (documentation_a)
{
doc (key, *documentation_a);
}
}
return tree->get_qualified (key)->as_array ();
}
/**
* Erase keys whose values are equal to the one in \p defaults
*/
void nano::tomlconfig::erase_default_values (tomlconfig & defaults_a)
{
std::shared_ptr<cpptoml::table> clone = std::dynamic_pointer_cast<cpptoml::table> (tree->clone ());
tomlconfig self (clone);
// The toml library doesn't offer a general way to compare values, so let the diff run on a stringified parse
std::stringstream ss_self;
write (ss_self);
self.read (ss_self);
tomlconfig defaults_l;
std::stringstream ss;
defaults_a.write (ss);
defaults_l.read (ss);
erase_defaults (defaults_l.get_tree (), self.get_tree (), get_tree ());
}
std::string nano::tomlconfig::to_string ()
{
std::stringstream ss;
cpptoml::toml_writer writer{ ss, "" };
tree->accept (writer);
return ss.str ();
}
std::string nano::tomlconfig::to_string_commented_entries ()
{
std::stringstream ss, ss_processed;
cpptoml::toml_writer writer{ ss, "" };
tree->accept (writer);
std::string line;
while (std::getline (ss, line, '\n'))
{
if (!line.empty () && line[0] != '#' && line[0] != '[')
{
line = "#" + line;
}
ss_processed << line << std::endl;
}
return ss_processed.str ();
}
// boost's lexical cast doesn't handle (u)int8_t
nano::tomlconfig & nano::tomlconfig::get_config (bool optional, std::string const & key, uint8_t & target, uint8_t default_value)
{
try
{
if (tree->contains_qualified (key))
{
int64_t tmp;
auto val (tree->get_qualified_as<std::string> (key));
if (!boost::conversion::try_lexical_convert<int64_t> (*val, tmp) || tmp < 0 || tmp > 255)
{
conditionally_set_error<uint8_t> (nano::error_config::invalid_value, optional, key);
}
else
{
target = static_cast<uint8_t> (tmp);
}
}
else if (!optional)
{
conditionally_set_error<uint8_t> (nano::error_config::missing_value, optional, key);
}
else
{
target = default_value;
}
}
catch (std::runtime_error & ex)
{
conditionally_set_error<uint8_t> (ex, optional, key);
}
return *this;
}
nano::tomlconfig & nano::tomlconfig::get_config (bool optional, std::string const & key, bool & target, bool default_value)
{
auto bool_conv = [this, &target, &key, optional] (std::string val) {
if (val == "true")
{
target = true;
}
else if (val == "false")
{
target = false;
}
else if (!*error)
{
conditionally_set_error<bool> (nano::error_config::invalid_value, optional, key);
}
};
try
{
if (tree->contains_qualified (key))
{
auto val (tree->get_qualified_as<std::string> (key));
bool_conv (*val);
}
else if (!optional)
{
conditionally_set_error<bool> (nano::error_config::missing_value, optional, key);
}
else
{
target = default_value;
}
}
catch (std::runtime_error & ex)
{
conditionally_set_error<bool> (ex, optional, key);
}
return *this;
}
/** Compare two stringified configs, remove keys where values are equal */
void nano::tomlconfig::erase_defaults (std::shared_ptr<cpptoml::table> const & base, std::shared_ptr<cpptoml::table> const & other, std::shared_ptr<cpptoml::table> const & update_target)
{
std::vector<std::string> erased;
debug_assert (other != nullptr);
for (auto & item : *other)
{
std::string const & key = item.first;
if (other->contains (key) && base->contains (key))
{
auto value = item.second;
if (value->is_table ())
{
auto child_base = base->get_table (key);
auto child_other = other->get_table (key);
auto child_target = update_target->get_table (key);
erase_defaults (child_base, child_other, child_target);
if (child_target->empty ())
{
erased.push_back (key);
}
}
else if (value->is_array ())
{
auto arr_other = other->get_array (key)->get ();
auto arr_base = base->get_array (key)->get ();
if (arr_other.size () == arr_base.size ())
{
bool equal = std::equal (arr_other.begin (), arr_other.end (), arr_base.begin (),
[] (auto const & item1, auto const & item2) -> bool {
return (item1->template as<std::string> ()->get () == item2->template as<std::string> ()->get ());
});
if (equal)
{
erased.push_back (key);
}
}
}
else if (value->is_value ())
{
auto val_other = std::dynamic_pointer_cast<cpptoml::value<std::string>> (other->get (key));
auto val_base = std::dynamic_pointer_cast<cpptoml::value<std::string>> (base->get (key));
if (val_other->get () == val_base->get ())
{
erased.push_back (key);
}
}
}
}
for (auto & key : erased)
{
update_target->erase (key);
}
}
nano::tomlconfig & nano::tomlconfig::get_config (bool optional, std::string key, boost::asio::ip::address_v6 & target, boost::asio::ip::address_v6 const & default_value)
{
try
{
if (tree->contains_qualified (key))
{
auto address_l (tree->get_qualified_as<std::string> (key));
boost::system::error_code bec;
target = boost::asio::ip::make_address_v6 (address_l.value_or (""), bec);
if (bec)
{
conditionally_set_error<boost::asio::ip::address_v6> (nano::error_config::invalid_value, optional, key);
}
}
else if (!optional)
{
conditionally_set_error<boost::asio::ip::address_v6> (nano::error_config::missing_value, optional, key);
}
else
{
target = default_value;
}
}
catch (std::runtime_error & ex)
{
conditionally_set_error<boost::asio::ip::address_v6> (ex, optional, key);
}
return *this;
}