@@ -213,6 +213,39 @@ A User object can be rendered into the following Mustache template:
213213{{ /posts }}
214214```
215215
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+
216249### Renderer outline
217250
218251In order to support repeated sections and value sections, a renderer for a type
@@ -382,29 +415,211 @@ render, and the parent context.
382415
383416#### Rendering a block
384417
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.
386445
387446#### Resolving a variable key
388447
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.
390491
391492#### Rendering a section
392493
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.
394513
395514##### Conditional section
396515
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+
397539##### Repeated section
398540
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+
399571##### Value section
400572
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+
401590#### Rendering a partial
402591
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 ` .
404617
405618[ value section ] : https://mustache.github.io/mustache.5.html#Sections
406619[ Rendering a block ] : #rendering-a-block
407620[ variable node ] : https://mustache.github.io/mustache.5.html#Variables
621+ [ renderIterable ] : #the-renderIterable-function
622+ [ renderValue ] : #the-renderValue-function
408623
409624### High level design for generating renderers
410625
0 commit comments