diff --git a/mola_yaml/include/mola_yaml/yaml_helpers.h b/mola_yaml/include/mola_yaml/yaml_helpers.h index 552beb3b..92a82c5e 100644 --- a/mola_yaml/include/mola_yaml/yaml_helpers.h +++ b/mola_yaml/include/mola_yaml/yaml_helpers.h @@ -14,7 +14,7 @@ #include #include -#include +#include #include namespace mola @@ -27,7 +27,10 @@ struct YAMLParseOptions { bool doIncludes{true}; //!< "$include{}"s bool doCmdRuns{true}; //!< "$()"s - bool doEnvVars{true}; //!< "${}"s + bool doEnvVars{true}; //!< "${}"s (from env vars and field "variables") + + /** Custom variables for replacements like `${name}` => `value` */ + std::map variables; /** If not empty, base reference path which respect to "$include{}"s are * specified. Automatically filled in by load_yaml_file() */ diff --git a/mola_yaml/src/yaml_helpers.cpp b/mola_yaml/src/yaml_helpers.cpp index 9e8fed62..a5c08bf8 100644 --- a/mola_yaml/src/yaml_helpers.cpp +++ b/mola_yaml/src/yaml_helpers.cpp @@ -37,7 +37,9 @@ namespace fs = std::filesystem; using mrpt::containers::yaml; -static std::string::size_type findClosing( +namespace +{ +std::string::size_type findClosing( size_t pos, const std::string& s, const char searchEndChar, const char otherStartChar) { @@ -59,8 +61,7 @@ static std::string::size_type findClosing( } // "foo|bar" -> {"foo","bar"} -static std::tuple splitVerticalBar( - const std::string& s) +std::tuple splitVerticalBar(const std::string& s) { const auto posBar = s.find("|"); if (posBar == std::string::npos) return {s, {}}; @@ -68,7 +69,7 @@ static std::tuple splitVerticalBar( return {s.substr(0, posBar), s.substr(posBar + 1)}; } -static std::string trimWSNL(const std::string& s) +std::string trimWSNL(const std::string& s) { std::string str = s; mrpt::system::trim(str); @@ -76,6 +77,8 @@ static std::string trimWSNL(const std::string& s) str.erase(std::remove(str.begin(), str.end(), '\n'), str.end()); return str; } +} // namespace + std::string mola::yaml_to_string(const mrpt::containers::yaml& cfg) { std::stringstream ss; @@ -83,7 +86,9 @@ std::string mola::yaml_to_string(const mrpt::containers::yaml& cfg) return ss.str(); } -static std::string parseEnvVars( +namespace +{ +std::string parseVars( const std::string& text, const mola::YAMLParseOptions& opts) { MRPT_TRY_START @@ -106,30 +111,43 @@ static std::string parseEnvVars( const auto [varname, defaultValue] = splitVerticalBar(varnameOrg); + // 1st try: match to env vars std::string varvalue; const char* v = ::getenv(varname.c_str()); if (v != nullptr) + { // match: varvalue = std::string(v); + } else { - // Handle special variable names: + // 2nd try: handle special variable names: // ${CURRENT_YAML_FILE_PATH} if (varname == "CURRENT_YAML_FILE_PATH") varvalue = opts.includesBasePath; - else if (!defaultValue.empty()) { varvalue = defaultValue; } else - { - THROW_EXCEPTION_FMT( - "YAML parseEnvVars(): Undefined environment variable: ${%s}", - varname.c_str()); - } + // 3rd try: custom user variables + if (auto it = opts.variables.find(varname); + it != opts.variables.end()) + { + varvalue = it->second; + } + else + // 4th: default: + if (!defaultValue.empty()) { varvalue = defaultValue; } + else + { + THROW_EXCEPTION_FMT( + "YAML parseEnvVars(): Undefined environment variable: " + "${%s}", + varname.c_str()); + } } - return parseEnvVars(pre + varvalue + post.substr(post_end + 1), opts); + return parseVars(pre + varvalue + post.substr(post_end + 1), opts); MRPT_TRY_END } -static std::string parseCmdRuns( +std::string parseCmdRuns( const std::string& text, const mola::YAMLParseOptions& opts) { MRPT_TRY_START @@ -167,7 +185,7 @@ static std::string parseCmdRuns( MRPT_TRY_END } -static void recursiveParseNodeForIncludes( +void recursiveParseNodeForIncludes( yaml::node_t& n, const mola::YAMLParseOptions& opts) { if (n.isScalar()) @@ -241,7 +259,7 @@ static void recursiveParseNodeForIncludes( } } -static std::string parseIncludes( +std::string parseIncludes( const std::string& text, const mola::YAMLParseOptions& opts) { MRPT_TRY_START @@ -255,6 +273,8 @@ static std::string parseIncludes( MRPT_TRY_END } +} // namespace + mrpt::containers::yaml mola::parse_yaml( const mrpt::containers::yaml& input, const mola::YAMLParseOptions& opts) { @@ -274,7 +294,7 @@ std::string mola::parse_yaml( if (opts.doCmdRuns) s = parseCmdRuns(s, opts); // 3) Parse "${}"s - if (opts.doEnvVars) s = parseEnvVars(s, opts); + if (opts.doEnvVars) s = parseVars(s, opts); return s; } diff --git a/mola_yaml/tests/test-yaml-parser.cpp b/mola_yaml/tests/test-yaml-parser.cpp index f31977c5..39f4d92a 100644 --- a/mola_yaml/tests/test-yaml-parser.cpp +++ b/mola_yaml/tests/test-yaml-parser.cpp @@ -18,7 +18,9 @@ using namespace std::string_literals; -static void test_yaml2string() +namespace +{ +void test_yaml2string() { { const auto data = mrpt::containers::yaml::Map({{"A", 1.0}, {"B", 3}}); @@ -43,9 +45,10 @@ b: "foo" - c d: va: 'z' +e: '${foo|default1}' )###"; -static void test_parseSimple() +void test_parseSimple() { { const auto y = mrpt::containers::yaml::FromText(txt1); @@ -57,7 +60,35 @@ static void test_parseSimple() } } -static void test_parseIncludes() +void test_parseCustomVars() +{ + { + const auto y = mola::parse_yaml(mrpt::containers::yaml::FromText(txt1)); + ASSERT_(y.isMap()); + ASSERT_EQUAL_(y["e"].as(), "default1"); + } + { + mola::YAMLParseOptions opts; + opts.doEnvVars = false; + + const auto y = + mola::parse_yaml(mrpt::containers::yaml::FromText(txt1), opts); + ASSERT_(y.isMap()); + ASSERT_EQUAL_(y["e"].as(), "${foo|default1}"); + } + { + mola::YAMLParseOptions opts; + const std::string se = "Something Else"; + opts.variables["foo"] = se; + + const auto y = + mola::parse_yaml(mrpt::containers::yaml::FromText(txt1), opts); + ASSERT_(y.isMap()); + ASSERT_EQUAL_(y["e"].as(), se); + } +} + +void test_parseIncludes() { { const auto file = MOLA_MODULE_SOURCE_DIR + "/test_include1.yaml"s; @@ -97,6 +128,8 @@ static void test_parseIncludes() } } +} // namespace + MRPT_TODO("Possible bug: #$include{} shouldn't be parsed") MRPT_TODO("bug: #${var} shouldn't be parsed") @@ -107,7 +140,7 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) test_yaml2string(); test_parseSimple(); test_parseIncludes(); - // test_parseEnvSimple(); + test_parseCustomVars(); std::cout << "Test successful." << std::endl; }