1 module handlebars.tpl;
2 
3 import std.array;
4 import std.string;
5 import std.traits;
6 import std.algorithm;
7 import std.conv;
8 import std.exception;
9 import std.meta;
10 
11 import handlebars.tokens;
12 import handlebars.lifecycle;
13 import handlebars.helper;
14 import handlebars.components.all;
15 
16 version(unittest) {
17   import fluent.asserts;
18 }
19 
20 ///
21 class RenderException : Exception {
22   pure nothrow @nogc @safe this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) {
23     super(msg, file, line, nextInChain);
24   }
25 }
26 
27 ///
28 class RenderContext(T, Components...) : Lifecycle {
29   private {
30     T controller;
31     ComponentGroup!(NoDuplicates!Components) components;
32   }
33 
34   ///
35   this(T controller) {
36     this.controller = controller;
37   }
38 
39   ///
40   string render(Token[] tokens) {
41     auto token = tokens[0];
42 
43     if(components.exists(token.value) && token.type != Token.Type.openBlock) {
44       return components.get(controller, token, Token.empty, this);
45     }
46 
47     if(token.type == Token.Type.plain) {
48       return token.value;
49     }
50 
51     if(token.type == Token.Type.value) {
52       return getField!T(controller, token.value);
53     }
54 
55     if(token.type == Token.Type.helper) {
56       return getHelper!T(controller, token);
57     }
58 
59     if(token.type == Token.Type.openBlock) {
60       enforce(components.exists(token.value), "There is no component defined as `" ~ token.value ~ "`.");
61 
62       return components.get(controller,
63         token,
64         tokens[1..$-1],
65         this);
66     }
67 
68     return "";
69   }
70 
71   ///
72   string render(Token[] tokens)() {
73     enum token = tokens[0];
74 
75     static if(components.exists(token.value) && token.type != Token.Type.openBlock) {
76       return components.get(controller, token, Token.empty, this);
77     }
78     else static if(token.type == Token.Type.plain) {
79       return token.value;
80     }
81     else static if(token.type == Token.Type.value) {
82       return getField!(T, token.value)(controller);
83     }
84     else static if(token.type == Token.Type.helper) {
85       return getHelper_!(T, token)(controller);
86     }
87     else static if(token.type == Token.Type.openBlock) {
88       enforce(components.exists(token.value), "There is no component defined as `" ~ token.value ~ "`.");
89 
90       return components.get!tokens(controller, this);
91     } else {
92       return "";
93     }
94   }
95 
96   ///
97   private string getField(U)(U value, string fieldName) {
98     static immutable ignoredMembers = [ __traits(allMembers, Object), "render", "content" ];
99     auto pieces = fieldName.splitMemberAccess;
100 
101     static foreach (memberName; __traits(allMembers, U)) {{
102       static if(__traits(hasMember, U, memberName)) {
103         enum protection = __traits(getProtection, __traits(getMember, U, memberName));
104       } else {
105         enum protection = "";
106       }
107 
108       static if(protection == "public" && memberName != "this" && !ignoredMembers.canFind(memberName)) {
109         if(pieces[0] == memberName) {
110           mixin(`alias field = U.` ~ memberName ~ `;`);
111 
112           static if(isCallable!(field) && arity!field > 0) {
113             throw new RenderException("`" ~ T.stringof ~ "." ~
114                 fieldName ~ "` can not be rendered as a value. Did you forget the template parameters?");
115           } else {
116             static if (isCallable!(field)) {
117               alias FieldType = ReturnType!field;
118             } else {
119               alias FieldType = typeof(field);
120             }
121 
122             static if (!isSomeString!FieldType && isArray!FieldType) {
123               auto index = pieces[1][1..$-1].to!size_t;
124 
125               if(pieces.length > 2) {
126                 static if(isAggregateType!(ForeachType!(FieldType))) {
127                   mixin(`return getField(value.` ~ memberName ~ `[index], pieces[2..$].join("."));`);
128                 }
129               } else {
130                 mixin(`return value.` ~ memberName ~ `[index].to!string;`);
131               }
132 
133             } else static if (is(FieldType == struct) || is(FieldType == class)) {
134               mixin(`return getField(value.` ~ memberName ~ `, pieces[1..$].join("."));`);
135             } else {
136               mixin(`return value.` ~ memberName ~ `.to!string;`);
137             }
138           }
139         }
140       }
141     }}
142 
143     return "";
144   }
145 
146   ///
147   private string getField(U, string fieldName)(U value) {
148     mixin(`return value.` ~ fieldName ~ `.to!string;`);
149   }
150 
151   ///
152   private string getHelper_(U, Token token)(U value) {
153     enum pieces = "controller" ~ token.value.split(".");
154     enum path = pieces[0..$-1].join(".");
155     enum memberName = pieces[pieces.length - 1];
156 
157     mixin(`enum protection = __traits(getProtection, __traits(getMember, typeof(`~path~`), memberName));`);
158 
159     static assert(protection == "public", "The member used in hbs template `" ~ path ~ "." ~ memberName ~ "` must be public.");
160 
161     mixin(`alias Params = Parameters!(` ~ path ~ `.` ~ memberName ~ `);`);
162     mixin(`string result = ` ~ path ~ `.` ~ memberName ~ `(` ~ helperParams!(Params)(token.properties.list) ~ `).to!string;`);
163 
164     return result;
165   }
166 
167   ///
168   string yield(Token[] tokens) {
169     return tokens.tokenLevelRange.map!(a => render(a)).joiner.array.to!string;
170   }
171 
172   ///
173   bool evaluateBoolean(string value) {
174     return evaluate!(bool)(controller, value);
175   }
176 
177   ///
178   long evaluateLong(string value) {
179     return evaluate!(long)(controller, value);
180   }
181 }
182 
183 /// Render a template at runtime
184 string render(T, Components...)(string tplValue, T controller) {
185   auto tokens = TokenRange(tplValue);
186   auto context = new RenderContext!(T, IfComponent, EachComponent, ScopeComponent, NoDuplicates!Components)(controller);
187 
188   return tokens.tokenLevelRange.map!(a => context.render(a)).joiner.array.to!string;
189 }
190 
191 /// Render a template at ctfe
192 string render(string tplValue, T, Components...)(T controller) {
193   enum tokens = TokenRange(tplValue).tokenLevelRange.array;
194   scope context = new RenderContext!(T, IfComponent, EachComponent, ScopeComponent, NoDuplicates!Components)(controller);
195 
196   string result;
197 
198   static foreach(group; tokens) {
199     result ~= context.render!(group);
200   }
201 
202   return result;
203 }
204 
205 
206 /// Render a template at ctfe
207 string render(Token[] tokens, T, Components...)(T controller) {
208   scope context = new RenderContext!(T, NoDuplicates!Components)(controller);
209 
210   string result;
211   enum groups = tokens.tokenLevelRange.array;
212 
213   static foreach(group; groups) {
214     result ~= context.render!(group);
215   }
216 
217   return result;
218 }
219 
220 /// Rendering an empty string
221 unittest {
222   enum tpl = "";
223   struct Controller {}
224 
225   render(tpl, Controller()).should.equal("");
226 }
227 
228 /// Rendering an empty string at ctfe
229 unittest {
230   enum tpl = "";
231   struct Controller {}
232 
233   render!(tpl)(Controller()).should.equal("");
234 }
235 
236 /// Rendering a string value
237 unittest {
238   enum tpl = "{{value}}";
239 
240   struct Controller {
241     string value;
242   }
243 
244   render(tpl, Controller("2")).should.equal("2");
245 }
246 
247 /// Rendering a string value at ctfe
248 unittest {
249   enum tpl = "{{value}}";
250 
251   struct Controller {
252     string value;
253   }
254 
255   render!(tpl)(Controller("2")).should.equal("2");
256 }
257 
258 /// Rendering a string property
259 unittest {
260   enum tpl = "{{value}}";
261 
262   struct Controller {
263     string value() {
264       return "3";
265     }
266   }
267 
268   render(tpl, Controller()).should.equal("3");
269 }
270 
271 /// Rendering a string property at ctfe
272 unittest {
273   enum tpl = "{{value}}";
274 
275   struct Controller {
276     string value() {
277       return "3";
278     }
279   }
280 
281   render!(tpl)(Controller()).should.equal("3");
282 }
283 
284 /// Rendering a string property
285 unittest {
286   enum tpl = "{{value}}";
287 
288   struct Controller {
289     string value() {
290       return "3";
291     }
292   }
293 
294   render(tpl, Controller()).should.equal("3");
295 }
296 
297 
298 /// Rendering a string property at ctfe
299 unittest {
300   enum tpl = "{{value}}";
301 
302   struct Controller {
303     string value() {
304       return "3";
305     }
306   }
307 
308   render!(tpl)(Controller()).should.equal("3");
309 }
310 
311 /// Rendering a numeric property
312 unittest {
313   enum tpl = "{{value}}";
314 
315   struct Controller {
316     int value() {
317       return 3;
318     }
319   }
320 
321   render(tpl, Controller()).should.equal("3");
322 }
323 
324 
325 /// Rendering a numeric property at ctfe
326 unittest {
327   enum tpl = "{{value}}";
328 
329   struct Controller {
330     int value() {
331       return 3;
332     }
333   }
334 
335   render!(tpl)(Controller()).should.equal("3");
336 }
337 
338 /// Rendering a numeric struct subproperty
339 unittest {
340   enum tpl = "{{child.value}}";
341 
342   struct Child {
343     int value = 3;
344   }
345 
346   struct Controller {
347     Child child() {
348       return Child();
349     }
350   }
351 
352   render(tpl, Controller()).should.equal("3");
353 }
354 
355 /// Rendering a numeric struct subproperty at ctfe
356 unittest {
357   enum tpl = "{{child.value}}";
358 
359   struct Child {
360     int value = 3;
361   }
362 
363   struct Controller {
364     Child child() {
365       return Child();
366     }
367   }
368 
369   render!(tpl)(Controller()).should.equal("3");
370 }
371 
372 /// Rendering a numeric class subproperty
373 unittest {
374   enum tpl = "{{child.value}}";
375 
376   class Child {
377     int value = 3;
378   }
379 
380   struct Controller {
381     Child child() {
382       return new Child();
383     }
384   }
385 
386   render(tpl, Controller()).should.equal("3");
387 }
388 
389 /// Rendering a numeric class subproperty
390 unittest {
391   enum tpl = "{{child.value}}";
392 
393   class Child {
394     int value = 3;
395   }
396 
397   struct Controller {
398     Child child() {
399       return new Child();
400     }
401   }
402 
403   render(tpl, Controller()).should.equal("3");
404 }
405 
406 /// Rendering a numeric class member
407 unittest {
408   enum tpl = "{{child.value}}";
409 
410   class Child {
411     int value = 3;
412   }
413 
414   struct Controller {
415     Child child;
416   }
417 
418   render(tpl, Controller( new Child() )).should.equal("3");
419 }
420 
421 /// Rendering a numeric struct member
422 unittest {
423   enum tpl = "{{child.value}}";
424 
425   struct Child {
426     int value = 3;
427   }
428 
429   struct Controller {
430     Child child;
431   }
432 
433   render(tpl, Controller()).should.equal("3");
434 }
435 
436 /// Rendering a string property from a class controller
437 unittest {
438   enum tpl = "{{value}}";
439 
440   class Controller {
441     string value() {
442       return "3";
443     }
444   }
445 
446   render(tpl, new Controller()).should.equal("3");
447 }
448 
449 /// it should not render a string method with parameters as value
450 unittest {
451   enum tpl = "{{value}}";
452 
453   class Controller {
454     string value(string param) {
455       return param;
456     }
457   }
458 
459   render(tpl, new Controller())
460     .should
461     .throwException!RenderException
462       .withMessage
463         .equal("`Controller.value` can not be rendered as a value. Did you forget the template parameters?");
464 }
465 
466 /// Rendering a string property from a class controller with plain values
467 unittest {
468   enum tpl = "a {{value}} b {{value}} c";
469 
470   class Controller {
471     string value() {
472       return "3";
473     }
474   }
475 
476   auto result = render(tpl, new Controller());
477   result.should.equal("a 3 b 3 c");
478 }
479 
480 /// Rendering a helper with string param
481 unittest {
482   enum tpl = `{{helper "value"}}`;
483 
484   struct Controller {
485     string helper(string value) {
486       return value;
487     }
488   }
489 
490   render!(tpl)(Controller()).should.equal("value");
491 }
492 
493 /// Rendering a helper with int param
494 unittest {
495   enum tpl = `{{helper 5}}`;
496 
497   struct Controller {
498     int helper(int value) {
499       return value;
500     }
501   }
502 
503   render!(tpl)(Controller()).should.equal("5");
504 }
505 
506 /// Rendering a nested helper with int param
507 unittest {
508   enum tpl = `{{child.helper 5}}`;
509 
510   struct Child {
511     int helper(int value) {
512       return value;
513     }
514   }
515 
516   struct Controller {
517     Child child;
518   }
519 
520   render!(tpl)(Controller()).should.equal("5");
521 }
522 
523 /// Rendering a helper with property value
524 unittest {
525   enum tpl = `{{helper value}}`;
526 
527   struct Controller {
528     int helper(int value) {
529       return value;
530     }
531 
532     int value = 12;
533   }
534 
535   render!(tpl)(Controller()).should.equal("12");
536 }
537 
538 /// Rendering a helper with computed value
539 unittest {
540   enum tpl = `{{helper value}}`;
541 
542   struct Controller {
543     int helper(int value) {
544       return value;
545     }
546 
547     int value() {
548       return 8;
549     }
550   }
551 
552   render!(tpl)(Controller()).should.equal("8");
553 }
554 
555 /// Rendering a nested helper with bool param
556 unittest {
557   enum tpl = `{{child.helper 5 true "test"}}`;
558 
559   struct Child {
560     string helper(int number, bool value, string str) {
561       return number.to!string ~ " " ~ value.to!string ~ " " ~ str;
562     }
563   }
564 
565   struct Controller {
566     Child child;
567     string value;
568   }
569 
570   render!(tpl)(Controller()).should.equal("5 true test");
571 }
572 
573 /// Rendering undefined helpers should throw an exception
574 unittest {
575   enum tpl1 = `{{helper 5 true "test"}}`;
576   enum tpl2 = `{{child.other 5 true "test"}}`;
577   enum tpl3 = `{{value 5 true "test"}}`;
578   enum tpl4 = `{{child.value 5 true "test"}}`;
579 
580   struct Child {
581     string helper(int number, bool value, string str) {
582       return number.to!string ~ " " ~ value.to!string ~ " " ~ str;
583     }
584 
585     string value;
586   }
587 
588   struct Controller {
589     Child child;
590   }
591 
592   render(tpl1, Controller())
593     .should
594     .throwException!RenderHelperException
595       .withMessage
596         .equal("`helper` can not be rendered becaues it is not defined.");
597 
598   render(tpl2, Controller())
599     .should
600     .throwException!RenderHelperException
601       .withMessage
602         .equal("`child.other` can not be rendered becaues it is not defined.");
603 
604   render(tpl3, Controller())
605     .should
606     .throwException!RenderHelperException
607       .withMessage
608         .equal("`value` can not be rendered becaues it is not defined.");
609 
610   render(tpl4, Controller())
611     .should
612     .throwException!RenderHelperException
613       .withMessage
614         .equal("The helpers must be inside a struct or a class.");
615 }
616 
617 version(unittest) {
618   class Component : HbsComponent!"" {
619     int a;
620     int b;
621 
622     string render(Component, Components...)() {
623       auto sum = a + b;
624       return this.yield ~ sum.to!string;
625     }
626   }
627 }
628 
629 /// Rendering components with no blocks
630 unittest {
631   enum tpl = `{{Component a=value b=3}}`;
632 
633   struct Controller {
634     int value() {
635       return 2;
636     }
637   }
638 
639   render!(Controller, Component)(tpl, Controller()).should.equal("5");
640 }
641 
642 /// Rendering components with blocks
643 unittest {
644   enum tpl = `{{#Component a=value b=3}}text 3+{{value}}={{/Component}};`;
645 
646   struct Controller {
647     int value() {
648       return 2;
649     }
650   }
651 
652   render!(Controller, Component)(tpl, Controller()).should.equal("text 3+2=5;");
653 }
654 
655 /// Rendering if block
656 unittest {
657   enum tpl = `{{#if value}}text{{/if}}`;
658 
659   struct Controller {
660     int value() {
661       return 2;
662     }
663   }
664 
665   render(tpl, Controller()).should.equal("text");
666 }
667 
668 /// Rendering if block at ctfe
669 unittest {
670   enum tpl = `{{#if value}}text{{/if}}`;
671 
672   struct Controller {
673     int value() {
674       return 2;
675     }
676   }
677 
678   render!(tpl)(Controller()).should.equal("text");
679 }
680 
681 /// Don't render if blocks if the condition is not satisfied
682 unittest {
683   enum tpl = `{{#if false}}text{{/if}}`;
684 
685   struct Controller {
686     int value() {
687       return 2;
688     }
689   }
690 
691   render!(Controller)(tpl, Controller()).should.equal("");
692 }
693 
694 /// Rendering if block and stop at the else block if the value is evaluated to true
695 unittest {
696   struct Controller {
697     bool value = true;
698   }
699 
700   enum tpl = `{{#if true}}text{{else value}}other{{/if}}`;
701   render(tpl, Controller()).should.equal("text");
702 }
703 
704 /// Rendering if block with new lines and stop at the else block if the value is evaluated to true
705 unittest {
706   struct Controller {
707     bool value = true;
708   }
709 
710   enum tpl = "  {{#if true}}\n" ~
711   "text\n" ~
712   "{{else value}}\n" ~ "other{{/if}}\n";
713   render(tpl, Controller()).should.equal("  text");
714 }
715 
716 /// Rendering at ctfe an if block and stop at the else block if the value is evaluated to true
717 unittest {
718   struct Controller {
719     bool value = true;
720   }
721 
722   enum tpl = `{{#if true}}text{{else value}}other{{/if}}`;
723   render!(tpl)(Controller()).should.equal("text");
724 }
725 
726 /// Rendering at ctfe the right else
727 unittest {
728   struct Controller {
729     bool value = true;
730   }
731 
732   enum tpl = `{{#if false}}text{{else false}}other{{else value}}this one{{else}}default{{/if}}`;
733   render!(tpl)(Controller()).should.equal("this one");
734 }
735 
736 /// Rendering at ctfe an if block should render the final else
737 unittest {
738   struct Controller {
739     bool value = true;
740   }
741 
742   enum tpl = `{{#if false}}text{{else false}}other{{else}}{{value}}{{/if}}`;
743   render!(tpl)(Controller()).should.equal("true");
744 }
745 
746 /// Rendering at ctfe nested if blocks
747 unittest {
748   struct Controller {
749     bool value = true;
750   }
751 
752   enum tpl = `{{#if false}}text{{else}} <-- {{#if value}}true{{else}}false{{/if}} --> {{/if}}`;
753   render!(tpl)(Controller()).should.equal(" <-- true --> ");
754 }
755 
756 /// Rendering an each block
757 unittest {
758   struct Controller {
759     int[] list() {
760       return [1,2,3,4];
761     }
762   }
763 
764   enum tpl = `{{#each list as |item|}} {{item}} {{/each}}`;
765   render(tpl, Controller()).should.equal(" 1  2  3  4 ");
766 }
767 
768 /// Rendering an each block with a list of structs
769 unittest {
770   struct Child {
771     string name;
772   }
773 
774   struct Controller {
775     Child[] list;
776   }
777 
778   enum tpl = `{{#each list as |item|}} {{item.name}} {{/each}}`;
779   render(tpl, Controller([Child("name1"), Child("name2")])).should.equal(" name1  name2 ");
780 }
781 
782 /// Rendering an indexed each block
783 unittest {
784   struct Controller {
785     int[] list() {
786       return [1,2,3,4];
787     }
788   }
789 
790   enum tpl = `{{#each list as |item index|}} {{index}}{{item}} {{/each}}`;
791   render!(Controller)(tpl, Controller()).should.equal(" 01  12  23  34 ");
792 }
793 
794 /// Rendering an indexed each block with ctfe parsing
795 unittest {
796   struct Controller {
797     int[] list() {
798       return [1,2,3,4];
799     }
800   }
801 
802   enum tpl = `{{#each list as |item index|}} {{index}}{{item}} {{/each}}`;
803   render!(tpl)(Controller()).should.equal(" 01  12  23  34 ");
804 }
805 
806 
807 /// Rendering an indexed each block with ctfe parsing
808 unittest {
809   struct Controller {
810     int[string] list() {
811       return ["a": 1, "b": 2, "c": 3,"d": 4];
812     }
813   }
814 
815   enum tpl = `{{#each list as |item index|}} {{index}}{{item}} {{/each}}`;
816   render!(tpl)(Controller()).should.equal(" c3  a1  b2  d4 ");
817 }
818 
819 /// Rendering an nested indexed each block with ctfe parsing
820 unittest {
821   struct Child {
822     int[] numbers() {
823       return [1,2,3,4];
824     }
825   }
826 
827   struct Controller {
828     Child[] list() {
829       return [Child(), Child()];
830     }
831   }
832 
833   enum tpl = `{{#each list as |child index|}} {{index}}.[{{#each child.numbers as |number index|}}{{index}}.{{number}} {{/each}}] {{/each}}`;
834   render!(tpl)(Controller()).should.equal(" 0.[0.1 1.2 2.3 3.4 ]  1.[0.1 1.2 2.3 3.4 ] ");
835 }
836 
837 /// Rendering parent values in an nested indexed each block with ctfe parsing
838 unittest {
839   struct Child {
840     int[] numbers() {
841       return [1,2,3,4];
842     }
843   }
844 
845   struct Controller {
846     string parentValue1 = "test1";
847     string parentValue2 = "test2";
848 
849     Child[] list() {
850       return [Child(), Child()];
851     }
852   }
853 
854   enum tpl = `{{#each list as |child parent_index|}} {{parentValue1}} [{{#each child.numbers as |number index|}}{{parentValue2}} {{parent_index}}.{{index}}.{{number}} {{/each}}] {{/each}}`;
855   render!(tpl)(Controller()).should.equal(" test1 [test2 0.0.1 test2 0.1.2 test2 0.2.3 test2 0.3.4 ]  test1 [test2 1.0.1 test2 1.1.2 test2 1.2.3 test2 1.3.4 ] ");
856 }
857 
858 /// Rendering scope component with helper
859 unittest {
860   struct Controller {
861     int[] list = [1,2,3,4];
862 
863     string helper(int a, int b){
864       return a.to!string ~ ":" ~ b.to!string;
865     }
866   }
867 
868   enum tpl = `{{#scope "list" "item" "1" "index" }} {{helper index item}} {{/scope}}`;
869   render(tpl, Controller()).should.equal(" 1:2 ");
870 }
871 
872 version(unittest) {
873   enum tplComponent = import("test-component.hbs");
874   class TestComponent : HbsComponent!(tplComponent) {
875     ///
876     enum ComponentName = "test-component";
877 
878     int a;
879     int b;
880   }
881 }
882 
883 /// Rendering component with external template at runtime
884 unittest {
885   struct Controller {
886     int a = 1;
887     int b = 2;
888   }
889 
890   enum tpl = `{{test-component a=a b=b}}`;
891   render!(Controller, TestComponent)(tpl, Controller()).should.equal("1:2");
892 }
893 
894 /// Rendering component with external template at ctfe
895 unittest {
896   struct Controller {
897     int a = 1;
898     int b = 2;
899   }
900 
901   enum tpl = `{{test-component a=a b=b}}`;
902   render!(tpl, Controller, TestComponent)(Controller()).should.equal("1:2");
903 }
904 
905 
906 version(unittest) {
907   enum tplEachComponent = import("test-each-component.hbs");
908   class TestEachComponent : HbsComponent!(tplEachComponent) {
909     ///
910     enum ComponentName = "test-each-component";
911 
912     int[] list = [1,2,3,4,5];
913   }
914 
915   enum tplSeparatorComponent = import("separator-component.hbs");
916   class SeparatorComponent : HbsComponent!(tplSeparatorComponent) {
917     ///
918     enum ComponentName = "separator-component";
919 
920     int value;
921   }
922 }
923 
924 /// Rendering component with external template at ctfe
925 unittest {
926   struct Controller {
927     int[] list = [10,20,30,40,50];
928   }
929 
930   enum tpl = `{{test-each-component list=list}}`;
931   render!(tpl, Controller, TestEachComponent, SeparatorComponent)(Controller()).should.equal("10,20,30,40,50,");
932 }
933 
934 /// Rendering component with external template at runtime
935 unittest {
936   struct Controller {
937     int[] list = [10,20,30,40,50];
938   }
939 
940   enum tpl = `{{test-each-component list=list}}`;
941   render!(Controller, TestEachComponent, SeparatorComponent)(tpl, Controller()).should.equal("10,20,30,40,50,");
942 }