Skip to content

Commit 847121d

Browse files
authored
Introduce a Mustachio Builder. (#2421)
Introduce a Mustachio Builder. This change is mostly concerned with gathering 'Renderer' annotations (which isn't included yet; only a mock is included in specs), and walking the graph of properties and types to gather all of the types which need renderers, for resolving keys into property values. There isn't much generated code in this change, but the base renderer is included, which is referenced in generated renderer classes (stubs, for now).
1 parent c4bd753 commit 847121d

File tree

5 files changed

+558
-0
lines changed

5 files changed

+558
-0
lines changed

lib/src/mustachio/renderer_base.dart

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'parser.dart';
6+
7+
/// The base class for a generated Mustache renderer.
8+
abstract class RendererBase<T> {
9+
/// The context object which this renderer can render.
10+
final T context;
11+
12+
/// The output buffer into which [context] is rendered, using a template.
13+
final buffer = StringBuffer();
14+
15+
RendererBase(this.context);
16+
17+
void write(String text) => buffer.write(text);
18+
19+
/// Renders a block of Mustache template, the [ast], into [buffer].
20+
void renderBlock(List<MustachioNode> ast) {
21+
for (var node in ast) {
22+
if (node is Text) {
23+
write(node.content);
24+
} else if (node is Variable) {
25+
var content = getFields(node.key);
26+
write(content);
27+
} else if (node is Section) {
28+
section(node);
29+
} else if (node is Partial) {
30+
partial(node);
31+
}
32+
}
33+
}
34+
35+
void section(Section node) {
36+
// TODO(srawlins): Implement.
37+
}
38+
39+
void partial(Partial node) {
40+
// TODO(srawlins): Implement.
41+
}
42+
43+
/// Resolves [key] into one or more field accesses, returning the result as a
44+
/// String.
45+
///
46+
/// [key] may have multiple dot-separate names, and [key] may not be a valid
47+
/// property of _this_ context type, in which the [parent] renderer is
48+
/// referenced.
49+
String getFields(List<String> names);
50+
}

pubspec.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ dev_dependencies:
2828
async: '>=2.0.8'
2929
build: ^1.5.0
3030
build_runner: ^1.10.0
31+
build_test: ^1.3.0
3132
build_version: ^2.0.1
3233
coverage: ^0.14.0
34+
dart_style: ^1.3.9
3335
dhttpd: ^3.0.0
3436
grinder: ^0.8.2
3537
http: ^0.12.0

test/mustachio/builder_test.dart

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
import 'package:build/build.dart';
2+
import 'package:build_test/build_test.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../tool/mustachio/builder.dart';
6+
7+
const _annotationsAsset = {
8+
'mustachio|lib/annotations.dart': '''
9+
class Renderer {
10+
final Symbol name;
11+
12+
final Context context;
13+
14+
final String templateUri;
15+
16+
const Renderer(this.name, this.context, this.templateUri);
17+
}
18+
19+
class Context<T> {
20+
const Context();
21+
}
22+
'''
23+
};
24+
25+
TypeMatcher<List<int>> _containsAllOf(Object a,
26+
[Object b, Object c, Object d]) {
27+
if (d != null) {
28+
return decodedMatches(
29+
allOf(contains(a), contains(b), contains(c), contains(d)));
30+
} else if (c != null) {
31+
return decodedMatches(allOf(contains(a), contains(b), contains(c)));
32+
} else {
33+
return decodedMatches(
34+
b != null ? allOf(contains(a), contains(b)) : allOf(contains(a)));
35+
}
36+
}
37+
38+
TypeMatcher<List<int>> _containsNoneOf(Object a, [Object b]) {
39+
return decodedMatches(b != null
40+
? allOf(isNot(contains(a)), isNot(contains(b)))
41+
: allOf(isNot(contains(a))));
42+
}
43+
44+
void main() {
45+
test('builds a renderer for a class which extends Object', () async {
46+
await testBuilder(mustachioBuilder(BuilderOptions({})), {
47+
..._annotationsAsset,
48+
'foo|lib/foo.dart': '''
49+
@Renderer(#renderFoo, Context<Foo>(), 'foo.html.mustache')
50+
library foo;
51+
import 'package:mustachio/annotations.dart';
52+
53+
class Foo {}
54+
''',
55+
}, outputs: {
56+
'foo|lib/foo.renderers.dart': _containsAllOf(
57+
// The requested 'renderFoo' function
58+
'''
59+
String renderFoo(Foo context, List<MustachioNode> ast) {
60+
var renderer = _Renderer_Foo(context);
61+
renderer.renderBlock(ast);
62+
return renderer.buffer.toString();
63+
}
64+
''',
65+
// The renderer class for Foo
66+
'''
67+
class _Renderer_Foo extends RendererBase<Foo> {
68+
_Renderer_Foo(Foo context) : super(context);
69+
}
70+
''',
71+
// The render function for Object
72+
'''
73+
String _render_Object(Object context, List<MustachioNode> ast) {
74+
var renderer = _Renderer_Object(context);
75+
renderer.renderBlock(ast);
76+
return renderer.buffer.toString();
77+
}
78+
''',
79+
// The renderer class for Object
80+
'''
81+
class _Renderer_Object extends RendererBase<Object> {
82+
_Renderer_Object(Object context) : super(context);
83+
}
84+
''')
85+
});
86+
});
87+
88+
test('builds renderers from multiple annotations', () async {
89+
await testBuilder(mustachioBuilder(BuilderOptions({})), {
90+
..._annotationsAsset,
91+
'foo|lib/foo.dart': '''
92+
@Renderer(#renderFoo, Context<Foo>(), 'foo.html.mustache')
93+
@Renderer(#renderBar, Context<Bar>(), 'bar.html.mustache')
94+
library foo;
95+
import 'package:mustachio/annotations.dart';
96+
97+
class Foo {}
98+
class Bar {}
99+
''',
100+
}, outputs: {
101+
'foo|lib/foo.renderers.dart': _containsAllOf(
102+
// The requested 'renderFoo' function
103+
'String renderFoo(Foo context, List<MustachioNode> ast)',
104+
// The renderer class for Foo
105+
'class _Renderer_Foo extends RendererBase<Foo>',
106+
// The requested 'renderBar' function
107+
'String renderBar(Bar context, List<MustachioNode> ast)',
108+
// The renderer class for Bar
109+
'class _Renderer_Bar extends RendererBase<Bar>')
110+
});
111+
});
112+
113+
test('builds a renderer for a class which extends another class', () async {
114+
await testBuilder(mustachioBuilder(BuilderOptions({})), {
115+
..._annotationsAsset,
116+
'foo|lib/foo.dart': '''
117+
@Renderer(#renderFoo, Context<Foo>(), 'bar.html.mustache')
118+
library foo;
119+
import 'package:mustachio/annotations.dart';
120+
121+
class FooBase {}
122+
123+
class Foo extends FooBase {}
124+
''',
125+
}, outputs: {
126+
'foo|lib/foo.renderers.dart': _containsAllOf(
127+
'String _render_FooBase(FooBase context, List<MustachioNode> ast)',
128+
'class _Renderer_FooBase extends RendererBase<FooBase>')
129+
});
130+
});
131+
132+
test('builds a renderer for a generic type', () async {
133+
await testBuilder(mustachioBuilder(BuilderOptions({})), {
134+
..._annotationsAsset,
135+
'foo|lib/foo.dart': '''
136+
@Renderer(#renderFoo, Context<Foo>(), 'bar.html.mustache')
137+
library foo;
138+
import 'package:mustachio/annotations.dart';
139+
140+
class Foo<T> {}
141+
''',
142+
}, outputs: {
143+
'foo|lib/foo.renderers.dart': _containsAllOf(
144+
// The requested 'renderFoo' function
145+
'String renderFoo<T>(Foo<T> context, List<MustachioNode> ast)',
146+
// The renderer class for Foo
147+
'class _Renderer_Foo<T> extends RendererBase<Foo<T>>')
148+
});
149+
});
150+
151+
test('builds a renderer for a type found in a getter', () async {
152+
await testBuilder(mustachioBuilder(BuilderOptions({})), {
153+
..._annotationsAsset,
154+
'foo|lib/foo.dart': '''
155+
@Renderer(#renderFoo, Context<Foo>(), 'bar.html.mustache')
156+
library foo;
157+
import 'package:mustachio/annotations.dart';
158+
159+
abstract class Foo {
160+
Bar get bar;
161+
}
162+
163+
class Bar {}
164+
''',
165+
}, outputs: {
166+
'foo|lib/foo.renderers.dart': _containsAllOf(
167+
// The render function for Bar
168+
'String _render_Bar(Bar context, List<MustachioNode> ast)',
169+
// The renderer class for Bar
170+
'class _Renderer_Bar extends RendererBase<Bar>')
171+
});
172+
});
173+
174+
test('skips a type found in a static or private getter', () async {
175+
await testBuilder(mustachioBuilder(BuilderOptions({})), {
176+
..._annotationsAsset,
177+
'foo|lib/foo.dart': '''
178+
@Renderer(#renderFoo, Context<Foo>(), 'bar.html.mustache')
179+
library foo;
180+
import 'package:mustachio/annotations.dart';
181+
182+
class Foo {
183+
static Bar get bar1 => Bar();
184+
Bar get _bar2 => Bar();
185+
}
186+
187+
class Bar {}
188+
''',
189+
}, outputs: {
190+
'foo|lib/foo.renderers.dart': _containsNoneOf(
191+
// No render function for Bar
192+
'String _render_Bar',
193+
// No renderer class for Bar
194+
'class _Renderer_Bar extends RendererBase<Bar>')
195+
});
196+
});
197+
198+
test('skips a type found in a setter or method', () async {
199+
await testBuilder(mustachioBuilder(BuilderOptions({})), {
200+
..._annotationsAsset,
201+
'foo|lib/foo.dart': '''
202+
@Renderer(#renderFoo, Context<Foo>(), 'bar.html.mustache')
203+
library foo;
204+
import 'package:mustachio/annotations.dart';
205+
206+
abstract class Foo {
207+
void set bar1(Bar b);
208+
Bar bar2(Bar b);
209+
}
210+
211+
class Bar {}
212+
''',
213+
}, outputs: {
214+
'foo|lib/foo.renderers.dart': _containsNoneOf(
215+
// No render function for Bar
216+
'String _render_Bar',
217+
// No renderer class for Bar
218+
'class _Renderer_Bar extends RendererBase<Bar>')
219+
});
220+
});
221+
222+
test('builds a renderer for a generic, bounded type', () async {
223+
await testBuilder(mustachioBuilder(BuilderOptions({})), {
224+
..._annotationsAsset,
225+
'foo|lib/foo.dart': '''
226+
@Renderer(#renderFoo, Context<Foo>(), 'bar.html.mustache')
227+
library foo;
228+
import 'package:mustachio/annotations.dart';
229+
230+
class Foo<T extends num> {}
231+
''',
232+
}, outputs: {
233+
'foo|lib/foo.renderers.dart': _containsAllOf(
234+
// The requested 'renderFoo' function
235+
'String renderFoo<T extends num>(Foo<T> context, List<MustachioNode> ast)',
236+
// The renderer class for Foo
237+
'''
238+
class _Renderer_Foo<T extends num> extends RendererBase<Foo<T>> {
239+
_Renderer_Foo(Foo<T> context) : super(context);
240+
}
241+
''',
242+
// The render function for num, found in Foo's type parameter bound
243+
'String _render_num(num context, List<MustachioNode> ast)',
244+
// The renderer class for num
245+
'class _Renderer_num extends RendererBase<num>')
246+
});
247+
});
248+
}

0 commit comments

Comments
 (0)