#ifndef XRPL_BASICS_BASICCONFIG_H_INCLUDED #define XRPL_BASICS_BASICCONFIG_H_INCLUDED #include #include #include #include #include #include #include #include namespace ripple { using IniFileSections = std::unordered_map>; //------------------------------------------------------------------------------ /** Holds a collection of configuration values. A configuration file contains zero or more sections. */ class Section { private: std::string name_; std::unordered_map lookup_; std::vector lines_; std::vector 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 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 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( "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 const& lines); /** Append a line to this section. */ void append(std::string const& line) { append(std::vector{line}); } /** Returns `true` if a key with the given name exists. */ bool exists(std::string const& name) const; template std::optional get(std::string const& name) const { auto const iter = lookup_.find(name); if (iter == lookup_.end()) return std::nullopt; return boost::lexical_cast(iter->second); } /// Returns a value if present, else another value. template T value_or(std::string const& name, T const& other) const { auto const v = get(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 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 bool set(T& target, std::string const& name, Section const& section) { bool found_and_valid = false; try { auto const val = section.get(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 bool set(T& target, T const& defaultValue, std::string const& name, Section const& section) { bool found_and_valid = set(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 T get(Section const& section, std::string const& name, T const& defaultValue = T{}) { try { return section.value_or(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 bool get_if_exists(Section const& section, std::string const& name, T& v) { return set(v, name, section); } template <> inline bool get_if_exists(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 ripple #endif