lmi-commits
[Top][All Lists]
Advanced

[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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]