[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi-commits] [lmi] master 0ecc8ec 036/156: Add support for partials to
From: |
Greg Chicares |
Subject: |
[lmi-commits] [lmi] master 0ecc8ec 036/156: Add support for partials to our ad hoc Mustache parser |
Date: |
Tue, 30 Jan 2018 17:22:04 -0500 (EST) |
branch: master
commit 0ecc8ec7ff8542103b46e3022af643ea4b7ddeef
Author: Vadim Zeitlin <address@hidden>
Commit: Vadim Zeitlin <address@hidden>
Add support for partials to our ad hoc Mustache parser
Use recursion for partials expansion, this might not be the most
efficient way to do it, but it is probably the simplest one.
Do guard against overflowing the stack by arbitrarily limiting the
recursion depth.
---
interpolate_string.cpp | 124 ++++++++++++++++++++++++++++++++------------
interpolate_string.hpp | 6 ++-
interpolate_string_test.cpp | 65 +++++++++++++++++++++--
3 files changed, 158 insertions(+), 37 deletions(-)
diff --git a/interpolate_string.cpp b/interpolate_string.cpp
index db3b9d3..f6153d1 100644
--- a/interpolate_string.cpp
+++ b/interpolate_string.cpp
@@ -29,45 +29,65 @@
#include <stdexcept>
#include <vector>
-std::string interpolate_string
+namespace
+{
+
+// Information about a single section that we're currently in.
+struct section_info
+{
+ section_info(std::string const& name, bool active)
+ :name_(name)
+ ,active_(active)
+ {
+ }
+
+ // Name of the section, i.e. the part after "#".
+ //
+ // TODO: In C++14 this could be replaced with string_view which would
+ // save on memory allocations without compromising safety, as we know
+ // that the input string doesn't change during this function execution.
+ std::string const name_;
+
+ // If true, output section contents, otherwise simply eat it.
+ bool const active_;
+
+ // Note: we could also store the position of the section start here to
+ // improve error reporting. Currently this is done as templates we use
+ // are small and errors shouldn't be difficult to find even without the
+ // exact position, but this could change in the future.
+};
+
+// The only context we need is the stack of sections entered so far.
+using context = std::stack<section_info, std::vector<section_info>>;
+
+// The real interpolation recursive function, called by the public one to do
+// all the work.
+void do_interpolate_string_in_context
(char const* s
,lookup_function const& lookup
+ ,std::string& out
+ ,context& sections
+ ,std::string const& partial = std::string()
+ ,int recursion_level = 0
)
{
- std::string out;
-
- // This is probably not going to be enough as replacements of the
- // interpolated variables tend to be longer than the variables names
- // themselves, but it's difficult to estimate the resulting string length
- // any better than this.
- out.reserve(strlen(s));
-
- // The stack contains all the sections that we're currently in.
- struct section_info
- {
- section_info(std::string const& name, bool active)
- :name_(name)
- ,active_(active)
+ // Guard against too deep recursion to avoid crashing on code using too
+ // many nested partials (either unintentionally, e.g. due to including a
+ // partial from itself, or maliciously).
+ //
+ // The maximum recursion level is chosen completely arbitrarily, the only
+ // criteria are that it shouldn't be too big to crash due to stack overflow
+ // before it is reached nor too small to break legitimate use cases.
+ if(recursion_level >= 100)
{
+ alarum()
+ << "Nesting level too deep while expanding the partial \""
+ << partial
+ << "\""
+ << std::flush
+ ;
}
- // Name of the section, i.e. the part after "#".
- //
- // TODO: In C++14 this could be replaced with string_view which would
- // save on memory allocations without compromising safety, as we know
- // that the input string doesn't change during this function execution.
- std::string const name_;
-
- // If true, output section contents, otherwise simply eat it.
- bool const active_;
-
- // Note: we could also store the position of the section start here to
- // improve error reporting. Currently this is done as templates we use
- // are small and errors shouldn't be difficult to find even without the
- // exact position, but this could change in the future.
- };
- std::stack<section_info, std::vector<section_info>> sections;
-
// Check if the output is currently active or suppressed because we're
// inside an inactive section.
auto const is_active = [§ions]()
@@ -171,6 +191,25 @@ std::string interpolate_string
sections.pop();
break;
+ case '>':
+ if(is_active())
+ {
+ auto const& real_name = name.substr(1);
+
+ do_interpolate_string_in_context
+ (lookup
+ (real_name
+ ,interpolate_lookup_kind::partial
+ ).c_str()
+ ,lookup
+ ,out
+ ,sections
+ ,real_name
+ ,recursion_level + 1
+ );
+ }
+ break;
+
default:
if(is_active())
{
@@ -217,6 +256,27 @@ std::string interpolate_string
out += *p;
}
}
+}
+
+} // Unnamed namespace.
+
+std::string interpolate_string
+ (char const* s
+ ,lookup_function const& lookup
+ )
+{
+ std::string out;
+
+ // This is probably not going to be enough as replacements of the
+ // interpolated variables tend to be longer than the variables names
+ // themselves, but it's difficult to estimate the resulting string length
+ // any better than this.
+ out.reserve(strlen(s));
+
+ // The stack contains all the sections that we're currently in.
+ std::stack<section_info, std::vector<section_info>> sections;
+
+ do_interpolate_string_in_context(s, lookup, out, sections);
if(!sections.empty())
{
diff --git a/interpolate_string.hpp b/interpolate_string.hpp
index ef2de4d..e71d1ed 100644
--- a/interpolate_string.hpp
+++ b/interpolate_string.hpp
@@ -30,7 +30,8 @@
enum class interpolate_lookup_kind
{
variable,
- section
+ section,
+ partial
};
using lookup_function
@@ -45,12 +46,13 @@ using lookup_function
/// - Simple variable expansion for {{variable}}.
/// - Conditional expansion using {{#variable}}...{{/variable}}.
/// - Negated checks of the form {{^variable}}...{{/variable}}.
+/// - Partials support, i.e. {{>filename}}.
///
/// 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, partials, comments, delimiter changes: omitted for simplicity.
+/// - Lambdas, comments, delimiter changes: omitted for simplicity.
///
/// To allow embedding literal "{{" fragment into the returned string, create a
/// pseudo-variable expanding to these characters as its expansion, there is no
diff --git a/interpolate_string_test.cpp b/interpolate_string_test.cpp
index 459a297..766c20d 100644
--- a/interpolate_string_test.cpp
+++ b/interpolate_string_test.cpp
@@ -82,6 +82,61 @@ int test_main(int, char*[])
,"ae"
);
+ // Partials.
+ auto const partial_test = [](char const* s)
+ {
+ return interpolate_string
+ (s
+ ,[](std::string const& s, interpolate_lookup_kind) -> std::string
+ {
+ if(s == "header") return "[header with {{var}}]";
+ if(s == "footer") return "[footer with {{var}}]";
+ if(s == "nested") return "[header with {{>footer}}]";
+ if(s == "recursive") return "{{>recursive}}";
+ if(s == "sec" ) return "1" ;
+ if(s == "var" ) return "variable" ;
+
+ throw std::runtime_error("no such variable '" + s + "'");
+ }
+ );
+ };
+
+ BOOST_TEST_EQUAL
+ (partial_test("{{>header}}")
+ ,"[header with variable]"
+ );
+
+ BOOST_TEST_EQUAL
+ (partial_test("{{>header}}{{var}} in body{{>footer}}")
+ ,"[header with variable]variable in body[footer with variable]"
+ );
+
+ BOOST_TEST_EQUAL
+ (partial_test("{{#sec}}{{>header}}{{/sec}}")
+ ,"[header with variable]"
+ );
+
+ BOOST_TEST_EQUAL
+ (partial_test("only{{^sec}}{{>header}}{{/sec}}{{>footer}}")
+ ,"only[footer with variable]"
+ );
+
+ BOOST_TEST_EQUAL
+ (partial_test("{{>nested}}")
+ ,"[header with [footer with variable]]"
+ );
+
+ BOOST_TEST_THROW
+ (partial_test("{{>recursive}}")
+ ,std::runtime_error
+ ,lmi_test::what_regex("Nesting level too deep")
+ );
+
+ BOOST_TEST_EQUAL
+ (partial_test("no {{^sec}}{{>recursive}}{{/sec}} problem")
+ ,"no problem"
+ );
+
// Some special cases.
BOOST_TEST_EQUAL
(interpolate_string
@@ -101,8 +156,9 @@ int test_main(int, char*[])
// Check that the kind of variable being expanded is correct.
BOOST_TEST_EQUAL
(interpolate_string
- ("{{#section1}}{{^section0}}{{variable}}{{/section0}}{{/section1}}"
- ,[](std::string const& s, interpolate_lookup_kind kind)
+ ("{{>test}}"
+ "{{#section1}}{{^section0}}{{variable}}{{/section0}}{{/section1}}"
+ ,[](std::string const& s, interpolate_lookup_kind kind) ->
std::string
{
switch(kind)
{
@@ -112,12 +168,15 @@ int test_main(int, char*[])
case interpolate_lookup_kind::section:
// Get rid of the "section" prefix.
return s.substr(7);
+
+ case interpolate_lookup_kind::partial:
+ return s + " partial included\n";
}
throw std::runtime_error("invalid lookup kind");
}
)
- ,"value of variable"
+ ,"test partial included\nvalue of variable"
);
// Should throw if the input syntax is invalid.
- [lmi-commits] [lmi] master 8bfb896 109/156: Add NASD illustration first explanatory notes page, (continued)
- [lmi-commits] [lmi] master 8bfb896 109/156: Add NASD illustration first explanatory notes page, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master ea20a31 095/156: Make pdf_illustration dtor virtual, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 61fae89 091/156: Reimplement the contract numbers fragment as an external template, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 0c55621 085/156: Replace trivial PDF page classes with single standard_page one, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master ea2564b 099/156: Split the NASD header template in the upper and lower parts, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master c1bf4a8 032/156: Adjust position of the right hand side of the header manually, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 89b4674 120/156: Add automatic support for multiline super-headers, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 0f885a2 116/156: Define CorpNameAbbrev60 and Insured1Abbrev30 in common code, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 08559c3 121/156: Factor out base_suffix() and ir_suffix() functions, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 36c84ce 123/156: Add another individual private placement illustration page, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 0ecc8ec 036/156: Add support for partials to our ad hoc Mustache parser,
Greg Chicares <=
- [lmi-commits] [lmi] master 6f09722 107/156: Add missing words to the narrative summary continuation page, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 6510601 150/156: Rename a mute variable to avoid gcc -Wshadow warning, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 048ae05 093/156: Start NASD PDF illustration class implementation, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 5ea5c86 101/156: Simplify and correct current PDF DC font handling, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master ec490eb 153/156: Revise and extend some comments in PDF generation code, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 38cab5e 096/156: Make footer template depend on illustration kind, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master d1204c2 111/156: Factor out common parts of render_or_measure_fixed_page_part(), Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master e388f6e 037/156: Add beginning of numeric summary page using an external template, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master 531699b 115/156: Factor out common page_with_basic_tabular_report class, Greg Chicares, 2018/01/30
- [lmi-commits] [lmi] master a85943b 124/156: Add the rest of individual private placement illustration pages, Greg Chicares, 2018/01/30