@@ -213,6 +213,39 @@ A User object can be rendered into the following Mustache template:
213
213
{{ /posts }}
214
214
```
215
215
216
+ ### Render function
217
+
218
+ Each generated renderer is paired with a generated _ public render function_ ,
219
+ which is the public interface for rendering objects into Mustache templates,
220
+ and _ private render function_ , which is a convenience function for constructing
221
+ a renderer and rendering an AST with it.
222
+
223
+ ``` dart
224
+ String renderUser(User context, Template template) {
225
+ return _render_User(context, template.ast, template);
226
+ }
227
+
228
+ String _render_User(User context, List<MustachioNode> ast, Template template,
229
+ {RendererBase<Object> parent}) {
230
+ var renderer = _Renderer_User(context, parent, template);
231
+ renderer.renderBlock(ast);
232
+ return renderer.buffer.toString();
233
+ }
234
+ ```
235
+
236
+ In order to use the public render function, one first needs a Template object.
237
+ This is a container for a parsed Mustache template. The ` Template.parse `
238
+ constructor accepts a file path and an optional partial resolver. It parses the
239
+ Mustache template at the given file path, and also reads and parses all partials
240
+ referenced in the template. The returned Template object contains a mapping of
241
+ all partial keys to partial file paths, and also a mapping of all partial file
242
+ paths to partial Template objects. This Template object can be used to render
243
+ various context objects, without needing to re-read or re-parse the template
244
+ file or any referenced partial files.
245
+
246
+ The ` renderUser ` function just requires two arguments, the User object to
247
+ render, and the Mustache Template object that it should be rendered into.
248
+
216
249
### Renderer outline
217
250
218
251
In order to support repeated sections and value sections, a renderer for a type
@@ -382,29 +415,211 @@ render, and the parent context.
382
415
383
416
#### Rendering a block
384
417
385
- TODO(srawlins): Write.
418
+ The RendererBase class defines a very simple ` renderBlock ` method. This method
419
+ iterates over an AST, delegating to other methods depending on the type of each
420
+ node:
421
+
422
+ ``` dart
423
+ /// Renders a block of Mustache template, the [ast], into [buffer].
424
+ void renderBlock(List<MustachioNode> ast) {
425
+ for (var node in ast) {
426
+ if (node is Text) {
427
+ write(node.content);
428
+ } else if (node is Variable) {
429
+ var content = getFields(node);
430
+ write(content);
431
+ } else if (node is Section) {
432
+ section(node);
433
+ } else if (node is Partial) {
434
+ partial(node);
435
+ }
436
+ }
437
+ }
438
+ ```
439
+
440
+ Text is rendered verbatim.
441
+
442
+ Rendering a variable is mostly a matter of resolving the variable (see below).
443
+
444
+ Sections and Partials are complex enough to warrant their own methods.
386
445
387
446
#### Resolving a variable key
388
447
389
- TODO(srawlins): Write.
448
+ Rendering a variable requires _ resolution_ ; the variable's _ key_ may consist of
449
+ multiple _ names_ , (e.g. ` {{ foo.bar.baz }} ` is a variable node with a key of
450
+ "foo.bar.baz"; this key has three names: "foo", "bar", and "baz") and resolution
451
+ may require context objects further down in the stack. This resolution is
452
+ performed in the renderer's ` getFields ` method.
453
+
454
+ ``` dart
455
+ String getFields(Variable node) {
456
+ var names = node.key;
457
+ if (names.length == 1 && names.single == '.') {
458
+ return context.toString();
459
+ }
460
+ var property = getProperty(names.first);
461
+ if (property != null) {
462
+ var remainingNames = [...names.skip(1)];
463
+ try {
464
+ return property.renderVariable(context, property, remainingNames);
465
+ } on PartialMustachioResolutionError catch (e) {
466
+ // The error thrown by [Property.renderVariable] does not have all of
467
+ // the names required for a decent error. We throw a new error here.
468
+ throw MustachioResolutionError(...);
469
+ }
470
+ } else if (parent != null) {
471
+ return parent.getFields(node);
472
+ } else {
473
+ throw MustachioResolutionError(...);
474
+ }
475
+ }
476
+ ```
477
+
478
+ We can see the entire resolution process here:
479
+
480
+ * If the key is just ".", then we render the current context object as a String.
481
+ * If the first name (which is often the whole key) is found on the context
482
+ object's property map, then we resolve the name as a property on the context
483
+ object.
484
+ * For each remaining name in the key names, we search the resolved object for
485
+ a property with this name. If it is found, we resolve the name as a property
486
+ on the previously resolved object. If it is not found, resolution has
487
+ failed.
488
+ * If the first name is not found on the context object, we request that the
489
+ parent renderer resolve the key.
490
+ * If there is no parent, resolution has failed.
390
491
391
492
#### Rendering a section
392
493
393
- TODO(srawlins): Write.
494
+ A section key is not allowed to have multiple names. We first search for a
495
+ property on the context object with the key as its name. If we don't find it, we
496
+ search the parent context:
497
+
498
+ ``` dart
499
+ var key = node.key.first;
500
+ var property = getProperty(key);
501
+ if (property == null) {
502
+ if (parent == null) {
503
+ throw MustachioResolutionError(...);
504
+ } else {
505
+ return parent.section(node);
506
+ }
507
+ }
508
+ ```
509
+
510
+ The ` getProperty ` method returns the Property instance for the specified name,
511
+ which has various methods on it which can access the property for various
512
+ purposes.
394
513
395
514
##### Conditional section
396
515
516
+ First we check if the property can be used in a conditional section:
517
+
518
+ ``` dart
519
+ if (property.getBool != null) {
520
+ var boolResult = property.getBool(context);
521
+ if ((boolResult && !node.invert) || (!boolResult && node.invert)) {
522
+ renderBlock(node.children);
523
+ }
524
+ return;
525
+ }
526
+ ```
527
+
528
+ If the getter's return type is not ` bool? ` or ` bool ` , then ` getBool ` returns
529
+ ` null ` .
530
+
531
+ If the getter's return type is ` bool? ` or ` bool ` , then ` getBool ` is a function
532
+ which takes the context object as an argument, and returns the non-nullable
533
+ ` bool ` value of the property on the context object (resolving a ` null ` value as
534
+ ` false ` ).
535
+
536
+ Since a conditional section can be inverted, we have to account for this when
537
+ deciding to render the children.
538
+
397
539
##### Repeated section
398
540
541
+ If the getter does not result in a conditional section, we check whether it is
542
+ iterable:
543
+
544
+ ``` dart
545
+ if (property.renderIterable != null) {
546
+ var renderedIterable =
547
+ property.renderIterable(context, this, node.children);
548
+ if (node.invert && renderedIterable.isEmpty) {
549
+ // An inverted section is rendered with the current context.
550
+ renderBlock(node.children);
551
+ } else if (!node.invert && renderedIterable.isNotEmpty) {
552
+ var buffer = StringBuffer()..writeAll(renderedIterable);
553
+ write(buffer.toString());
554
+ }
555
+ // Otherwise, render nothing.
556
+
557
+ return;
558
+ }
559
+ ```
560
+
561
+ If the getter's return type is not a subtype of ` Iterable<Object?>? ` , then
562
+ ` renderIterable ` returns ` null ` .
563
+
564
+ If the getter's return type is a subtype of ` Iterable<Object?>? ` , then
565
+ ` renderIterable ` , [ detailed here] [ renderIterable ] , is a function which returns
566
+ the non-nullable String value of the rendered section.
567
+
568
+ An inverted repeated section is rendered with the current context if the
569
+ iterable is ` null ` or empty.
570
+
399
571
##### Value section
400
572
573
+ If the getter does not result in a conditional section, nor a repeated section, we render the section as a value section:
574
+
575
+ ``` dart
576
+ if (node.invert && property.isNullValue(context)) {
577
+ renderBlock(node.children);
578
+ } else if (!node.invert && !property.isNullValue(context)) {
579
+ write(property.renderValue(context, this, node.children));
580
+ }
581
+ ```
582
+
583
+ An inverted value section is rendered with the current context if the value is
584
+ ` null ` .
585
+
586
+ The ` renderValue ` function, [ detailed here] [ renderValue ] , takes the context
587
+ object, the renderer, and the section's children as arguments, and returns the
588
+ non-nullable String value of the rendered section.
589
+
401
590
#### Rendering a partial
402
591
403
- TODO(srawlins): Write.
592
+ A partial key is not resolved as a sequence of names; it is instead a free form
593
+ text key which maps to a partial file. Mustachio can either use a built-in
594
+ partial resolver, in which case each key is a path which is relative to the
595
+ template in which the key is found, or a custom partial resolver which can use
596
+ custom logic to map the key to a file path. The keys have been mapped ahead of
597
+ time (when the Template was parsed) to paths and the paths have been mapped
598
+ ahead of time to Template objects. We map the key to the partial's file path,
599
+ and map the partial's file path to the partial's Template:
600
+
601
+ ``` dart
602
+ void partial(Partial node) {
603
+ var key = node.key;
604
+ var partialFile = template.partials[key];
605
+ var partialTemplate = template.partialTemplates[partialFile];
606
+ var outerTemplate = _template;
607
+ _template = partialTemplate;
608
+ renderBlock(partialTemplate.ast);
609
+ _template = outerTemplate;
610
+ }
611
+ ```
612
+
613
+ To render the partial, we first replace the renderer's template with the
614
+ partial's template (for further partial key resolution of any partial tags found
615
+ inside this partial) and render the partial with the same renderer, using
616
+ ` renderBlock ` .
404
617
405
618
[ value section ] : https://mustache.github.io/mustache.5.html#Sections
406
619
[ Rendering a block ] : #rendering-a-block
407
620
[ variable node ] : https://mustache.github.io/mustache.5.html#Variables
621
+ [ renderIterable ] : #the-renderIterable-function
622
+ [ renderValue ] : #the-renderValue-function
408
623
409
624
### High level design for generating renderers
410
625
0 commit comments