add maphash to the array wrapper
[bse.git] / t / 020-templater / 040-original.t
1 #!perl -w
2 # Basic tests for Squirrel::Template
3 use strict;
4 use Test::More;
5 use HTML::Entities;
6
7 sub template_test($$$$;$$);
8
9 my $gotmodule = require_ok('Squirrel::Template');
10
11 my %extra_opts;
12
13 SKIP: {
14   skip "couldn't load module", 15 unless $gotmodule;
15
16   my $flag = 0;
17   my $str = "ABC";
18   my $str2 = "DEF";
19   my ($repeat_limit, $repeat_value);
20
21   my %acts =
22     (
23      ifEq => \&tag_ifeq,
24      iterate_repeat_reset =>
25      [ \&iter_repeat_reset, \$repeat_limit, \$repeat_value ],
26      iterate_repeat =>
27      [ \&iter_repeat, \$repeat_limit, \$repeat_value ],
28      repeat => \$repeat_value,
29      strref => \$str,
30      str => $str,
31      str2 => $str2,
32      with_upper => \&tag_with_upper,
33      cat => \&tag_cat,
34      ifFalse => 0,
35      dead => sub { die "foo\n" },
36      noimpl => sub { die "ENOIMPL\n" },
37     );
38   my %vars =
39     (
40      a =>
41      {
42       b =>
43       {
44        c => "CEE"
45       }
46      },
47      str => $str,
48      somelist => [ 'a' .. 'f' ],
49      somehash => { qw(a 11 b 12 c 14 e 8) },
50      num1 => 101,
51      num2 => 202,
52      testclass => Squirrel::Template::Expr::WrapClass->new("TestClass"),
53      error =>
54      {
55       noimpl => sub { die "ENOIMPL\n" },
56      },
57      callback1 => sub {
58        my ($cb, $templater, @args) = @_;
59
60        for my $foo ("a" .. "e") {
61          $templater->set_var(foo => $foo);
62          $cb->();
63        }
64      },
65      somecode1 => sub { return "FOO" },
66      somecode2 => sub { return [ @_ ] },
67      dumper => sub {
68        use Data::Dumper;
69        Dumper($_[0]);
70      },
71      objs =>
72      [
73       { id => 1, val1 => "abc", val2 => "def" },
74       { id => 2, val1 => "ghi", val2 => "jkl" }
75      ],
76     );
77   template_test("<:str:>", "ABC", "simple", \%acts);
78   template_test("<:strref:>", "ABC", "scalar ref", \%acts);
79   $str = "DEF";
80   template_test("<:strref:>", "DEF", "scalar ref2", \%acts);
81   template_test(<<TEMPLATE, "12345", "iterate", \%acts, "in");
82 <:iterator begin repeat 1 5:><:repeat:><:iterator end repeat:>
83 TEMPLATE
84   template_test(<<TEMPLATE, "1|2|3|4|5", "iterate sep", \%acts, "in");
85 <:iterator begin repeat 1 5:><:repeat:><:
86 iterator separator repeat:>|<:iterator end repeat:>
87 TEMPLATE
88   template_test('<:ifEq [str] "ABC":>YES<:or:>NO<:eif:>', "YES", 
89                 "cond1", \%acts);
90   template_test('<:if Eq [str] "ABC":>YES<:or Eq:>NO<:eif Eq:>', "YES", 
91                 "cond2", \%acts);
92   template_test("<:dead:>", "* foo\n *", "dead", \%acts);
93   template_test("<:noimpl:>", "<:noimpl:>", "noimpl", \%acts);
94   template_test("<:unknown:>", "<:unknown:>", "unknown tag", \%acts);
95   template_test("<:ifDead:><:str:><:or:><:str2:><:eif:>",
96                 "* foo\n *<:ifDead:>ABC<:or:>DEF<:eif:>", "ifDead", \%acts);
97   template_test("<:ifNoimpl:><:str:><:or:><:str2:><:eif:>",
98                 "<:ifNoimpl:>ABC<:or:>DEF<:eif:>", "ifNoimpl", \%acts);
99
100   template_test("<:if!False:>FOO<:eif:>", "FOO", "if!False", \%acts);
101   template_test("<:if !False:>FOO<:eif:>", "FOO", "if !False", \%acts);
102   template_test("<:if!Str:>FOO<:eif:>", "", "if!Str", \%acts);
103   template_test("<:if!Dead:><:str:><:eif:>",
104                 "* foo\n *<:if!Dead:>ABC<:eif:>", "if!Dead", \%acts);
105   template_test("<:if!Noimpl:><:str:><:eif:>",
106                 "<:if!Noimpl:>ABC<:eif:>", "if!Noimpl", \%acts);
107
108   template_test(<<TEMPLATE, <<OUTPUT, "wrap", \%acts, "in");
109 <:wrap wraptest.tmpl title=>[cat "foo " [str]], menu => 1, showtitle => "abc" :>Alpha
110 <:param menu:>
111 <:param showtitle:>
112 <:= params.showtitle :>
113 TEMPLATE
114 <title>foo ABC</title>
115 Alpha
116 1
117 abc
118 abc
119 OUTPUT
120
121   template_test(<<TEMPLATE, <<OUTPUT, "wrap more", \%acts, "both");
122 Before
123 <:wrap wraptest.tmpl title=>[cat "foo " [str]], menu => 1, showtitle => "abc" -:>
124 Alpha
125 <:param menu:>
126 <:param showtitle:>
127 <:-endwrap-:>
128 After
129 TEMPLATE
130 Before
131 <title>foo ABC</title>
132 Alpha
133 1
134 abc
135 After
136 OUTPUT
137
138   template_test(<<TEMPLATE, <<OUTPUT, "wrap with too much parameter text", \%acts, "in");
139 <:wrap wraptest.tmpl title=>[cat "foo " [str]], menu => 1, showtitle => "abc" junk :>Alpha
140 <:param menu:>
141 <:param showtitle:>
142 TEMPLATE
143 * WARNING: Extra data after parameters ' junk' *<title>foo ABC</title>
144 Alpha
145 1
146 abc
147 OUTPUT
148
149   template_test(<<TEMPLATE, <<OUTPUT, "wrap recursive", \%acts, "both");
150 <:wrap wrapself.tmpl title=>[cat "foo " [str]], menu => 1, showtitle => "abc" :>Alpha
151 <:param menu:>
152 <:param showtitle:>
153 TEMPLATE
154 * Error starting wrap: Too many levels of wrap for 'wrapself.tmpl' *<title>foo ABC</title>
155 <title>foo ABC</title>
156 <title>foo ABC</title>
157 <title>foo ABC</title>
158 <title>foo ABC</title>
159 <title>foo ABC</title>
160 <title>foo ABC</title>
161 <title>foo ABC</title>
162 <title>foo ABC</title>
163 <title>foo ABC</title>
164 Alpha
165 1
166 abc
167 OUTPUT
168
169   template_test(<<TEMPLATE, <<OUTPUT, "wrap unknown", \%acts, "both");
170 <:wrap unknown.tmpl:>
171 Body
172 TEMPLATE
173 * Loading wrap: File unknown.tmpl not found *
174 OUTPUT
175
176   template_test(<<TEMPLATE, <<OUTPUT, "unwrapped wrap here", \%acts, "both");
177 before
178 <:wrap here:>
179 after
180 TEMPLATE
181 before
182 * wrap here without being wrapped *
183 after
184 OUTPUT
185
186   template_test(<<TEMPLATE, <<OUTPUT, ".wrap simple", \%acts, "in", \%vars);
187 <:.wrap "wraptest.tmpl", "title": "foo " _ str, "menu": 1, "showtitle":"abc" :>Alpha
188 <:param menu:>
189 <:param showtitle:>
190 <:= params.showtitle :>
191 TEMPLATE
192 <title>foo ABC</title>
193 Alpha
194 1
195 abc
196 abc
197 OUTPUT
198
199   template_test(<<TEMPLATE, <<OUTPUT, ".wrap unknown", \%acts, "both");
200 <:.wrap "unknown.tmpl":>
201 Body
202 TEMPLATE
203 * Loading wrap: File unknown.tmpl not found *
204 OUTPUT
205
206   template_test(<<TEMPLATE, <<OUTPUT, ".wrap recursive", \%acts, "both", \%vars);
207 <:.wrap "wrapself.tmpl", "title":"foo " _ str, "menu":1, "showtitle":"abc" :>Alpha
208 <:= params.menu :>
209 <:= params.showtitle :>
210 TEMPLATE
211 * Error starting wrap: Too many levels of wrap for 'wrapself.tmpl' *<title>foo ABC</title>
212 <title>foo ABC</title>
213 <title>foo ABC</title>
214 <title>foo ABC</title>
215 <title>foo ABC</title>
216 <title>foo ABC</title>
217 <title>foo ABC</title>
218 <title>foo ABC</title>
219 <title>foo ABC</title>
220 <title>foo ABC</title>
221 Alpha
222 1
223 abc
224 OUTPUT
225
226   template_test(<<TEMPLATE, <<OUTPUT, ".wrap more", \%acts, "both", \%vars);
227 Before
228 <:.wrap "wraptest.tmpl", "title":"foo " _ str, "menu":1, "showtitle":"abc" -:>
229 Alpha
230 <:= params.menu:>
231 <:= params.showtitle:>
232 <:-.end wrap-:>
233 After
234 TEMPLATE
235 Before
236 <title>foo ABC</title>
237 Alpha
238 1
239 abc
240 After
241 OUTPUT
242
243   # undefined iterator - replacement should happen on the inside
244   template_test(<<TEMPLATE, <<OUTPUT, "undefined iterator", \%acts);
245 <:iterator begin unknown:>
246 <:if Eq "1" "1":>TRUE<:or:>FALSE<:eif:>
247 <:iterator separator unknown:>
248 <:if Eq "1" "0":>TRUE<:or:>FALSE<:eif:>
249 <:iterator end unknown:>
250 TEMPLATE
251 <:iterator begin unknown:>
252 TRUE
253 <:iterator separator unknown:>
254 FALSE
255 <:iterator end unknown:>
256 OUTPUT
257
258   template_test(<<TEMPLATE, <<OUTPUT, "multi wrap", \%acts, "in");
259 <:wrap wrapinner.tmpl title => "ABC":>
260 Test
261 TEMPLATE
262 <title>ABC</title>
263
264 <head1>ABC</head1>
265
266 Test
267 OUTPUT
268
269   my $switch = <<IN;
270 <:switch:>ignored<:case Eq [strref] "ABC":>ONE<:case Eq [strref] "XYZ":>TWO<:
271 case default:>DEF<:endswitch:>
272 IN
273   $str = "ABC";
274   template_test($switch, "ONE", "switch1", \%acts, "both");
275   $str = "XYZ";
276   template_test($switch, "TWO", "switch2", \%acts, "both");
277   $str = "DEF";
278   template_test($switch, "DEF", "switch def", \%acts, "both");
279
280   my $switch2 = <<IN;
281 <:switch:><:case Eq [strref] "ABC":>ONE<:case Eq [strref] "XYZ":>TWO<:
282 case default:>DEF<:endswitch:>
283 IN
284   $str = "ABC";
285   template_test($switch2, "ONE", "switch without ignored", \%acts, "both");
286
287   template_test(<<IN, <<OUT, "unimplemented switch (by die)", \%acts, "both");
288 <foo><:strref bar |h:></foo><:switch:><:case Eq [strref] "XYZ":>FAIL<:case Eq [unknown] "ABC":><:endswitch:>
289 IN
290 <foo>ABC</foo><:switch:><:case Eq [unknown] "ABC":><:endswitch:>
291 OUT
292
293   template_test(<<IN, <<OUT, "unimplemented switch (by missing)", \%acts, "both");
294 <foo><:strref bar |h:></foo><:switch:><:case Eq [strref] "XYZ":>FAIL<:case Unknown:><:str:><:case Eq [unknown] "ABC":><:str2:><:endswitch:>
295 IN
296 <foo>ABC</foo><:switch:><:case Unknown:>ABC<:case Eq [unknown] "ABC":>DEF<:endswitch:>
297 OUT
298
299   template_test(<<IN, <<OUT, "switch with die in case and unknown", \%acts, "both");
300 <:switch:><:case Eq [strref] "XYZ":>FAIL<:case Dead:><:str:><:case Eq [unknown] "ABC":><:str2:><:endswitch:>
301 IN
302 * foo
303  *<:switch:><:case Eq [unknown] "ABC":>DEF<:endswitch:>
304 OUT
305
306   template_test(<<IN, <<OUT, "switch with die no matches", \%acts, "both");
307 <:switch:><:case Eq [strref] "XYZ":>FAIL<:case Dead:><:str:><:case False:><:str2:><:endswitch:>
308 IN
309 * foo
310  *
311 OUT
312
313   template_test(<<IN, <<OUT, "switch with case !", \%acts, "both");
314 <:switch:><:case !Str:>NOT STR<:case !False:>FALSE<:endswitch:>
315 IN
316 FALSE
317 OUT
318
319   template_test("<:with begin upper:>Alpha<:with end upper:>", "ALPHA", "with", \%acts);
320
321   template_test("<:with begin unknown:>Alpha<:str:><:with end unknown:>", <<EOS, "with", \%acts, "out");
322 <:with begin unknown:>AlphaABC<:with end unknown:>
323 EOS
324
325   template_test("<:include doesnt/exist optional:>", "", "optional include", \%acts);
326   template_test("<:include doesnt/exist:>", "* cannot find include doesnt/exist in path *", "failed include", \%acts);
327   template_test("x<:include included.include:>z", "xyz", "include", \%acts);
328
329   template_test <<IN, <<OUT, "nested in undefined if", \%acts;
330 <:if Unknown:><:if Eq "1" "1":>Equal<:or Eq:>Not Equal<:eif Eq:><:or Unknown:>false unknown<:eif Unknown:>
331 IN
332 <:if Unknown:>Equal<:or Unknown:>false unknown<:eif Unknown:>
333 OUT
334   template_test <<IN, <<OUT, "nested in undefined switch case", \%acts;
335 <:switch:>
336 <:case ifUnknown:><:if Eq 1 1:>Equal<:or Eq:>Unequal<:eif Eq:>
337 <:endswitch:>
338 IN
339 <:switch:><:case ifUnknown:>Equal
340 <:endswitch:>
341 OUT
342
343   { # using - for removing whitespace
344     template_test(<<IN, <<OUT, "space value", \%acts, "both");
345 <foo>
346 <:-str-:>
347 </foo>
348 <foo>
349 <:str-:>
350 </foo>
351 <foo>
352 <:str:>
353 </foo>
354 IN
355 <foo>ABC</foo>
356 <foo>
357 ABC</foo>
358 <foo>
359 ABC
360 </foo>
361 OUT
362
363     template_test(<<IN, <<OUT, "space simple cond", \%acts, "both");
364 <foo>
365 <:-ifStr:>TRUE<:or-:><:eif-:>
366 </foo>
367 <foo2>
368 <:-ifStr-:>
369 TRUE
370 <:-or:><:eif-:>
371 </foo2>
372 <foo3>
373 <:-ifStr-:>
374 TRUE
375 <:-or-:>
376 <:-eif-:>
377 </foo3>
378 <foo4>
379 <:-ifFalse-:>TRUE<:-or-:>FALSE<:-eif-:>
380 </foo4>
381 <foo5>
382 <:-ifFalse-:>
383 TRUE
384 <:-or-:>
385 FALSE
386 <:-eif-:>
387 </foo5>
388 <foo6>
389 <:ifFalse:>
390 TRUE
391 <:or:>
392 FALSE
393 <:eif:>
394 </foo6>
395 IN
396 <foo>TRUE</foo>
397 <foo2>TRUE</foo2>
398 <foo3>TRUE</foo3>
399 <foo4>FALSE</foo4>
400 <foo5>FALSE</foo5>
401 <foo6>
402
403 FALSE
404
405 </foo6>
406 OUT
407
408     template_test(<<IN, <<OUT, "space iterator", \%acts, "both");
409 <foo>
410 <:-iterator begin repeat 1 5 -:>
411 <:-repeat-:>
412 <:-iterator end repeat -:>
413 </foo>
414 <foo2>
415 <:-iterator begin repeat 1 5 -:>
416 <:-repeat-:>
417 <:-iterator separator repeat -:>
418 ,
419 <:-iterator end repeat -:>
420 </foo2>
421 IN
422 <foo>12345</foo>
423 <foo2>1,2,3,4,5</foo2>
424 OUT
425
426     template_test(<<IN, <<OUT, "space switch", \%acts, "both");
427 <foo>
428 <:- switch:>
429
430  <:- case default:>FOO
431 <:- endswitch:>
432 </foo>
433 IN
434 <foo>FOO
435 </foo>
436 OUT
437
438     template_test(<<IN, <<OUT, "while", \%acts);
439 <:.set work = [ "a" .. "z" ] -:>
440 <:.while work.size and work[0] ne "d" -:>
441 <:= work.shift :>
442 <:.end while -:>
443 IN
444 a
445 b
446 c
447 OUT
448
449     template_test(<<IN, <<OUT, "space complex", \%acts, "both");
450 <div class="window">
451   <h1><:str:></h1>
452   <ul class="children list">
453     <:iterator begin repeat 1 2:>
454     <:- switch:>
455     <:- case False:>
456     <li class="error message"><:repeat:></li>
457     <:case str:>
458   </ul>
459   <h2><:repeat:></h2>
460   <ul class="children list">
461     <:- case default:>
462     <li><:repeat:></li>
463     <:- endswitch:>
464     <:iterator end repeat:>
465   </ul>
466 </div>
467 IN
468 <div class="window">
469   <h1>ABC</h1>
470   <ul class="children list">
471     
472   </ul>
473   <h2>1</h2>
474   <ul class="children list">
475     
476   </ul>
477   <h2>2</h2>
478   <ul class="children list">
479     
480   </ul>
481 </div>
482 OUT
483   }
484
485   template_test("<:= unknown :>", "<:= unknown :>", "unknown", \%acts, "", \%vars);
486   template_test(<<TEMPLATE, "2", "multi-statement", \%acts, "", \%vars);
487 <:.set foo = [] :><:% foo.push(1); foo.push(2) :><:= foo.size() -:>
488 TEMPLATE
489
490   template_test(<<TEMPLATE, "2", "multi-statement no ws", \%acts, "", \%vars);
491 <:.set foo=[]:><:%foo.push(1);foo.push(2):><:= foo.size() -:>
492 TEMPLATE
493
494   template_test("<:= str :>", "ABC", "simple exp", \%acts, "", \%vars);
495   template_test("<:=str:>", "ABC", "simple exp no ws", \%acts, "", \%vars);
496   template_test("<:= a.b.c :>", "CEE", "hash methods", \%acts, "", \%vars);
497   template_test(<<IN, <<OUT, "simple set", \%acts, "both", \%vars);
498 <:.set d = "test" -:><:= d :>
499 IN
500 test
501 OUT
502   my @expr_tests =
503     (
504      [ 'num1 + num2', 303 ],
505      [ 'num1 - num2', -101 ],
506      [ 'num1 + num2 * 2', 505 ],
507      [ 'num2 mod 5', '2' ],
508      [ 'num1 / 5', '20.2' ],
509      [ 'num1 div 5', 20 ],
510      [ '+num1', 101 ],
511      [ '-(num1 + num2)', -303 ],
512      [ '"hello " _ str', 'hello ABC' ],
513      [ 'num1 < num2', 1 ],
514      [ 'num1 < 101', '' ],
515      [ 'num1 < 100', '' ],
516      [ 'num1 > num2', '' ],
517      [ 'num2 > num1', 1 ],
518      [ 'num1 > 101', '' ],
519      [ 'num1 == 101.0', '1' ],
520      [ 'num1 == 101', '1' ],
521      [ 'num1 == 100', '' ],
522      [ 'num1 != 101', '' ],
523      [ 'num1 != "101.0"', '' ],
524      [ 'num1 != 100', 1 ],
525      [ 'num1 >= 101', 1 ],
526      [ 'num1 >= 100', 1 ],
527      [ 'num1 >= 102', '' ],
528      [ 'num1 <= 101', 1 ],
529      [ 'num1 <= 100', '' ],
530      [ 'num1 <= 102', '1' ],
531      [ 'num1 <=> 102', '-1' ],
532      [ 'num1 <=> 101', '0' ],
533      [ 'str eq "ABC"', '1' ],
534      [ 'str eq "AB"', '' ],
535      [ 'str ne "AB"', '1' ],
536      [ 'str ne "ABC"', '' ],
537      [ 'str cmp "ABC"', 0 ],
538      [ 'str cmp "AB"', 1 ],
539      [ 'str.lower', 'abc' ],
540      [ 'somelist.size', 6 ],
541      [ '[ 4, 2, 3 ].first', 4 ],
542      [ '[ 1, 4, 9 ].join(",")', "1,4,9" ],
543      [ '[ "xx", "aa" .. "ad", "zz" ].join(" ")', "xx aa ab ac ad zz" ],
544      [ '1 ? "TRUE" : "FALSE"', 'TRUE' ],
545      [ '0 ? "TRUE" : "FALSE"', 'FALSE' ],
546      [ '[ 1 .. 4 ][2]', 3 ],
547      [ 'somelist[2]', "c" ],
548      [ 'somehash["b"]', "12" ],
549      [ 'not 1', '' ],
550      [ 'not 1 or 1', 1 ],
551      [ 'not 1 and 1', "" ],
552      [ '"xabcy" =~ /abc/', 1 ],
553      [ '"xabcy" !~ /abc/', "" ],
554      [ '[ "abc" =~ /(.)(.)/ ][1]', "b" ],
555      [ '"xabcy" !~ /abg/', 1 ],
556      [ '{ "a": 11, "b": 12, "c": 20 }["b"]', 12 ],
557      [ '[ 1, 2, 3 ][1]', 2 ],
558      [ 'testclass.foo', "[TestClass.foo]" ],
559      [ '@undef.defined', "" ],
560      [ '(@{: "abc" })()', "abc" ],
561      [ '(@{a,b: a+b})(12, 13)', '25' ],
562      [ '(@{a,b: a; b; a-b })(10, 5)', '5' ],
563
564      # WrapScalar
565      [ '"foo".length', 3 ],
566      [ '"foo".length(1)', "* scalar.length takes no parameters *" ],
567      [ '"foo".upper', "FOO" ],
568      [ '"foo".upper(1)', "* scalar.upper takes no parameters *" ],
569      [ '"Foo".lower', "foo" ],
570      [ '"Foo".lower(1)', "* scalar.lower takes no parameters *" ],
571      [ '"foo".defined', '1' ],
572      [ '"foo".defined(1)', '* scalar.defined takes no parameters *' ],
573      [ '"foo".trim', "foo" ],
574      [ '" a b ".trim', "a b" ],
575      [ '" a b ".trim(1)', "* scalar.trim takes no parameters *" ],
576      [ '"a b".split.join("|")', "a|b" ],
577      [ '"a,b,c".split(",").join("|")', 'a|b|c' ],
578      [ '"a,b,c".split(",",2).join("|")', 'a|b,c' ],
579      [ '(10.1).format("%.2f")', "10.10" ],
580      [ '(10.1).format("%.2f", 1)', "* scalar.format takes one parameter *" ],
581      [ '"str".evaltag', 'ABC' ],
582      [ '"cat [str] [str2]".evaltag', 'ABCDEF' ],
583      [ '"abc*".quotemeta', "abc\\*" ],
584      [ '"abc".quotemeta(1)', "* scalar.quotemeta takes no parameters *" ],
585      [ '"abcdef".contains("cde")', 1 ],
586      [ '"abcdef".contains("cdf")', "" ],
587      [ '"abcdef".contains("cdf",1)', "* scalar.contains requires one parameter *" ],
588      [ '"abcdefabcdef".index("cde")', 2 ],
589      [ '"abcdefabcdef".index("cdf")', -1 ],
590      [ '"abcdefabcdef".index("cde", 5)', 8 ],
591      [ '"abc".index("ab",1,2)', '* scalar.index requires one or two parameters *' ],
592      [ '"abcdefabcdef".rindex("cde")', 8 ],
593      [ '"abcdefabcdef".rindex("cde", 7)', 2 ],
594      [ '"abcdefabcdef".rindex("cde", 7, 3)', '* scalar.rindex requires one or two parameters *' ],
595      [ "(65).chr", "A" ],
596      [ "(10.1).int", 10 ],
597      [ "(10).int", 10 ],
598      [ "(10).rand < 10 and (10).rand >= 0", 1 ],
599      [ "(-10).abs", "10" ],
600      [ '(10).floor', 10 ],
601      [ '(10.1).floor', 10 ],
602      [ '(-10.1).floor', -11 ],
603      [ '(10).ceil', 10 ],
604      [ '(10.1).ceil', 11 ],
605      [ '(-10.1).ceil', -10 ],
606      [ '"test".is_list', 0 ],
607      [ '"test".is_hash', 0 ],
608      [ '"abc".replace(/(.)(.)(.)/, "$3$2$1")', "cba" ],
609      [ '"abc def ghi".replace(/(?:^|\b)([a-z])/, @{m: m.text.upper }, 1)',
610        'Abc Def Ghi' ],
611      [ '"a&b".escape("html")', 'a&amp;b' ],
612      [ '"abc".match(/b/).start', "1" ],
613      [ '"abc".match(/b/).end', "2" ],
614      [ '"abc".match(/b/).length', "1" ],
615      [ '"abc".match(/b/).text', "b" ],
616      [ '"abc".match(/(b)/).subexpr[0].start', "1" ],
617      [ '"abc".match(/(b)/).subexpr[0].end', "2" ],
618      [ '"abc".match(/(b)/).subexpr[0].length', "1" ],
619      [ '"abc".match(/(b)/).subexpr[0].text', "b" ],
620      [ '"abc".match(/(?<foo>b)/).named["foo"].text', "b" ],
621      [ '"abc".match(/(?<foo>b)/).named["bar"]', "" ],
622      [ '"abcd".substring(1)', "bcd" ],
623      [ '"abcd".substring(1,2)', "bc" ],
624      [ '"abcd".substring(1,-2)', "b" ],
625
626      # WrapArray
627      [ '[ [ 1, 2 ], 3 ].expand.join(",")', "1,2,3" ],
628      [ '[ 1, 2 ].is_list', 1 ],
629      [ '[ 1, 2 ].is_hash', 0 ],
630      [ '[ 1 .. 5 ].shuffle.size', 5 ],
631      [ '([ "a", 1, "b", "2" ].as_hash)["a"]', 1 ],
632      [ '[ 1 .. 5].grep(@{a: a mod 2 == 0 }).join(",")', '2,4' ],
633      [ '[ { a: 3 }, { a: 1 }, { a: 2 } ].sort(@{a,b: b.a <=> a.a }).map(@{a: a.a}).join("")', '321' ],
634      [ '[ "A" .. "Z" ].slice([ 0 .. 5 ]).join("")', 'ABCDEF' ],
635      [ '[ "A" .. "Z" ].slice(0, 1, -2, -1).join("")', 'ABYZ' ],
636      [ '[ "A" .. "Z" ].splice(0, 5).join("")', 'ABCDE' ],
637      [ 'objs.maphash("id")[2].val2', 'jkl' ],
638      [ 'objs.maphash("id", "val1")[1]', 'abc' ],
639      [ 'objs.maphash("val1", "val2")["abc"]', 'def' ],
640      [ 'objs.maphash("val1", @{i: i.id _ i.val2 })["abc"]', '1def' ],
641      [ 'objs.maphash(@{i: i.val1 _ i.val2 }, @{i: i.id _ i.val2 })["abcdef"]', '1def' ],
642      [ 'somelist.maphash().exists("a")', '1' ],
643      [ 'somelist.maphash().exists("k")', '' ],
644
645      # WrapHash
646      [ '{ "foo": 1 }.is_list', 0 ],
647      [ '{ "foo": 1 }.is_hash', 1 ],
648      [ '{ foo: 1, bar: 1 }.extend({ bar:2 })["bar"]', 2 ],
649     );
650   for my $test (@expr_tests) {
651     my ($expr, $result) = @$test;
652
653     template_test("<:= $expr :>", $result, "expr: $expr", \%acts, "", \%vars);
654   }
655
656   template_test(<<IN, "", "define no use", \%acts, "both", \%vars);
657 <:-.define foo:>
658 <:.end-:>
659 <:-.define bar:>
660 <:.end define-:>
661 IN
662   template_test(<<IN, "avaluebvalue", "define with call", \%acts, "both", \%vars);
663 <:-.define foo:>
664 <:-= avar -:>
665 <:.end-:>
666 <:.call "foo", avar:"avalue"-:>
667 <:.call "foo",
668   avar:"bvalue"-:>
669 IN
670   template_test(<<IN, "2716", "define defaults with call", \%acts, "both", \%vars);
671 <:-.define foo; abc:1, def:abc+5 :>
672 <:-= abc -:><:= def -:>
673 <:.end-:>
674 <:.call "foo", "abc":"2"-:>
675 <:.call "foo" -:>
676 IN
677   template_test(<<IN, "other value", "external call", \%acts, "", \%vars);
678 <:.call "called.tmpl", "avar":"other value"-:>
679 IN
680   template_test(<<IN, "This was preloaded", "call preloaded", \%acts, "both", \%vars);
681 <:.call "preloaded"-:>
682 IN
683   template_test(<<IN, <<OUT, "simple .for", \%acts, "", \%vars);
684 <:.for x in [ "a" .. "d" ] -:>
685 Value: <:= x :> Index: <:= loop.index :> Count: <:= loop.count:> Prev: <:= loop.prev :> Next: <:= loop.next :> Even: <:= loop.even :> Odd: <:= loop.odd :> Parity: <:= loop.parity :> is_first: <:= loop.is_first :> is_last: <:= loop.is_last :> Current by index: <:= loop.current :>-
686 <:.end-:>
687 IN
688 Value: a Index: 0 Count: 1 Prev:  Next: b Even:  Odd: 1 Parity: odd is_first: 1 is_last:  Current by index: a-
689 Value: b Index: 1 Count: 2 Prev: a Next: c Even: 1 Odd:  Parity: even is_first:  is_last:  Current by index: b-
690 Value: c Index: 2 Count: 3 Prev: b Next: d Even:  Odd: 1 Parity: odd is_first:  is_last:  Current by index: c-
691 Value: d Index: 3 Count: 4 Prev: c Next:  Even: 1 Odd:  Parity: even is_first:  is_last: 1 Current by index: d-
692 OUT
693   template_test(<<IN, <<OUT, "simple .if", \%acts, "", \%vars);
694 <:.if "a" eq "b" :>FAIL<:.else:>SUCCESS<:.end:>
695 <:.if "a" eq "a" :>SUCCESS<:.else:>FAIL<:.end:>
696 <:.if "a" eq "c" :>FAIL1<:.elsif "a" eq "a":>SUCCESS<:.else:>FAIL2<:.end:>
697 IN
698 SUCCESS
699 SUCCESS
700 SUCCESS
701 OUT
702   template_test(<<IN, <<OUT, "unknown .if", \%acts, "", \%vars);
703 <:.if unknown:>TRUE<:.end:>
704 <:.if "a" eq "a":>TRUE<:.elsif unknown:>TRUE<:.end:>
705 <:.if "a" eq "b" :>TRUE<:.elsif unknown:>TRUE<:.end:>
706 <:.if "a" ne "a" :>TRUE<:.elsif 0:>ELIF<:.elsif unknown:>TRUE<:.end:>
707 IN
708 <:.if unknown:>TRUE<:.end:>
709 TRUE
710 <:.if 0 :><:.elsif unknown:>TRUE<:.end:>
711 <:.if 0 :><:.elsif unknown:>TRUE<:.end:>
712 OUT
713
714   template_test(<<IN, <<OUT, "stack overflow on .call", \%acts, "", \%vars);
715 <:.define foo:>
716 <:-.call "foo"-:>
717 <:.end:>
718 <:-.call "foo"-:>
719 IN
720 Error opening scope for call: Too many scope levels
721 Backtrace:
722   .call 'foo' from test:1
723   .call 'foo' from test:1
724   .call 'foo' from test:1
725   .call 'foo' from test:1
726   .call 'foo' from test:1
727   .call 'foo' from test:1
728   .call 'foo' from test:1
729   .call 'foo' from test:1
730   .call 'foo' from test:1
731   .call 'foo' from test:1
732   .call 'foo' from test:1
733   .call 'foo' from test:1
734   .call 'foo' from test:1
735   .call 'foo' from test:1
736   .call 'foo' from test:1
737   .call 'foo' from test:1
738   .call 'foo' from test:1
739   .call 'foo' from test:1
740   .call 'foo' from test:1
741   .call 'foo' from test:1
742   .call 'foo' from test:1
743   .call 'foo' from test:1
744   .call 'foo' from test:1
745   .call 'foo' from test:1
746   .call 'foo' from test:1
747   .call 'foo' from test:1
748   .call 'foo' from test:1
749   .call 'foo' from test:1
750   .call 'foo' from test:1
751   .call 'foo' from test:1
752   .call 'foo' from test:1
753   .call 'foo' from test:1
754   .call 'foo' from test:1
755   .call 'foo' from test:1
756   .call 'foo' from test:1
757   .call 'foo' from test:1
758   .call 'foo' from test:1
759   .call 'foo' from test:1
760   .call 'foo' from test:1
761   .call 'foo' from test:1
762   .call 'foo' from test:1
763   .call 'foo' from test:1
764   .call 'foo' from test:1
765   .call 'foo' from test:1
766   .call 'foo' from test:1
767   .call 'foo' from test:1
768   .call 'foo' from test:1
769   .call 'foo' from test:3
770 OUT
771
772   template_test(<<IN, <<OUT, "evaltags", \%acts, "", \%vars);
773 <:= "str".evaltag :>
774 <:= "cat [str] [str2]".evaltag :>
775 IN
776 ABC
777 ABCDEF
778 OUT
779
780 template_test(<<IN, <<OUT, "hash methods", \%acts, "", \%vars);
781 <:.set foo = { "abc": 1, "def":2 } -:>
782 <:% foo.set("ghi", 3) -:>
783 ghi: <:= foo.ghi :>
784 keys: <:= foo.keys.sort.join(",") :>
785 size: <:= foo.size :>
786 values: <:= foo.values.sort.join(",") :>
787 <:.for i in foo.list -:>
788 <:= i.key _ "=" _ i.value :>
789 <:.end for-:>
790 IN
791 ghi: 3
792 keys: abc,def,ghi
793 size: 3
794 values: 1,2,3
795 abc=1
796 def=2
797 ghi=3
798 OUT
799
800 template_test(<<IN, <<OUT, "array methods", \%acts, "", \%vars);
801 <:.set foo = [ 1, 2, 4 ] -:>
802 <:% foo.set(2, 3) -:>
803 2: <:= foo[2] :>
804 <:.set foo = [ "A" .. "J" ] -:>
805 <:= foo.splice(8, 2, [ "Y", "Z" ]).join("") :>
806 <:= foo.join("") :>
807 <:.set foo = [ "A" .. "J" ] -:>
808 <:= foo.splice(5).join("") :>
809 <:= foo.join("") :>
810 IN
811 2: 3
812 IJ
813 ABCDEFGHYZ
814 FGHIJ
815 ABCDE
816 OUT
817
818   template_test(<<IN, <<OUT, "set undef", \%acts, "", \%vars);
819 <:.set foo = unknown :>
820 <:.set bar = error.noimpl :>
821 IN
822 <:.set foo = unknown :>
823 <:.set bar = error.noimpl :>
824 OUT
825
826   template_test(<<IN, <<OUT, "iterateover", \%acts, "", \%vars);
827 <:.iterateover callback1-:>
828 <:= foo -:>
829 <:.end:>
830 IN
831 abcde
832 OUT
833
834   template_test(<<'IN', <<OUT, "globals default variable", \%acts, "", \%vars);
835 <:.set globals.foo = "test" -:>
836 <:= globals.foo :>
837 <:.set name="foo" -:>
838 <:= testclass.$name :>
839 <:= testclass.$name() :>
840 <:= testclass.$name(1) :>
841 IN
842 test
843 [TestClass.foo]
844 [TestClass.foo]
845 [TestClass.foo]
846 OUT
847
848   template_test(<<'IN', <<OUT, "function calls", \%acts, "", \%vars);
849 <:= somecode1() :>
850 <:= somecode2().join(",") :>
851 <:= somecode2("a", "b").join(",") :>
852 IN
853 FOO
854
855 a,b
856 OUT
857
858   {
859     local $extra_opts{error_not_defined} = 1;
860     template_test(<<'IN', <<'OUT', "error !defined: expr", \%acts, "", \%vars);
861 <:= unknown_var :>
862 IN
863 * Variable 'unknown_var' not set
864  *
865 OUT
866     template_test(<<'IN', <<'OUT', "error !defined: .if", \%acts, "", \%vars);
867 <:.if unknown_var -:>
868 a
869 <:-.else -:>
870 b
871 <:-.end if:>
872 IN
873 * Variable 'unknown_var' not set
874  *
875 OUT
876     template_test(<<'IN', <<'OUT', "error !defined: .set", \%acts, "", \%vars);
877 <:.set foo = unknown_var :>
878 IN
879 * Variable 'unknown_var' not set
880  *
881 OUT
882   }
883 }
884
885 done_testing();
886
887 sub template_test ($$$$;$$) {
888   my ($in, $out, $desc, $acts, $stripnl, $vars) = @_;
889
890   $stripnl ||= 'none';
891   $in =~ s/\n$// if $stripnl eq 'in' || $stripnl eq 'both';
892   $out =~ s/\n$// if $stripnl eq 'out' || $stripnl eq 'both';
893
894   my $templater = Squirrel::Template->new
895     (
896      template_dir=>'t/templates',
897      preload => "preload.tmpl",
898      formats =>
899      {
900       html => sub {
901         encode_entities($_[0], '&<>');
902       }
903      },
904      %extra_opts,
905     );
906
907   my $result = $templater->replace_template($in, $acts, undef, "test", $vars);
908
909   is($result, $out, $desc);
910 }
911
912 sub iter_repeat_reset {
913   my ($rlimit, $rvalue, $args) = @_;
914
915   ($$rvalue, $$rlimit) = split ' ', $args;
916   --$$rvalue;
917 }
918
919 sub iter_repeat {
920   my ($rlimit, $rvalue) = @_;
921
922   ++$$rvalue <= $$rlimit;
923 }
924
925 sub tag_ifeq {
926   my ($args, $acts, $func, $templater) = @_;
927
928   my @args = get_expr($args, $acts, $templater);
929
930   @args >= 2
931     or die "ifEq takes 2 arguments";
932
933   $args[0] eq $args[1];
934 }
935
936 sub get_expr {
937   my ($origargs, $acts, $templater) = @_;
938
939   my @values;
940   my $args = $origargs;
941   while ($args) {
942     if ($args =~ s/\s*\[([^\[\]]+)\]\s*//) {
943       my $expr = $1;
944       my ($func, $funcargs) = split ' ', $expr, 2;
945       exists $acts->{$func} or die "ENOIMPL\n";
946       push @values, scalar $templater->perform($acts, $func, $funcargs, $expr);
947     }
948     elsif ($args =~ s/\s*\"((?:[^\"\\]|\\[\"\\]|\"\")*)\"\s*//) {
949       my $str = $1;
950       $str =~ s/(?:\\([\"\\])|\"(\"))/$1 || $2/eg;
951       push @values, $str;
952     }
953     elsif ($args =~ s/\s*(\S+)\s*//) {
954       push @values, $1;
955     }
956     else {
957       print "Arg parse failure with '$origargs' at '$args'\n";
958       exit;
959     }
960   }
961   
962   @values;
963 }
964
965 sub tag_with_upper {
966   my ($args, $text) = @_;
967
968   return uc($text);
969 }
970
971 sub tag_cat {
972   my ($args, $acts, $func, $templater) = @_;
973
974   return join "", $templater->get_parms($args, $acts);
975 }
976
977 package TestClass;
978
979 sub foo {
980   return "[TestClass.foo]";
981 }