Skip to content

Commit 3b1a453

Browse files
committed
Merge branch 'master' into commentref-parse
2 parents ce7fece + de5c302 commit 3b1a453

File tree

2 files changed

+219
-6
lines changed

2 files changed

+219
-6
lines changed

lib/src/render/element_type_renderer.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,6 @@ class ParameterizedElementTypeRendererMd
203203
elementType.typeArguments.map((t) => t.nameWithGenerics), ', ');
204204
buf.write('>');
205205
}
206-
buf.write(elementType.nullabilitySuffix);
207206
return wrapNullability(elementType, buf.toString());
208207
}
209208
}
@@ -236,7 +235,6 @@ class AliasedElementTypeRendererMd
236235
elementType.aliasArguments.map((t) => t.nameWithGenerics), ', ');
237236
buf.write('>');
238237
}
239-
buf.write(elementType.nullabilitySuffix);
240238
return wrapNullability(elementType, buf.toString());
241239
}
242240
}

tool/mustachio/README.md

Lines changed: 219 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

218251
In 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

Comments
 (0)