├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── dub.json ├── src ├── app.d └── temple │ ├── delims.d │ ├── func_string_gen.d │ ├── output_stream.d │ ├── package.d │ ├── temple_context.d │ ├── tests │ ├── capture.d │ ├── common.d │ ├── filter.d │ ├── todos.d │ └── vibe.d │ ├── util.d │ └── vibe.d └── test ├── test1.emd ├── test1.emd.txt ├── test10_fp.emd.txt ├── test10_fp_layout.emd ├── test10_fp_partial.emd ├── test11_propogate_fp.emd ├── test12_vibe1.emd ├── test13_vibelayout.emd ├── test13_vibepartial.emd ├── test14_unicode.emd ├── test14_unicode.emd.txt ├── test2.emd ├── test2.emd.txt ├── test3.emd.txt ├── test3_nested.emd ├── test3_nester.emd ├── test4.emd.txt ├── test4_root.emd ├── test4_subroot.emd ├── test4_subsubroot.emd ├── test5_layout.emd ├── test5_partial1.emd ├── test5_partial1.emd.txt ├── test5_partial2.emd ├── test5_partial2.emd.txt ├── test6_layout.emd ├── test6_partial.emd ├── test6_partial.emd.txt ├── test7_error.emd ├── test8_building_helpers.emd ├── test8_building_helpers.emd.txt └── test9_filter.emd /.gitignore: -------------------------------------------------------------------------------- 1 | ideas.txt 2 | dub.selections.json 3 | *.sublime-project 4 | *.sublime-workspace 5 | __test* 6 | *.exe 7 | *.obj 8 | *.lib 9 | *.o 10 | *.a 11 | *.sublime-* 12 | *.lst 13 | /temple 14 | /.dub 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: d 2 | sudo: false 3 | 4 | d: 5 | - dmd 6 | - ldc 7 | - gdc 8 | 9 | env: 10 | - CONFIG=unittest 11 | - CONFIG=unittest-vibed 12 | 13 | matrix: 14 | allow_failures: 15 | - d: gdc 16 | env: CONFIG=unittest-vibed 17 | 18 | install: 19 | - $DC | head -3 20 | 21 | script: 22 | - export PATH=$PATH:~/bin/ 23 | - dub --build=unittest --config=$CONFIG --compiler=$DC -v 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Temple [![Build Status](https://travis-ci.org/dymk/temple.png?branch=master)](https://travis-ci.org/dymk/temple) 2 | ====== 3 | Surprisingly Flexible, Compile Time, Zero Overhead, Embedded Template Engine for D 4 | 5 | About 6 | ----- 7 | Temple is a templating engine written in D, allowing D code to be embedded and 8 | executed in text files. The engine converts text to code at compile time, so there 9 | is zero overhead interpreting templates at runtime, allowing for very fast rendering. 10 | 11 | Temple supports passing any number of arbitrary variables to templates, as well as 12 | nesting and yielding templates within each other, capturing blocks of template code, and optional 13 | fine-grain filtering of generated text (e.g for escaping generated strings/safe strings). 14 | 15 | [Vibe.d](http://vibed.org/) compatible! See the [Vibe.d Usage](#vibed) section. 16 | 17 | Temple works with 18 | - DMD >= `2.066` 19 | - LDC >= `0.15-alpha1` 20 | - GDC >= `4.9.2` 21 | 22 | And probably more, but that's what it's been officially tested with. 23 | 24 | Table of Contents 25 | ----------------- 26 | 27 | - [Usage](#usage) 28 | - [The Temple Syntax](#the-temple-syntax) 29 | - [Contexts](#contexts) 30 | - [Compiling Templates](#using-compiledtemple) 31 | - [Templates from Files](#compile_temple_file) 32 | - [Nested Templates](#nested-templates) 33 | - [Layouts and Yielding](#layouts-and-yielding) 34 | - [Capture Blocks](#capture-blocks) 35 | - [Helpers](#helpers-a-la-rails-view-helpers) 36 | - [Filters](#filters) 37 | - [TempleFilter](#templefilter) 38 | - [Vibe.d Usage](#vibed-integration) 39 | - [Example: Simple Webpages](#example-simple-webpages) 40 | 41 | Usage 42 | ----- 43 | If using dub, include `temple` as a dependency in `dub.json`, or all of the files in 44 | `src/temple` in your build process, and `import temple;`. 45 | 46 | Going from a template string to a rendered template follows the process: 47 | - Compile the template with the `compile_temple` or `compile_temple_file` functions, resulting in a `CompiledTemple` 48 | - Call 'toString' or 'render' on the resulting `CompiledTemple` 49 | - Optionally pass in a `TempleContext` to pass variables to the template 50 | 51 | Optional [Filters](#filters) can be supplied to `compile_template[_file]` to filter dynamic template content. 52 | 53 | The Temple Syntax 54 | --------------- 55 | The template syntax is based off of that of [eRuby](https://en.wikipedia.org/wiki/ERuby) (but can be changed by modifying `temple/delims.d`). 56 | D statements go between `<% %>` delimers. If you wish to capture the result of a D expression, place it between `<%= %>` delimers, and it will be converted to a `string` using std.conv's `to`. 57 | 58 | Shorthand delimers are also supported: A line beginning with `%` is considered a statement and executed; a line beginning with `%=` is evaluated and the result is written to the output stream. 59 | 60 | > Note that expressions within `<%= %>` and `%=` should _not_ end with a semicolon, while statements within `<% %>` and `%` should. 61 | 62 | #### Quick Reference: 63 | 64 | | Input | Output | 65 | | ----- | ------ | 66 | | `foo` | `foo` | 67 | | `<% "foo"; %>` | `` | 68 | | `<%= "foo" %>` | `foo` | 69 | | `%= "foo" ~ " " ~ "bar"` | `foo bar` | 70 | | `% "foo";` | `` | 71 | 72 | ###### Foreach 73 | ```d 74 | % foreach(i; 0..3) { 75 | Index: <%= i %> 76 | % } 77 | ``` 78 | ``` 79 | Index: 0 80 | Index: 1 81 | Index: 2 82 | ``` 83 | 84 | ###### Foreach, alt 85 | ```d 86 | % import std.conv; 87 | <% foreach(i; 0..3) { %> 88 | %= "Index: " ~ to!string(i) 89 | <% } %> 90 | ``` 91 | ``` 92 | Index: 0 93 | Index: 1 94 | Index: 2 95 | ``` 96 | 97 | ###### If/else if/else statements 98 | ```d 99 | % auto a = "bar"; 100 | % if(a == "foo") { 101 | Foo! 102 | % } else if(a == "bar") { 103 | Bar! 104 | % } else { 105 | Baz! 106 | % } 107 | ``` 108 | ``` 109 | Bar! 110 | ``` 111 | 112 | ###### Overall usage 113 | ```d 114 | auto hello = compile_temple!`Hello, <%= var.name %>!` 115 | auto ctx = new TempleContext(); 116 | ctx.name = "Jimmy"; 117 | 118 | writeln(hello.toString(ctx)); 119 | ``` 120 | ``` 121 | Hello, Jimmy! 122 | ``` 123 | 124 | Contexts 125 | -------- 126 | The `TempleContext` type is used to pass variables to templates. The struct responds to 127 | `opDispatch`, and returns variables in the `Variant` type. Use `Variant#get` to 128 | convert the variable to its intended type. `TemplateContext#var(string)` is used 129 | to retrieve variables in the context, and can be called directly with `var` in the 130 | template: 131 | 132 | ```d 133 | auto context = new TempleContext(); 134 | context.name = "dymk"; 135 | context.should_bort = true; 136 | ``` 137 | Passed to: 138 | ```d 139 | <% /* Variant can be converted to a string automatically */ %> 140 | Hello, <%= var("name") %> 141 | 142 | <% /* Conversion of a Variant to a bool */ %> 143 | % if(var("should_bort").get!bool) { 144 | Yep, gonna bort 145 | % } else { 146 | Nope, not gonna bort 147 | % } 148 | 149 | <% /* Variants are returned by reference, and can be (re)assigned */ %> 150 | <% var("written_in") = "D" %> 151 | Temple is written in: <%= var("written_in") %> 152 | } 153 | ``` 154 | 155 | Results in: 156 | ``` 157 | Hello, dymk 158 | Yep, gonna bort 159 | Temple is written in: D 160 | ``` 161 | 162 | Variables can also be accessed directly via the dot operator, much like 163 | setting them. 164 | 165 | ```d 166 | auto context = new TempleContext(); 167 | context.foo = "Foo!"; 168 | context.bar = 10; 169 | ``` 170 | 171 | ```erb 172 | <%= var.foo %> 173 | <%= var.bar %> 174 | 175 | <% var.baz = true; %> 176 | <%= var.baz %> 177 | } 178 | ``` 179 | 180 | Prints: 181 | ``` 182 | Foo! 183 | 10 184 | true 185 | ``` 186 | 187 | For more information, see the Variant documentation on [the dlang website](http://dlang.org/phobos/std_variant.html) 188 | 189 | Using CompiledTemple 190 | ------------------- 191 | Both `compile_temple!"template string"` and `compile_temple_file!"filename"` return a `CompiledTemple`. 192 | The `CompiledTemple` exposes two rendering methods, `toString`, and `render`, both of which take an optional `TemplateContext`. 193 | 194 | ##### `string CompiledTemple#toString(TempleContext = null)` 195 | ------- 196 | Evaluates the template and returns the resulting string 197 | ```d 198 | import 199 | temple.temple, 200 | std.stdio, 201 | std.string; 202 | 203 | void main() 204 | { 205 | auto tlate = compile_temple!"foo, bar, baz"; 206 | writeln(tlate.toString()); // Prints "foo, bar, baz" 207 | } 208 | ``` 209 | 210 | An example passing a `TempleContext`: 211 | ```d 212 | void main() 213 | { 214 | auto tlate = compile_temple!q{ 215 | Hello, <%= var("name") %> 216 | }; 217 | 218 | auto context = new TempleContext(); 219 | context.name = "dymk"; 220 | 221 | writeln(tlate.toString(ctx)); // Prints "Hello, dymk" 222 | } 223 | ``` 224 | 225 | ##### `void CompiledTemple#render(Sink, TempleContext = null)` 226 | ----- 227 | Incrementally evaluates the template into Sink, which can be one of the following: 228 | - an `std.stdio.File` 229 | - an arbitrary OutputRange (as determined by `std.range.isOutputRange!(T, string)`) 230 | - a function or delegate that can take a string: `void delegate(string str) {}` 231 | 232 | Using `render` greatly decreases the number of allocations that must be made compared to `toString`. 233 | 234 | ```d 235 | auto tlate = compile_temple!q{ 236 | Hello, <%= var("name") %> 237 | }; 238 | 239 | tlate.render(stdout); // render into stdout 240 | 241 | // incrementally render into function/delegate 242 | tlate.render(function(str) { 243 | write(str); 244 | }); 245 | ``` 246 | 247 | `compile_temple_file` 248 | ----------------------- 249 | `compile_temple_file` is the same as `compile_temple`, but 250 | takes a file name to read as a template instead of the template string directly. 251 | Temple template files typically end with the extension `.emd` ("embedded d"). 252 | 253 | `template.emd`: 254 | ```d 255 | It's <%= var("hour") %> o'clock. 256 | ``` 257 | 258 | `main.d`: 259 | ```d 260 | import 261 | templ.templ, 262 | std.stdio; 263 | 264 | void main() { 265 | auto tplate = compile_temple_file!"template.emd"; 266 | 267 | auto context = new TempleContext(); 268 | context.hour = 5; 269 | 270 | tplate.render(stdout); 271 | } 272 | ``` 273 | ``` 274 | It's 5 o'clock 275 | ``` 276 | 277 | Nested Templates 278 | ---------------- 279 | 280 | `#render` can be called within templates to nest templates. By default, 281 | the current context is passed to the nested template, and any filters applied to the nester 282 | are applied to the nestee. A different context can be passed explicitly by calling 283 | `#render_with(TemplateContext)` instead. 284 | 285 | `a.emd` 286 | ```erb 287 | 288 | 289 |

Hello, from the 'a' template!

290 | <%= render!"b.emd"() %> 291 | 292 | 293 | ``` 294 | 295 | `b.emd` 296 | ```erb 297 |

And this is the 'b' template!

298 | ``` 299 | 300 | Rendering `a.emd` would result in: 301 | ```html 302 | 303 | 304 |

Hello, from the 'a' template!

305 |

And this is the 'b' template!

306 | 307 | 308 | ``` 309 | 310 | Layouts and Yielding 311 | ------------------------------- 312 | 313 | Templates can be made into parent layouts, where the child is rendered when `#yield` is called. 314 | Setting the child for a template is done by calling `layout` on the parent. 315 | 316 | ```d 317 | void main() 318 | { 319 | auto parent = compile_temple!"before <%= yield %> after"; 320 | auto child = compile_temple!"between"; 321 | 322 | auto composed = parent.layout(&child); 323 | composed.render(stdout); 324 | } 325 | ``` 326 | ``` 327 | before between after 328 | ``` 329 | 330 | Capture Blocks 331 | -------------- 332 | Blocks of template can be captured into a variable, by wrapping the desired 333 | code inside of a delegate, and passing that to `capture`. Capture blocks 334 | can be nested. Capture blocks do not render directly to a string, but rather a range, 335 | meaning that evaluating a capture block will result in evaluating the entire capture block 336 | multiple times. 337 | 338 | Capture has the signature `string capture(T...)(void delegate(T) block, T args)`, 339 | and can be called from user defined functions as well (See the Helpers section). 340 | 341 | Example: 342 | 343 | ```d 344 | <% auto outer = capture(() { %> 345 | Outer, first 346 | <% auto inner = capture(() { %> 347 | Inner, first 348 | <% }); %> 349 | Outer, second 350 | 351 | <%= inner %> 352 | <% }); %> 353 | 354 | <%= outer %> 355 | ``` 356 | ``` 357 | Outer, first 358 | Outer, second 359 | Inner, first 360 | ``` 361 | 362 | Helpers (A-la Rails View Helpers) 363 | --------------------------------- 364 | 365 | Helpers aren't a built in feature of Temple, but they are a very useful pattern for DRYing up templates. 366 | Here's a partial implementation of Rails' `form_for` helper: 367 | 368 | ```d 369 | <% 370 | import std.string; 371 | struct FormHelper 372 | { 373 | string model_name; 374 | 375 | auto field_for(string field_name, string type="text") 376 | { 377 | if(model_name != "") 378 | { 379 | field_name = "%s[%s]".format(model_name, field_name); 380 | } 381 | 382 | return ``.format(type, field_name); 383 | } 384 | 385 | auto submit(string value = "Submit") 386 | { 387 | return ``.format(value); 388 | } 389 | } 390 | 391 | auto form_for( 392 | string action, 393 | string name, 394 | void delegate(FormHelper) block) 395 | { 396 | auto form_body = capture(block, FormHelper(name)); 397 | return ` 398 |
399 | %s 400 |
`.format(action, form_body); 401 | } 402 | %> 403 | 404 | <%= form_for("/shorten", "", (f) { %> 405 | Shorten a URL: 406 | <%= f.field_for("url") %> 407 | <%= f.submit("Shorten URL") %> 408 | <% }); %> 409 | 410 | <%= form_for("/person", "person", (f) { %> 411 | Name: <%= f.field_for("name") %> 412 | Age: <%= f.field_for("age") %> 413 | DOB: <%= f.field_for("date_of_birth", "date") %> 414 | <%= f.submit %> 415 | <% }); %> 416 | ``` 417 | 418 | Renders: 419 | ```html 420 |
421 | Shorten a URL: 422 | 423 | 424 |
425 | 426 |
427 | Name: 428 | Age: 429 | DOB: 430 | 431 |
432 | ``` 433 | 434 | Filters 435 | ------- 436 | 437 | Filters are a way to filter and transform the dynamic parts of the template, before 438 | it is written to the output buffer. 439 | A filter takes the form of a `struct` or `class` that defines various overloads of the static 440 | method `temple_filter`. The `temple_filter` methods can either: 441 | - Take two parameters: a `TempleOutputStream` to write their result to, and the input to filter, e.g. 442 | - `void temple_filter(ref TempleOutputStream ob, string str) { ob.put(str); } 443 | - Take one parameter and return a string: 444 | 445 | 446 | Example, wrapping evaluated text in quotes: 447 | 448 | ```d 449 | struct QuoteFilter 450 | { 451 | static string temple_filter(string raw) 452 | { 453 | return `"` ~ raw ~ `"`; 454 | } 455 | 456 | static string temple_filter(T)(T raw) 457 | { 458 | return temple_filter(to!string(raw)); 459 | } 460 | } 461 | 462 | auto render = compile_temple!(QuoteFilter, q{ 463 | Won't be quoted 464 | <%= "Will be quoted" %> 465 | <%= 10 %> 466 | }); 467 | writeln(templeToString(&render)); 468 | ``` 469 | ``` 470 | Won't be quoted 471 | "Will be quoted" 472 | "10" 473 | ``` 474 | 475 | The `temple_filter` method isn't limited to filtering only strings, however. 476 | Any arbitrary type can be passed in. They can also define any arbitrary methods to use 477 | in the template, provided they don't clash with the methods that `TempleContext` defines. 478 | 479 | Example, implementing safe/unsafe strings for conditional escaping of input: 480 | 481 | ```d 482 | private struct SafeDemoFilter 483 | { 484 | static private struct SafeString 485 | { 486 | string value; 487 | } 488 | 489 | static string temple_filter(SafeString ts) 490 | { 491 | return ts.value; 492 | } 493 | 494 | static string temple_filter(string str) 495 | { 496 | return "!" ~ str ~ "!"; 497 | } 498 | 499 | static SafeString safe(string str) 500 | { 501 | return SafeString(str); 502 | } 503 | } 504 | 505 | auto templ = compile_temple!(q{ 506 | foo (filtered): <%= "mark me" %> 507 | foo (unfiltered): <%= safe("don't mark me") %> 508 | }, SafeDemoFilter); 509 | 510 | templ.render(stdout); 511 | ``` 512 | ``` 513 | foo (filtered): !mark me! 514 | foo (unfiltered): don't mark me 515 | ``` 516 | 517 | Filters are propagated to nested templates: 518 | 519 | `a.emd`: 520 | ```d 521 | <%= safe("foo1") %> 522 | <%= "foo2" %> 523 | foo3 524 | <%= render!"b.emd" %> 525 | foo4 526 | ``` 527 | 528 | `b.emd` 529 | ```d 530 | <%= safe("bar1") %> 531 | <%= "bar2" %> 532 | bar3 533 | ``` 534 | 535 | `a.emd` rendered with the `SafeDemoFilter`: 536 | ``` 537 | foo1 538 | !foo2! 539 | foo3 540 | bar1 541 | !bar2! 542 | bar3 543 | foo4 544 | ``` 545 | 546 | TempleFilter 547 | ------------ 548 | 549 | `TempleFilter` is not a filter itself, but rather allows a filter to be applied to entire family of `Temple` 550 | templates, grouping them under a single name. 551 | 552 | Example usage: 553 | ```d 554 | struct MyFilter { 555 | static string temple_filter(string unfiltered) { 556 | return "!" ~ unfiltered ~ "!"; 557 | } 558 | } 559 | 560 | // All Temple templates under Filtered will have MyFilter applied to them 561 | alias Filtered = TempleFilter!MyFilter; 562 | 563 | auto child = Filtered.Temple!` 564 | foo 565 | <%= "bar" %> 566 | `; 567 | 568 | auto parent = Filtered.TempleLayout!` 569 | header 570 | <%= yield %> 571 | footer 572 | ` 573 | 574 | parent.layout(&child).render(stdout); 575 | ``` 576 | 577 | Which would render: 578 | ``` 579 | header 580 | foo 581 | !bar! 582 | footer 583 | ``` 584 | 585 | Vibe.d Integration 586 | ------------------ 587 | 588 | Temple will expose functions for integrating with Vibe.d if compiled together (e.g. `Have_vibe_d` is defined) 589 | The addition to the Temple API is: 590 | - `void renderTemple(string temple)(HTTPServerRequest, Context = null)` 591 | - `void renderTempleFile(string filename)(HTTPServerRequest, Context = null)` 592 | - `void renderTempleLayoutFile(string layoutfile, string partialfile)(HTTPServerRequest, Context = null)` 593 | - `struct TempleHtmlFilter` 594 | 595 | where `Context` can be an `HTTPServerResponse`, or a `TempleContext`. If it is a `HTTPServerResponse`, then the contents of the `params` hash will be the context for the template. 596 | 597 | Usage is similar to Vibe.d's `render` function: 598 | 599 | ```d 600 | void doRequest(HTTPServerRequest req, HTTPServerResponse res) { 601 | 602 | // Client requested with query string `?name=foo` 603 | 604 | req.renderTemple!(` 605 | Hello, world! 606 | And hello, <%= var.name %>! 607 | `)(res); 608 | 609 | } 610 | ``` 611 | 612 | Would result in this HTTP response: 613 | ``` 614 | Hello, world! 615 | And hello, foo! 616 | ``` 617 | 618 | --- 619 | 620 | Dynamic content is passed through Vibe's HTML filter before being rendered, unless it is 621 | marked as safe, by calling `safe("your string")`. 622 | 623 | ```d 624 | void doRequest(HTTPServerRequest req, HTTPServerResponse res) { 625 | 626 | // Client requested with query string `?name=foo` 627 | 628 | req.renderTemple!(` 629 | 630 | 631 | Here's a thing! 632 | <%= "

Escape me!

" %> 633 | <%= safe("Don't escape me!") %> 634 | 635 | 636 | Hello, world! 637 | And hello, <%= var.name %>! 638 | `)(res); 639 | 640 | } 641 | ``` 642 | 643 | Would result in the HTTP response: 644 | ```html 645 | 646 | 647 | Here's a thing! 648 | <p>Escape me!</p> 649 | Don't escape me! 650 | 651 | 652 | ``` 653 | 654 | Example: Simple Webpages 655 | ------------------------ 656 | 657 | Here's a slightly more complex example, demonstrating how to use the library 658 | to render HTML templates inside of a common layout. 659 | 660 | ```d 661 | void main() 662 | { 663 | auto parent = compile_temple_file!"layout.html.emd"; 664 | auto child = compile_temple_file!"_partial.html.emd"; 665 | 666 | parent.layout(&child).render(stdout); 667 | } 668 | ``` 669 | 670 | `layout.html.emd` 671 | ```d 672 | 673 | 674 | dymk's awesome website 675 | 676 | 677 | %= render!"common/_sidebar.html.emd"() 678 | %= yield 679 | %= render!"common/_footer.html.emd"() 680 | 681 | 682 | ``` 683 | 684 | `common/_sidebar.html.emd` 685 | ```html 686 | 691 | ``` 692 | 693 | `common/_footer.html.emd` 694 | ```html 695 |
696 | 2013 (C) dymk .: built with Temple :. 697 |
698 | ``` 699 | 700 | `_partial.html.emd` 701 | ```html 702 |
703 | TODO: Write a website 704 |
705 | ``` 706 | 707 | Output: 708 | ```html 709 | 710 | 711 | dymk's awesome website 712 | 713 | 714 | 719 |
720 | TODO: Write a website 721 |
722 |
723 | 2013 (C) dymk .: built with Temple :. 724 |
725 | 726 | 727 | ``` 728 | 729 | Notes 730 | ----- 731 | The D compiler must be told which directories are okay to import text from. 732 | Use the `-J` compiler switch or `stringImportPaths` in Dub to include your template 733 | directory so Temple can access them. 734 | 735 | For more examples, take a look at`src/temple/test/common.d`'s unittests; they provide 736 | very good coverage of the library's abilities. 737 | 738 | License 739 | ------- 740 | *Temple* is distributed under the [Boost Software License](http://www.boost.org/LICENSE_1_0.txt). 741 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "temple", 3 | "description": "Embedded D, Compile Time Template Engine (Vibe.d Compatible)", 4 | "copyright": "Copyright © 2013, Dylan Knutson", 5 | "license": "BSL-1.0", 6 | "authors": 7 | [ 8 | "Dylan Knutson" 9 | ], 10 | 11 | "targetType": "library", 12 | "ignoredFiles": ["source/app.d"], 13 | 14 | "configurations": [ 15 | { 16 | "name": "lib", 17 | "targetType": "library" 18 | }, 19 | 20 | { 21 | "name": "unittest", 22 | "dependencies": {}, 23 | "targetType": "executable", 24 | "mainSourceFile": "src/app.d", 25 | "versions": ["TempleUnittest", "VibeCustomMain"], 26 | "stringImportPaths": ["test"] 27 | }, 28 | 29 | { 30 | "name": "unittest-vibed", 31 | "dependencies": { 32 | "vibe-d": "~>0.7.28" 33 | }, 34 | "targetType": "executable", 35 | "mainSourceFile": "src/app.d", 36 | "versions": ["TempleUnittest", "VibeCustomMain"], 37 | "stringImportPaths": ["test"] 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /src/app.d: -------------------------------------------------------------------------------- 1 | import temple; 2 | import std.stdio; 3 | 4 | version(TempleUnittest): 5 | 6 | const parent = compile_temple!("layout <%= yield %>"); 7 | const child = compile_temple!(`partial`); 8 | 9 | void main() { 10 | writeln("temple unittests pass"); 11 | 12 | //auto lay = parent.layout(&child); 13 | 14 | //parent.layout(&lay).render(stdout); 15 | //writeln(); 16 | } 17 | -------------------------------------------------------------------------------- /src/temple/delims.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Temple (C) Dylan Knutson, 2013, distributed under the: 3 | * Boost Software License - Version 1.0 - August 17th, 2003 4 | * 5 | * Permission is hereby granted, free of charge, to any person or organization 6 | * obtaining a copy of the software and accompanying documentation covered by 7 | * this license (the "Software") to use, reproduce, display, distribute, 8 | * execute, and transmit the Software, and to prepare derivative works of the 9 | * Software, and to permit third-parties to whom the Software is furnished to 10 | * do so, all subject to the following: 11 | * 12 | * The copyright notices in the Software and this entire statement, including 13 | * the above license grant, this restriction and the following disclaimer, 14 | * must be included in all copies of the Software, in whole or in part, and 15 | * all derivative works of the Software, unless such copies or derivative 16 | * works are solely in the form of machine-executable object code generated by 17 | * a source language processor. 18 | */ 19 | 20 | module temple.delims; 21 | 22 | import 23 | std.traits, 24 | std.typecons; 25 | 26 | /// Represents a delimer and the index that it is located at 27 | template DelimPos(D = Delim) 28 | { 29 | alias DelimPos = Tuple!(size_t, "pos", D, "delim"); 30 | } 31 | 32 | /// All of the delimer types parsed by Temple 33 | enum Delim 34 | { 35 | OpenShort, 36 | OpenShortStr, 37 | Open, 38 | OpenStr, 39 | CloseShort, 40 | Close 41 | } 42 | 43 | enum Delims = [EnumMembers!Delim]; 44 | 45 | /// Subset of Delims, only including opening delimers 46 | enum OpenDelim : Delim 47 | { 48 | OpenShort = Delim.OpenShort, 49 | Open = Delim.Open, 50 | OpenShortStr = Delim.OpenShortStr, 51 | OpenStr = Delim.OpenStr 52 | } 53 | enum OpenDelims = [EnumMembers!OpenDelim]; 54 | 55 | /// Subset of Delims, only including close delimers 56 | enum CloseDelim : Delim 57 | { 58 | CloseShort = Delim.CloseShort, 59 | Close = Delim.Close 60 | } 61 | enum CloseDelims = [EnumMembers!CloseDelim]; 62 | 63 | /// Maps an open delimer to its matching closing delimer 64 | /// Formally, an onto function 65 | enum OpenToClose = 66 | [ 67 | OpenDelim.OpenShort : CloseDelim.CloseShort, 68 | OpenDelim.OpenShortStr : CloseDelim.CloseShort, 69 | OpenDelim.Open : CloseDelim.Close, 70 | OpenDelim.OpenStr : CloseDelim.Close 71 | ]; 72 | 73 | string toString(in Delim d) 74 | { 75 | final switch(d) with(Delim) 76 | { 77 | case OpenShort: return "%"; 78 | case OpenShortStr: return "%="; 79 | case Open: return "<%"; 80 | case OpenStr: return "<%="; 81 | case CloseShort: return "\n"; 82 | case Close: return "%>"; 83 | } 84 | } 85 | 86 | /// Is the delimer a shorthand delimer? 87 | /// e.g., `%=`, or `%` 88 | bool isShort(in Delim d) 89 | { 90 | switch(d) with(Delim) 91 | { 92 | case OpenShortStr: 93 | case OpenShort : return true; 94 | default: return false; 95 | } 96 | } 97 | 98 | unittest { 99 | static assert(Delim.OpenShort.isShort() == true); 100 | static assert(Delim.Close.isShort() == false); 101 | } 102 | 103 | /// Is the contents of the delimer evaluated and appended to 104 | /// the template buffer? E.g. the content within `<%= %>` delims 105 | bool isStr(in Delim d) 106 | { 107 | switch(d) with(Delim) 108 | { 109 | case OpenShortStr: 110 | case OpenStr : return true; 111 | default: return false; 112 | } 113 | } 114 | 115 | unittest 116 | { 117 | static assert(Delim.OpenShort.isStr() == false); 118 | static assert(Delim.OpenShortStr.isStr() == true); 119 | } 120 | -------------------------------------------------------------------------------- /src/temple/func_string_gen.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Temple (C) Dylan Knutson, 2013, distributed under the: 3 | * Boost Software License - Version 1.0 - August 17th, 2003 4 | * 5 | * Permission is hereby granted, free of charge, to any person or organization 6 | * obtaining a copy of the software and accompanying documentation covered by 7 | * this license (the "Software") to use, reproduce, display, distribute, 8 | * execute, and transmit the Software, and to prepare derivative works of the 9 | * Software, and to permit third-parties to whom the Software is furnished to 10 | * do so, all subject to the following: 11 | * 12 | * The copyright notices in the Software and this entire statement, including 13 | * the above license grant, this restriction and the following disclaimer, 14 | * must be included in all copies of the Software, in whole or in part, and 15 | * all derivative works of the Software, unless such copies or derivative 16 | * works are solely in the form of machine-executable object code generated by 17 | * a source language processor. 18 | */ 19 | 20 | module temple.func_string_gen; 21 | 22 | private import 23 | temple, 24 | temple.util, 25 | temple.delims, 26 | std.conv, 27 | std.string, 28 | std.array, 29 | std.exception, 30 | std.uni, 31 | std.algorithm; 32 | 33 | /** 34 | * Stack and generator for unique temporary variable names 35 | */ 36 | private struct TempBufferNameStack 37 | { 38 | private: 39 | const string base; 40 | uint counter = 0; 41 | string[] stack; 42 | 43 | public: 44 | this(string base) 45 | { 46 | this.base = base; 47 | } 48 | 49 | /** 50 | * getNew 51 | * Gets a new unique buffer variable name 52 | */ 53 | string pushNew() 54 | { 55 | auto name = base ~ counter.to!string; 56 | counter++; 57 | stack ~= name; 58 | return name; 59 | } 60 | 61 | /** 62 | * Pops the topmost unique variable name off the stack 63 | */ 64 | string pop() 65 | { 66 | auto ret = stack[$-1]; 67 | stack.length--; 68 | return ret; 69 | } 70 | 71 | /** 72 | * Checks if there are any names to pop off 73 | */ 74 | bool empty() 75 | { 76 | return !stack.length; 77 | } 78 | } 79 | 80 | /** 81 | * Represents a unit of code that makes up the template function 82 | */ 83 | private struct FuncPart { 84 | enum Type { 85 | StrLit, // String literal appended to the buffer 86 | Expr, // Runtime computed expression appended to the buffer 87 | Stmt, // Any arbitrary statement/declaration making up the function 88 | Line, // #line directive 89 | } 90 | 91 | Type type; 92 | string value; 93 | uint indent; 94 | } 95 | 96 | /** 97 | * __temple_gen_temple_func_string 98 | * Generates the function string to be mixed into a template which renders 99 | * a temple file. 100 | */ 101 | package string __temple_gen_temple_func_string( 102 | string temple_str, in string temple_name, in string filter_ident = "") 103 | { 104 | // Output function string being composed 105 | FuncPart[] func_parts; 106 | 107 | // Indendation level for a line being pushed 108 | uint indent_level = 0; 109 | 110 | // Current line number in the temple_str being scanned 111 | size_t line_number = 0; 112 | 113 | // Generates temporary variable names and keeps them on a stack 114 | auto temp_var_names = TempBufferNameStack("__temple_capture_var_"); 115 | 116 | // Content removed from the head of temple_str is put on the tail of this 117 | string prev_temple_str = ""; 118 | 119 | /* ----- func_parts appending functions ----- */ 120 | void push_expr(string expr) { 121 | func_parts ~= FuncPart(FuncPart.Type.Expr, expr, indent_level); 122 | } 123 | 124 | void push_stmt(string stmt) 125 | { 126 | func_parts ~= FuncPart(FuncPart.Type.Stmt, stmt ~ '\n', indent_level); 127 | } 128 | 129 | void push_string_literal(string str) 130 | { 131 | func_parts ~= FuncPart(FuncPart.Type.StrLit, str, indent_level); 132 | } 133 | 134 | void push_linenum() 135 | { 136 | func_parts ~= FuncPart( 137 | FuncPart.Type.Line, 138 | `#line %d "%s"`.format(line_number+1, temple_name) ~ "\n", 139 | indent_level); 140 | } 141 | /* ----------------------------------------- */ 142 | 143 | void indent() { indent_level++; } 144 | void outdent() { indent_level--; } 145 | 146 | // Tracks if the block that the parser has just 147 | // finished processing should be printed (e.g., is 148 | // it the block who's contents are assigned to the last tmp_buffer_var) 149 | bool[] printStartBlockTracker; 150 | void sawBlockStart(bool will_be_printed) 151 | { 152 | printStartBlockTracker ~= will_be_printed; 153 | } 154 | bool sawBlockEnd() 155 | { 156 | auto will_be_printed = printStartBlockTracker[$-1]; 157 | printStartBlockTracker.length--; 158 | return will_be_printed; 159 | } 160 | 161 | // Generate the function signature, taking into account if it has a 162 | // FilterParam to use 163 | push_stmt(build_function_head(filter_ident)); 164 | 165 | indent(); 166 | if(filter_ident.length) 167 | { 168 | push_stmt(`with(%s)`.format(filter_ident)); 169 | } 170 | push_stmt(`with(__temple_context) {`); 171 | indent(); 172 | 173 | // Keeps infinite loops from outright crashing the compiler 174 | // The limit should be set to some arbitrary large number 175 | uint safeswitch = 0; 176 | 177 | while(temple_str.length) 178 | { 179 | // This imposes the limiatation of a max of 10_000 delimers parsed for 180 | // a template function. Probably will never ever hit this in a single 181 | // template file without running out of compiler memory 182 | if(safeswitch++ > 10_000) 183 | { 184 | assert(false, "nesting level too deep; throwing saftey switch: \n" ~ temple_str); 185 | } 186 | 187 | DelimPos!(OpenDelim)* oDelimPos = temple_str.nextDelim(OpenDelims); 188 | 189 | if(oDelimPos is null) 190 | { 191 | //No more delims; append the rest as a string 192 | push_linenum(); 193 | push_string_literal(temple_str); 194 | prev_temple_str.munchHeadOf(temple_str, temple_str.length); 195 | } 196 | else 197 | { 198 | immutable OpenDelim oDelim = oDelimPos.delim; 199 | immutable CloseDelim cDelim = OpenToClose[oDelim]; 200 | 201 | if(oDelimPos.pos == 0) 202 | { 203 | if(oDelim.isShort()) 204 | { 205 | if(!prev_temple_str.validBeforeShort()) 206 | { 207 | // Chars before % weren't all whitespace, assume it's part of a 208 | // string literal. 209 | push_linenum(); 210 | push_string_literal(temple_str[0..oDelim.toString().length]); 211 | prev_temple_str.munchHeadOf(temple_str, oDelim.toString().length); 212 | continue; 213 | } 214 | } 215 | 216 | // If we made it this far, we've got valid open/close delims 217 | DelimPos!(CloseDelim)* cDelimPos = temple_str.nextDelim([cDelim]); 218 | if(cDelimPos is null) 219 | { 220 | if(oDelim.isShort()) 221 | { 222 | // don't require a short close delim at the end of the template 223 | temple_str ~= cDelim.toString(); 224 | cDelimPos = enforce(temple_str.nextDelim([cDelim])); 225 | } 226 | else 227 | { 228 | assert(false, "Missing close delimer: " ~ cDelim.toString()); 229 | } 230 | } 231 | 232 | // Made it this far, we've got the position of the close delimer. 233 | push_linenum(); 234 | 235 | // Get a slice to the content between the delimers 236 | immutable string inbetween_delims = 237 | temple_str[oDelim.toString().length .. cDelimPos.pos]; 238 | 239 | // track block starts 240 | immutable bool is_block_start = inbetween_delims.isBlockStart(); 241 | immutable bool is_block_end = inbetween_delims.isBlockEnd(); 242 | 243 | // Invariant 244 | assert(!(is_block_start && is_block_end), "Internal bug: " ~ inbetween_delims); 245 | 246 | if(is_block_start) 247 | { 248 | sawBlockStart(oDelim.isStr()); 249 | } 250 | 251 | if(oDelim.isStr()) 252 | { 253 | // Check if this is a block; in that case, put the block's 254 | // contents into a temporary variable, then render that 255 | // variable after the block close delim 256 | 257 | // The line would look like: 258 | // <%= capture(() { %> 259 | // <% }); %> 260 | // so look for something like "){" or ") {" at the end 261 | 262 | if(is_block_start) 263 | { 264 | string tmp_name = temp_var_names.pushNew(); 265 | push_stmt(`auto %s = %s`.format(tmp_name, inbetween_delims)); 266 | indent(); 267 | } 268 | else 269 | { 270 | push_expr(inbetween_delims); 271 | 272 | if(cDelim == CloseDelim.CloseShort) 273 | { 274 | push_stmt(`__temple_buff_filtered_put("\n");`); 275 | } 276 | } 277 | } 278 | else 279 | { 280 | // It's just raw code, push it into the function body 281 | push_stmt(inbetween_delims); 282 | 283 | // Check if the code looks like the ending to a block; 284 | // e.g. for block: 285 | // <%= capture(() { %> 286 | // <% }, "foo"); %>` 287 | // look for it starting with }); 288 | // If it does, output the last tmp buffer var on the stack 289 | if(is_block_end && !temp_var_names.empty) 290 | { 291 | 292 | // the block at this level should be printed 293 | if(sawBlockEnd()) 294 | { 295 | outdent(); 296 | push_stmt(`__temple_context.put(%s);`.format(temp_var_names.pop())); 297 | } 298 | } 299 | } 300 | 301 | // remove up to the closing delimer 302 | prev_temple_str.munchHeadOf( 303 | temple_str, 304 | cDelimPos.pos + cDelim.toString().length); 305 | } 306 | else 307 | { 308 | // Move ahead to the next open delimer, rendering 309 | // everything between here and there as a string literal 310 | push_linenum(); 311 | immutable delim_pos = oDelimPos.pos; 312 | push_string_literal(temple_str[0..delim_pos]); 313 | prev_temple_str.munchHeadOf(temple_str, delim_pos); 314 | } 315 | } 316 | 317 | // count the number of newlines in the previous part of the template; 318 | // that's the current line number 319 | line_number = prev_temple_str.count('\n'); 320 | } 321 | 322 | outdent(); 323 | push_stmt("}"); 324 | outdent(); 325 | push_stmt("}"); 326 | 327 | 328 | return buildFromParts(func_parts); 329 | } 330 | 331 | private: 332 | 333 | string build_function_head(string filter_ident) { 334 | string ret = ""; 335 | 336 | string function_type_params = 337 | filter_ident.length ? 338 | "(%s)".format(filter_ident) : 339 | "" ; 340 | 341 | ret ~= ( 342 | `static void TempleFunc%s(TempleContext __temple_context) {` 343 | .format(function_type_params)); 344 | 345 | // This isn't just an overload of __temple_buff_filtered_put because D doesn't allow 346 | // overloading of nested functions 347 | ret ~= ` 348 | // Ensure that __temple_context is never null 349 | assert(__temple_context); 350 | 351 | void __temple_put_expr(T)(T expr) { 352 | 353 | // TempleInputStream should never be passed through 354 | // a filter; it should be directly appended to the stream 355 | static if(is(typeof(expr) == TempleInputStream)) 356 | { 357 | expr.into(__temple_context.sink); 358 | } 359 | 360 | // But other content should be filtered 361 | else 362 | { 363 | __temple_buff_filtered_put(expr); 364 | } 365 | } 366 | 367 | deprecated auto renderWith(string __temple_file)(TempleContext tc = null) 368 | { 369 | return render_with!__temple_file(tc); 370 | } 371 | TempleInputStream render(string __temple_file)() { 372 | return render_with!__temple_file(__temple_context); 373 | } 374 | `; 375 | 376 | // Is the template using a filter? 377 | if(filter_ident.length) 378 | { 379 | ret ~= ` 380 | /// Run 'thing' through the Filter's templeFilter static 381 | void __temple_buff_filtered_put(T)(T thing) 382 | { 383 | static if(__traits(compiles, __fp__.templeFilter(__temple_context.sink, thing))) 384 | { 385 | pragma(msg, "Deprecated: templeFilter on filters is deprecated; please use temple_filter"); 386 | __fp__.templeFilter(__temple_context.sink, thing); 387 | } 388 | else static if(__traits(compiles, __fp__.templeFilter(thing))) 389 | { 390 | pragma(msg, "Deprecated: templeFilter on filters is deprecated; please use temple_filter"); 391 | __temple_context.put(__fp__.templeFilter(thing)); 392 | } 393 | else static if(__traits(compiles, __fp__.temple_filter(__temple_context.sink, thing))) { 394 | __fp__.temple_filter(__temple_context.sink, thing); 395 | } 396 | else static if(__traits(compiles, __fp__.temple_filter(thing))) 397 | { 398 | __temple_context.put(__fp__.temple_filter(thing)); 399 | } 400 | else { 401 | // Fall back to templeFilter returning a string 402 | static assert(false, "Filter does not have a case that accepts a " ~ T.stringof); 403 | } 404 | } 405 | 406 | /// with filter, render subtemplate with an explicit context (which defaults to null) 407 | TempleInputStream render_with(string __temple_file)(TempleContext tc = null) 408 | { 409 | return TempleInputStream(delegate(ref TempleOutputStream s) { 410 | auto nested = compile_temple_file!(__temple_file, __fp__)(); 411 | nested.render(s, tc); 412 | }); 413 | } 414 | `.replace("__fp__", filter_ident); 415 | } 416 | else 417 | { 418 | // No filter means just directly append the thing to the 419 | // buffer, converting it to a string if needed 420 | ret ~= ` 421 | void __temple_buff_filtered_put(T)(T thing) 422 | { 423 | __temple_context.put(std.conv.to!string(thing)); 424 | } 425 | 426 | /// without filter, render subtemplate with an explicit context (which defaults to null) 427 | TempleInputStream render_with(string __temple_file)(TempleContext tc = null) 428 | { 429 | return TempleInputStream(delegate(ref TempleOutputStream s) { 430 | auto nested = compile_temple_file!(__temple_file)(); 431 | nested.render(s, tc); 432 | }); 433 | } 434 | `; 435 | } 436 | 437 | return ret; 438 | } 439 | 440 | string buildFromParts(in FuncPart[] parts) { 441 | string func_str = ""; 442 | 443 | foreach(index, immutable part; parts) { 444 | string indent() { 445 | string ret = ""; 446 | for(int i = 0; i < part.indent; i++) 447 | ret ~= '\t'; 448 | return ret; 449 | } 450 | 451 | func_str ~= indent(); 452 | 453 | final switch(part.type) 454 | with(FuncPart.Type) { 455 | case Stmt: 456 | case Line: 457 | func_str ~= part.value; 458 | break; 459 | 460 | case Expr: 461 | func_str ~= "__temple_put_expr(" ~ part.value.strip ~ ");\n"; 462 | break; 463 | 464 | case StrLit: 465 | if(index > 1 && (index + 2) < parts.length) { 466 | // look ahead/behind 2 because the generator inserts 467 | // #line annotations after each statement/expr/literal 468 | immutable prev_type = parts[index-2].type; 469 | immutable next_type = parts[index+2].type; 470 | 471 | // if the previous and next parts are statements, and this part is all 472 | // whitespace, skip inserting it into the template 473 | if( 474 | prev_type == FuncPart.Type.Stmt && 475 | next_type == FuncPart.Type.Stmt && 476 | part.value.all!((chr) => chr.isWhite())) 477 | { 478 | break; 479 | } 480 | } 481 | 482 | func_str ~= `__temple_context.put("` ~ part.value.replace("\n", "\\n").escapeQuotes() ~ "\");\n"; 483 | break; 484 | } 485 | } 486 | 487 | return func_str; 488 | } 489 | -------------------------------------------------------------------------------- /src/temple/output_stream.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Temple (C) Dylan Knutson, 2013, distributed under the: 3 | * Boost Software License - Version 1.0 - August 17th, 2003 4 | * 5 | * Permission is hereby granted, free of charge, to any person or organization 6 | * obtaining a copy of the software and accompanying documentation covered by 7 | * this license (the "Software") to use, reproduce, display, distribute, 8 | * execute, and transmit the Software, and to prepare derivative works of the 9 | * Software, and to permit third-parties to whom the Software is furnished to 10 | * do so, all subject to the following: 11 | * 12 | * The copyright notices in the Software and this entire statement, including 13 | * the above license grant, this restriction and the following disclaimer, 14 | * must be included in all copies of the Software, in whole or in part, and 15 | * all derivative works of the Software, unless such copies or derivative 16 | * works are solely in the form of machine-executable object code generated by 17 | * a source language processor. 18 | */ 19 | 20 | module temple.output_stream; 21 | 22 | private { 23 | import std.range; 24 | import std.stdio; 25 | } 26 | 27 | // Wraps any generic output stream/sink 28 | struct TempleOutputStream { 29 | private: 30 | //void delegate(string) scope_sink; 31 | void delegate(string) sink; 32 | 33 | public: 34 | this(T)(ref T os) 35 | if(isOutputRange!(T, string)) { 36 | this.sink = delegate(str) { 37 | os.put(str); 38 | }; 39 | } 40 | 41 | this(ref File f) { 42 | this.sink = delegate(str) { 43 | f.write(str); 44 | }; 45 | } 46 | 47 | this(void delegate(string) s) { 48 | this.sink = s; 49 | } 50 | 51 | this(void function(string) s) { 52 | this.sink = delegate(str) { 53 | s(str); 54 | }; 55 | } 56 | 57 | void put(string s) { 58 | this.sink(s); 59 | } 60 | 61 | // for vibe.d's html escape 62 | // TODO: write own html escaping mechanism, as this one requires an allocation 63 | // made for each char written to the output 64 | void put(dchar d) { 65 | import std.conv; 66 | this.sink(d.to!string); 67 | } 68 | 69 | invariant() { 70 | assert(this.sink !is null); 71 | } 72 | } 73 | 74 | // newtype struct 75 | struct TempleInputStream { 76 | // when called, 'into' pipes its output into OutputStream 77 | void delegate(ref TempleOutputStream os) into; 78 | 79 | invariant() { 80 | assert(this.into !is null); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/temple/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Temple (C) Dylan Knutson, 2014, distributed under the: 3 | * Boost Software License - Version 1.0 - August 17th, 2003 4 | * 5 | * Permission is hereby granted, free of charge, to any person or organization 6 | * obtaining a copy of the software and accompanying documentation covered by 7 | * this license (the "Software") to use, reproduce, display, distribute, 8 | * execute, and transmit the Software, and to prepare derivative works of the 9 | * Software, and to permit third-parties to whom the Software is furnished to 10 | * do so, all subject to the following: 11 | * 12 | * The copyright notices in the Software and this entire statement, including 13 | * the above license grant, this restriction and the following disclaimer, 14 | * must be included in all copies of the Software, in whole or in part, and 15 | * all derivative works of the Software, unless such copies or derivative 16 | * works are solely in the form of machine-executable object code generated by 17 | * a source language processor. 18 | */ 19 | 20 | module temple; 21 | 22 | private import 23 | temple.util, 24 | temple.delims, 25 | temple.func_string_gen; 26 | private import std.array : appender, Appender; 27 | private import std.range : isOutputRange; 28 | private import std.typecons : scoped; 29 | 30 | public { 31 | import temple.temple_context : TempleContext; 32 | import temple.output_stream : TempleOutputStream, TempleInputStream; 33 | import temple.vibe; 34 | } 35 | 36 | /** 37 | * Temple 38 | * Main template for generating Temple functions 39 | */ 40 | CompiledTemple compile_temple(string __TempleString, __Filter = void, uint line = __LINE__, string file = __FILE__)() 41 | { 42 | import std.conv : to; 43 | return compile_temple!(__TempleString, file~":"~line.to!string ~ ": InlineTemplate", __Filter); 44 | } 45 | deprecated("Please use compile_temple") 46 | auto Temple(ARGS...)() { 47 | return .compile_temple!(ARGS)(); 48 | } 49 | 50 | private 51 | CompiledTemple compile_temple( 52 | string __TempleString, 53 | string __TempleName, 54 | __Filter = void)() 55 | { 56 | // __TempleString: The template string to compile 57 | // __TempleName: The template's file name, or 'InlineTemplate' 58 | // __Filter: FP for the rendered template 59 | 60 | // Is a Filter present? 61 | enum __TempleHasFP = !is(__Filter == void); 62 | 63 | // Needs to be kept in sync with the param name of the Filter 64 | // passed to Temple 65 | enum __TempleFilterIdent = __TempleHasFP ? "__Filter" : ""; 66 | 67 | // Generates the actual function string, with the function name being 68 | // `TempleFunc`. 69 | const __TempleFuncStr = __temple_gen_temple_func_string( 70 | __TempleString, 71 | __TempleName, 72 | __TempleFilterIdent); 73 | 74 | //pragma(msg, __TempleFuncStr); 75 | 76 | #line 1 "TempleFunc" 77 | mixin(__TempleFuncStr); 78 | #line 75 "src/temple/temple.d" 79 | 80 | static if(__TempleHasFP) { 81 | alias temple_func = TempleFunc!__Filter; 82 | } 83 | else { 84 | alias temple_func = TempleFunc; 85 | } 86 | 87 | return CompiledTemple(&temple_func, null); 88 | } 89 | 90 | /** 91 | * TempleFile 92 | * Compiles a file on the disk into a Temple render function 93 | * Takes an optional Filter 94 | */ 95 | CompiledTemple compile_temple_file(string template_file, Filter = void)() 96 | { 97 | pragma(msg, "Compiling ", template_file, "..."); 98 | return compile_temple!(import(template_file), template_file, Filter); 99 | } 100 | 101 | deprecated("Please use compile_temple_file") 102 | auto TempleFile(ARGS...)() { 103 | return .compile_temple_file!(ARGS)(); 104 | } 105 | 106 | /** 107 | * TempleFilter 108 | * Curries a Temple to always use a given template filter, for convienence 109 | */ 110 | template TempleFilter(Filter) { 111 | template compile_temple(ARGS...) { 112 | alias compile_temple = .compile_temple!(ARGS, Filter); 113 | } 114 | template compile_temple_file(ARGS...) { 115 | alias compile_temple_file = .compile_temple_file!(ARGS, Filter); 116 | } 117 | 118 | deprecated("Please compile_temple") alias Temple = compile_temple; 119 | deprecated("Please compile_temple_file") alias TempleFile = compile_temple_file; 120 | } 121 | 122 | /** 123 | * CompiledTemple 124 | */ 125 | package 126 | struct CompiledTemple { 127 | 128 | package: 129 | alias TempleFuncSig = void function(TempleContext); 130 | TempleFuncSig render_func = null; 131 | 132 | // renderer used to handle 'yield's 133 | const(CompiledTemple)* partial_rendr = null; 134 | 135 | this(TempleFuncSig rf, const(CompiledTemple*) cf) 136 | in { assert(rf); } 137 | body { 138 | this.render_func = rf; 139 | this.partial_rendr = cf; 140 | } 141 | 142 | public: 143 | //deprecated static Temple opCall() 144 | 145 | // render template directly to a string 146 | string toString(TempleContext tc = null) const { 147 | auto a = appender!string(); 148 | this.render(a, tc); 149 | return a.data; 150 | } 151 | 152 | // render using an arbitrary output range 153 | void render(T)(ref T os, TempleContext tc = null) const 154 | if( isOutputRange!(T, string) && 155 | !is(T == TempleOutputStream)) 156 | { 157 | auto oc = TempleOutputStream(os); 158 | return render(oc, tc); 159 | } 160 | 161 | // render using a sink function (DMD can't seem to cast a function to a delegate) 162 | void render(void delegate(string) sink, TempleContext tc = null) const { 163 | auto oc = TempleOutputStream(sink); 164 | this.render(oc, tc); } 165 | void render(void function(string) sink, TempleContext tc = null) const { 166 | auto oc = TempleOutputStream(sink); 167 | this.render(oc, tc); } 168 | void render(ref std.stdio.File f, TempleContext tc = null) const { 169 | auto oc = TempleOutputStream(f); 170 | this.render(oc, tc); 171 | } 172 | 173 | // normalized render function, using an TempleOutputStream 174 | package 175 | void render(ref TempleOutputStream os, TempleContext tc) const 176 | { 177 | // the context never escapes the scope of a template, so it's safe 178 | // to allocate a new context here 179 | // TODO: verify this is safe 180 | auto local_tc = scoped!TempleContext(); 181 | 182 | // and always ensure that a template is passed, at the very least, 183 | // an empty context (needed for various template scope book keeping) 184 | if(tc is null) { 185 | tc = local_tc.Scoped_payload; 186 | //tc = new TempleContext(); 187 | } 188 | 189 | // template renders into given output stream 190 | auto old = tc.sink; 191 | scope(exit) tc.sink = old; 192 | tc.sink = os; 193 | 194 | // use the layout if we've got one 195 | if(this.partial_rendr !is null) { 196 | auto old_partial = tc.partial; 197 | 198 | tc.partial = this.partial_rendr; 199 | scope(exit) tc.partial = old_partial; 200 | 201 | this.render_func(tc); 202 | } 203 | // else, call this render function directly 204 | else { 205 | this.render_func(tc); 206 | } 207 | } 208 | 209 | // render using a vibe.d OutputStream 210 | version(Have_vibe_d) { 211 | private import vibe.core.stream : OutputStream; 212 | private import vibe.stream.wrapper : StreamOutputRange; 213 | 214 | void render(vibe.core.stream.OutputStream os, TempleContext tc = null) { 215 | static assert(isOutputRange!(vibe.stream.wrapper.StreamOutputRange, string)); 216 | 217 | auto sor = StreamOutputRange(os); 218 | this.render(sor, tc); 219 | } 220 | } 221 | 222 | CompiledTemple layout(const(CompiledTemple*) partial) const 223 | in { 224 | assert(this.partial_rendr is null, "attempting to set already-set partial of a layout"); 225 | } 226 | body { 227 | return CompiledTemple(this.render_func, partial); 228 | } 229 | 230 | invariant() { 231 | assert(render_func); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/temple/temple_context.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Temple (C) Dylan Knutson, 2013, distributed under the: 3 | * Boost Software License - Version 1.0 - August 17th, 2003 4 | * 5 | * Permission is hereby granted, free of charge, to any person or organization 6 | * obtaining a copy of the software and accompanying documentation covered by 7 | * this license (the "Software") to use, reproduce, display, distribute, 8 | * execute, and transmit the Software, and to prepare derivative works of the 9 | * Software, and to permit third-parties to whom the Software is furnished to 10 | * do so, all subject to the following: 11 | * 12 | * The copyright notices in the Software and this entire statement, including 13 | * the above license grant, this restriction and the following disclaimer, 14 | * must be included in all copies of the Software, in whole or in part, and 15 | * all derivative works of the Software, unless such copies or derivative 16 | * works are solely in the form of machine-executable object code generated by 17 | * a source language processor. 18 | */ 19 | 20 | module temple.temple_context; 21 | 22 | import temple; 23 | import temple.output_stream; 24 | 25 | public import std.variant : Variant; 26 | private import std.array, std.string, std.typetuple; 27 | 28 | final class TempleContext 29 | { 30 | private: 31 | // context variables 32 | Variant[string] vars; 33 | 34 | 35 | package: 36 | const(CompiledTemple)* partial; 37 | 38 | /// package 39 | // TODO: This needs to be marked as a "safe" string 40 | static Appender!string __templeRenderWith(in CompiledTemple temple, TempleContext ctx) 41 | body 42 | { 43 | // Allocate a buffer and call the render func with it 44 | auto buff = appender!string; 45 | temple.render(buff, ctx); 46 | return buff; 47 | } 48 | 49 | // sink for rendering templates to 50 | TempleOutputStream sink; 51 | 52 | // called by generated temple function 53 | void put(string s) { 54 | sink.put(s); 55 | } 56 | 57 | public: 58 | string capture(T...)(void delegate(T) block, T args) 59 | { 60 | auto saved = this.sink; 61 | scope(exit) this.sink = saved; 62 | 63 | auto buffer = appender!string; 64 | this.sink = TempleOutputStream(buffer); 65 | 66 | // Call the block (which resides inside the template, and will 67 | // now write to `buffer`) 68 | block(args); 69 | 70 | return buffer.data; 71 | } 72 | 73 | bool isSet(string name) 74 | { 75 | return (name in vars && vars[name] != Variant()); 76 | } 77 | 78 | ref Variant var(string name) 79 | { 80 | if(name !in vars) 81 | vars[name] = Variant(); 82 | 83 | return vars[name]; 84 | } 85 | 86 | void opIndexAssign(T)(string name, T val) { 87 | if(name !in vars) 88 | vars[name] = Variant(); 89 | 90 | vars[name] = val; 91 | } 92 | 93 | VarDispatcher var() 94 | { 95 | return VarDispatcher(this); 96 | } 97 | 98 | Variant opDispatch(string op)() @property 99 | if(op != "__ctor") // seems a scoped!T bug requires this 100 | in { 101 | assert(op in vars, "variant does not have key: " ~ op); 102 | } 103 | body { 104 | return vars[op]; 105 | } 106 | 107 | void opDispatch(string op, T)(T other) @property 108 | { 109 | vars[op] = other; 110 | } 111 | 112 | TempleInputStream yield() @property 113 | { 114 | auto noop = TempleInputStream(delegate(ref TempleOutputStream) { 115 | //debug debug_writeln("yielded input stream called (was a noop)"); 116 | }); 117 | 118 | if(partial !is null) 119 | { 120 | return TempleInputStream(delegate(ref TempleOutputStream os) { 121 | partial.render(os, this); 122 | }); 123 | } 124 | 125 | return noop; 126 | } 127 | } 128 | 129 | private struct VarDispatcher 130 | { 131 | private: 132 | TempleContext context; 133 | 134 | public: 135 | this(TempleContext context) 136 | { 137 | this.context = context; 138 | } 139 | 140 | ref Variant opDispatch(string op)() @property 141 | { 142 | return context.var(op); 143 | } 144 | 145 | void opDispatch(string op, T)(T other) @property 146 | { 147 | context.var(op) = other; 148 | } 149 | } 150 | 151 | unittest 152 | { 153 | auto context = new TempleContext(); 154 | context.foo = "bar"; 155 | context.bar = 10; 156 | 157 | with(context) 158 | { 159 | assert(var.foo == "bar"); 160 | assert(var.bar == 10); 161 | 162 | var.baz = true; 163 | assert(var.baz == true); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/temple/tests/capture.d: -------------------------------------------------------------------------------- 1 | module temple.tests.capture; 2 | 3 | version(TempleUnittest): 4 | 5 | import temple.tests.common; 6 | unittest 7 | { 8 | // test captures 9 | auto test = compile_temple!q{ 10 | <% auto a = capture(() { %> 11 | This is captured in A 12 | <% }); %> 13 | <% auto b = capture(() { %> 14 | This is captured in B 15 | <% }); %> 16 | 17 | B said: "<%= b %>" 18 | A said: "<%= a %>" 19 | }; 20 | 21 | assert(isSameRender(test, ` 22 | B said: "This is captured in B" 23 | A said: "This is captured in A" 24 | `)); 25 | } 26 | 27 | unittest 28 | { 29 | // Nested captures 30 | auto test = compile_temple!q{ 31 | <% auto outer = capture(() { %> 32 | Outer, first 33 | <% auto inner = capture(() { %> 34 | Inner, first 35 | <% }); %> 36 | Outer, second 37 | 38 | <%= inner %> 39 | <% }); %> 40 | 41 | <%= outer %> 42 | }; 43 | 44 | assert(isSameRender(test, ` 45 | Outer, first 46 | Outer, second 47 | Inner, first 48 | `)); 49 | } 50 | 51 | unittest 52 | { 53 | auto render = compile_temple_file!"test8_building_helpers.emd"; 54 | assert(isSameRender(render.toString(), readText("test/test8_building_helpers.emd.txt"))); 55 | } 56 | 57 | unittest 58 | { 59 | auto test = compile_temple!q{ 60 | <%= capture(() { %> 61 | directly printed 62 | 63 | <% auto a = capture(() { %> 64 | a, captured 65 | <% }); %> 66 | <% auto b = capture(() { %> 67 | b, captured 68 | <% }); %> 69 | 70 | <%= a %> 71 | <%= capture(() { %> 72 | directly printed from a nested capture 73 | <% }); %> 74 | <%= b %> 75 | 76 | <% }); %> 77 | }; 78 | 79 | assert(isSameRender(test, ` 80 | directly printed 81 | a, captured 82 | directly printed from a nested capture 83 | b, captured`)); 84 | } 85 | 86 | unittest 87 | { 88 | auto test = compile_temple!q{ 89 | <% string a; %> 90 | <%= a = capture(() { %> 91 | a capture 92 | <% }); %> 93 | <%= a %> 94 | }; 95 | 96 | assert(isSameRender(test, ` 97 | a capture 98 | a capture 99 | `)); 100 | } 101 | 102 | /** 103 | * Test CTFE compatibility 104 | * Disabled for the API rewrite 105 | */ 106 | version(none): 107 | unittest 108 | { 109 | const render = compile_temple!q{ <%= "foo" %> }; 110 | static assert(isSameRender(render, "foo")); 111 | } 112 | 113 | unittest 114 | { 115 | alias render = compile_temple!q{ 116 | <% if(true) { %> 117 | Bort 118 | <% } else { %> 119 | No bort! 120 | <% } %> 121 | 122 | <% auto a = capture(() { %> 123 | inside a capture block 124 | <% }); %> 125 | 126 | Before capture 127 | <%= a %> 128 | After capture 129 | }; 130 | 131 | const result = templeToString(&render); 132 | static assert(isSameRender(result, ` 133 | Bort 134 | Before capture 135 | inside a capture block 136 | After capture 137 | `)); 138 | } 139 | -------------------------------------------------------------------------------- /src/temple/tests/common.d: -------------------------------------------------------------------------------- 1 | module temple.tests.common; 2 | 3 | 4 | version(TempleUnittest): 5 | 6 | public import std.stdio, std.file : readText; 7 | public import 8 | temple, 9 | temple.util, 10 | temple.output_stream; 11 | 12 | bool isSameRender(in CompiledTemple t, TempleContext tc, string r2) { 13 | return isSameRender(t, r2, tc); 14 | } 15 | bool isSameRender(in CompiledTemple t, string r2, TempleContext tc = null) { 16 | return isSameRender(t.toString(tc), r2); 17 | } 18 | bool isSameRender(string r1, string r2) 19 | { 20 | auto ret = r1.stripWs == r2.stripWs; 21 | 22 | if(ret == false) 23 | { 24 | writeln("Renders differ: "); 25 | writeln("Got: -------------------------"); 26 | writeln(r1); 27 | writeln("Expected: --------------------"); 28 | writeln(r2); 29 | writeln("------------------------------"); 30 | } 31 | 32 | return ret; 33 | } 34 | 35 | deprecated("Please use template.toString()") 36 | string templeToString(CompiledTemple function() getr, TempleContext tc = null) { 37 | return getr().toString(tc); 38 | } 39 | 40 | unittest 41 | { 42 | auto render = compile_temple!""; 43 | assert(render.toString() == ""); 44 | } 45 | 46 | unittest 47 | { 48 | //Test to!string of eval delimers 49 | auto render = compile_temple!`<%= "foo" %>`; 50 | assert(render.toString == "foo"); 51 | } 52 | 53 | unittest 54 | { 55 | // Test delimer parsing 56 | auto render = compile_temple!("<% if(true) { %>foo<% } %>"); 57 | assert(render.toString == "foo"); 58 | } 59 | unittest 60 | { 61 | //Test raw text with no delimers 62 | auto render = compile_temple!(`foo`); 63 | assert(render.toString == "foo"); 64 | } 65 | 66 | unittest 67 | { 68 | //Test looping 69 | const templ = `<% foreach(i; 0..3) { %>foo<% } %>`; 70 | auto render = compile_temple!templ; 71 | assert(render.toString == "foofoofoo"); 72 | } 73 | 74 | unittest 75 | { 76 | //Test looping 77 | const templ = `<% foreach(i; 0..3) { %><%= i %><% } %>`; 78 | auto render = compile_temple!templ; 79 | assert(render.toString == "012"); 80 | } 81 | 82 | unittest 83 | { 84 | //Test escaping of " 85 | const templ = `"`; 86 | auto render = compile_temple!templ; 87 | assert(render.toString == `"`); 88 | } 89 | 90 | unittest 91 | { 92 | //Test escaping of ' 93 | const templ = `'`; 94 | auto render = compile_temple!templ; 95 | assert(render.toString == `'`); 96 | } 97 | 98 | unittest 99 | { 100 | auto render = compile_temple!`"%"`; 101 | assert(render.toString == `"%"`); 102 | } 103 | 104 | unittest 105 | { 106 | // Test shorthand 107 | const templ = ` 108 | % if(true) { 109 | Hello! 110 | % } 111 | `; 112 | auto render = compile_temple!(templ); 113 | assert(isSameRender(render.toString, "Hello!")); 114 | } 115 | 116 | unittest 117 | { 118 | // Test shorthand string eval 119 | const templ = ` 120 | % if(true) { 121 | %= "foo" 122 | % } 123 | `; 124 | auto render = compile_temple!(templ); 125 | //static assert(false); 126 | assert(isSameRender(render.toString, "foo")); 127 | } 128 | unittest 129 | { 130 | // Test shorthand only after newline 131 | const templ = `foo%bar`; 132 | auto render = compile_temple!(templ); 133 | assert(render.toString == "foo%bar"); 134 | } 135 | 136 | unittest 137 | { 138 | // Ditto 139 | auto render = compile_temple!`<%= "foo%bar" %>`; 140 | assert(render.toString == "foo%bar"); 141 | } 142 | 143 | unittest 144 | { 145 | auto context = new TempleContext(); 146 | context.foo = 123; 147 | context.bar = "test"; 148 | 149 | auto render = compile_temple!`<%= var("foo") %> <%= var("bar") %>`; 150 | assert(render.toString(context) == "123 test"); 151 | } 152 | 153 | unittest 154 | { 155 | // Loading templates from a file 156 | auto render = compile_temple_file!"test1.emd"; 157 | auto compare = readText("test/test1.emd.txt"); 158 | assert(isSameRender(render.toString, compare)); 159 | } 160 | 161 | unittest 162 | { 163 | auto render = compile_temple_file!"test2.emd"; 164 | auto compare = readText("test/test2.emd.txt"); 165 | 166 | auto ctx = new TempleContext(); 167 | ctx.name = "dymk"; 168 | ctx.will_work = true; 169 | 170 | assert(isSameRender(render.toString(ctx), compare)); 171 | } 172 | 173 | unittest 174 | { 175 | auto render = compile_temple_file!"test3_nester.emd"; 176 | auto compare = readText("test/test3.emd.txt"); 177 | assert(isSameRender(render.toString, compare)); 178 | } 179 | 180 | unittest 181 | { 182 | auto render = compile_temple_file!"test4_root.emd"; 183 | auto compare = readText("test/test4.emd.txt"); 184 | 185 | auto ctx = new TempleContext(); 186 | ctx.var1 = "this_is_var1"; 187 | 188 | assert(isSameRender(render.toString(ctx), compare)); 189 | } 190 | 191 | unittest 192 | { 193 | auto parent = compile_temple!"before <%= yield %> after"; 194 | auto partial = compile_temple!"between"; 195 | 196 | assert(isSameRender(parent.layout(&partial), "before between after")); 197 | } 198 | 199 | unittest 200 | { 201 | auto parent = compile_temple!"before <%= yield %> after"; 202 | auto partial = compile_temple!"between"; 203 | 204 | assert(isSameRender(parent.layout(&partial), "before between after")); 205 | } 206 | 207 | unittest 208 | { 209 | auto parent = compile_temple_file!"test5_layout.emd"; 210 | auto partial1 = compile_temple_file!"test5_partial1.emd"; 211 | auto partial2 = compile_temple_file!"test5_partial2.emd"; 212 | 213 | auto p1 = parent.layout(&partial1); 214 | auto p2 = parent.layout(&partial2); 215 | 216 | assert(isSameRender(p1, readText("test/test5_partial1.emd.txt"))); 217 | assert(isSameRender(p2, readText("test/test5_partial2.emd.txt"))); 218 | } 219 | 220 | // Layouts and contexts 221 | unittest 222 | { 223 | auto parent = compile_temple_file!"test6_layout.emd"; 224 | auto partial = compile_temple_file!"test6_partial.emd"; 225 | 226 | auto context = new TempleContext(); 227 | context.name = "dymk"; 228 | context.uni = "UCSD"; 229 | context.age = 19; 230 | 231 | assert(isSameRender(parent.layout(&partial), context, readText("test/test6_partial.emd.txt"))); 232 | } 233 | 234 | // opDispatch variable getting 235 | unittest 236 | { 237 | auto render = compile_temple!"<%= var.foo %>"; 238 | 239 | auto context = new TempleContext(); 240 | context.foo = "Hello, world"; 241 | 242 | assert(isSameRender(render, context, "Hello, world")); 243 | } 244 | 245 | unittest 246 | { 247 | // 22 Nov, 2014: Disabled this bit, because DMD now ICEs when 248 | // evaluating the erronious template (but not before spitting out 249 | // a lot of errors). This will have to do for finding out that a templtae 250 | // has a lot of errors in it. 251 | // Uncomment to view the line numbers inserted into the template 252 | //TODO: check if this works in future DMD releases 253 | //auto render = compile_temple_file!"test7_error.emd"; 254 | //assert(!__traits(compiles, { 255 | // auto t = compile_temple_file!"test7_error.emd"; 256 | //})); 257 | } 258 | 259 | unittest 260 | { 261 | import temple.func_string_gen; 262 | // Test returning early from templates 263 | //auto str = ` 264 | auto render = compile_temple!` 265 | one 266 | % auto blah = true; 267 | % if(blah) { 268 | two 269 | % return; 270 | % } 271 | three 272 | `; 273 | 274 | //writeln(__temple_gen_temple_func_string(str, "Inline")); 275 | assert(isSameRender(render.toString, 276 | `one 277 | two`)); 278 | } 279 | -------------------------------------------------------------------------------- /src/temple/tests/filter.d: -------------------------------------------------------------------------------- 1 | module temple.tests.filter; 2 | 3 | version(TempleUnittest): 4 | 5 | import temple.tests.common; 6 | 7 | private struct SafeDemoFilter 8 | { 9 | static struct SafeString 10 | { 11 | string value; 12 | } 13 | 14 | static string temple_filter(SafeString ts) 15 | { 16 | return ts.value; 17 | } 18 | 19 | static string temple_filter(string str) 20 | { 21 | return "!" ~ str ~ "!"; 22 | } 23 | 24 | static SafeString safe(string str) 25 | { 26 | return SafeString(str); 27 | } 28 | } 29 | 30 | unittest 31 | { 32 | static struct Filter 33 | { 34 | static string temple_filter(string raw_str) 35 | { 36 | return "!" ~ raw_str ~ "!"; 37 | } 38 | } 39 | 40 | auto render1 = compile_temple!(`<%= "foo" %> bar`, Filter); 41 | assert(isSameRender(render1, "!foo! bar")); 42 | 43 | auto render2 = compile_temple_file!("test9_filter.emd", Filter); 44 | assert(isSameRender(render2, `!foo! bar`)); 45 | } 46 | 47 | unittest 48 | { 49 | static struct Filter 50 | { 51 | static void temple_filter(ref TempleOutputStream os, string raw_str) 52 | { 53 | //return "!" ~ raw_str ~ "!"; 54 | os.put("!"); 55 | os.put(raw_str); 56 | os.put("!"); 57 | } 58 | } 59 | 60 | auto parent = compile_temple_file!("test10_fp_layout.emd", Filter); 61 | auto partial = compile_temple_file!("test10_fp_partial.emd", Filter); 62 | 63 | assert(isSameRender(parent.layout(&partial), readText("test/test10_fp.emd.txt"))); 64 | } 65 | 66 | unittest 67 | { 68 | auto render1 = compile_temple!(q{ 69 | foo (filtered): <%= "mark me" %> 70 | foo (unfiltered): <%= safe("don't mark me") %> 71 | }, SafeDemoFilter); 72 | 73 | assert(isSameRender(render1, ` 74 | foo (filtered): !mark me! 75 | foo (unfiltered): don't mark me 76 | `)); 77 | 78 | auto render2 = compile_temple!(q{ 79 | <% 80 | auto helper1(void delegate() block) 81 | { 82 | return "a " ~ capture(block) ~ " b"; 83 | } 84 | %> 85 | 86 | <%= capture(() { %> 87 | foo1 88 | <%= "foo2" %> 89 | <% }); %> 90 | 91 | <%= helper1(() { %> 92 | <%= "foo3" %> 93 | <% }); %> 94 | 95 | <%= helper1(() { %> 96 | <%= safe("foo4") %> 97 | <% }); %> 98 | }, SafeDemoFilter); 99 | 100 | assert(isSameRender(render2, ` 101 | foo1 102 | !foo2! 103 | a !foo3! b 104 | a foo4 b 105 | `)); 106 | } 107 | 108 | unittest 109 | { 110 | // Test nested filter (e.g., filters are propogated with calls to render() 111 | // and renderWith()) 112 | 113 | auto render = compile_temple!(q{ 114 | <%= safe("foo1") %> 115 | <%= "foo2" %> 116 | <%= render!"test11_propogate_fp.emd"() %> 117 | <%= "after1" %> 118 | after2 119 | }, SafeDemoFilter); 120 | 121 | assert(isSameRender(render, ` 122 | foo1 123 | !foo2! 124 | bar1 125 | !bar2! 126 | bar3 127 | !after1! 128 | after2 129 | `)); 130 | } 131 | 132 | unittest 133 | { 134 | alias FPGroup = TempleFilter!SafeDemoFilter; 135 | auto render = FPGroup.compile_temple!q{ 136 | foo1 137 | <%= "foo2" %> 138 | }; 139 | 140 | assert(isSameRender(render, ` 141 | foo1 142 | !foo2! 143 | `)); 144 | } 145 | 146 | unittest 147 | { 148 | // Test unicode charachters embedded in templates 149 | 150 | auto render = compile_temple!(` 151 | Ю ю Ю ю Yu /ju/, /ʲu/ 152 | Я я Я я Ya /ja/, /ʲa/ 153 | 154 | % if(true) { 155 | А а А а A /a/ 156 | Б б Б б Be /b/ 157 | В в В в Ve /v/ 158 | % } 159 | `); 160 | 161 | assert(isSameRender(render, ` 162 | Ю ю Ю ю Yu /ju/, /ʲu/ 163 | Я я Я я Ya /ja/, /ʲa/ 164 | А а А а A /a/ 165 | Б б Б б Be /b/ 166 | В в В в Ve /v/ 167 | `)); 168 | } 169 | 170 | unittest 171 | { 172 | auto render = compile_temple_file!"test14_unicode.emd"; 173 | auto compare = readText("test/test14_unicode.emd.txt"); 174 | assert(isSameRender(render, compare)); 175 | } 176 | -------------------------------------------------------------------------------- /src/temple/tests/todos.d: -------------------------------------------------------------------------------- 1 | module temple.tests.todos; 2 | 3 | version(TempleUnittest): 4 | 5 | /** 6 | * Use cases that can hopefully be made to work at some time in the future. 7 | * These might not work for a number of reasons, such as CTFE, Phobos, DMD, or 8 | * internal library bugs. 9 | */ 10 | 11 | version(none): 12 | 13 | // std.variant needs to be made CTFE compatible before this can work 14 | unittest 15 | { 16 | alias render = Temple!q{ 17 | Name: <%= var.name %> 18 | Number: <%= var.number %> 19 | 20 | <% auto captured = capture(() { %> 21 | Here is some captured content! 22 | var.name: <%= var.name %> 23 | <% }); %> 24 | <%= captured %> 25 | 26 | <%= capture(() { %> 27 | A capture directly being rendered, for completeness. 28 | <% }); %> 29 | }; 30 | 31 | // The lambda is a hack to set up a temple context 32 | // at compile time, using a self executing function literal 33 | 34 | const result = templeToString(&render, (() { 35 | auto ctx = new TempleContext; 36 | ctx.name = "dymk"; 37 | ctx.number = 1234; 38 | return ctx; 39 | })() ); 40 | 41 | static assert(isSameRender(result, ` 42 | Name: dymk 43 | Number: 1234 44 | Here is some captured content! 45 | var.name: dymk 46 | A capture directly being rendered, for completeness. 47 | `)); 48 | } 49 | -------------------------------------------------------------------------------- /src/temple/tests/vibe.d: -------------------------------------------------------------------------------- 1 | module temple.tests.vibe; 2 | 3 | version(TempleUnittest): 4 | version(Have_vibe_d): 5 | 6 | private { 7 | import temple.tests.common; 8 | import vibe.http.server; 9 | import vibe.core.stream; 10 | import vibe.stream.memory; 11 | import core.time; 12 | } 13 | 14 | /* 15 | * Drops HTTP headers from the stream output and uses proper newlines 16 | */ 17 | private string rendered(MemoryOutputStream output) { 18 | import std.string: split, join; 19 | 20 | string data = cast(string)output.data; 21 | string[] lines = data.split("\r\n"); 22 | lines = lines[4 .. $]; 23 | 24 | return lines.join("\n"); 25 | } 26 | 27 | unittest { 28 | auto output = new MemoryOutputStream(); 29 | auto resp = createTestHTTPServerResponse(output); 30 | resp.renderTemple!` 31 | Something here 32 |

Something more here

33 | <%= "

Escape me!

" %> 34 | `; 35 | resp.bodyWriter.flush; //flushes resp's output stream wrapping the MemoryOutputStream 36 | 37 | assert(isSameRender(output.rendered, ` 38 | Something here 39 |

Something more here

40 | <p>Escape me!</p> 41 | `)); 42 | } 43 | 44 | unittest { 45 | auto output = new MemoryOutputStream(); 46 | auto resp = createTestHTTPServerResponse(output); 47 | auto ctx = new TempleContext; 48 | ctx.abc = ""; 49 | ctx.def = ""; 50 | resp.renderTemple!` 51 | <%= safe(var.abc) %> 52 | <%= var.def %> 53 | `(ctx); 54 | resp.bodyWriter.flush; //flushes resp's output stream wrapping the MemoryOutputStream 55 | 56 | assert(isSameRender(output.rendered, ` 57 | 58 | <escaped> 59 | `)); 60 | } 61 | 62 | unittest { 63 | auto output = new MemoryOutputStream(); 64 | auto resp = createTestHTTPServerResponse(output); 65 | resp.renderTempleFile!"test12_vibe1.emd"; 66 | resp.bodyWriter.flush; //flushes resp's output stream wrapping the MemoryOutputStream 67 | 68 | assert(isSameRender(output.rendered, ` 69 | Rendering with renderTempleFile in temple.vibe 70 |

Don't escape

71 | <p>Do escape</p> 72 | `)); 73 | } 74 | 75 | unittest { 76 | auto output = new MemoryOutputStream(); 77 | auto resp = createTestHTTPServerResponse(output); 78 | resp.renderTempleLayoutFile!("test13_vibelayout.emd", "test13_vibepartial.emd"); 79 | resp.bodyWriter.flush; //flushes resp's output stream wrapping the MemoryOutputStream 80 | 81 | assert(isSameRender(output.rendered, ` 82 | <div>escaped header</div> 83 |
header div
84 | header 85 | partial 86 | <p>Escaped paragraph in partial</p> 87 | footer 88 |
footer div
89 | `)); 90 | } 91 | -------------------------------------------------------------------------------- /src/temple/util.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Temple (C) Dylan Knutson, 2013, distributed under the: 3 | * Boost Software License - Version 1.0 - August 17th, 2003 4 | * 5 | * Permission is hereby granted, free of charge, to any person or organization 6 | * obtaining a copy of the software and accompanying documentation covered by 7 | * this license (the "Software") to use, reproduce, display, distribute, 8 | * execute, and transmit the Software, and to prepare derivative works of the 9 | * Software, and to permit third-parties to whom the Software is furnished to 10 | * do so, all subject to the following: 11 | * 12 | * The copyright notices in the Software and this entire statement, including 13 | * the above license grant, this restriction and the following disclaimer, 14 | * must be included in all copies of the Software, in whole or in part, and 15 | * all derivative works of the Software, unless such copies or derivative 16 | * works are solely in the form of machine-executable object code generated by 17 | * a source language processor. 18 | */ 19 | 20 | module temple.util; 21 | 22 | private import 23 | std.algorithm, 24 | std.typecons, 25 | std.array, 26 | std.uni, 27 | std.conv, 28 | std.utf; 29 | 30 | private import temple.delims; 31 | 32 | package: 33 | 34 | bool validBeforeShort(string str) { 35 | // Check that the tail of str is whitespace 36 | // before a newline, or nothing. 37 | foreach_reverse(dchar chr; str) { 38 | if(chr == '\n') { return true; } 39 | if(!chr.isWhite()) { return false; } 40 | } 41 | return true; 42 | } 43 | 44 | unittest 45 | { 46 | static assert(" ".validBeforeShort()); 47 | static assert(" \t".validBeforeShort()); 48 | static assert("foo\n".validBeforeShort()); 49 | static assert("foo\n ".validBeforeShort()); 50 | static assert("foo\n \t".validBeforeShort); 51 | 52 | static assert("foo \t".validBeforeShort() == false); 53 | static assert("foo".validBeforeShort() == false); 54 | static assert("\nfoo".validBeforeShort() == false); 55 | } 56 | 57 | void munchHeadOf(ref string a, ref string b, size_t amt) { 58 | // Transfers amt of b's head onto a's tail 59 | a = a ~ b[0..amt]; 60 | b = b[amt..$]; 61 | } 62 | 63 | unittest 64 | { 65 | auto a = "123"; 66 | auto b = "abc"; 67 | a.munchHeadOf(b, 1); 68 | assert(a == "123a"); 69 | assert(b == "bc"); 70 | } 71 | unittest 72 | { 73 | auto a = "123"; 74 | auto b = "abc"; 75 | a.munchHeadOf(b, b.length); 76 | assert(a == "123abc"); 77 | assert(b == ""); 78 | } 79 | 80 | /// Returns the next matching delimeter in 'delims' found in the haystack, 81 | /// or null 82 | DelimPos!(D)* nextDelim(D)(string haystack, const(D)[] delims) 83 | if(is(D : Delim)) 84 | { 85 | struct DelimStrPair { Delim delim; string str; } 86 | 87 | /// The foreach is there to get around some DMD bugs 88 | /// Preferrably, one of the next two lines would be used instead 89 | //auto delims_strs = delims.map!(a => new DelimStrPair(a, a.toString()) )().array(); 90 | //auto delim_strs = delims_strs.map!(a => a.str)().array(); 91 | DelimStrPair[] delims_strs; 92 | foreach(delim; delims) 93 | { 94 | delims_strs ~= DelimStrPair(delim, toString(delim)); 95 | } 96 | 97 | // Map delims into their string representations 98 | // e.g. OpenDelim.OpenStr => `<%=` 99 | string[] delim_strs; 100 | foreach(delim; delims) 101 | { 102 | // BUG: Would use ~= here, but CTFE in 2.063 can't handle it 103 | delim_strs = delim_strs ~ toString(delim); 104 | } 105 | 106 | // Find the first occurance of any of the delimers in the haystack 107 | immutable index = countUntilAny(haystack, delim_strs); 108 | if(index == -1) 109 | { 110 | return null; 111 | } 112 | 113 | // Jump to where the delim is on haystack, using stride to handle 114 | // unicode correctly 115 | size_t pos = 0; 116 | foreach(_; 0 .. index) { 117 | auto size = stride(haystack, 0); 118 | 119 | haystack = haystack[size .. $]; 120 | pos += size; 121 | } 122 | 123 | // Make sure that we match the longest of the delimers first, 124 | // e.g. `<%=` is matched before `<%` for maximal munch 125 | auto sorted = delims_strs.sort!("a.str.length > b.str.length")(); 126 | foreach(s; sorted) 127 | { 128 | if(startsWith(haystack, s.str)) 129 | { 130 | return new DelimPos!D(pos, cast(D) s.delim); 131 | } 132 | } 133 | 134 | // invariant 135 | assert(false, "internal bug: \natPos: " ~ index.to!string ~ "\nhaystack: " ~ haystack); 136 | } 137 | 138 | unittest 139 | { 140 | const haystack = "% Я"; 141 | static assert(*(haystack.nextDelim([Delim.OpenShort])) == 142 | DelimPos!Delim(0, Delim.OpenShort)); 143 | } 144 | unittest 145 | { 146 | const haystack = "Я"; 147 | static assert(haystack.nextDelim([Delim.OpenShort]) == null); 148 | } 149 | unittest 150 | { 151 | const haystack = "Я%"; 152 | static assert(*(haystack.nextDelim([Delim.OpenShort])) == 153 | DelimPos!Delim(codeLength!char('Я'), Delim.OpenShort)); 154 | } 155 | unittest 156 | { 157 | const haystack = Delim.Open.toString(); 158 | static assert(*(haystack.nextDelim([Delim.Open])) == DelimPos!Delim(0, Delim.Open)); 159 | } 160 | unittest 161 | { 162 | const haystack = "foo"; 163 | static assert(haystack.nextDelim([Delim.Open]) is null); 164 | } 165 | 166 | /// Returns the location of the first occurance of any of 'subs' found in 167 | /// haystack, or -1 if none are found 168 | ptrdiff_t countUntilAny(string haystack, string[] subs) { 169 | // First, calculate first occurance for all subs 170 | auto indexes_of = subs.map!( sub => haystack.countUntil(sub) ); 171 | ptrdiff_t min_index = -1; 172 | 173 | // Then find smallest index that isn't -1 174 | foreach(index_of; indexes_of) 175 | { 176 | if(index_of != -1) 177 | { 178 | if(min_index == -1) 179 | { 180 | min_index = index_of; 181 | } 182 | else 183 | { 184 | min_index = min(min_index, index_of); 185 | } 186 | } 187 | } 188 | 189 | return min_index; 190 | } 191 | unittest 192 | { 193 | enum a = "1, 2, 3, 4"; 194 | static assert(a.countUntilAny(["1", "2"]) == 0); 195 | static assert(a.countUntilAny(["2", "1"]) == 0); 196 | static assert(a.countUntilAny(["4", "2"]) == 3); 197 | } 198 | unittest 199 | { 200 | enum a = "1, 2, 3, 4"; 201 | static assert(a.countUntilAny(["5", "1"]) == 0); 202 | static assert(a.countUntilAny(["5", "6"]) == -1); 203 | } 204 | unittest 205 | { 206 | enum a = "%>"; 207 | static assert(a.countUntilAny(["<%", "<%="]) == -1); 208 | } 209 | 210 | string escapeQuotes(string unclean) 211 | { 212 | unclean = unclean.replace(`"`, `\"`); 213 | unclean = unclean.replace(`'`, `\'`); 214 | return unclean; 215 | } 216 | unittest 217 | { 218 | static assert(escapeQuotes(`"`) == `\"`); 219 | static assert(escapeQuotes(`'`) == `\'`); 220 | } 221 | 222 | // Internal, inefficiant function for removing the whitespace from 223 | // a string (for comparing that templates generate the same output, 224 | // ignoring whitespace exactnes) 225 | string stripWs(string unclean) { 226 | return unclean 227 | .filter!(a => !isWhite(a) ) 228 | .map!( a => cast(char) a ) 229 | .array 230 | .idup; 231 | } 232 | unittest 233 | { 234 | static assert(stripWs("") == ""); 235 | static assert(stripWs(" \t") == ""); 236 | static assert(stripWs(" a s d f ") == "asdf"); 237 | static assert(stripWs(" a\ns\rd f ") == "asdf"); 238 | } 239 | 240 | // Checks if haystack ends with needle, ignoring the whitespace in either 241 | // of them 242 | bool endsWithIgnoreWhitespace(string haystack, string needle) 243 | { 244 | haystack = haystack.stripWs; 245 | needle = needle.stripWs; 246 | 247 | return haystack.endsWith(needle); 248 | } 249 | 250 | unittest 251 | { 252 | static assert(endsWithIgnoreWhitespace(") { ", "){")); 253 | static assert(!endsWithIgnoreWhitespace(") {}", "){")); 254 | } 255 | 256 | bool startsWithBlockClose(string haystack) 257 | { 258 | haystack = haystack.stripWs; 259 | 260 | // something that looks like }); passes this 261 | if(haystack.startsWith("}") && haystack.canFind(");")) return true; 262 | return false; 263 | } 264 | 265 | unittest 266 | { 267 | static assert(startsWithBlockClose(`}, 10);`)); 268 | static assert(startsWithBlockClose(`});`)); 269 | static assert(startsWithBlockClose(`}, "foo");`)); 270 | static assert(startsWithBlockClose(`}); auto a = "foo";`)); 271 | 272 | static assert(!startsWithBlockClose(`if() {}`)); 273 | static assert(!startsWithBlockClose(`};`)); 274 | } 275 | 276 | bool isBlockStart(string haystack) 277 | { 278 | return haystack.endsWithIgnoreWhitespace("){"); 279 | } 280 | 281 | bool isBlockEnd(string haystack) 282 | { 283 | return haystack.startsWithBlockClose(); 284 | } 285 | -------------------------------------------------------------------------------- /src/temple/vibe.d: -------------------------------------------------------------------------------- 1 | module temple.vibe; 2 | 3 | version(Have_vibe_d): 4 | 5 | pragma(msg, "Compiling Temple with Vibed support"); 6 | 7 | private { 8 | import temple; 9 | import vibe.http.server; 10 | import vibe.textfilter.html; 11 | import vibe.utils.dictionarylist; 12 | import std.stdio; 13 | import std.variant; 14 | } 15 | 16 | struct TempleHtmlFilter { 17 | 18 | private static struct SafeString { 19 | const string payload; 20 | } 21 | 22 | static void temple_filter(ref TempleOutputStream stream, string unsafe) { 23 | filterHTMLEscape(stream, unsafe); 24 | } 25 | 26 | static void temple_filter(ref TempleOutputStream stream, Variant variant) { 27 | temple_filter(stream, variant.toString); 28 | } 29 | 30 | static string temple_filter(SafeString safe) { 31 | return safe.payload; 32 | } 33 | 34 | static SafeString safe(string str) { 35 | return SafeString(str); 36 | } 37 | 38 | static SafeString safe(Variant variant) { 39 | return SafeString(variant.toString); 40 | } 41 | } 42 | 43 | private enum SetupContext = q{ 44 | static if(is(Ctx == HTTPServerRequest)) { 45 | TempleContext context = new TempleContext(); 46 | copyContextParams(context, req); 47 | } 48 | else { 49 | TempleContext context = req; 50 | } 51 | }; 52 | 53 | private template isSupportedCtx(Ctx) { 54 | enum isSupportedCtx = is(Ctx : HTTPServerRequest) || is(Ctx == TempleContext); 55 | } 56 | 57 | void renderTemple(string temple, Ctx = TempleContext) 58 | (HTTPServerResponse res, Ctx req = null) 59 | if(isSupportedCtx!Ctx) 60 | { 61 | mixin(SetupContext); 62 | 63 | auto t = compile_temple!(temple, TempleHtmlFilter); 64 | t.render(res.bodyWriter, context); 65 | } 66 | 67 | void renderTempleFile(string file, Ctx = TempleContext) 68 | (HTTPServerResponse res, Ctx req = null) 69 | if(isSupportedCtx!Ctx) 70 | { 71 | mixin(SetupContext); 72 | 73 | auto t = compile_temple_file!(file, TempleHtmlFilter); 74 | t.render(res.bodyWriter, context); 75 | } 76 | 77 | void renderTempleLayoutFile(string layout_file, string partial_file, Ctx = TempleContext) 78 | (HTTPServerResponse res, Ctx req = null) 79 | if(isSupportedCtx!Ctx) 80 | { 81 | mixin(SetupContext); 82 | 83 | auto layout = compile_temple_file!(layout_file, TempleHtmlFilter); 84 | auto partial = compile_temple_file!(partial_file, TempleHtmlFilter); 85 | auto composed = layout.layout(&partial); 86 | composed.render(res.bodyWriter, context); 87 | } 88 | 89 | private void copyContextParams(ref TempleContext ctx, ref HTTPServerRequest req) { 90 | static if(is(typeof(req.params) == string[string])) { 91 | if(!req || !(req.params)) 92 | return; 93 | } else if(is(typeof(req.params) == DictionaryList!(string, true, 32))) { 94 | if(!req || req.params.length < 1) 95 | return; 96 | } 97 | 98 | foreach(key, val; req.params) { 99 | ctx[key] = val; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /test/test1.emd: -------------------------------------------------------------------------------- 1 | <% foreach(i; 0..3) { %> 2 | i is: <%= i %> 3 | <% } %> -------------------------------------------------------------------------------- /test/test1.emd.txt: -------------------------------------------------------------------------------- 1 | i is: 0 2 | i is: 1 3 | i is: 2 4 | -------------------------------------------------------------------------------- /test/test10_fp.emd.txt: -------------------------------------------------------------------------------- 1 | layout before 2 | !layout generated! 3 | partial before 4 | !partial generated! 5 | partial after 6 | layout after -------------------------------------------------------------------------------- /test/test10_fp_layout.emd: -------------------------------------------------------------------------------- 1 | layout before 2 | <%= "layout generated" %> 3 | <%= yield %> 4 | layout after -------------------------------------------------------------------------------- /test/test10_fp_partial.emd: -------------------------------------------------------------------------------- 1 | partial before 2 | <%= "partial generated" %> 3 | partial after -------------------------------------------------------------------------------- /test/test11_propogate_fp.emd: -------------------------------------------------------------------------------- 1 | <%= safe("bar1") %> 2 | <%= "bar2" %> 3 | bar3 -------------------------------------------------------------------------------- /test/test12_vibe1.emd: -------------------------------------------------------------------------------- 1 | Rendering with renderTempleFile in temple.vibe 2 |

Don't escape

3 | <%= "

Do escape

" %> 4 | -------------------------------------------------------------------------------- /test/test13_vibelayout.emd: -------------------------------------------------------------------------------- 1 | <%= "
escaped header
" %> 2 |
header div
3 | header 4 | <%= yield %> 5 | footer 6 |
footer div
7 | -------------------------------------------------------------------------------- /test/test13_vibepartial.emd: -------------------------------------------------------------------------------- 1 | partial 2 | <%= "

Escaped paragraph in partial

" %> 3 | -------------------------------------------------------------------------------- /test/test14_unicode.emd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymk/temple/911bd49329de383f015add45075450a6a0de1bfa/test/test14_unicode.emd -------------------------------------------------------------------------------- /test/test14_unicode.emd.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymk/temple/911bd49329de383f015add45075450a6a0de1bfa/test/test14_unicode.emd.txt -------------------------------------------------------------------------------- /test/test2.emd: -------------------------------------------------------------------------------- 1 | 2 | 3 | Welcome, <%= var("name") %> 4 | Will this work? <%= var("will_work").get!bool ? "yes" : "no" %> 5 | <% var("will_work") = false; %> 6 | And now? <%= var("will_work").get!bool ? "yes" : "no" %> 7 | 8 | -------------------------------------------------------------------------------- /test/test2.emd.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | Welcome, dymk 4 | Will this work? yes 5 | And now? no 6 | 7 | -------------------------------------------------------------------------------- /test/test3.emd.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | Nested template: 4 |

I am nested!

5 | 6 | -------------------------------------------------------------------------------- /test/test3_nested.emd: -------------------------------------------------------------------------------- 1 |

I am nested!

-------------------------------------------------------------------------------- /test/test3_nester.emd: -------------------------------------------------------------------------------- 1 | 2 | 3 | Nested template: 4 | <%= render!("test3_nested.emd")() %> 5 | 6 | -------------------------------------------------------------------------------- /test/test4.emd.txt: -------------------------------------------------------------------------------- 1 | test4_root rendered 2 | var1 here: 3 | this_is_var1 4 | test4_subroot rendered 5 | var1 here: 6 | this_is_var1 7 | rendering subsub view with explicit new context 8 | test4_subsubroot here 9 | var1 is set? false 10 | -------------------------------------------------------------------------------- /test/test4_root.emd: -------------------------------------------------------------------------------- 1 | test4_root rendered 2 | var1 here: 3 | %= var("var1") 4 | %= render!"test4_subroot.emd"() -------------------------------------------------------------------------------- /test/test4_subroot.emd: -------------------------------------------------------------------------------- 1 | test4_subroot rendered 2 | var1 here: 3 | %= var("var1") 4 | rendering subsub view with explicit new context 5 | %= render_with!"test4_subsubroot.emd"() 6 | -------------------------------------------------------------------------------- /test/test4_subsubroot.emd: -------------------------------------------------------------------------------- 1 | test4_subsubroot here 2 | var1 is set? <%= isSet("var1") %> 3 | -------------------------------------------------------------------------------- /test/test5_layout.emd: -------------------------------------------------------------------------------- 1 | 2 | 3 | Isn't this neat? 4 | 5 | 6 | Test 5: This is the layout. 7 | <%= yield %> 8 |
9 | Here's a nice little footer. 10 |
11 | 12 | -------------------------------------------------------------------------------- /test/test5_partial1.emd: -------------------------------------------------------------------------------- 1 |
2 | Here's a section from the test5 partial. 3 | Rendering something even more nested: 4 | <%= render!"test1.emd"() %> 5 |
-------------------------------------------------------------------------------- /test/test5_partial1.emd.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | Isn't this neat? 4 | 5 | 6 | Test 5: This is the layout. 7 |
8 | Here's a section from the test5 partial. 9 | Rendering something even more nested: 10 | 11 | i is: 0 12 | i is: 1 13 | i is: 2 14 |
15 |
16 | Here's a nice little footer. 17 |
18 | 19 | -------------------------------------------------------------------------------- /test/test5_partial2.emd: -------------------------------------------------------------------------------- 1 |
2 | Partial number 2 section here. Embedding partial 1: 3 | <%= render!"test5_partial1.emd"() %> 4 |
-------------------------------------------------------------------------------- /test/test5_partial2.emd.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | Isn't this neat? 4 | 5 | 6 | Test 5: This is the layout. 7 |
8 | Partial number 2 section here. Embedding partial 1: 9 |
10 | Here's a section from the test5 partial. 11 | Rendering something even more nested: 12 | 13 | i is: 0 14 | i is: 1 15 | i is: 2 16 |
17 |
18 |
19 | Here's a nice little footer. 20 |
21 | 22 | -------------------------------------------------------------------------------- /test/test6_layout.emd: -------------------------------------------------------------------------------- 1 | 2 | 3 | The Blog of <%= var("name") %> 4 | 5 | 6 | Hello, welcome to page blah blah! Here's a little bit about me: 7 | <%= render!"test6_partial.emd"() %> 8 | 9 | -------------------------------------------------------------------------------- /test/test6_partial.emd: -------------------------------------------------------------------------------- 1 |
2 |

Name: <%= var("name") %>

3 |

University: <%= var("uni") %>

4 |

Age: <%= var("age") %>

5 |
-------------------------------------------------------------------------------- /test/test6_partial.emd.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | The Blog of dymk 4 | 5 | 6 | Hello, welcome to page blah blah! Here's a little bit about me: 7 |
8 |

Name: dymk

9 |

University: UCSD

10 |

Age: 19

11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /test/test7_error.emd: -------------------------------------------------------------------------------- 1 | this is a line here 2 | <% a %> 3 | Also lines here? What's up with that? 4 | <%= foobar; %> <%= bazzle? %> 5 | This is line 5. -------------------------------------------------------------------------------- /test/test8_building_helpers.emd: -------------------------------------------------------------------------------- 1 | <% 2 | import std.string; 3 | struct FormHelper 4 | { 5 | string model_name; 6 | 7 | auto field_for(string field_name, string type="text") 8 | { 9 | if(model_name != "") 10 | { 11 | field_name = "%s[%s]".format(model_name, field_name); 12 | } 13 | 14 | return ``.format(type, field_name); 15 | } 16 | 17 | auto submit(string value = "Submit") 18 | { 19 | return ``.format(value); 20 | } 21 | } 22 | 23 | auto form_for( 24 | string action, 25 | string name, 26 | void delegate(FormHelper) block) 27 | { 28 | auto form_body = capture(block, FormHelper(name)); 29 | return ` 30 |
31 | %s 32 |
`.format(action, form_body); 33 | } 34 | %> 35 | 36 | <%= form_for("/shorten", "", (f) { %> 37 | Shorten a URL: 38 | <%= f.field_for("url") %> 39 | <%= f.submit("Shorten URL") %> 40 | <% }); %> 41 | 42 | <%= form_for("/person", "person", (f) { %> 43 | Name: <%= f.field_for("name") %> 44 | Age: <%= f.field_for("age") %> 45 | DOB: <%= f.field_for("date_of_birth", "date") %> 46 | <%= f.submit %> 47 | <% }); %> 48 | -------------------------------------------------------------------------------- /test/test8_building_helpers.emd.txt: -------------------------------------------------------------------------------- 1 |
2 | Shorten a URL: 3 | 4 | 5 |
6 | 7 |
8 | Name: 9 | Age: 10 | DOB: 11 | 12 |
-------------------------------------------------------------------------------- /test/test9_filter.emd: -------------------------------------------------------------------------------- 1 | <%= "foo" %> 2 | bar --------------------------------------------------------------------------------