diff --git a/src/lib/text-template.hpp b/src/lib/text-template.hpp index 435ca7b95..4a16bee04 100644 --- a/src/lib/text-template.hpp +++ b/src/lib/text-template.hpp @@ -106,7 +106,7 @@ #include "lib/regex.hpp" #include "lib/util.hpp" -#include +#include #include #include #include @@ -116,8 +116,6 @@ namespace lib { namespace error = lumiera::error; - using std::optional; - using std::nullopt; using std::string; using StrView = std::string_view; @@ -143,6 +141,17 @@ namespace lib { return explore (util::RegexSearchIter{iterDef, ACCEPT_DATA_ELM}) .transform ([&](smatch mat){ return key+"."+string{mat[1]}+"."; }); } + + //-----------Syntax-for-key-value-data-from-string------ + const string MATCH_BINDING_TOK { R"~(([\w\.]+)\s*=\s*([^,;"\s]*)\s*)~"}; + const regex ACCEPT_BINDING_ELM {MATCH_DELIMITER + MATCH_BINDING_TOK}; + + inline auto + iterBindingSeq (string const& dataDef) + { + return explore (util::RegexSearchIter{dataDef, ACCEPT_BINDING_ELM}) + .transform ([&](smatch mat){ return std::make_pair (string{mat[1]},string{mat[2]}); }); + } //-----------Syntax-for-TextTemplate-tags-------- @@ -320,12 +329,19 @@ namespace lib { template InstanceIter + submit (DAT const& data) const; + + template + string render (DAT const& data) const; template static string apply (string spec, DAT const& data); + auto keys() const; + + /// @internal exposed for testing static ActionSeq compile (string const&); friend class test::TextTemplate_test; }; @@ -550,6 +566,10 @@ namespace lib { bool isNested() { return not isnil (keyPrefix_); } + DataSource() = default; + DataSource(MapS const& map) + : data_{&map} + { } bool contains (string key) @@ -596,6 +616,31 @@ namespace lib { } }; + using PairS = std::pair; + + template<> + struct TextTemplate::DataSource + : TextTemplate::DataSource + { + std::shared_ptr spec_; + + DataSource (string const& dataSpec) + : spec_{new MapS} + { + data_ = spec_.get(); + explore (iterBindingSeq (dataSpec)) + .foreach([this](PairS const& bind){ spec_->insert (bind); }); + } + + DataSource + openContext (Iter& iter) + { + DataSource nested(*this); + auto nestedBase = DataSource::openContext (iter); + nested.keyPrefix_ = nestedBase.keyPrefix_; + return nested; + } + }; @@ -772,23 +817,40 @@ namespace lib { /** * Instantiate this (pre-compiled) TextTemplate using the given data binding. + * @return iterator to perform the evaluation and substitution step-by step, + * thereby producing a sequence of `std::string_view&` */ template inline TextTemplate::InstanceIter - TextTemplate::render (DAT const& data) const + TextTemplate::submit (DAT const& data) const { - return explore (InstanceCore{actions_, DataSource{&data}}); + return explore (InstanceCore{actions_, DataSource{data}}); } - /** */ + /** submit data and materialise rendered results into a single string */ + template + inline string + TextTemplate::render (DAT const& data) const + { + return util::join (submit (data), ""); + } + + /** one-shot shorthand: compile a template and apply it to the given data */ template inline string TextTemplate::apply (string spec, DAT const& data) { - return util::join (TextTemplate(spec).render (data) - ,""); + return TextTemplate(spec).render (data); } + /** diagnostics: query a list of all active keys expected by the template. */ + inline auto + TextTemplate::keys() const + { + return explore (actions_) + .filter ([](Action const& a){ return a.code == KEY or a.code == COND or a.code == ITER; }) + .transform([](Action const& a){ return a.val; }); + } }// namespace lib diff --git a/tests/library/text-template-test.cpp b/tests/library/text-template-test.cpp index b3f9f9433..c5df636f4 100644 --- a/tests/library/text-template-test.cpp +++ b/tests/library/text-template-test.cpp @@ -42,6 +42,7 @@ using std::regex_search; using std::smatch; using util::_Fmt; +using util::join; namespace lib { @@ -352,12 +353,20 @@ for} tail... } - /** @test TODO - * @todo WIP 4/24 🔁 define ⟶ implement + + /** @test TODO Compile a template and instantiate with various data bindings. + * @todo WIP 4/24 🔁 define ⟶ ✔ implement */ void verify_instantiation() { + string wonder = "${a} / ${b} = (${a} + ${b})/${a} ≕ ${phi}"; + TextTemplate temple{wonder}; + CHECK (join(temple.keys()) == "a, b, a, b, a, phi"_expect); + + auto insta = temple.submit (string{"phi=Φ, b=b, a=a"}); + CHECK (not isnil(insta)); + CHECK (join(insta,"⁐") == "⁐a⁐ / ⁐b⁐ = (⁐a⁐ + ⁐b⁐)/⁐a⁐ ≕ ⁐Φ⁐"_expect); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index de3fb22ec..0ead2d102 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -113485,7 +113485,31 @@ std::cout << tmpl.render({"what", "World"}) << s - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -113519,8 +113543,17 @@ std::cout << tmpl.render({"what", "World"}) << s - - + + + + + + + + + + +