Files
rippled/include/xrpl/basics/BasicConfig.h
2025-11-10 11:49:19 -05:00

386 lines
9.0 KiB
C++

#ifndef XRPL_BASICS_BASICCONFIG_H_INCLUDED
#define XRPL_BASICS_BASICCONFIG_H_INCLUDED
#include <xrpl/basics/contract.h>
#include <boost/beast/core/string.hpp>
#include <boost/lexical_cast.hpp>
#include <algorithm>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
namespace xrpl {
using IniFileSections =
std::unordered_map<std::string, std::vector<std::string>>;
//------------------------------------------------------------------------------
/** Holds a collection of configuration values.
A configuration file contains zero or more sections.
*/
class Section
{
private:
std::string name_;
std::unordered_map<std::string, std::string> lookup_;
std::vector<std::string> lines_;
std::vector<std::string> values_;
bool had_trailing_comments_ = false;
using const_iterator = decltype(lookup_)::const_iterator;
public:
/** Create an empty section. */
explicit Section(std::string const& name = "");
/** Returns the name of this section. */
std::string const&
name() const
{
return name_;
}
/** Returns all the lines in the section.
This includes everything.
*/
std::vector<std::string> const&
lines() const
{
return lines_;
}
/** Returns all the values in the section.
Values are non-empty lines which are not key/value pairs.
*/
std::vector<std::string> const&
values() const
{
return values_;
}
/**
* Set the legacy value for this section.
*/
void
legacy(std::string value)
{
if (lines_.empty())
lines_.emplace_back(std::move(value));
else
lines_[0] = std::move(value);
}
/**
* Get the legacy value for this section.
*
* @return The retrieved value. A section with an empty legacy value returns
an empty string.
*/
std::string
legacy() const
{
if (lines_.empty())
return "";
if (lines_.size() > 1)
Throw<std::runtime_error>(
"A legacy value must have exactly one line. Section: " + name_);
return lines_[0];
}
/** Set a key/value pair.
The previous value is discarded.
*/
void
set(std::string const& key, std::string const& value);
/** Append a set of lines to this section.
Lines containing key/value pairs are added to the map,
else they are added to the values list. Everything is
added to the lines list.
*/
void
append(std::vector<std::string> const& lines);
/** Append a line to this section. */
void
append(std::string const& line)
{
append(std::vector<std::string>{line});
}
/** Returns `true` if a key with the given name exists. */
bool
exists(std::string const& name) const;
template <class T = std::string>
std::optional<T>
get(std::string const& name) const
{
auto const iter = lookup_.find(name);
if (iter == lookup_.end())
return std::nullopt;
return boost::lexical_cast<T>(iter->second);
}
/// Returns a value if present, else another value.
template <class T>
T
value_or(std::string const& name, T const& other) const
{
auto const v = get<T>(name);
return v.has_value() ? *v : other;
}
// indicates if trailing comments were seen
// during the appending of any lines/values
bool
had_trailing_comments() const
{
return had_trailing_comments_;
}
friend std::ostream&
operator<<(std::ostream&, Section const& section);
// Returns `true` if there are no key/value pairs.
bool
empty() const
{
return lookup_.empty();
}
// Returns the number of key/value pairs.
std::size_t
size() const
{
return lookup_.size();
}
// For iteration of key/value pairs.
const_iterator
begin() const
{
return lookup_.cbegin();
}
// For iteration of key/value pairs.
const_iterator
cbegin() const
{
return lookup_.cbegin();
}
// For iteration of key/value pairs.
const_iterator
end() const
{
return lookup_.cend();
}
// For iteration of key/value pairs.
const_iterator
cend() const
{
return lookup_.cend();
}
};
//------------------------------------------------------------------------------
/** Holds unparsed configuration information.
The raw data sections are processed with intermediate parsers specific
to each module instead of being all parsed in a central location.
*/
class BasicConfig
{
private:
std::unordered_map<std::string, Section> map_;
public:
/** Returns `true` if a section with the given name exists. */
bool
exists(std::string const& name) const;
/** Returns the section with the given name.
If the section does not exist, an empty section is returned.
*/
/** @{ */
Section&
section(std::string const& name);
Section const&
section(std::string const& name) const;
Section const&
operator[](std::string const& name) const
{
return section(name);
}
Section&
operator[](std::string const& name)
{
return section(name);
}
/** @} */
/** Overwrite a key/value pair with a command line argument
If the section does not exist it is created.
The previous value, if any, is overwritten.
*/
void
overwrite(
std::string const& section,
std::string const& key,
std::string const& value);
/** Remove all the key/value pairs from the section.
*/
void
deprecatedClearSection(std::string const& section);
/**
* Set a value that is not a key/value pair.
*
* The value is stored as the section's first value and may be retrieved
* through section::legacy.
*
* @param section Name of the section to modify.
* @param value Contents of the legacy value.
*/
void
legacy(std::string const& section, std::string value);
/**
* Get the legacy value of a section. A section with a
* single-line value may be retrieved as a legacy value.
*
* @param sectionName Retrieve the contents of this section's
* legacy value.
* @return Contents of the legacy value.
*/
std::string
legacy(std::string const& sectionName) const;
friend std::ostream&
operator<<(std::ostream& ss, BasicConfig const& c);
// indicates if trailing comments were seen
// in any loaded Sections
bool
had_trailing_comments() const
{
return std::any_of(map_.cbegin(), map_.cend(), [](auto s) {
return s.second.had_trailing_comments();
});
}
protected:
void
build(IniFileSections const& ifs);
};
//------------------------------------------------------------------------------
/** Set a value from a configuration Section
If the named value is not found or doesn't parse as a T,
the variable is unchanged.
@return `true` if value was set.
*/
template <class T>
bool
set(T& target, std::string const& name, Section const& section)
{
bool found_and_valid = false;
try
{
auto const val = section.get<T>(name);
if ((found_and_valid = val.has_value()))
target = *val;
}
catch (boost::bad_lexical_cast&)
{
}
return found_and_valid;
}
/** Set a value from a configuration Section
If the named value is not found or doesn't cast to T,
the variable is assigned the default.
@return `true` if the named value was found and is valid.
*/
template <class T>
bool
set(T& target,
T const& defaultValue,
std::string const& name,
Section const& section)
{
bool found_and_valid = set<T>(target, name, section);
if (!found_and_valid)
target = defaultValue;
return found_and_valid;
}
/** Retrieve a key/value pair from a section.
@return The value string converted to T if it exists
and can be parsed, or else defaultValue.
*/
// NOTE This routine might be more clumsy than the previous two
template <class T = std::string>
T
get(Section const& section,
std::string const& name,
T const& defaultValue = T{})
{
try
{
return section.value_or<T>(name, defaultValue);
}
catch (boost::bad_lexical_cast&)
{
}
return defaultValue;
}
inline std::string
get(Section const& section, std::string const& name, char const* defaultValue)
{
try
{
auto const val = section.get(name);
if (val.has_value())
return *val;
}
catch (boost::bad_lexical_cast&)
{
}
return defaultValue;
}
template <class T>
bool
get_if_exists(Section const& section, std::string const& name, T& v)
{
return set<T>(v, name, section);
}
template <>
inline bool
get_if_exists<bool>(Section const& section, std::string const& name, bool& v)
{
int intVal = 0;
auto stat = get_if_exists(section, name, intVal);
if (stat)
v = bool(intVal);
return stat;
}
} // namespace xrpl
#endif