1 /* 2 * Hunt - A high-level D Programming Language Web framework that encourages rapid development and clean, pragmatic design. 3 * 4 * Copyright (C) 2015-2019, HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.framework.view.Render; 13 14 private 15 { 16 import std.range; 17 import std.format: fmt = format; 18 19 import hunt.framework.view.ast.Node; 20 import hunt.framework.view.ast.Visitor; 21 import hunt.framework.view.algo; 22 import hunt.framework.view.algo.Wrapper; 23 import hunt.framework.view.Lexer; 24 import hunt.framework.view.Parser; 25 import hunt.framework.view.Exception : TemplateRenderException, 26 assertTemplate = assertTemplateRender; 27 28 import hunt.framework.view.Uninode; 29 import hunt.framework.http.Request; 30 31 import hunt.framework.Simplify; 32 import hunt.framework.view.Util; 33 import hunt.logging; 34 } 35 36 37 38 39 struct FormArg 40 { 41 string name; 42 Nullable!UniNode def; 43 44 this (string name) 45 { 46 this.name = name; 47 this.def = Nullable!UniNode.init; 48 } 49 50 this (string name, UniNode def) 51 { 52 this.name = name; 53 this.def = Nullable!UniNode(def); 54 } 55 } 56 57 58 struct Macro 59 { 60 FormArg[] args; 61 Nullable!Context context; 62 Nullable!Node block; 63 64 this(FormArg[] args, Context context, Node block) 65 { 66 this.args = args; 67 this.context = context.toNullable; 68 this.block = block.toNullable; 69 } 70 } 71 72 73 class Context 74 { 75 private Context prev; 76 77 UniNode data; 78 Function[string] functions; 79 Macro[string] macros; 80 81 this () 82 { 83 prev = null; 84 data = UniNode.emptyObject(); 85 } 86 87 this (Context ctx, UniNode data) 88 { 89 prev = ctx; 90 this.data = data; 91 } 92 93 Context previos() @property 94 { 95 if (prev !is null) 96 return prev; 97 return this; 98 } 99 100 bool has(string name) 101 { 102 if (name in data) 103 return true; 104 if (prev is null) 105 return false; 106 return prev.has(name); 107 } 108 109 UniNode get(string name) 110 { 111 if (name in data) 112 return data[name]; 113 if (prev is null) 114 return UniNode(null); 115 return prev.get(name); 116 } 117 118 UniNode* getPtr(string name) 119 { 120 if (name in data) 121 return &(data[name]); 122 if (prev is null) 123 assertTemplate(0, "Non declared var `%s`".fmt(name)); 124 return prev.getPtr(name); 125 } 126 127 T get(T)(string name) 128 { 129 return this.get(name).get!T; 130 } 131 132 bool hasFunc(string name) 133 { 134 if (name in functions) 135 return true; 136 if (prev is null) 137 return false; 138 return prev.hasFunc(name); 139 } 140 141 142 Function getFunc(string name) 143 { 144 if (name in functions) 145 return functions[name]; 146 if (prev is null) 147 assertTemplate(0, "Non declared function `%s`".fmt(name)); 148 return prev.getFunc(name); 149 } 150 151 152 bool hasMacro(string name) 153 { 154 if (name in macros) 155 return true; 156 if (prev is null) 157 return false; 158 return prev.hasMacro(name); 159 } 160 161 162 Macro getMacro(string name) 163 { 164 if (name in macros) 165 return macros[name]; 166 if (prev is null) 167 assertTemplate(0, "Non declared macro `%s`".fmt(name)); 168 return prev.getMacro(name); 169 } 170 } 171 172 173 struct AppliedFilter 174 { 175 string name; 176 UniNode args; 177 } 178 179 180 class Render : VisitorInterface 181 { 182 private 183 { 184 TemplateNode _root; 185 Context _globalContext; 186 Context _rootContext; 187 UniNode[] _dataStack; 188 AppliedFilter[] _appliedFilters; 189 TemplateNode[] _extends; 190 191 Context _context; 192 Request _request; 193 194 string _renderedResult; 195 bool _isExtended; 196 197 string _routeGroup = DEFAULT_ROUTE_GROUP; 198 string _locale = "en-us"; 199 } 200 201 this(TemplateNode root) 202 { 203 _root = root; 204 _rootContext = new Context(); 205 206 foreach(key, value; globalFunctions) { 207 _rootContext.functions[key] = cast(Function)value; 208 } 209 210 foreach(key, value; globalFilters) { 211 _rootContext.functions[key] = cast(Function)value; 212 } 213 214 foreach(key, value; globalTests) { 215 _rootContext.functions[key] = cast(Function)value; 216 } 217 218 _rootContext.functions["input"] = &input; 219 } 220 221 Request request() { 222 return _request; 223 } 224 225 void request(Request value) { 226 _request = value; 227 } 228 229 UniNode input(UniNode node) { 230 version(HUNT_VIEW_DEBUG) { 231 tracef("node: %s, kind: %s", node.toString(), node.kind()); 232 } 233 234 if(_request is null) { 235 warningf("The reques does NOT set for node: %s", node.toString()); 236 return UniNode(""); 237 } 238 239 UniNode varargs = node["varargs"]; 240 if(varargs.length == 0) { 241 UniNode[string] data; 242 243 foreach(string key, string value; _request.input()) { 244 data[key] = UniNode(value); 245 } 246 return UniNode(data); 247 } else { 248 UniNode[] argNodes = varargs.get!(UniNode[])(); 249 UniNode keyNode = argNodes[0]; 250 UniNode defaultValueNode = UniNode(""); 251 252 if(varargs.length >= 2) { 253 defaultValueNode = argNodes[1]; 254 } 255 256 if(keyNode.kind == UniNode.Kind.text) { 257 string key = keyNode.get!string(); 258 string[string] allInputs = _request.input(); 259 auto itemPtr = key in allInputs; 260 if(itemPtr is null) { 261 warning(fmt("No value found for %s", key)); 262 return defaultValueNode; 263 } else { 264 return UniNode(*itemPtr); 265 } 266 } else { 267 // return UniNode("Only string can be accepted."); 268 warning("Only string can be accepted."); 269 return defaultValueNode; 270 } 271 } 272 } 273 274 void setRouteGroup(string rg) 275 { 276 _routeGroup = rg; 277 } 278 279 void setLocale(string locale) 280 { 281 _locale = locale; 282 } 283 284 string render(UniNode data) 285 { 286 import hunt.logging; 287 version(HUNT_VIEW_DEBUG) logDebug("----render data : ", data); 288 _context = new Context(_rootContext, data); 289 _globalContext = _context; 290 291 _extends = [_root]; 292 _isExtended = false; 293 294 _renderedResult = ""; 295 if (_root !is null) 296 tryAccept(_root); 297 return _renderedResult; 298 } 299 300 301 override void visit(TemplateNode node) 302 { 303 tryAccept(node.stmt.get); 304 } 305 306 override void visit(BlockNode node) 307 { 308 void super_() 309 { 310 tryAccept(node.stmt.get); 311 } 312 313 foreach (tmpl; _extends[0 .. $-1]) 314 if (node.name in tmpl.blocks) 315 { 316 pushNewContext(); 317 _context.functions["super"] = wrapper!super_; 318 tryAccept(tmpl.blocks[node.name].stmt.get); 319 popContext(); 320 return; 321 } 322 323 super_(); 324 } 325 326 327 override void visit(StmtBlockNode node) 328 { 329 pushNewContext(); 330 foreach(ch; node.children) 331 tryAccept(ch); 332 popContext(); 333 } 334 335 override void visit(RawNode node) 336 { 337 writeToResult(node.raw); 338 } 339 340 override void visit(ExprNode node) 341 { 342 tryAccept(node.expr.get); 343 auto n = pop(); 344 n.toStringType; 345 writeToResult(n.get!string); 346 } 347 348 override void visit(InlineIfNode node) 349 { 350 bool condition = true; 351 352 if (!node.cond.isNull) 353 { 354 tryAccept(node.cond.get); 355 auto res = pop(); 356 res.toBoolType; 357 condition = res.get!bool; 358 } 359 360 if (condition) 361 { 362 tryAccept(node.expr.get); 363 } 364 else if (!node.other.isNull) 365 { 366 tryAccept(node.other.get); 367 } 368 else 369 { 370 push(UniNode(null)); 371 } 372 } 373 374 override void visit(BinOpNode node) 375 { 376 UniNode calc(Operator op)() 377 { 378 tryAccept(node.lhs); 379 UniNode lhs = pop(); 380 381 tryAccept(node.rhs); 382 auto rhs = pop(); 383 384 return binary!op(lhs, rhs); 385 } 386 387 UniNode calcLogic(bool stopCondition)() 388 { 389 tryAccept(node.lhs); 390 auto lhs = pop(); 391 lhs.toBoolType; 392 if (lhs.get!bool == stopCondition) 393 return UniNode(stopCondition); 394 395 tryAccept(node.rhs); 396 auto rhs = pop(); 397 rhs.toBoolType; 398 return UniNode(rhs.get!bool); 399 } 400 401 UniNode calcCall(string type)() 402 { 403 tryAccept(node.lhs); 404 auto lhs = pop(); 405 406 tryAccept(node.rhs); 407 auto args = pop(); 408 auto name = args["name"].get!string; 409 args["varargs"] = UniNode([lhs] ~ args["varargs"].get!(UniNode[])); 410 411 if (_context.hasFunc(name)) 412 return visitFunc(name, args); 413 else if (_context.hasMacro(name)) 414 return visitMacro(name, args); 415 else 416 assertTemplate(0, "Undefined " ~ type ~ " %s".fmt(name), node.pos); 417 assert(0); 418 } 419 420 UniNode calcFilter() 421 { 422 return calcCall!"filter"; 423 } 424 425 UniNode calcIs() 426 { 427 auto res = calcCall!"test"; 428 res.toBoolType; 429 return res; 430 } 431 432 UniNode doSwitch() 433 { 434 switch (node.op) with (Operator) 435 { 436 case Concat: return calc!Concat; 437 case Plus: return calc!Plus; 438 case Minus: return calc!Minus; 439 case DivInt: return calc!DivInt; 440 case DivFloat: return calc!DivFloat; 441 case Rem: return calc!Rem; 442 case Mul: return calc!Mul; 443 case Greater: return calc!Greater; 444 case Less: return calc!Less; 445 case GreaterEq: return calc!GreaterEq; 446 case LessEq: return calc!LessEq; 447 case Eq: return calc!Eq; 448 case NotEq: return calc!NotEq; 449 case Pow: return calc!Pow; 450 case In: return calc!In; 451 452 case Or: return calcLogic!true; 453 case And: return calcLogic!false; 454 455 case Filter: return calcFilter; 456 case Is: return calcIs; 457 458 default: 459 assert(0, "Not implemented binary operator"); 460 } 461 } 462 463 push(doSwitch()); 464 } 465 466 override void visit(UnaryOpNode node) 467 { 468 tryAccept(node.expr); 469 auto res = pop(); 470 UniNode doSwitch() 471 { 472 switch (node.op) with (Operator) 473 { 474 case Plus: return unary!Plus(res); 475 case Minus: return unary!Minus(res); 476 case Not: return unary!Not(res); 477 default: 478 assert(0, "Not implemented unary operator"); 479 } 480 } 481 482 push(doSwitch()); 483 } 484 485 override void visit(NumNode node) 486 { 487 if (node.type == NumNode.Type.Integer) 488 push(UniNode(node.data._integer)); 489 else 490 push(UniNode(node.data._float)); 491 } 492 493 override void visit(BooleanNode node) 494 { 495 push(UniNode(node.boolean)); 496 } 497 498 override void visit(NilNode node) 499 { 500 push(UniNode(null)); 501 } 502 503 override void visit(IdentNode node) 504 { 505 UniNode curr; 506 if (node.name.length) 507 curr = _context.get(node.name); 508 else 509 curr = UniNode(null); 510 511 auto lastPos = node.pos; 512 foreach (sub; node.subIdents) 513 { 514 tryAccept(sub); 515 auto key = pop(); 516 517 switch (key.kind) with (UniNode.Kind) 518 { 519 // Index of list/tuple 520 case integer: 521 case uinteger: 522 curr.checkNodeType(array, lastPos); 523 if (key.get!size_t < curr.length) 524 curr = curr[key.get!size_t]; 525 else 526 assertTemplate(0, "Range violation on %s...[%d]".fmt(node.name, key.get!size_t), sub.pos); 527 break; 528 529 // Key of dict 530 case text: 531 auto keyStr = key.get!string; 532 if (curr.kind == UniNode.Kind.object && keyStr in curr) 533 curr = curr[keyStr]; 534 else if (_context.hasFunc(keyStr)) 535 { 536 auto args = [ 537 "name": UniNode(keyStr), 538 "varargs": UniNode([curr]), 539 "kwargs": UniNode.emptyObject 540 ]; 541 curr = visitFunc(keyStr, UniNode(args)); 542 } 543 else if (_context.hasMacro(keyStr)) 544 { 545 auto args = [ 546 "name": UniNode(keyStr), 547 "varargs": UniNode([curr]), 548 "kwargs": UniNode.emptyObject 549 ]; 550 curr = visitMacro(keyStr, UniNode(args)); 551 } 552 else 553 { 554 curr.checkNodeType(object, lastPos); 555 assertTemplate(0, "Unknown attribute %s".fmt(key.get!string), sub.pos); 556 } 557 break; 558 559 // Call of function 560 case object: 561 auto name = key["name"].get!string; 562 563 if (!curr.isNull) 564 key["varargs"] = UniNode([curr] ~ key["varargs"].get!(UniNode[])); 565 566 if (_context.hasFunc(name)) 567 { 568 curr = visitFunc(name, key); 569 } 570 else if (_context.hasMacro(name)) 571 { 572 curr = visitMacro(name, key); 573 } 574 else 575 assertTemplate(0, "Not found any macro, function or filter `%s`".fmt(name), sub.pos); 576 break; 577 578 default: 579 assertTemplate(0, "Unknown attribute %s for %s".fmt(key.toString, node.name), sub.pos); 580 } 581 582 lastPos = sub.pos; 583 } 584 585 push(curr); 586 } 587 588 override void visit(AssignableNode node) 589 { 590 auto expr = pop(); 591 592 // TODO: check flag of set scope 593 if (!_context.has(node.name)) 594 { 595 if (node.subIdents.length) 596 assertTemplate(0, "Unknow variable %s".fmt(node.name), node.pos); 597 _context.data[node.name] = expr; 598 return; 599 } 600 601 UniNode* curr = _context.getPtr(node.name); 602 603 if (!node.subIdents.length) 604 { 605 (*curr) = expr; 606 return; 607 } 608 609 auto lastPos = node.pos; 610 for(int i = 0; i < cast(int)(node.subIdents.length) - 1; i++) 611 { 612 tryAccept(node.subIdents[i]); 613 auto key = pop(); 614 615 switch (key.kind) with (UniNode.Kind) 616 { 617 // Index of list/tuple 618 case integer: 619 case uinteger: 620 checkNodeType(*curr, array, lastPos); 621 if (key.get!size_t < curr.length) 622 curr = &((*curr)[key.get!size_t]); 623 else 624 assertTemplate(0, "Range violation on %s...[%d]".fmt(node.name, key.get!size_t), node.subIdents[i].pos); 625 break; 626 627 // Key of dict 628 case text: 629 checkNodeType(*curr, object, lastPos); 630 if (key.get!string in *curr) 631 curr = &((*curr)[key.get!string]); 632 else 633 assertTemplate(0, "Unknown attribute %s".fmt(key.get!string), node.subIdents[i].pos); 634 break; 635 636 default: 637 assertTemplate(0, "Unknown attribute %s for %s".fmt(key.toString, node.name), node.subIdents[i].pos); 638 } 639 lastPos = node.subIdents[i].pos; 640 } 641 642 if (node.subIdents.length) 643 { 644 tryAccept(node.subIdents[$-1]); 645 auto key = pop(); 646 647 switch (key.kind) with (UniNode.Kind) 648 { 649 // Index of list/tuple 650 case integer: 651 case uinteger: 652 checkNodeType(*curr, array, lastPos); 653 if (key.get!size_t < curr.length) 654 (*curr).opIndex(key.get!size_t) = expr; // ¯\_(ツ)_/¯ 655 else 656 assertTemplate(0, "Range violation on %s...[%d]".fmt(node.name, key.get!size_t), node.subIdents[$-1].pos); 657 break; 658 659 // Key of dict 660 case text: 661 checkNodeType(*curr, object, lastPos); 662 (*curr)[key.get!string] = expr; 663 break; 664 665 default: 666 assertTemplate(0, "Unknown attribute %s for %s".fmt(key.toString, node.name, node.subIdents[$-1].pos)); 667 } 668 } 669 } 670 671 override void visit(StringNode node) 672 { 673 push(UniNode(node.str)); 674 } 675 676 override void visit(ListNode node) 677 { 678 UniNode[] list = []; 679 foreach (l; node.list) 680 { 681 tryAccept(l); 682 list ~= pop(); 683 } 684 push(UniNode(list)); 685 } 686 687 override void visit(DictNode node) 688 { 689 UniNode[string] dict; 690 foreach (key, value; node.dict) 691 { 692 tryAccept(value); 693 dict[key] = pop(); 694 } 695 push(UniNode(dict)); 696 } 697 698 override void visit(IfNode node) 699 { 700 tryAccept(node.cond); 701 702 auto cond = pop(); 703 cond.toBoolType; 704 705 if (cond.get!bool) 706 { 707 tryAccept(node.then); 708 } 709 else if (node.other) 710 { 711 tryAccept(node.other); 712 } 713 } 714 715 override void visit(ForNode node) 716 { 717 bool iterated = false; 718 int depth = 0; 719 bool calcCondition() 720 { 721 bool condition = true; 722 if (!node.cond.isNull) 723 { 724 tryAccept(node.cond.get); 725 auto cond = pop(); 726 cond.toBoolType; 727 condition = cond.get!bool; 728 } 729 return condition; 730 } 731 732 UniNode cycle(UniNode loop, UniNode varargs) 733 { 734 if (!varargs.length) 735 return UniNode(null); 736 return varargs[loop["index0"].get!size_t % varargs.length]; 737 } 738 739 740 void loop(UniNode iterable) 741 { 742 Nullable!UniNode lastVal; 743 bool changed(UniNode loop, UniNode val) 744 { 745 if (!lastVal.isNull && val == lastVal.get) 746 return false; 747 lastVal = val; 748 return true; 749 } 750 751 depth++; 752 pushNewContext(); 753 754 iterable.toIterableNode; 755 756 if (!node.cond.isNull) 757 { 758 auto newIterable = UniNode.emptyArray; 759 for (int i = 0; i < iterable.length; i++) 760 { 761 if (node.keys.length == 1) 762 _context.data[node.keys[0]] = iterable[i]; 763 else 764 { 765 iterable[i].checkNodeType(UniNode.Kind.array, node.iterable.get.pos); 766 assertTemplate(iterable[i].length >= node.keys.length, "Num of keys less then values", node.iterable.get.pos); 767 foreach(j, key; node.keys) 768 _context.data[key] = iterable[i][j]; 769 } 770 771 if (calcCondition()) 772 newIterable ~= iterable[i]; 773 } 774 iterable = newIterable; 775 } 776 777 _context.data["loop"] = UniNode.emptyObject; 778 _context.data["loop"]["length"] = UniNode(iterable.length); 779 _context.data["loop"]["depth"] = UniNode(depth); 780 _context.data["loop"]["depth0"] = UniNode(depth - 1); 781 _context.functions["cycle"] = wrapper!cycle; 782 _context.functions["changed"] = wrapper!changed; 783 784 for (int i = 0; i < iterable.length; i++) 785 { 786 _context.data["loop"]["index"] = UniNode(i + 1); 787 _context.data["loop"]["index0"] = UniNode(i); 788 _context.data["loop"]["revindex"] = UniNode(iterable.length - i); 789 _context.data["loop"]["revindex0"] = UniNode(iterable.length - i - 1); 790 _context.data["loop"]["first"] = UniNode(i == 0); 791 _context.data["loop"]["last"] = UniNode(i == iterable.length - 1); 792 _context.data["loop"]["previtem"] = i > 0 ? iterable[i - 1] : UniNode(null); 793 _context.data["loop"]["nextitem"] = i < iterable.length - 1 ? iterable[i + 1] : UniNode(null); 794 795 if (node.isRecursive) 796 _context.functions["loop"] = wrapper!loop; 797 798 if (node.keys.length == 1) 799 _context.data[node.keys[0]] = iterable[i]; 800 else 801 { 802 iterable[i].checkNodeType(UniNode.Kind.array, node.iterable.get.pos); 803 assertTemplate(iterable[i].length >= node.keys.length, "Num of keys less then values", node.iterable.get.pos); 804 foreach(j, key; node.keys) 805 _context.data[key] = iterable[i][j]; 806 } 807 808 tryAccept(node.block.get); 809 iterated = true; 810 } 811 popContext(); 812 depth--; 813 } 814 815 816 817 tryAccept(node.iterable.get); 818 UniNode iterable = pop(); 819 loop(iterable); 820 821 if (!iterated && !node.other.isNull) 822 tryAccept(node.other.get); 823 } 824 825 826 override void visit(SetNode node) 827 { 828 tryAccept(node.expr); 829 830 if (node.assigns.length == 1) 831 tryAccept(node.assigns[0]); 832 else 833 { 834 auto expr = pop(); 835 expr.checkNodeType(UniNode.Kind.array, node.expr.pos); 836 837 if (expr.length < node.assigns.length) 838 assertTemplate(0, "Iterable length less then number of assigns", node.expr.pos); 839 840 foreach(idx, assign; node.assigns) 841 { 842 push(expr[idx]); 843 tryAccept(assign); 844 } 845 } 846 } 847 848 849 override void visit(MacroNode node) 850 { 851 FormArg[] args; 852 853 foreach(arg; node.args) 854 { 855 if (arg.defaultExpr.isNull) 856 args ~= FormArg(arg.name); 857 else 858 { 859 tryAccept(arg.defaultExpr.get); 860 args ~= FormArg(arg.name, pop()); 861 } 862 } 863 864 _context.macros[node.name] = Macro(args, _context, node.block.get); 865 } 866 867 868 override void visit(CallNode node) 869 { 870 FormArg[] args; 871 872 foreach(arg; node.formArgs) 873 { 874 if (arg.defaultExpr.isNull) 875 args ~= FormArg(arg.name); 876 else 877 { 878 tryAccept(arg.defaultExpr.get); 879 args ~= FormArg(arg.name, pop()); 880 } 881 } 882 883 auto caller = Macro(args, _context, node.block.get); 884 885 tryAccept(node.factArgs.get); 886 auto factArgs = pop(); 887 888 visitMacro(node.macroName, factArgs, caller.nullable); 889 } 890 891 892 override void visit(FilterBlockNode node) 893 { 894 tryAccept(node.args.get); 895 auto args = pop(); 896 897 pushFilter(node.filterName, args); 898 tryAccept(node.block.get); 899 popFilter(); 900 } 901 902 903 override void visit(ImportNode node) 904 { 905 if (node.tmplBlock.isNull) 906 return; 907 908 auto stashedContext = _context; 909 auto stashedResult = _renderedResult; 910 911 if (!node.withContext) 912 _context = _globalContext; 913 914 _renderedResult = ""; 915 916 pushNewContext(); 917 918 foreach (child; node.tmplBlock.get.stmt.get.children) 919 tryAccept(child); 920 921 auto macros = _context.macros; 922 923 popContext(); 924 925 _renderedResult = stashedResult; 926 927 if (!node.withContext) 928 _context = stashedContext; 929 930 if (node.macrosNames.length) 931 foreach (name; node.macrosNames) 932 { 933 assertTemplate(cast(bool)(name.was in macros), "Undefined macro `%s` in `%s`".fmt(name.was, node.fileName), node.pos); 934 _context.macros[name.become] = macros[name.was]; 935 } 936 else 937 foreach (key, val; macros) 938 _context.macros[key] = val; 939 } 940 941 942 override void visit(IncludeNode node) 943 { 944 if (node.tmplBlock.isNull) 945 return; 946 947 auto stashedContext = _context; 948 949 if (!node.withContext) 950 _context = _globalContext; 951 952 tryAccept(node.tmplBlock.get); 953 954 if (!node.withContext) 955 _context = stashedContext; 956 } 957 958 959 override void visit(ExtendsNode node) 960 { 961 Nullable!TemplateNode templateNode = node.tmplBlock; 962 if(!templateNode.isNull()) { 963 _extends ~= templateNode.get(); 964 tryAccept(templateNode.get()); 965 } 966 _extends.popBack; 967 _isExtended = true; 968 } 969 970 private void tryAccept(Node node) 971 { 972 if (!_isExtended) 973 node.accept(this); 974 } 975 976 977 private UniNode visitFunc(string name, UniNode args) 978 { 979 version(HUNT_VIEW_DEBUG) info("---Func: ", name,", args: ",args); 980 981 if(name == "trans") 982 { 983 if("varargs" in args) 984 { 985 return doTrans(args["varargs"]); 986 } 987 } 988 else if(name == "date") 989 { 990 import hunt.util.DateTime; 991 auto format = args["varargs"][0].get!string; 992 auto timestamp = args["varargs"][1].get!int; 993 return UniNode(date(format, timestamp)); 994 } 995 else if(name == "url") 996 { 997 import hunt.framework.Simplify : url; 998 999 auto mca = args["varargs"][0].get!string; 1000 auto params = args["varargs"][1].get!string; 1001 return UniNode(url(mca, Util.parseFormData(params), _routeGroup)); 1002 } 1003 return _context.getFunc(name)(args); 1004 } 1005 1006 private UniNode doTrans(UniNode arg) 1007 { 1008 import hunt.framework.i18n; 1009 import hunt.framework.util.uninode.Serialization; 1010 1011 if(arg.kind == UniNode.Kind.array) 1012 { 1013 if(arg.length == 1) 1014 { 1015 return UniNode(transWithLocale(_locale,arg[0].get!string)); 1016 } 1017 else if(arg.length > 1) 1018 { 1019 string msg = arg[0].get!string; 1020 UniNode[] args; 1021 for(int i=1; i < arg.length ; i++) 1022 { 1023 args ~= arg[i]; 1024 } 1025 1026 return UniNode(transWithLocale(_locale,msg,uniNodeToJSON(UniNode(args)))); 1027 } 1028 } 1029 throw new TemplateRenderException("unsupport param : " ~ arg.toString); 1030 } 1031 1032 1033 private UniNode visitMacro(string name, UniNode args, Nullable!Macro caller = Nullable!Macro.init) 1034 { 1035 UniNode result; 1036 1037 auto macro_ = _context.getMacro(name); 1038 auto stashedContext = _context; 1039 _context = macro_.context.get; 1040 pushNewContext(); 1041 1042 UniNode[] varargs; 1043 UniNode[string] kwargs; 1044 1045 foreach(arg; macro_.args) 1046 if (!arg.def.isNull) 1047 _context.data[arg.name] = arg.def.get; 1048 1049 for(int i = 0; i < args["varargs"].length; i++) 1050 { 1051 if (i < macro_.args.length) 1052 _context.data[macro_.args[i].name] = args["varargs"][i]; 1053 else 1054 varargs ~= args["varargs"][i]; 1055 } 1056 1057 foreach (string key, value; args["kwargs"]) 1058 { 1059 if (macro_.args.has(key)) 1060 _context.data[key] = value; 1061 else 1062 kwargs[key] = value; 1063 } 1064 1065 _context.data["varargs"] = UniNode(varargs); 1066 _context.data["kwargs"] = UniNode(kwargs); 1067 1068 foreach(arg; macro_.args) 1069 if (arg.name !in _context.data) 1070 assertTemplate(0, "Missing value for argument `%s` in macro `%s`".fmt(arg.name, name)); 1071 1072 if (!caller.isNull) 1073 _context.macros["caller"] = caller.get(); 1074 1075 tryAccept(macro_.block.get); 1076 result = pop(); 1077 1078 popContext(); 1079 _context = stashedContext; 1080 1081 return result; 1082 } 1083 1084 private void writeToResult(string str) 1085 { 1086 if (!_appliedFilters.length) 1087 { 1088 _renderedResult ~= str; 1089 } 1090 else 1091 { 1092 UniNode curr = UniNode(str); 1093 foreach_reverse (filter; _appliedFilters) 1094 { 1095 auto args = filter.args; 1096 args["varargs"] = UniNode([curr] ~ args["varargs"].get!(UniNode[])); 1097 1098 if (_context.hasFunc(filter.name)) 1099 curr = visitFunc(filter.name, args); 1100 else if (_context.hasMacro(filter.name)) 1101 curr = visitMacro(filter.name, args); 1102 else 1103 assert(0); 1104 1105 curr.toStringType; 1106 } 1107 1108 _renderedResult ~= curr.get!string; 1109 } 1110 } 1111 1112 private void pushNewContext() 1113 { 1114 _context = new Context(_context, UniNode.emptyObject); 1115 } 1116 1117 1118 private void popContext() 1119 { 1120 _context = _context.previos; 1121 } 1122 1123 1124 private void push(UniNode un) 1125 { 1126 _dataStack ~= un; 1127 } 1128 1129 1130 private UniNode pop() 1131 { 1132 if (!_dataStack.length) 1133 assertTemplate(0, "Unexpected empty stack"); 1134 1135 auto un = _dataStack.back; 1136 _dataStack.popBack; 1137 return un; 1138 } 1139 1140 1141 private void pushFilter(string name, UniNode args) 1142 { 1143 _appliedFilters ~= AppliedFilter(name, args); 1144 } 1145 1146 1147 private void popFilter() 1148 { 1149 if (!_appliedFilters.length) 1150 assertTemplate(0, "Unexpected empty filter stack"); 1151 1152 _appliedFilters.popBack; 1153 } 1154 } 1155 1156 1157 void registerFunction(alias func)(Render render, string name) 1158 { 1159 render._rootContext.functions[name] = wrapper!func; 1160 } 1161 1162 1163 void registerFunction(alias func)(Render render) 1164 { 1165 enum name = __traits(identifier, func); 1166 render._rootContext.functions[name] = wrapper!func; 1167 } 1168 1169 1170 private bool has(FormArg[] arr, string name) 1171 { 1172 foreach(a; arr) { 1173 if (a.name == name) 1174 return true; 1175 } 1176 return false; 1177 }