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 }