[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi-commits] [lmi] master e3632cb 1/2: Make variable interpolation in M
From: |
Greg Chicares |
Subject: |
[lmi-commits] [lmi] master e3632cb 1/2: Make variable interpolation in Mustache-like templates recursive |
Date: |
Tue, 30 Jul 2019 23:18:41 -0400 (EDT) |
branch: master
commit e3632cbd3cf9fc899d5543ebf790e1334a6aa34b
Author: Vadim Zeitlin <address@hidden>
Commit: Gregory W. Chicares <address@hidden>
Make variable interpolation in Mustache-like templates recursive
Expand {{var}} created in the result of expansion of another variable,
recursively.
I.e. if "foo" has the value "{{bar}}" and "bar" has the value "quux",
expanding "{{foo}}" now results in "quux", instead of just "{{bar}}" as
before.
Adjust the unit tests accordingly.
---
interpolate_string.cpp | 21 ++++++++++++-------
interpolate_string.hpp | 24 +++++++++++----------
interpolate_string_test.cpp | 51 +++++++++++++++++++++++++++++++--------------
3 files changed, 62 insertions(+), 34 deletions(-)
diff --git a/interpolate_string.cpp b/interpolate_string.cpp
index 7b979d2..c477b4c 100644
--- a/interpolate_string.cpp
+++ b/interpolate_string.cpp
@@ -68,12 +68,12 @@ void do_interpolate_string_in_context
,lookup_function const& lookup
,std::string& out
,context& sections
- ,std::string const& partial = std::string()
+ ,std::string const& variable_name = std::string()
,int recursion_level = 0
)
{
// Guard against too deep recursion to avoid crashing on code using too
- // many nested partials (either unintentionally, e.g. due to including a
+ // many nested expansions (either unintentionally, e.g. due to including a
// partial from itself, or maliciously).
//
// The maximum recursion level is chosen completely arbitrarily, the only
@@ -82,8 +82,8 @@ void do_interpolate_string_in_context
if(100 <= recursion_level)
{
alarum()
- << "Nesting level too deep while expanding the partial \""
- << partial
+ << "Nesting level too deep while expanding \""
+ << variable_name
<< "\""
<< std::flush
;
@@ -223,9 +223,16 @@ void do_interpolate_string_in_context
// variable name may seem strange, but why not
// allow using "{{}}" to insert something into
// the interpolated string, after all?
- out += lookup
- (name
- ,interpolate_lookup_kind::variable
+ do_interpolate_string_in_context
+ (lookup
+ (name
+ ,interpolate_lookup_kind::variable
+ ).c_str()
+ ,lookup
+ ,out
+ ,sections
+ ,name
+ ,recursion_level + 1
);
}
}
diff --git a/interpolate_string.hpp b/interpolate_string.hpp
index 9a448ac..bfc145d 100644
--- a/interpolate_string.hpp
+++ b/interpolate_string.hpp
@@ -40,26 +40,28 @@ using lookup_function
/// Interpolate string containing embedded variable references.
///
-/// Return the input string after replacing all {{variable}} references in it
-/// with the value of the variable as returned by the provided function. The
-/// syntax is a (strict) subset of Mustache templates, the following features
-/// are supported:
-/// - Simple variable expansion for {{variable}}.
+/// Return the input string after recursively replacing all {{variable}}
+/// references in it with the value of the variable as returned by the provided
+/// function. The syntax is a subset of Mustache templates with the following
+/// features being are supported:
+/// - Recursive variable expansion for {{variable}}, i.e. -- unlike in
+/// Mustache -- any {{...}} in the returned expansion are expanded again.
/// - Conditional expansion using {{#variable}}...{{/variable}}.
/// - Negated checks of the form {{^variable}}...{{/variable}}.
/// - Partials support, i.e. {{>filename}}.
+/// - Comments of the form {{!this is ignored}}.
///
/// The following features are explicitly _not_ supported:
/// - HTML escaping: this is done by a separate html::text class.
/// - Separate types: 0/1 is false/true, anything else is an error.
/// - Lists/section iteration (not needed yet).
-/// - Lambdas, comments, delimiter changes: omitted for simplicity.
+/// - Lambdas: can't be implemented in non-dynamic languages such as C++.
+/// - Changing delimiters: omitted for simplicity (to allow embedding literal
+/// "{{" fragment into the returned string, create a pseudo-variable
+/// expanding to these characters).
///
-/// To allow embedding literal "{{" fragment into the returned string, create a
-/// pseudo-variable expanding to these characters as its expansion, there is no
-/// built-in way to escape them.
-///
-/// Throw if the lookup function throws or if the string uses invalid syntax.
+/// Throw if the lookup function throws, if the string uses invalid syntax or
+/// if the maximum recursion level is exceeded.
std::string LMI_SO interpolate_string
(char const* s
diff --git a/interpolate_string_test.cpp b/interpolate_string_test.cpp
index d3f6c9f..d84bdcf 100644
--- a/interpolate_string_test.cpp
+++ b/interpolate_string_test.cpp
@@ -51,6 +51,41 @@ int test_main(int, char*[])
BOOST_TEST_EQUAL( test_interpolate("{{! too}}{{x}}"), "x" );
BOOST_TEST_EQUAL( test_interpolate("{{x}}{{!also}}"), "x" );
+ // Recursive interpolation should work too.
+ auto const test_recursive = [](char const* s)
+ {
+ return interpolate_string
+ (s
+ ,[](std::string const& k, interpolate_lookup_kind) -> std::string
+ {
+ if(k == "rec1") return "1 {{rec2}}";
+ if(k == "rec2") return "2 {{rec3}}";
+ if(k == "rec3") return "3" ;
+ if(k == "inf" ) return "{{inf}}" ;
+ if(k == "infA") return "{{infB}}" ;
+ if(k == "infB") return "{{infA}}" ;
+
+ throw std::runtime_error("no such variable '" + k + "'");
+ }
+ );
+ };
+
+ BOOST_TEST_EQUAL( test_recursive("{{rec3}}"), "3" );
+ BOOST_TEST_EQUAL( test_recursive("{{rec2}}"), "2 3" );
+ BOOST_TEST_EQUAL( test_recursive("{{rec1}}"), "1 2 3" );
+
+ BOOST_TEST_THROW
+ (test_recursive("error due to infinite recursion in {{inf}}")
+ ,std::runtime_error
+ ,lmi_test::what_regex("Nesting level too deep")
+ );
+
+ BOOST_TEST_THROW
+ (test_recursive("infinite co-recursion in {{infA}} is detected too")
+ ,std::runtime_error
+ ,lmi_test::what_regex("Nesting level too deep")
+ );
+
// Sections.
auto const section_test = [](char const* str)
{
@@ -144,22 +179,6 @@ int test_main(int, char*[])
,"no problem"
);
- // Some special cases.
- BOOST_TEST_EQUAL
- (interpolate_string
- ("{{expanded}}"
- ,[](std::string const& s, interpolate_lookup_kind) -> std::string
- {
- if(s == "expanded")
- {
- return "{{unexpanded}}";
- }
- throw std::runtime_error("no such variable '" + s + "'");
- }
- )
- ,"{{unexpanded}}"
- );
-
// Check that the kind of variable being expanded is correct.
BOOST_TEST_EQUAL
(interpolate_string