Lumiera  0.pre.03
»edit your freedom«
text-template-test.cpp
Go to the documentation of this file.
1 /*
2  TextTemplate(Test) - verify the minimalistic text substitution engine
3 
4  Copyright (C) Lumiera.org
5  2024, Hermann Vosseler <Ichthyostega@web.de>
6 
7  This program is free software; you can redistribute it and/or
8  modify it under the terms of the GNU General Public License as
9  published by the Free Software Foundation; either version 2 of
10  the License, or (at your option) any later version.
11 
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 
21 * *****************************************************/
22 
29 #include "lib/test/run.hpp"
30 #include "lib/test/test-helper.hpp"
31 #include "lib/text-template.hpp"
33 
34 #include <map>
35 
36 //using std::array;
37 using std::regex_search;
38 using std::smatch;
39 using util::_Fmt;
40 using util::join;
41 using lib::diff::Rec;
42 using lib::diff::MakeRec;
43 using lib::diff::GenNode;
44 
45 
46 namespace lib {
47 namespace test {
48 
49  using MapS = std::map<string, string>;
50  using LERR_(ITER_EXHAUST);
51 
52  using text_template::ACCEPT_MARKUP;
53  using text_template::TagSyntax;
54 
55 
56  /***************************************************************************/
66  class TextTemplate_test : public Test
67  {
68 
69  virtual void
70  run (Arg)
71  {
72  simpeUsage();
79 
80  }
81 
82 
84  void
86  {
87  MapS snaps{{"whatever", "cruel world"}
88  ,{"greeting", "farewell"}};
89  CHECK (TextTemplate::apply ("${greeting} ${whatever} ↯", snaps)
90  == "farewell cruel world ↯"_expect);
91  }
92 
93 
103  void
105  {
106  smatch mat;
107  string input;
108  CHECK (not regex_search (input, mat, ACCEPT_MARKUP));
109 
110  input = " Hallelujah ";
111  CHECK (not regex_search (input, mat, ACCEPT_MARKUP)); // walk away ... nothing to see here...
112 
113  input = " stale${beer}forever";
114  CHECK (regex_search (input, mat, ACCEPT_MARKUP));
115  CHECK (mat.position() == 6);
116  CHECK (mat.length() == 7);
117  CHECK (mat.prefix() == " stale"_expect);
118  CHECK (mat.suffix() == "forever"_expect);
119  CHECK (mat[0] == "${beer}"_expect); // so this first example demonstrates placeholder recognition
120  CHECK (not mat[1].matched); // Sub-1 : this is not an escaped pattern
121  CHECK (not mat[2].matched); // Sub-2 : this pattern does not start with "else"
122  CHECK (not mat[3].matched); // Sub-3 : no "end" keyword
123  CHECK (not mat[4].matched); // Sub-4 : no further logic syntax
124  CHECK (mat[5] == "beer"_expect); // Sub-5 : extracts the Key ID
125 
126  input = " watch ${for stale}${beer} whatever ";
127  CHECK (regex_search (input, mat, ACCEPT_MARKUP));
128  CHECK (mat.position() == 7);
129  CHECK (mat.length() == 12);
130  CHECK (mat.prefix() == " watch "_expect);
131  CHECK (mat.suffix() == "${beer} whatever "_expect); // (performing only one search here...)
132  CHECK (mat[0] == "${for stale}"_expect); // Matched a regular opening iteration tag
133  CHECK (not mat[2].matched); // Sub-2 does not trigger, since there is no "else" mark
134  CHECK (not mat[3].matched); // Sub-3 does not trigger, no end mark either
135  CHECK (mat[4] == "for"_expect); // Sub-4 picks the "for" keyword
136  CHECK (mat[5] == "stale"_expect); // Sub-5 extracts a simple Key ≡ "stale"
137 
138  input = " work ${ end if beer \t } however ";
139  CHECK (regex_search (input, mat, ACCEPT_MARKUP));
140  CHECK (mat.position() == 6);
141  CHECK (mat.length() == 19);
142  CHECK (mat.prefix() == " work "_expect);
143  CHECK (mat.suffix() == " however "_expect);
144  CHECK (mat[0] == "${ end if beer \t }"_expect); // A regular end marker of an conditional
145  CHECK (mat[3] == "end "_expect); // Sub-3 triggers on the "end" token
146  CHECK (mat[4] == "if"_expect); // Sub-4 picks the "if" keyword
147  CHECK (mat[5] == "beer"_expect); // Sub-5 extracts a simple Key ≡ "beer"
148 
149  input = " catch ${endgame stale}${endfor brown.beer} ever ";
150  CHECK (regex_search (input, mat, ACCEPT_MARKUP));
151  CHECK (mat.position() == 23);
152  CHECK (mat.length() == 20);
153  CHECK (mat.prefix() == " catch ${endgame stale}"_expect);// "while" is no valid keyword at the second position of the syntax
154  CHECK (mat.suffix() == " ever "_expect);
155  CHECK (mat[0] == "${endfor brown.beer}"_expect); // ...thus search proceeds to match on the second pattern installment
156  CHECK (mat[3] == "end"_expect); // Sub-3 triggers on the "end" token
157  CHECK (mat[4] == "for"_expect); // Sub-4 picks the "for" keyword
158  CHECK (mat[5] == "brown.beer"_expect); // Sub-5 extracts a hierarchical key ID
159 
160  input = " catch ${else} ever ";
161  CHECK (regex_search (input, mat, ACCEPT_MARKUP));
162  CHECK (mat.position() == 7);
163  CHECK (mat.length() == 7);
164  CHECK (mat.prefix() == " catch "_expect);
165  CHECK (mat.suffix() == " ever "_expect);
166  CHECK (mat[0] == "${else}"_expect); // Standard match on an "else"-tag
167  CHECK (mat[2] == "else"_expect); // Sub-2 confirmed a solitary "else" keyword
168  CHECK (not mat[1].matched);
169  CHECK (not mat[3].matched);
170  CHECK (not mat[4].matched);
171  CHECK (not mat[5].matched);
172 
173  input = " catch ${else if} fever \\${can.beer} ";
174  CHECK (regex_search (input, mat, ACCEPT_MARKUP));
175  CHECK (mat.position() == 24);
176  CHECK (mat.length() == 2);
177  CHECK (mat.prefix() == " catch ${else if} fever "_expect); // Note: first pattern does not match as "else" must be solitary
178  CHECK (mat.suffix() == "{can.beer} "_expect); // Note: the following braced expression is tossed aside
179  CHECK (mat[0] == "\\$"_expect); // Only the escaped pattern mark opening is picked up
180  CHECK (not mat[2].matched);
181  CHECK (not mat[3].matched);
182  CHECK (not mat[4].matched);
183  CHECK (not mat[5].matched);
184  CHECK (mat[1] == "\\$"_expect); // Sub-1 picks the escaped mark (and the remainder is no complete tag)
185 
186 
187 
188  // Demonstration: can use this regular expression in a matching pipeline....
189  input = "one ${two} three \\${four} ${if high} five";
190  CHECK (util::join(
191  explore (util::RegexSearchIter{input, ACCEPT_MARKUP})
192  .transform ([](smatch mat){ return mat.str(); }))
193  ==
194  "${two}, \\$, ${if high}"_expect);
195 
196 
197  // Parse matches of this regexp into well defined syntax elements
198  auto parser = text_template::parse (input);
199  CHECK (not isnil(parser));
200  CHECK (parser->syntax == TagSyntax::KEYID);
201  CHECK (parser->lead == "one "_expect);
202  CHECK (parser->key == "two"_expect); // extract "two" as key for data lookup
203  ++parser;
204  CHECK (parser);
205  CHECK (parser->syntax == TagSyntax::ESCAPE);
206  CHECK (parser->lead == " three "_expect);
207  CHECK (parser->key == ""_expect); // empty since this tag has been escaped
208  ++parser;
209  CHECK (parser);
210  CHECK (parser->syntax == TagSyntax::IF);
211  CHECK (parser->lead == "${four} "_expect); // note: leading escape sign removed
212  CHECK (parser->key == "high"_expect); // key ≡ "high" used to evaluate conditional
213  ++parser;
214  CHECK (isnil (parser)); // note: the /parser/ stops right behind last token
215  VERIFY_ERROR (ITER_EXHAUST, *parser);
216  VERIFY_ERROR (ITER_EXHAUST, ++parser);
217 
218 
219 
220  // Generate sequence of Action tokens from parsing results
221  input = R"~(
222  Prefix-1 ${some.key} next one is \${escaped}
223  Prefix-2 ${if cond1} active ${else} inactive ${end if
224 }Prefix-3 ${if cond2} active2${end if cond2} more
225  Prefix-4 ${for data} fixed ${embedded}
226  Pre-5 ${if nested}nested-active${
227  else }nested-inactive${ end
228  if nested}loop-suffix${else}${end
229 for} tail...
230 )~";
231  auto actions = TextTemplate::compile (input);
232  CHECK (25 == actions.size());
233 
234  CHECK (actions[ 0].code == TextTemplate::Code::TEXT);
235  CHECK (actions[ 0].val == "\n Prefix-1 "_expect); // static text prefix
236  CHECK (actions[ 0].refIDX == 0);
237 
238  CHECK (actions[ 1].code == TextTemplate::Code::KEY); // a placeholder to be substituted
239  CHECK (actions[ 1].val == "some.key"_expect); // use "some.key" for data retrieval
240 
241  CHECK (actions[ 2].code == TextTemplate::Code::TEXT); // static text between active fields
242  CHECK (actions[ 2].val == " next one is "_expect);
243 
244  CHECK (actions[ 3].code == TextTemplate::Code::TEXT); // since next tag was escaped, it appears in static segment,
245  CHECK (actions[ 3].val == "${escaped}\n Prefix-2 "_expect); // yet without the leading escape, which has been absorbed.
246 
247  CHECK (actions[ 4].code == TextTemplate::Code::COND); // start of an if-bracket construct
248  CHECK (actions[ 4].val == "cond1"_expect); // data marked with "cond1" will be used to determine true/false
249  CHECK (actions[ 4].refIDX == 7 ); // IDX ≡ 7 marks start of the else-branch
250 
251  CHECK (actions[ 5].code == TextTemplate::Code::TEXT); // this static block will only be included if "cond1" evaluates to true
252  CHECK (actions[ 5].val == " active "_expect);
253 
254  CHECK (actions[ 6].code == TextTemplate::Code::JUMP); // unconditional jump at the end of the if-true-block
255  CHECK (actions[ 6].val == ""_expect);
256  CHECK (actions[ 6].refIDX == 8 ); // IDX ≡ 8 points to the next element after the conditional construct
257 
258  CHECK (actions[ 7].code == TextTemplate::Code::TEXT); // this static (else)-block will be included if "cond1" does not hold
259  CHECK (actions[ 7].val == " inactive "_expect);
260 
261  CHECK (actions[ 8].code == TextTemplate::Code::TEXT); // again a static segment, displayed unconditionally
262  CHECK (actions[ 8].val == "Prefix-3 "_expect); // Note: no newline, since the closing bracket was placed at line start
263 
264  CHECK (actions[ 9].code == TextTemplate::Code::COND); // again a conditional (but this time without else-branch)
265  CHECK (actions[ 9].val == "cond2"_expect); // data marked with "cond2" will be evaluated as condition
266  CHECK (actions[ 9].refIDX == 11 ); // IDX ≡ 11 is the alternative route, this time pointing behind the conditional
267 
268  CHECK (actions[10].code == TextTemplate::Code::TEXT); // static text block to be displayed as content of the conditional
269  CHECK (actions[10].val == " active2"_expect);
270 
271  CHECK (actions[11].code == TextTemplate::Code::TEXT); // again an unconditional static segment (behind end of preceding conditional)
272  CHECK (actions[11].val == " more\n Prefix-4 "_expect);
273 
274  CHECK (actions[12].code == TextTemplate::Code::ITER); // Start of a for-construct (iteration)
275  CHECK (actions[12].val == "data"_expect); // data marked with "data" will be used to find and iterate nested elements
276  CHECK (actions[12].refIDX == 23 ); // IDX ≡ 23 points to the alternative "else" block, in case no iteration takes place
277 
278  CHECK (actions[13].code == TextTemplate::Code::TEXT); // static block to appear for each nested "data" element
279  CHECK (actions[13].val == " fixed "_expect);
280 
281  CHECK (actions[14].code == TextTemplate::Code::KEY); // placeholder to be substituted
282  CHECK (actions[14].val == "embedded"_expect); // _typically_ the data "embedded" will live in the iterated, nested elements
283 
284  CHECK (actions[15].code == TextTemplate::Code::TEXT); // again a static block, which however lives within the iterated segment
285  CHECK (actions[15].val == "\n Pre-5 "_expect);
286 
287  CHECK (actions[16].code == TextTemplate::Code::COND); // a nested conditional, thus nested on second level within the iteration construct
288  CHECK (actions[16].val == "nested"_expect); // data marked with "nested" will control the conditional (typically from iterated data elements)
289  CHECK (actions[16].refIDX == 19 ); // IDX ≡ 19 points to the else-block of this nested conditional
290 
291  CHECK (actions[17].code == TextTemplate::Code::TEXT); // static content to appear as nested if-true-section
292  CHECK (actions[17].val == "nested-active"_expect);
293 
294  CHECK (actions[18].code == TextTemplate::Code::JUMP); // jump code at end of the true-section
295  CHECK (actions[18].val == ""_expect);
296  CHECK (actions[18].refIDX == 20 ); // IDX ≡ 20 points behind the end of this nested conditional construct
297 
298  CHECK (actions[19].code == TextTemplate::Code::TEXT); // static content comprising the else-section
299  CHECK (actions[19].val == "nested-inactive"_expect); // Note: no whitespace due to placement of the tag brackets of "else" / "end if"
300 
301  CHECK (actions[20].code == TextTemplate::Code::TEXT); // again an unconditional static segment, yet still within the looping construct
302  CHECK (actions[20].val == "loop-suffix"_expect);
303 
304  CHECK (actions[21].code == TextTemplate::Code::LOOP); // the loop-end code, where evaluation will consider the next iteration
305  CHECK (actions[21].val == ""_expect);
306  CHECK (actions[21].refIDX == 12 ); // IDX ≡ 12 points back to the opening ITER code
307 
308  CHECK (actions[22].code == TextTemplate::Code::JUMP); // if however the iteration is complete, evaluation will jump over the "else" section
309  CHECK (actions[22].val == ""_expect);
310  CHECK (actions[22].refIDX == 24 );
311 
312  CHECK (actions[23].code == TextTemplate::Code::TEXT); // this static else-segment will appear whenever no iteration takes place
313  CHECK (actions[23].val == ""_expect); // Note: in this example there is an ${else}-tag, yet the content is empty
314 
315  CHECK (actions[24].code == TextTemplate::Code::TEXT); // a final static segment after the last active tag
316  CHECK (actions[24].val == " tail...\n"_expect);
317  CHECK (actions[24].refIDX == 0);
318 
319 
320 
321  VERIFY_FAIL ("TextTemplate spec without active placeholders"
322  , TextTemplate::compile("O tempora O mores"));
323 
324  VERIFY_FAIL ("Tag without key: ...horror ${<placeholder> |↯|}"
325  , TextTemplate::compile("horror ${ } vacui"));
326 
327  VERIFY_FAIL (" ...horror ${if <conditional> |↯|}"
328  , TextTemplate::compile("horror ${if} late"));
329 
330  VERIFY_FAIL (" ...horror ${for <data-id> |↯|}"
331  , TextTemplate::compile("horror ${for} all"));
332 
333  VERIFY_FAIL ("Misplaced ...horror |↯|${else}"
334  , TextTemplate::compile("horror ${else} deaf"));
335 
336  VERIFY_FAIL ("unqualified \"end\" without logic-keyword"
337  , TextTemplate::compile("horror without ${end}"));
338 
339  VERIFY_FAIL ("Unbalanced Logic: expect ${end ?? } -- found ...horror ${end |↯|for }"
340  , TextTemplate::compile("horror ${end for} ever"));
341 
342  VERIFY_FAIL ("Unbalanced Logic: expect ${end for free} -- found ... horror ${end |↯|if }"
343  , TextTemplate::compile("${for free} horror ${end if}"));
344 
345  VERIFY_FAIL ("Unbalanced Logic: expect ${end for free} -- found ... yet ${end |↯|for me}"
346  , TextTemplate::compile("${if wee} horror ${for free} yet ${end for me}"));
347 
348  VERIFY_FAIL ("Conflicting ... precipitous ${else} ⟷ ... callous |↯|${else}"
349  , TextTemplate::compile("${if smarmy} precipitous ${else} callous ${else} horror"));
350 
351  VERIFY_FAIL ("Unclosed Logic tags: |↯|${end if sleazy} missing"
352  , TextTemplate::compile("${if sleazy} precipitous ${else} horror"));
353 
354  VERIFY_FAIL ("Unclosed Logic tags: |↯|${end for horror} missing"
355  , TextTemplate::compile("${for horror}${if flimsy} atrocious ${end if} precipitous"));
356  }
357 
358 
359 
361  void
363  {
364  string wonder = "${a} / ${b} = (${a} + ${b})/${a} ≕ ${phi}";
365  TextTemplate temple{wonder};
366  CHECK (join(temple.keys()) == "a, b, a, b, a, phi"_expect);
367 
368  auto insta = temple.submit (string{"phi=Φ, b=b, a=a"});
369  CHECK (not isnil(insta));
370  CHECK (join(insta,"⁐") == "⁐a⁐ / ⁐b⁐ = (⁐a⁐ + ⁐b⁐)/⁐a⁐ ≕ ⁐Φ⁐"_expect);
371 
372  CHECK (temple.render("phi=Φ,a=μ,b=ν") == "μ / ν = (μ + ν)/μ ≕ Φ"_expect );
373  CHECK (temple.render("phi=schmuh,a=8,b=5") == "8 / 5 = (8 + 5)/8 ≕ schmuh"_expect);
374  CHECK (temple.render("phi=1.6180,a=55,b=34") == "55 / 34 = (55 + 34)/55 ≕ 1.6180"_expect);
375  }
376 
377 
378 
382  void
384  {
385  TextTemplate t1{"Value ${if val}= ${val} ${else}missing${endif}..."};
386 
387  CHECK (t1.render("val=55") == "Value = 55 ..."_expect );
388  CHECK (t1.render("val=\"\"") == "Value missing..."_expect); // empty value counts as false
389  CHECK (t1.render("val=\" \"") == "Value = ..."_expect ); // one space counts as content (=true)
390  CHECK (t1.render("val=false") == "Value missing..."_expect); // various bool-false tokens recognised
391  CHECK (t1.render("val=NO" ) == "Value missing..."_expect);
392  CHECK (t1.render("val= 0 " ) == "Value missing..."_expect);
393  CHECK (t1.render("val=true") == "Value = true ..."_expect); // bool true token treated as content
394  CHECK (t1.render("vol=high") == "Value missing..."_expect); // missing key treated as false
395 
396 
397  TextTemplate t2{"Solution${if val} is ${val} ${endif val}..."};
398  CHECK (t2.render("val=42") == "Solution is 42 ..."_expect );
399  CHECK (t2.render("nil=42") == "Solution..."_expect );
400 
401 
402  TextTemplate t3{" 1 ${if a} 2 ${if b} 3 ${else} ${b} ${endif b} 4 ${else}${if a} 5 ${else} ${a} ${endif a}${endif a} 6 "};
403  CHECK (t3.render("a=2,b=3") == " 1 2 3 4 6 "_expect ); // ^^^^^ Note can never be true here
404  CHECK (t3.render("a=2,b=0") == " 1 2 0 4 6 "_expect );
405  CHECK (t3.render("a=0,b=3") == " 1 0 6 "_expect ); // thus if a ≙ false we see only 1 §{a} 6
406  CHECK (t3.render("a=0,b=0") == " 1 0 6 "_expect );
407  }
408 
409 
410 
421  void
423  {
424  TextTemplate t1{"▶${for i} ${x} ▷${else} ∅${end for} ◇ ${i} ▶"};
425 
426  CHECK (t1.render("i=\"1,2,3\", i.1.x=3, i.2.x=5, i.3.x=8 ") == "▶ 3 ▷ 5 ▷ 8 ▷ ◇ 1,2,3 ▶"_expect ); // fully defined
427  CHECK (t1.render("i=\"3,1,2\", i.1.x=3, i.2.x=5, i.3.x=8 ") == "▶ 8 ▷ 3 ▷ 5 ▷ ◇ 3,1,2 ▶"_expect ); // order changed
428  CHECK (t1.render("i=\"3,2,3\", i.1.x=3, i.2.x=5, i.3.x=8 ") == "▶ 8 ▷ 5 ▷ 8 ▷ ◇ 3,2,3 ▶"_expect ); // duplicate entities
429  CHECK (t1.render("i=\"3,2,1\", i.2.x=5, i.3.x=8 ") == "▶ 8 ▷ 5 ▷ ▷ ◇ 3,2,1 ▶"_expect ); // missing key for entity-1
430  CHECK (t1.render("i=\"3,2,1\", x=↯, i.2.x=5, i.3.x=8 ") == "▶ 8 ▷ 5 ▷ ↯ ▷ ◇ 3,2,1 ▶"_expect ); // top-level key "x" partially shadowed
431  CHECK (t1.render("i=\"p,q,r\", x=↯, i.q.x=5, i.3.x=8 ") == "▶ ↯ ▷ 5 ▷ ↯ ▷ ◇ p,q,r ▶"_expect ); // arbitrary names for the entities
432  CHECK (t1.render("i= 0 , x=↯, i.q.x=5, i.3.x=8 ") == "▶ ∅ ◇ 0 ▶"_expect ); // "0" is false, thus no iteration
433  CHECK (t1.render(" x=↯, i.q.x=5, i.3.x=8 ") == "▶ ∅ ◇ ▶"_expect ); // no binding for iteration-control key i
434 
435 
436  TextTemplate t2{"▶${for i}${if x}${for j}${x}▷${else}${x}●${end for j}${end if x} 🔁 ${end for i} ▶"};
437 
438  CHECK (t2.render("i=\"1,2\",j=\"1,2\", x=1 , i.1.j.1.x=11, i.1.j.2.x=12, i.2.j.1.x=21, i.2.j.2.x=22") == "▶11▷12▷ 🔁 21▷22▷ 🔁 ▶"_expect );
439  CHECK (t2.render("i=\"1,2\",j=\"1,2\", i.1.x=1, i.1.j.1.x=11, i.1.j.2.x=12, i.2.j.1.x=21, i.2.j.2.x=22") == "▶11▷12▷ 🔁 🔁 ▶"_expect );
440  CHECK (t2.render("i=\"1,2\" , x=00 , i.1.j.1.x=11, i.1.j.2.x=12, i.2.j.1.x=21, i.2.j.2.x=22") == "▶00● 🔁 00● 🔁 ▶"_expect );
441  CHECK (t2.render("i=\"1,2\" , x=00 , i.1.x =10, i.2.x =20, ") == "▶10● 🔁 20● 🔁 ▶"_expect );
442  CHECK (t2.render(" j=\"1,2\" ") == "▶ ▶"_expect );
443  CHECK (t2.render(" ") == "▶ ▶"_expect );
444  }
445 
446 
447 
452  void
454  {
455  MapS data{{"a","5"}
456  ,{"i","p,q,r"}
457  ,{"i.p.a","11"}
458  ,{"i.q.a","22"}
459  ,{"i.q.aa","222"}};
460 
461  auto binding = text_template::DataSource{data};
462  CHECK (meta::typeStr(binding) == "text_template::DataSource<map<string, string>, void>"_expect );
463  CHECK ( binding.contains("a"));
464  CHECK (not binding.contains("b"));
465  CHECK (binding.retrieveContent("a") == "5"_expect );
466  CHECK (binding.retrieveContent("i") == "p,q,r"_expect );
467  CHECK (binding.retrieveContent("i.q.aa") == "222"_expect );
468  CHECK (not binding.isSubScope());
469 
470  auto it = binding.getSequence("i");
471  CHECK (it);
472  CHECK (*it == "i.p."_expect );
473  CHECK (meta::typeStr(it) == "IterExplorer<IterableDecorator<string, CheckedCore<iter_explorer::Transformer<iter_explorer::BaseAdapter<RegexSearchIter>, string> > > >"_expect );
474 
475  auto subBind = binding.openContext(it);
476  CHECK (subBind.isSubScope());
477  CHECK ((meta::is_same<decltype(binding),decltype(subBind)>()));
478  CHECK ( subBind.contains("a"));
479  CHECK (not subBind.contains("b"));
480  CHECK (not subBind.contains("aa"));
481  CHECK ( subBind.contains("i"));
482  CHECK (subBind.retrieveContent("i") == "p,q,r"_expect );
483  CHECK (subBind.retrieveContent("a") == "11"_expect );
484 
485  ++it;
486  CHECK (it);
487  CHECK (*it == "i.q."_expect );
488 
489  // Note: existing sub-ctx is not automatically linked to the Iterator
490  CHECK (subBind.retrieveContent("a") == "11"_expect );
491  // ...rather need to open a new sub-ctx explicitly
492  subBind = binding.openContext(it);
493  CHECK (subBind.isSubScope());
494  CHECK (subBind.contains("a"));
495  CHECK (subBind.contains("aa"));
496  CHECK (subBind.retrieveContent("a") == "22"_expect );
497  CHECK (subBind.retrieveContent("aa") == "222"_expect);
498  CHECK (subBind.retrieveContent("i.p.a") == "11"_expect );
499  CHECK (subBind.retrieveContent("i.q.a") == "22"_expect );
500 
501  ++it;
502  CHECK (it);
503  CHECK (*it == "i.r."_expect );
504 
505  subBind = binding.openContext(it);
506  CHECK ( subBind.contains("a"));
507  CHECK (not subBind.contains("aa"));
508  CHECK (subBind.retrieveContent("a") == "5"_expect );
509  CHECK (subBind.retrieveContent("i.p.a") == "11"_expect );
510  CHECK (subBind.retrieveContent("i.q.a") == "22"_expect );
511 
512  ++it;
513  CHECK (isnil (it));
514  VERIFY_ERROR (ITER_EXHAUST, *it);
515  }
516 
517 
528  void
530  {
531  auto root = MakeRec()
532  .set("a", 5)
533  .set("i", MakeRec()
534  .scope( MakeRec()
535  .set("a", 11)
536  .genNode()
537  , MakeRec()
538  .set("a", 22)
539  .set("aa", 222)
540  .genNode()
541  , MakeRec()
542  /*——empty——*/
543  .genNode()
544  ))
545  .genNode();
546 
547  auto binding = text_template::DataSource{root};
548  CHECK (meta::typeStr(binding) == "text_template::DataSource<GenNode, void>"_expect );
549  CHECK ( binding.contains("a"));
550  CHECK (not binding.contains("b"));
551  CHECK (binding.retrieveContent("a") == "5"_expect );
552  CHECK (binding.retrieveContent("i") == "{|{a=11}, {a=22, aa=222}, {}}"_expect );
553  CHECK (not binding.isSubScope());
554 
555  auto it = binding.getSequence("i");
556  CHECK (it);
557  CHECK (renderCompact(*it) == "{a=11}");
558  CHECK (*it == root.data.get<Rec>().get("i").data.get<Rec>().child(0));
559 
560  auto subBind = binding.openContext(it);
561  CHECK (subBind.isSubScope());
562  CHECK ((meta::is_same<decltype(binding),decltype(subBind)>()));
563  CHECK ( subBind.contains("a"));
564  CHECK (not subBind.contains("b"));
565  CHECK (not subBind.contains("aa"));
566  CHECK ( subBind.contains("i"));
567  CHECK (subBind.retrieveContent("i") == "{|{a=11}, {a=22, aa=222}, {}}"_expect );
568  CHECK (subBind.retrieveContent("a") == "11"_expect );
569 
570  ++it;
571  CHECK (it);
572  CHECK (renderCompact(*it) == "{a=22, aa=222}");
573  CHECK (subBind.retrieveContent("a") == "11"_expect );
574 
575  subBind = binding.openContext(it);
576  CHECK (subBind.isSubScope());
577  CHECK (subBind.contains("a"));
578  CHECK (subBind.contains("aa"));
579  CHECK (subBind.retrieveContent("a") == "22"_expect );
580  CHECK (subBind.retrieveContent("aa") == "222"_expect);
581 
582  ++it;
583  CHECK (it);
584  CHECK (renderCompact(*it) == "{}");
585 
586  subBind = binding.openContext(it);
587  CHECK ( subBind.contains("a"));
588  CHECK (not subBind.contains("aa"));
589  CHECK (subBind.retrieveContent("a") == "5"_expect );
590 
591  ++it;
592  CHECK (isnil (it));
593  VERIFY_ERROR (ITER_EXHAUST, *it);
594 
595 
596  TextTemplate tt{"${for i}a=${a} ${if aa}and aa=${aa} ${endif}${endfor}."};
597  CHECK (tt.render(root) == "a=11 a=22 and aa=222 a=5 ."_expect);
598  }
599  };
600 
601  LAUNCHER (TextTemplate_test, "unit common");
602 
603 
604 }} // namespace lib::test
auto explore(IT &&srcSeq)
start building a IterExplorer by suitably wrapping the given iterable source.
Definition: run.hpp:49
#define VERIFY_ERROR(ERROR_ID, ERRONEOUS_STATEMENT)
Macro to verify that a statement indeed raises an exception.
Text template substitution engine.
A front-end for using printf-style formatting.
Implementation namespace for support and library code.
A minimalistic text templating engine with flexible data binding.
Simple test class runner.
represents text content
A collection of frequently used helper functions to support unit testing.
static ActionSeq compile(string const &)
wrapped regex iterator to allow usage in foreach loops
Definition: regex.hpp:49
static string apply(string spec, DAT const &data)
one-shot shorthand: compile a template and apply it to the given data
A complement to allow instantiation of a TextTemplate with ETD data.
object-like record of data.
Definition: record.hpp:150
generic data element node within a tree
Definition: gen-node.hpp:231
#define VERIFY_FAIL(FAILURE_MSG, ERRONEOUS_STATEMENT)
Macro to verify that a statement indeed raises a std::exception, which additionally contains some FAI...