diff --git a/CMakeLists.txt b/CMakeLists.txt index fc91fca..0eb14e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,6 +86,7 @@ set(INCLUDE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/include") set(SRCS src/Backends/Ini/IniBackend.cxx + src/Backends/String/StringBackend.cxx src/Backends/Json/JsonBackend.cxx src/ConfigurationInterface.cxx src/ConfigurationFactory.cxx @@ -95,12 +96,13 @@ set(SRCS message(STATUS "Backends") message(STATUS " Compiling INI backend") message(STATUS " Compiling JSON backend") +message(STATUS " Compiling STRING backend") # Create library add_library(Configuration SHARED ${SRCS} $<$:src/Backends/Consul/ConsulBackend.cxx>) target_include_directories(Configuration - PUBLIC - $ + PUBLIC + $ $ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src @@ -140,6 +142,7 @@ set(TEST_SRCS test/TestExamples.cxx test/TestIni.cxx test/TestJson.cxx + test/TestString.cxx ) if(ppconsul_FOUND) diff --git a/src/Backends/String/StringBackend.cxx b/src/Backends/String/StringBackend.cxx new file mode 100644 index 0000000..6d236be --- /dev/null +++ b/src/Backends/String/StringBackend.cxx @@ -0,0 +1,87 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file StringBackend.cxx +/// \brief Configuration interface for files. Port of Configuration.h & Configuration.cxx +/// + +#include "StringBackend.h" +#include +#include +#include +#include + +namespace o2 +{ +namespace configuration +{ +namespace backends +{ + +StringBackend::StringBackend(const std::string& s) +{ + auto cfgStr = boost::trim_copy_if(s, boost::is_any_of(" \n\t")); + if (cfgStr.empty()) { + throw std::runtime_error("string cfg is empty"); + } + + std::vector tokens; + boost::split(tokens, s, boost::is_any_of(";")); + + for (auto& token : tokens) { + const auto equals_idx = token.find_first_of('='); + if (std::string::npos != equals_idx) { + mTree.put(boost::trim_copy(token.substr(0, equals_idx)), + boost::trim_copy(token.substr(equals_idx + 1))); + } else { + throw std::runtime_error("Not a key value pair" + token); + } + } +} + +void StringBackend::putString(const std::string&, const std::string&) +{ + throw std::runtime_error("String backend does not support putting values"); +} + +boost::optional StringBackend::getString(const std::string& path) +{ + // To use a custom separator instead of the default '.', we need to construct the path_type object explicitly + return mTree.get_optional(decltype(mTree)::path_type(addPrefix(path), getSeparator())); +} + +boost::property_tree::ptree + StringBackend::getRecursive(const std::string& path) +{ + return mTree.get_child(decltype(mTree)::path_type(addPrefix(path), getSeparator())); +} + +KeyValueMap StringBackend::getRecursiveMap(const std::string& path) +{ + KeyValueMap map; + auto subTree = mTree.get_child(decltype(mTree)::path_type(addPrefix(path), getSeparator())); + + // define lambda to recursively interate tree + using boost::property_tree::ptree; + std::function parse = [&](const ptree& node, std::string key) { + map[key] = std::move(node.data()); + key = key.empty() ? "" : key + getSeparator(); + for (auto const& it : node) { + parse(it.second, key + it.first); + } + }; + + parse(subTree, std::string()); + return map; +} + +} // namespace backends +} // namespace configuration +} // namespace o2 diff --git a/src/Backends/String/StringBackend.h b/src/Backends/String/StringBackend.h new file mode 100644 index 0000000..1996c9f --- /dev/null +++ b/src/Backends/String/StringBackend.h @@ -0,0 +1,55 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file StringBackend.h +/// \brief Interpret a key=value;key2=value2... etc string +/// + +#ifndef O2_CONFIGURATION_BACKENDS_STRINGBACKEND_H_ +#define O2_CONFIGURATION_BACKENDS_STRINGBACKEND_H_ + +#include "../BackendBase.h" +#include +#include + +namespace o2 +{ +namespace configuration +{ +namespace backends +{ + +class StringBackend final : public BackendBase +{ + public: + /// Interprets a string as key value pairs. + /// \param s the key=value;key2=value2 string to be used as configuration + StringBackend(const std::string& s); + + /// Default destructor + virtual ~StringBackend() = default; + virtual void putString(const std::string& path, + const std::string& value) override; + virtual boost::optional + getString(const std::string& path) override; + virtual boost::property_tree::ptree + getRecursive(const std::string& path) override; + virtual KeyValueMap getRecursiveMap(const std::string& path) override; + + private: + boost::property_tree::ptree mTree; + std::string mPath; +}; + +} // namespace backends +} // namespace configuration +} // namespace o2 + +#endif // O2_CONFIGURATION_BACKENDS_STRINGBACKEND_H_ diff --git a/src/ConfigurationFactory.cxx b/src/ConfigurationFactory.cxx index 8a02765..9faceb6 100644 --- a/src/ConfigurationFactory.cxx +++ b/src/ConfigurationFactory.cxx @@ -13,12 +13,13 @@ /// /// \author Pascal Boeschoten, CERN +#include "Configuration/ConfigurationFactory.h" +#include "Backends/Json/JsonBackend.h" +#include "Backends/String/StringBackend.h" +#include #include #include #include -#include -#include "Configuration/ConfigurationFactory.h" -#include "Backends/Json/JsonBackend.h" #ifdef FLP_CONFIGURATION_BACKEND_CONSUL_ENABLED # include "Backends/Consul/ConsulBackend.h" @@ -50,6 +51,13 @@ auto getJson(const http::url& uri) -> UniqueConfiguration return backend; } +auto getString(const http::url& uri) -> UniqueConfiguration +{ + auto path = uri.host + uri.path; + auto backend = std::make_unique(path); + return backend; +} + #ifdef FLP_CONFIGURATION_BACKEND_CONSUL_ENABLED auto getConsul(const http::url& uri) -> UniqueConfiguration { @@ -76,11 +84,12 @@ auto ConfigurationFactory::getConfiguration(const std::string& uri) -> UniqueCon throw std::runtime_error("Ill-formed URI"); } - static const std::map> map = { - {"ini", getIni}, - {"json", getJson}, - {"consul", getConsul}, - }; + static const std::map> + map = {{"ini", getIni}, + {"json", getJson}, + {"consul", getConsul}, + {"str", getString}}; auto iterator = map.find(parsedUrl.protocol); if (iterator != map.end()) { diff --git a/test/TestString.cxx b/test/TestString.cxx new file mode 100644 index 0000000..4c5b09f --- /dev/null +++ b/test/TestString.cxx @@ -0,0 +1,74 @@ +/// \file TestString.cxx +/// \brief String backend unit tests. +/// +/// \author Adam Wegrzynek, CERN +/// + +#include +#include +#include +#include "Configuration/ConfigurationFactory.h" + +#define BOOST_TEST_MODULE StringBackend +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK +#include + +using namespace o2::configuration; + +namespace +{ + +BOOST_AUTO_TEST_CASE(StringSimple) +{ + auto conf = ConfigurationFactory::getConfiguration("str://key=value;key2=2;key2.key3=3.3"); + BOOST_CHECK_EQUAL(conf->get("key"), "value"); + BOOST_CHECK_EQUAL(conf->get("key2"), 2); + BOOST_CHECK_EQUAL(conf->get("key2.key3"), 3.3); +} + +BOOST_AUTO_TEST_CASE(StringEntireRec) +{ + auto conf = ConfigurationFactory::getConfiguration("str://key=value;key2=2;key2.key3=3.3"); + auto tree = conf->getRecursive(); + BOOST_CHECK_EQUAL(tree.get("key"), "value"); + BOOST_CHECK_EQUAL(tree.get("key2"), 2); + BOOST_CHECK_EQUAL(tree.get("key2.key3"), 3.3); +} + +BOOST_AUTO_TEST_CASE(StringSubRec) +{ + auto conf = ConfigurationFactory::getConfiguration("str://key=value;key2=2;key2.key3=3.3"); + auto tree = conf->getRecursive("key2"); + BOOST_CHECK_EQUAL(tree.get("key3"), 3.3); +} + +BOOST_AUTO_TEST_CASE(StringSimplePrefix) +{ + auto conf = ConfigurationFactory::getConfiguration("str://key=value;key2=2;key2.key3=3.3"); + conf->setPrefix("key2"); + BOOST_CHECK_EQUAL(conf->get("key3"), 3.3); +} + +BOOST_AUTO_TEST_CASE(StringRecMap) +{ + auto conf = ConfigurationFactory::getConfiguration("str://key=value;key2=2;key2.key3=3.3"); + auto map = conf->getRecursiveMap("key2"); + BOOST_CHECK_EQUAL(map["key3"], "3.3"); +} + +bool exceptionCheck(const std::runtime_error& e) +{ + if (e.what() == std::string("String backend does not support putting values")) { + return true; + } + return false; +} + +BOOST_AUTO_TEST_CASE(StringPutExc) +{ + auto conf = ConfigurationFactory::getConfiguration("str://key=value;key2=2;key2.key3=3.3"); + BOOST_CHECK_EXCEPTION(conf->put("key2.key4", 4), std::runtime_error, exceptionCheck); +} + +} // Anonymous namespace