From 50c50ce48a49fc287050df8c9518e3dd59a9aedf Mon Sep 17 00:00:00 2001 From: Willem Deconinck Date: Tue, 9 Jan 2024 16:27:51 +0100 Subject: [PATCH] Add introspection to eckit::Configuration --- src/eckit/config/Configuration.cc | 102 +++++++++++++++++++++++++++++ src/eckit/config/Configuration.h | 80 ++++++++++++++++++++++ tests/config/test_configuration.cc | 44 +++++++++++++ 3 files changed, 226 insertions(+) diff --git a/src/eckit/config/Configuration.cc b/src/eckit/config/Configuration.cc index 4e47d3b03..9cf88bf1b 100644 --- a/src/eckit/config/Configuration.cc +++ b/src/eckit/config/Configuration.cc @@ -460,6 +460,108 @@ LocalConfiguration Configuration::getSubConfiguration(const std::string& name) c return result; } +bool Configuration::isIntegral(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + return found && v.isNumber(); +} + +bool Configuration::isBoolean(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + return found && v.isBool(); +} + +bool Configuration::isFloatingPoint(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + return found && v.isDouble(); +} + +bool Configuration::isString(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + return found && v.isString(); +} + +bool Configuration::isList(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + return found && v.isList(); +} + +bool Configuration::isSubConfiguration(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + return found && (v.isMap() || v.isOrderedMap()); +} + + +bool Configuration::isIntegralList(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + if (found && v.isList()) { + if (v.size() == 0) { + return true; + } + auto& firstElement = v[0]; + return firstElement.isNumber(); + } + return false; +} + +bool Configuration::isBooleanList(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + if (found && v.isList()) { + if (v.size() == 0) { + return true; + } + auto& firstElement = v[0]; + return firstElement.isBool(); + } + return false; +} + +bool Configuration::isFloatingPointList(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + if (found && v.isList()) { + if (v.size() == 0) { + return true; + } + auto& firstElement = v[0]; + return firstElement.isDouble(); + } + return false; +} + +bool Configuration::isStringList(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + if (found && v.isList()) { + if (v.size() == 0) { + return true; + } + auto& firstElement = v[0]; + return firstElement.isString(); + } + return false; +} + +bool Configuration::isSubConfigurationList(const std::string& name) const { + bool found = false; + eckit::Value v = lookUp(name, found); + if (found && v.isList()) { + if (v.size() == 0) { + return true; + } + auto& firstElement = v[0]; + return firstElement.isMap() || firstElement.isOrderedMap(); + } + return false; +} + template void Configuration::_getWithDefault(const std::string& name, T& value, const T& defaultVal) const { if (!get(name, value)) { diff --git a/src/eckit/config/Configuration.h b/src/eckit/config/Configuration.h index 8f08f7802..435812b97 100644 --- a/src/eckit/config/Configuration.h +++ b/src/eckit/config/Configuration.h @@ -18,6 +18,7 @@ #include #include +#include #include "eckit/config/Parametrisation.h" @@ -130,6 +131,70 @@ class Configuration : public Parametrisation { virtual void hash(eckit::Hash&) const; + // -- Introspection methods + + bool isSubConfiguration(const std::string& name) const; + + bool isIntegral(const std::string& name) const; + + bool isBoolean(const std::string& name) const; + + bool isFloatingPoint(const std::string& name) const; + + bool isString(const std::string& name) const; + + bool isList(const std::string& name) const; + + bool isSubConfigurationList(const std::string& name) const; + + bool isIntegralList(const std::string& name) const; + + bool isBooleanList(const std::string& name) const; + + bool isFloatingPointList(const std::string& name) const; + + bool isStringList(const std::string& name) const; + + template + bool isConvertible(const std::string& name) const { + using _T = std::decay_t; + if constexpr(std::is_base_of_v) { + return isSubConfiguration(name); + } + else if constexpr(std::is_same_v<_T,int> || std::is_same_v<_T,long> || std::is_same_v<_T,long long> || std::is_same_v<_T,std::size_t>) { + return isIntegral(name) || isBoolean(name); + } + else if constexpr(std::is_same_v<_T,float> || std::is_same_v<_T,double>) { + return isFloatingPoint(name) || isIntegral(name) || isBoolean(name); + } + else if constexpr(std::is_same_v<_T,std::string>) { + return isString(name); + } + else if constexpr(is_vector<_T>::value) { + using _V = std::decay_t; + if constexpr(std::is_base_of_v) { + return isSubConfigurationList(name); + } + else if constexpr(std::is_same_v<_V,int> || std::is_same_v<_V,long> || std::is_same_v<_V,long long> || std::is_same_v<_V,std::size_t>) { + return isIntegralList(name) || isBooleanList(name); + } + else if constexpr(std::is_same_v<_V,float> || std::is_same_v<_V,double>) { + return isFloatingPointList(name) || isIntegralList(name) || isBooleanList(name); + } + else if constexpr(std::is_same_v<_V,std::string>) { + return isStringList(name); + } + } + else { + return false; + } + } + + template + bool isConvertible(const std::string& name, T&) const { + return isConvertible(name); + } + protected: // methods Configuration(const eckit::Value&, char separator = '.'); @@ -167,6 +232,21 @@ class Configuration : public Parametrisation { p.print(s); return s; } + +private: + + // Helper structs for introspection of template T in isConvertible method + template + struct is_vector { + using type = T ; + constexpr static bool value = false; + }; + + template + struct is_vector> { + using type = std::vector ; + constexpr static bool value = true; + }; }; //---------------------------------------------------------------------------------------------------------------------- diff --git a/tests/config/test_configuration.cc b/tests/config/test_configuration.cc index 623ed2646..624cec6a3 100644 --- a/tests/config/test_configuration.cc +++ b/tests/config/test_configuration.cc @@ -353,6 +353,8 @@ CASE("test_local_configuration") { LocalConfiguration manager; manager.set("name", "Sidonia"); manager.set("office", 1); + manager.set("height", 1.78); + manager.set("free", false); std::vector staff(2); staff[0].set("name", "Suske"); @@ -362,6 +364,8 @@ CASE("test_local_configuration") { local.set("manager", manager); local.set("staff", staff); + + local.set("books.count", 10); } const Configuration& conf = local; @@ -369,6 +373,9 @@ CASE("test_local_configuration") { std::vector staff; EXPECT(conf.get("manager", manager)); + + EXPECT(conf.isSubConfigurationList("staff")); + EXPECT(conf.isConvertible("staff",staff)); EXPECT(conf.get("staff", staff)); std::string name; @@ -388,6 +395,43 @@ CASE("test_local_configuration") { EXPECT(staff[1].get("office", office)); EXPECT(name == std::string("Wiske")); EXPECT(office == 3); + + int books_count; + EXPECT(conf.has("books")); + EXPECT(conf.get("books.count", books_count)); + EXPECT(books_count == 10); + + LocalConfiguration books; + EXPECT(conf.isSubConfiguration("books")); + conf.get("books",books); + EXPECT(books.getInt("count") == 10); + + EXPECT(conf.isConvertible("books")); + + EXPECT(conf.isList("staff")); + EXPECT(conf.isIntegral("manager.office")); + EXPECT(!conf.isFloatingPoint("manager.office")); + EXPECT(conf.isConvertible("manager.office")); + EXPECT(conf.isConvertible("manager.office")); + EXPECT(conf.isConvertible("manager.office")); + EXPECT(conf.isConvertible("manager.office")); + EXPECT(conf.isConvertible("manager.office")); + EXPECT(conf.isConvertible("manager.office")); + EXPECT(!conf.isConvertible("manager.office")); + EXPECT(!conf.isConvertible("manager.office")); + EXPECT(!conf.isConvertible("manager.office")); + + EXPECT(conf.isConvertible("manager.height")); + EXPECT(conf.isConvertible("manager.height")); + EXPECT(!conf.isConvertible("manager.height")); + EXPECT(!conf.isConvertible("manager.height")); + EXPECT(!conf.isConvertible("manager.height")); + EXPECT(!conf.isConvertible("manager.height")); + + double manager_height; + EXPECT(conf.get("manager.height", manager_height)); + EXPECT(manager_height == 1.78); + } CASE("Hash a configuration") {