From c141ee889156c2a9d0cf49b122e66553e7645fac Mon Sep 17 00:00:00 2001 From: Nathan Hughes Date: Tue, 16 Jul 2024 20:48:54 +0000 Subject: [PATCH 1/2] add ability to parse yaml maps in the order they are specified in the file --- .../config_utilities/internal/visitor.h | 18 +++++++++- .../internal/visitor_impl.hpp | 36 ++++++++++++++++--- .../config_utilities/internal/yaml_utils.h | 3 +- config_utilities/src/yaml_utils.cpp | 11 +++--- config_utilities/test/tests/config_maps.cpp | 21 ++++++++++- 5 files changed, 76 insertions(+), 13 deletions(-) diff --git a/config_utilities/include/config_utilities/internal/visitor.h b/config_utilities/include/config_utilities/internal/visitor.h index 4e39145..37717b9 100644 --- a/config_utilities/include/config_utilities/internal/visitor.h +++ b/config_utilities/include/config_utilities/internal/visitor.h @@ -47,6 +47,11 @@ #include "config_utilities/internal/yaml_parser.h" namespace config { + +//! Aliased vector type to represent "insertion" ordered map (instead of key ordered) +template +using OrderedMap = std::vector>; + namespace internal { /** @@ -99,10 +104,16 @@ struct Visitor { template (), bool>::type = true> static void visitField(std::vector& config, const std::string& field_name, const std::string& /* unit */); - // Map (ordered) of config types. + // Map (ordered by key) of config types. template (), bool>::type = true> static void visitField(std::map& config, const std::string& field_name, const std::string& /* unit */); + // Map (ordered by yaml order) of config types. + template (), bool>::type = true> + static void visitField(OrderedMap& config, + const std::string& field_name, + const std::string& /* unit */); + // Execute a check. static void visitCheck(const CheckBase& check); @@ -179,6 +190,11 @@ void declare_config(std::map& map_config) { Visitor::visitField(map_config, "", ""); } +template (), bool>::type = true> +void declare_config(OrderedMap& map_config) { + Visitor::visitField(map_config, "", ""); +} + // Public interfaces to declare properties in declare_config. // argument-dependent-lookup (ADL) so definitions of 'declare_config()' can be found anywhere. Note that diff --git a/config_utilities/include/config_utilities/internal/visitor_impl.hpp b/config_utilities/include/config_utilities/internal/visitor_impl.hpp index 66ea3fc..0c11ab8 100644 --- a/config_utilities/include/config_utilities/internal/visitor_impl.hpp +++ b/config_utilities/include/config_utilities/internal/visitor_impl.hpp @@ -269,7 +269,33 @@ void Visitor::visitField(std::vector& config, const std::string& field_ // Visit a map of subconfigs. template (), bool>::type> -void Visitor::visitField(std::map& config, const std::string& field_name, const std::string& /* unit */) { +void Visitor::visitField(std::map& config, const std::string& field_name, const std::string& unit) { + Visitor& visitor = Visitor::instance(); + if (visitor.mode == Visitor::Mode::kGetDefaults) { + return; + } + + OrderedMap intermediate; + // copy current config state if doing something else other than settings the config + if (visitor.mode != Visitor::Mode::kSet) { + intermediate.insert(intermediate.begin(), config.begin(), config.end()); + } + + // make use of more general named parsing + visitField(intermediate, field_name, unit); + + // assign parsed configs to actual map if we're setting the config + if (visitor.mode == Visitor::Mode::kSet) { + config.clear(); + // note that we don't use insert here to guarantee that duplicates behave as expected (overriding with the last) + for (const auto& [key, value] : intermediate) { + config[key] = value; + } + } +} + +template (), bool>::type> +void Visitor::visitField(OrderedMap& config, const std::string& field_name, const std::string& /* unit */) { Visitor& visitor = Visitor::instance(); if (visitor.mode == Visitor::Mode::kGetDefaults) { return; @@ -281,10 +307,10 @@ void Visitor::visitField(std::map& config, const std::string& field_ const auto map_ns = visitor.name_space.empty() ? field_name : visitor.name_space + "/" + field_name; const auto nodes = getNodeMap(lookupNamespace(visitor.data.data, map_ns)); for (auto&& [key, node] : nodes) { - auto iter = config.emplace(YAML::Node(key).template as(), ConfigT()).first; - auto& sub_config = iter->second; - visitor.data.sub_configs.emplace_back(setValues(sub_config, node, false, "", field_name, false)); - visitor.data.sub_configs.back().map_config_key = key; + auto& entry = config.emplace_back(); + entry.first = key.template as(); + visitor.data.sub_configs.emplace_back(setValues(entry.second, node, false, "", field_name, false)); + visitor.data.sub_configs.back().map_config_key = key.as(); } } diff --git a/config_utilities/include/config_utilities/internal/yaml_utils.h b/config_utilities/include/config_utilities/internal/yaml_utils.h index 95faecd..2d0fe70 100644 --- a/config_utilities/include/config_utilities/internal/yaml_utils.h +++ b/config_utilities/include/config_utilities/internal/yaml_utils.h @@ -78,7 +78,6 @@ std::vector getNodeArray(const YAML::Node& node); * @param node The node to convert. * @return The list of nodes. Nodes stored in this struct are references to the original data. */ -std::map getNodeMap(const YAML::Node& node); - +std::vector> getNodeMap(const YAML::Node& node); } // namespace config::internal diff --git a/config_utilities/src/yaml_utils.cpp b/config_utilities/src/yaml_utils.cpp index 75ca5e3..6eb5ae9 100644 --- a/config_utilities/src/yaml_utils.cpp +++ b/config_utilities/src/yaml_utils.cpp @@ -147,19 +147,22 @@ std::vector getNodeArray(const YAML::Node& node) { return result; } -std::map getNodeMap(const YAML::Node& node) { - std::map result; +std::vector> getNodeMap(const YAML::Node& node) { + std::vector> result; if (node.IsMap()) { for (const auto& kv_pair : node) { - result.emplace(kv_pair.first.as(), kv_pair.second); + result.emplace_back(kv_pair); } } else if (node.IsSequence()) { size_t index = 0; for (const auto& sub_node : node) { - result.emplace(std::to_string(index), sub_node); + auto& new_pair = result.emplace_back(); + new_pair.first = index; + new_pair.second = YAML::Clone(sub_node); ++index; } } + return result; } diff --git a/config_utilities/test/tests/config_maps.cpp b/config_utilities/test/tests/config_maps.cpp index af5edac..8f753c5 100644 --- a/config_utilities/test/tests/config_maps.cpp +++ b/config_utilities/test/tests/config_maps.cpp @@ -71,7 +71,7 @@ void declare_config(MapConfig& config) { check(config.f, GE, 0, "f"); } -void PrintTo(const MapConfig& conf, std::ostream* os) { *os << toString(conf); } +void PrintTo(const MapConfig& conf, std::ostream* os) { *os << "\n" << toString(conf); } struct ConfigWithMaps { int i = 0; @@ -118,6 +118,25 @@ TEST(ConfigMaps, FromYamlMap) { EXPECT_EQ(configs, expected); } +TEST(ConfigMaps, FromYamlMapOrdered) { + const std::string yaml_map = R"( +z: + s: "a" + f: 1 +x: + s: "b" + f: 2 +y: + s: "c" + f: 3 +)"; + const auto node = YAML::Load(yaml_map); + + const auto configs = fromYaml>(node); + OrderedMap expected{{"z", {"a", 1}}, {"x", {"b", 2}}, {"y", {"c", 3}}}; + EXPECT_EQ(configs, expected); +} + TEST(ConfigMaps, FromYamlSeq) { const std::string yaml_seq = R"( - s: "a" From 9bcee743bfbb66a6254cf273ccad8fcdfac1da25 Mon Sep 17 00:00:00 2001 From: Nathan Hughes Date: Tue, 16 Jul 2024 21:17:34 +0000 Subject: [PATCH 2/2] use template syntax --- .../include/config_utilities/internal/visitor_impl.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_utilities/include/config_utilities/internal/visitor_impl.hpp b/config_utilities/include/config_utilities/internal/visitor_impl.hpp index 0c11ab8..b171b83 100644 --- a/config_utilities/include/config_utilities/internal/visitor_impl.hpp +++ b/config_utilities/include/config_utilities/internal/visitor_impl.hpp @@ -310,7 +310,7 @@ void Visitor::visitField(OrderedMap& config, const std::string& fiel auto& entry = config.emplace_back(); entry.first = key.template as(); visitor.data.sub_configs.emplace_back(setValues(entry.second, node, false, "", field_name, false)); - visitor.data.sub_configs.back().map_config_key = key.as(); + visitor.data.sub_configs.back().map_config_key = key.template as(); } }