From 18ee23c2a3ac7245f6186653f973734ec2534b0c Mon Sep 17 00:00:00 2001 From: Stephen Shelton Date: Tue, 24 Mar 2020 21:34:23 -0600 Subject: [PATCH] Support for comments in config definition --- llarp/config/definition.cpp | 31 +++++++ llarp/config/definition.hpp | 24 ++++++ test/config/test_llarp_config_output.cpp | 100 +++++++++++++++++++++++ 3 files changed, 155 insertions(+) diff --git a/llarp/config/definition.cpp b/llarp/config/definition.cpp index a169c39cf..359c8310d 100644 --- a/llarp/config/definition.cpp +++ b/llarp/config/definition.cpp @@ -112,6 +112,21 @@ Configuration::acceptAllOptions() }); } +void +Configuration::addSectionComment(const std::string& section, + std::string comment) +{ + m_sectionComments[section].push_back(std::move(comment)); +} + +void +Configuration::addOptionComment(const std::string& section, + const std::string& name, + std::string comment) +{ + m_definitionComments[section][name].push_back(std::move(comment)); +} + std::string Configuration::generateINIConfig(bool useValues) { @@ -123,9 +138,25 @@ Configuration::generateINIConfig(bool useValues) if (sectionsVisited > 0) oss << "\n"; + // TODO: this will create empty objects as a side effect of map's operator[] + // TODO: this also won't handle sections which have no definition + for (const std::string& comment : m_sectionComments[section]) + { + oss << "# " << comment << "\n"; + } + oss << "[" << section << "]\n"; visitDefinitions(section, [&](const std::string& name, const ConfigDefinition_ptr& def) { + + // TODO: as above, this will create empty objects + // TODO: as above (but more important): this won't handle definitions with no entries + // (i.e. those handled by UndeclaredValueHandler's) + for (const std::string& comment : m_definitionComments[section][name]) + { + oss << "# " << comment << "\n"; + } + if (useValues and def->numFound > 0) { oss << name << "=" << def->valueAsString(false) << "\n"; diff --git a/llarp/config/definition.hpp b/llarp/config/definition.hpp index c68a9603e..f64beffaf 100644 --- a/llarp/config/definition.hpp +++ b/llarp/config/definition.hpp @@ -299,6 +299,24 @@ namespace llarp void acceptAllOptions(); + /// Add comments for a given section. Comments are replayed in-order during config file + /// generation. A proper comment prefix will automatically be applied, and the entire comment + /// will otherwise be used verbatim (no automatic line separation, etc.). + /// + /// @param section + /// @param comment + void + addSectionComment(const std::string& section, std::string comment); + + /// Add comments for a given option. Similar to addSectionComment, but applies to a specific + /// [section]:name pair. + /// + /// @param section + /// @param name + /// @param comment + void + addOptionComment(const std::string& section, const std::string& name, std::string comment); + /// Generate a config string from the current config definition, optionally using overridden /// values. The generated config will preserve insertion order of both sections and their /// definitions. @@ -331,6 +349,12 @@ namespace llarp // track insertion order std::vector m_sectionOrdering; std::unordered_map> m_definitionOrdering; + + // comments for config file generation + using CommentList = std::vector; + using CommentsMap = std::unordered_map; + CommentsMap m_sectionComments; + std::unordered_map m_definitionComments; }; } // namespace llarp diff --git a/test/config/test_llarp_config_output.cpp b/test/config/test_llarp_config_output.cpp index e8d774725..3e69988f7 100644 --- a/test/config/test_llarp_config_output.cpp +++ b/test/config/test_llarp_config_output.cpp @@ -54,3 +54,103 @@ TEST_CASE("Configuration useValue test", "[config]") CHECK(config.generateINIConfig(true) == expectedWhenValueProvided); } +TEST_CASE("Configuration section comments test") +{ + llarp::Configuration config; + + config.addSectionComment("foo", "test comment"); + config.addSectionComment("foo", "test comment 2"); + config.defineOption(std::make_unique>( + "foo", "bar", true, 1)); + + std::string output = config.generateINIConfig(); + + CHECK(output == R"raw(# test comment +# test comment 2 +[foo] +bar=1 +)raw"); +} + +TEST_CASE("Configuration option comments test") +{ + llarp::Configuration config; + + config.addOptionComment("foo", "bar", "test comment 1"); + config.addOptionComment("foo", "bar", "test comment 2"); + config.defineOption(std::make_unique>( + "foo", "bar", true, 1)); + + std::string output = config.generateINIConfig(); + + CHECK(output == R"raw([foo] +# test comment 1 +# test comment 2 +bar=1 +)raw"); +} + +TEST_CASE("Configuration empty comments test") +{ + llarp::Configuration config; + + config.addSectionComment("foo", "section comment"); + config.addSectionComment("foo", ""); + + config.addOptionComment("foo", "bar", "option comment"); + config.addOptionComment("foo", "bar", ""); + config.defineOption(std::make_unique>( + "foo", "bar", true, 1)); + + std::string output = config.generateINIConfig(); + + CHECK(output == R"raw(# section comment +# +[foo] +# option comment +# +bar=1 +)raw"); +} + +TEST_CASE("Configuration multi option comments") +{ + llarp::Configuration config; + + config.addSectionComment("foo", "foo section comment"); + + config.addOptionComment("foo", "bar", "foo bar option comment"); + config.defineOption(std::make_unique>( + "foo", "bar", true, 1)); + + config.addOptionComment("foo", "baz", "foo baz option comment"); + config.defineOption(std::make_unique>( + "foo", "baz", true, 1)); + + std::string output = config.generateINIConfig(); + + CHECK(output == R"raw(# foo section comment +[foo] +# foo bar option comment +bar=1 +# foo baz option comment +baz=1 +)raw"); +} + +TEST_CASE("Configuration should print comments for missing keys") +{ + // TODO: this currently fails: how to implement? + llarp::Configuration config; + + config.addSectionComment("foo", "foo section comment"); + config.addOptionComment("foo", "bar", "foo bar option comment"); + + std::string output = config.generateINIConfig(); + + CHECK(output == R"raw(# foo section comment +[foo] +# foo bar option comment +bar= +)raw"); +}