22// for details. All rights reserved. Use of this source code is governed by a
33// BSD-style license that can be found in the LICENSE file.
44
5+ import 'dart:collection' ;
6+
57import 'package:analyzer/file_system/file_system.dart' ;
68import 'package:meta/meta.dart' ;
7- import 'package:path/path.dart' as path;
89import 'parser.dart' ;
910
1011/// The signature of a partial resolver function.
11- typedef PartialResolver = String Function (String path );
12+ typedef PartialResolver = Future < File > Function (String uri );
1213
1314/// The signature of a generated render function.
14- typedef Renderer <T > = String Function (T context, File file,
15- {PartialResolver partialResolver});
15+ typedef RenderFunction <T > = String Function (T context, Template template);
16+
17+ /// A parsed Mustache template.
18+ ///
19+ /// This container includes Templates for all partials parsed in the template
20+ /// file.
21+ class Template {
22+ /// The parsed Mustache syntax tree.
23+ final List <MustachioNode > ast;
24+
25+ /// A mapping of partial keys to partial [File] s.
26+ ///
27+ /// This may appear redundant with [partialTemplates] , the mapping of partial
28+ /// files to partial templates. The reason for two mappings is that
29+ /// [partialTemplates] functions as a cache, so that each [File] containing a
30+ /// partial template is only parsed once, and also so that partial templates
31+ /// can reference themselves, or otherwise recurse. The cache is passed down
32+ /// from template to partial. Mapping a partial key to a partial [File] ,
33+ /// however, cannot be shared between templates and partials because they
34+ /// typically contain relative file paths. Each partial file path is given
35+ /// relative to the template or partial making the reference.
36+ // TODO(srawlins): Resolving partials from the Parser rather than
37+ // [Template.parse] would allow the [File] to be stored on the [Partial] node,
38+ // removing the need for this mapping.
39+ final Map <String , File > partials;
40+
41+ /// A mapping of partial [File] s to parsed Mustache templates.
42+ final Map <File , Template > partialTemplates;
43+
44+ Template ._(
45+ {@required this .ast,
46+ @required this .partials,
47+ @required this .partialTemplates});
48+
49+ /// Parses [file] as a Mustache template, returning a [Template] .
50+ ///
51+ /// A [partialResolver] can be passed if custom partial resolution is required
52+ /// (for example, to load any partial from a single directory, and to append a
53+ /// particular suffix).
54+ ///
55+ /// By default, a partial key is resolved as a file path which is either
56+ /// absolute, or relative to the directory containing the template which
57+ /// references the partial.
58+ ///
59+ /// For example, if the Mustache template at `/foo/template.html` includes a
60+ /// Partial node, `{{>partials/p1.html}}` , then the partial file path is
61+ /// resolved as `/foo/partials/p1.html` . If this template includes a Partial
62+ /// node, `{{>p2.html}}` , then this partial file path is resolved (relative to
63+ /// the directory containing `p1.html` , not relative to the top-level
64+ /// template), as `/foo/partials/p2.html` .
65+ static Future <Template > parse (File file,
66+ {PartialResolver partialResolver,
67+ @internal Map <File , Template > partialTemplates}) async {
68+ partialTemplates ?? = < File , Template > {};
69+ if (partialResolver == null ) {
70+ var pathContext = file.provider.pathContext;
71+ // By default, resolve partials as absolute file paths, or as relative
72+ // file paths, relative to the template directory from which they are
73+ // referenced.
74+ partialResolver = (String path) async {
75+ var partialPath = pathContext.isAbsolute (path)
76+ ? path
77+ : pathContext.join (file.parent.path, path);
78+ var partialFile =
79+ file.provider.getFile (pathContext.normalize (partialPath));
80+ return partialFile;
81+ };
82+ }
83+
84+ // If we fail to read [file], and an exception is thrown, one of two
85+ // things happen:
86+ // 1) In the case of a reference from a partial, the exception is caught
87+ // below, when parsing partials, and rethrown with information about the
88+ // template with the reference.
89+ // 2) In the case of a reference from a top-level template, user code has
90+ // called [Template.parse], and the user is responsible for handling the
91+ // exception.
92+ var ast = MustachioParser (file.readAsStringSync ()).parse ();
93+ var nodeQueue = Queue .of (ast);
94+ var partials = < String , File > {};
95+
96+ // Walk the Mustache syntax tree, looking for Partial nodes.
97+ while (nodeQueue.isNotEmpty) {
98+ var node = nodeQueue.removeFirst ();
99+ if (node is Text ) {
100+ // Nothing to do.
101+ } else if (node is Variable ) {
102+ // Nothing to do.
103+ } else if (node is Section ) {
104+ nodeQueue.addAll (node.children);
105+ } else if (node is Partial ) {
106+ var key = node.key;
107+ if (! partials.containsKey (key)) {
108+ partials[key] = await partialResolver (key);
109+ }
110+ var partialFile = partials[key];
111+ if (! partialTemplates.containsKey (partialFile)) {
112+ try {
113+ var partialTemplate = await Template .parse (partialFile,
114+ partialResolver: partialResolver,
115+ partialTemplates: {...partialTemplates});
116+ partialTemplates[partialFile] = partialTemplate;
117+ } on FileSystemException catch (e) {
118+ throw MustachioResolutionError (
119+ 'FileSystemException when reading partial "$key " found in '
120+ 'template "${file .path }": ${e .message }' );
121+ }
122+ }
123+ }
124+ }
125+
126+ return Template ._(
127+ ast: ast, partials: partials, partialTemplates: partialTemplates);
128+ }
129+ }
16130
17131/// The base class for a generated Mustache renderer.
18132abstract class RendererBase <T > {
@@ -22,25 +136,12 @@ abstract class RendererBase<T> {
22136 /// The renderer of the parent context, if any, otherwise `null` .
23137 final RendererBase parent;
24138
25- final File template;
26-
27- final PartialResolver partialResolver;
139+ final Template template;
28140
29141 /// The output buffer into which [context] is rendered, using a template.
30142 final buffer = StringBuffer ();
31143
32- RendererBase (this .context, this .parent, this .template,
33- {this .partialResolver});
34-
35- path.Context get pathContext => template.provider.pathContext;
36-
37- String _defaultResolver (String path) {
38- var partialPath = pathContext.isAbsolute (path)
39- ? path
40- : pathContext.join (template.parent.path, path);
41- var file = template.provider.getFile (pathContext.normalize (partialPath));
42- return file.readAsStringSync ();
43- }
144+ RendererBase (this .context, this .parent, this .template);
44145
45146 void write (String text) => buffer.write (text);
46147
@@ -143,28 +244,20 @@ abstract class RendererBase<T> {
143244
144245 void partial (Partial node) {
145246 var key = node.key;
146- try {
147- var partial = (partialResolver ?? _defaultResolver)(key);
148- var parser = MustachioParser (partial);
149- var ast = parser.parse ();
150- renderBlock (ast);
151- } on FileSystemException catch (e) {
152- throw MustachioResolutionError (
153- 'FileSystemException when reading partial "$key " found in template '
154- '"${template .path }": ${e .message }' );
155- }
247+ var partialFile = template.partials[key];
248+ renderBlock (template.partialTemplates[partialFile].ast);
156249 }
157250}
158251
159- String renderSimple (Object context, List <MustachioNode > ast, File template,
252+ String renderSimple (Object context, List <MustachioNode > ast, Template template,
160253 {RendererBase parent}) {
161254 var renderer = SimpleRenderer (context, parent, template);
162255 renderer.renderBlock (ast);
163256 return renderer.buffer.toString ();
164257}
165258
166259class SimpleRenderer extends RendererBase <Object > {
167- SimpleRenderer (Object context, RendererBase <Object > parent, File template)
260+ SimpleRenderer (Object context, RendererBase <Object > parent, Template template)
168261 : super (context, parent, template);
169262
170263 @override
0 commit comments