Skip to content

Commit 59166fd

Browse files
authored
Refactor header and footer support to use mustache templating. (#2554)
Refactor header and footer support to use mustache templating. * Remove `mixin GeneratorContext` and move contained code into DartdocGeneratorOptionContext, the only class which mixed in this mixin. * **Breaking change**: Move DartdocGeneratorOptionContext from `lib/dartdoc.dart` to `lib/options.dart`, which is more directly related to the class, and is the only library in which this class is constructed or extended. * Add `header`, `footer`, and `footerText` getters to DartdocGeneratorOptionContext. * Add `customHeaderContent`, `customFooterContent`, and `customInnerFooterText` to DartdocGneratorBackendOptions. * **Breaking change**: Privatize the DartdocGeneratorBackendOptions into a new private constructor which takes no parameters. * Change all header/footer/footer-text placeholder comments in templates to instead be mustache interpolations.
1 parent 044754d commit 59166fd

File tree

17 files changed

+143
-113
lines changed

17 files changed

+143
-113
lines changed

lib/dartdoc.dart

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'dart:convert';
1313
import 'dart:io' show exitCode, stderr;
1414

1515
import 'package:analyzer/file_system/file_system.dart';
16+
import 'package:dartdoc/options.dart';
1617
import 'package:dartdoc/src/dartdoc_options.dart';
1718
import 'package:dartdoc/src/generator/empty_generator.dart';
1819
import 'package:dartdoc/src/generator/generator.dart';
@@ -40,14 +41,6 @@ const String programName = 'dartdoc';
4041
// Update when pubspec version changes by running `pub run build_runner build`
4142
const String dartdocVersion = packageVersion;
4243

43-
/// Helper class that consolidates option contexts for instantiating generators.
44-
class DartdocGeneratorOptionContext extends DartdocOptionContext
45-
with GeneratorContext {
46-
DartdocGeneratorOptionContext(
47-
DartdocOptionSet optionSet, Folder dir, ResourceProvider resourceProvider)
48-
: super(optionSet, dir, resourceProvider);
49-
}
50-
5144
class DartdocFileWriter implements FileWriter {
5245
final String outputDir;
5346
@override

lib/options.dart

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,52 @@ import 'package:args/args.dart';
55
import 'package:dartdoc/dartdoc.dart';
66
import 'package:dartdoc/src/logging.dart';
77

8+
/// Helper class that consolidates option contexts for instantiating generators.
9+
class DartdocGeneratorOptionContext extends DartdocOptionContext {
10+
DartdocGeneratorOptionContext(
11+
DartdocOptionSet optionSet, Folder dir, ResourceProvider resourceProvider)
12+
: super(optionSet, dir, resourceProvider);
13+
14+
// TODO(migration): Make late final with initializer when Null Safe.
15+
String _header;
16+
17+
/// Returns the joined contents of any 'header' files specified in options.
18+
String get header =>
19+
_header ??= _joinCustomTextFiles(optionSet['header'].valueAt(context));
20+
21+
// TODO(migration): Make late final with initializer when Null Safe.
22+
String _footer;
23+
24+
/// Returns the joined contents of any 'footer' files specified in options.
25+
String get footer =>
26+
_footer ??= _joinCustomTextFiles(optionSet['footer'].valueAt(context));
27+
28+
// TODO(migration): Make late final with initializer when Null Safe.
29+
String _footerText;
30+
31+
/// Returns the joined contents of any 'footer-text' files specified in
32+
/// options.
33+
String get footerText => _footerText ??=
34+
_joinCustomTextFiles(optionSet['footerText'].valueAt(context));
35+
36+
String _joinCustomTextFiles(Iterable<String> paths) => paths
37+
.map((p) => resourceProvider.getFile(p).readAsStringSync())
38+
.join('\n');
39+
40+
bool get prettyIndexJson => optionSet['prettyIndexJson'].valueAt(context);
41+
42+
String get favicon => optionSet['favicon'].valueAt(context);
43+
44+
String get relCanonicalPrefix =>
45+
optionSet['relCanonicalPrefix'].valueAt(context);
46+
47+
String get templatesDir => optionSet['templatesDir'].valueAt(context);
48+
49+
// TODO(jdkoren): duplicated temporarily so that GeneratorContext is enough for configuration.
50+
@override
51+
bool get useBaseHref => optionSet['useBaseHref'].valueAt(context);
52+
}
53+
854
class DartdocProgramOptionContext extends DartdocGeneratorOptionContext
955
with LoggingContext {
1056
DartdocProgramOptionContext(

lib/src/generator/dartdoc_generator_backend.dart

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:dartdoc/dartdoc.dart';
6+
import 'package:dartdoc/options.dart';
67
import 'package:dartdoc/src/generator/generator_frontend.dart';
78
import 'package:dartdoc/src/generator/generator_utils.dart' as generator_util;
89
import 'package:dartdoc/src/generator/template_data.dart';
@@ -18,6 +19,7 @@ import 'package:path/path.dart' as path show Context;
1819
class DartdocGeneratorBackendOptions implements TemplateOptions {
1920
@override
2021
final String relCanonicalPrefix;
22+
2123
@override
2224
final String toolVersion;
2325

@@ -28,20 +30,35 @@ class DartdocGeneratorBackendOptions implements TemplateOptions {
2830
@override
2931
final bool useBaseHref;
3032

33+
@override
34+
final String customHeaderContent;
35+
36+
@override
37+
final String customFooterContent;
38+
39+
@override
40+
final String customInnerFooterText;
41+
3142
DartdocGeneratorBackendOptions.fromContext(
3243
DartdocGeneratorOptionContext context)
3344
: relCanonicalPrefix = context.relCanonicalPrefix,
3445
toolVersion = dartdocVersion,
3546
favicon = context.favicon,
3647
prettyIndexJson = context.prettyIndexJson,
37-
useBaseHref = context.useBaseHref;
38-
39-
DartdocGeneratorBackendOptions(
40-
{this.relCanonicalPrefix,
41-
this.toolVersion,
42-
this.favicon,
43-
this.prettyIndexJson = false,
44-
this.useBaseHref = false});
48+
useBaseHref = context.useBaseHref,
49+
customHeaderContent = context.header,
50+
customFooterContent = context.footer,
51+
customInnerFooterText = context.footerText;
52+
53+
DartdocGeneratorBackendOptions._defaults()
54+
: relCanonicalPrefix = null,
55+
toolVersion = null,
56+
favicon = null,
57+
prettyIndexJson = false,
58+
useBaseHref = false,
59+
customHeaderContent = '',
60+
customFooterContent = '',
61+
customInnerFooterText = '';
4562
}
4663

4764
class SidebarGenerator<T extends Documentable> {
@@ -67,7 +84,7 @@ abstract class DartdocGeneratorBackend implements GeneratorBackend {
6784

6885
DartdocGeneratorBackend(
6986
DartdocGeneratorBackendOptions options, this.templates, this._pathContext)
70-
: options = options ?? DartdocGeneratorBackendOptions(),
87+
: options = options ?? DartdocGeneratorBackendOptions._defaults(),
7188
sidebarForContainer =
7289
SidebarGenerator(templates.sidebarContainerTemplate),
7390
sidebarForLibrary = SidebarGenerator(templates.sidebarLibraryTemplate);

lib/src/generator/generator.dart

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,6 @@ abstract class Generator {
3232
Future<void> generate(PackageGraph packageGraph, FileWriter writer);
3333
}
3434

35-
/// Dartdoc options related to generators generally.
36-
mixin GeneratorContext on DartdocOptionContextBase {
37-
List<String> get footer => optionSet['footer'].valueAt(context);
38-
39-
List<String> get footerText => optionSet['footerText'].valueAt(context);
40-
41-
List<String> get header => optionSet['header'].valueAt(context);
42-
43-
bool get prettyIndexJson => optionSet['prettyIndexJson'].valueAt(context);
44-
45-
String get favicon => optionSet['favicon'].valueAt(context);
46-
47-
String get relCanonicalPrefix =>
48-
optionSet['relCanonicalPrefix'].valueAt(context);
49-
50-
String get templatesDir => optionSet['templatesDir'].valueAt(context);
51-
52-
// TODO(jdkoren): duplicated temporarily so that GeneratorContext is enough for configuration.
53-
bool get useBaseHref => optionSet['useBaseHref'].valueAt(context);
54-
}
55-
5635
Future<List<DartdocOption<Object>>> createGeneratorOptions(
5736
PackageMetaProvider packageMetaProvider) async {
5837
var resourceProvider = packageMetaProvider.resourceProvider;

lib/src/generator/html_generator.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44

55
library dartdoc.html_generator;
66

7-
import 'package:dartdoc/dartdoc.dart';
7+
import 'package:dartdoc/options.dart';
88
import 'package:dartdoc/src/generator/dartdoc_generator_backend.dart';
99
import 'package:dartdoc/src/generator/generator.dart';
1010
import 'package:dartdoc/src/generator/generator_frontend.dart';
1111
import 'package:dartdoc/src/generator/html_resources.g.dart' as resources;
1212
import 'package:dartdoc/src/generator/resource_loader.dart';
1313
import 'package:dartdoc/src/generator/template_data.dart';
1414
import 'package:dartdoc/src/generator/templates.dart';
15+
import 'package:dartdoc/src/model/package.dart';
16+
import 'package:dartdoc/src/model/package_graph.dart';
1517
import 'package:path/path.dart' as path show Context;
1618

1719
Future<Generator> initHtmlGenerator(

lib/src/generator/markdown_generator.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
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 'package:dartdoc/dartdoc.dart';
5+
import 'package:dartdoc/options.dart';
66
import 'package:dartdoc/src/generator/dartdoc_generator_backend.dart';
77
import 'package:dartdoc/src/generator/generator.dart';
88
import 'package:dartdoc/src/generator/generator_frontend.dart';

lib/src/generator/template_data.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ abstract class TemplateOptions {
1212
String get relCanonicalPrefix;
1313
String get toolVersion;
1414
bool get useBaseHref;
15+
String get customHeaderContent;
16+
String get customFooterContent;
17+
String get customInnerFooterText;
1518
}
1619

1720
abstract class TemplateData<T extends Documentable> {
@@ -61,6 +64,12 @@ abstract class TemplateData<T extends Documentable> {
6164
String _layoutTitle(String name, String kind, bool isDeprecated) =>
6265
_packageGraph.rendererFactory.templateRenderer
6366
.composeLayoutTitle(name, kind, isDeprecated);
67+
68+
String get customHeader => htmlOptions.customHeaderContent;
69+
70+
String get customFooter => htmlOptions.customFooterContent;
71+
72+
String get customInnerFooter => htmlOptions.customInnerFooterText;
6473
}
6574

6675
/// A [TemplateData] which contains a library, for rendering the

lib/src/generator/templates.dart

Lines changed: 9 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ library dartdoc.templates;
99

1010
import 'package:analyzer/file_system/file_system.dart';
1111
import 'package:dartdoc/dartdoc.dart';
12+
import 'package:dartdoc/options.dart';
1213
import 'package:dartdoc/src/generator/resource_loader.dart';
1314
import 'package:dartdoc/src/generator/template_data.dart';
1415
import 'package:dartdoc/src/mustachio/annotations.dart';
@@ -67,36 +68,6 @@ const _partials_md = <String>[
6768
'source_link',
6869
];
6970

70-
const String _headerPlaceholder = '{{! header placeholder }}';
71-
const String _footerPlaceholder = '{{! footer placeholder }}';
72-
const String _footerTextPlaceholder = '{{! footer-text placeholder }}';
73-
74-
Future<Map<String, String>> _loadPartials(
75-
_TemplatesLoader templatesLoader,
76-
List<String> headerPaths,
77-
List<String> footerPaths,
78-
List<String> footerTextPaths) async {
79-
var partials = await templatesLoader.loadPartials();
80-
81-
void replacePlaceholder(String key, String placeholder, List<String> paths) {
82-
var template = partials[key];
83-
if (template != null && paths != null && paths.isNotEmpty) {
84-
var replacement = paths
85-
.map((p) =>
86-
templatesLoader.resourceProvider.getFile(p).readAsStringSync())
87-
.join('\n');
88-
template = template.replaceAll(placeholder, replacement);
89-
partials[key] = template;
90-
}
91-
}
92-
93-
replacePlaceholder('head', _headerPlaceholder, headerPaths);
94-
replacePlaceholder('footer', _footerPlaceholder, footerPaths);
95-
replacePlaceholder('footer', _footerTextPlaceholder, footerTextPaths);
96-
97-
return partials;
98-
}
99-
10071
abstract class _TemplatesLoader {
10172
ResourceProvider get resourceProvider;
10273

@@ -207,53 +178,29 @@ class Templates {
207178
DartdocGeneratorOptionContext context) async {
208179
var templatesDir = context.templatesDir;
209180
var format = context.format;
210-
var footerTextPaths = context.footerText;
211181

212182
if (templatesDir != null) {
213183
return _fromDirectory(
214184
context.resourceProvider.getFolder(templatesDir), format,
215-
resourceProvider: context.resourceProvider,
216-
headerPaths: context.header,
217-
footerPaths: context.footer,
218-
footerTextPaths: footerTextPaths);
185+
resourceProvider: context.resourceProvider);
219186
} else {
220-
return createDefault(format,
221-
resourceProvider: context.resourceProvider,
222-
headerPaths: context.header,
223-
footerPaths: context.footer,
224-
footerTextPaths: footerTextPaths);
187+
return createDefault(format, resourceProvider: context.resourceProvider);
225188
}
226189
}
227190

228191
@visibleForTesting
229192
static Future<Templates> createDefault(String format,
230-
{@required ResourceProvider resourceProvider,
231-
List<String> headerPaths = const <String>[],
232-
List<String> footerPaths = const <String>[],
233-
List<String> footerTextPaths = const <String>[]}) async {
234-
return _create(_DefaultTemplatesLoader.create(format, resourceProvider),
235-
headerPaths: headerPaths,
236-
footerPaths: footerPaths,
237-
footerTextPaths: footerTextPaths);
193+
{@required ResourceProvider resourceProvider}) async {
194+
return _create(_DefaultTemplatesLoader.create(format, resourceProvider));
238195
}
239196

240197
static Future<Templates> _fromDirectory(Folder dir, String format,
241-
{@required ResourceProvider resourceProvider,
242-
@required List<String> headerPaths,
243-
@required List<String> footerPaths,
244-
@required List<String> footerTextPaths}) async {
245-
return _create(_DirectoryTemplatesLoader(dir, format, resourceProvider),
246-
headerPaths: headerPaths,
247-
footerPaths: footerPaths,
248-
footerTextPaths: footerTextPaths);
198+
{@required ResourceProvider resourceProvider}) async {
199+
return _create(_DirectoryTemplatesLoader(dir, format, resourceProvider));
249200
}
250201

251-
static Future<Templates> _create(_TemplatesLoader templatesLoader,
252-
{@required List<String> headerPaths,
253-
@required List<String> footerPaths,
254-
@required List<String> footerTextPaths}) async {
255-
var partials = await _loadPartials(
256-
templatesLoader, headerPaths, footerPaths, footerTextPaths);
202+
static Future<Templates> _create(_TemplatesLoader templatesLoader) async {
203+
var partials = await templatesLoader.loadPartials();
257204

258205
Template _partial(String name) {
259206
var partial = partials[name];

lib/src/generator/templates.renderers.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,42 @@ class _Renderer_TemplateData<T extends Documentable>
12831283
return renderSimple(c.bareHref, ast, r.template, parent: r);
12841284
},
12851285
),
1286+
'customFooter': Property(
1287+
getValue: (CT_ c) => c.customFooter,
1288+
renderVariable: (CT_ c, Property<CT_> self,
1289+
List<String> remainingNames) =>
1290+
self.renderSimpleVariable(c, remainingNames, 'String'),
1291+
isNullValue: (CT_ c) => c.customFooter == null,
1292+
renderValue:
1293+
(CT_ c, RendererBase<CT_> r, List<MustachioNode> ast) {
1294+
return renderSimple(c.customFooter, ast, r.template,
1295+
parent: r);
1296+
},
1297+
),
1298+
'customHeader': Property(
1299+
getValue: (CT_ c) => c.customHeader,
1300+
renderVariable: (CT_ c, Property<CT_> self,
1301+
List<String> remainingNames) =>
1302+
self.renderSimpleVariable(c, remainingNames, 'String'),
1303+
isNullValue: (CT_ c) => c.customHeader == null,
1304+
renderValue:
1305+
(CT_ c, RendererBase<CT_> r, List<MustachioNode> ast) {
1306+
return renderSimple(c.customHeader, ast, r.template,
1307+
parent: r);
1308+
},
1309+
),
1310+
'customInnerFooter': Property(
1311+
getValue: (CT_ c) => c.customInnerFooter,
1312+
renderVariable: (CT_ c, Property<CT_> self,
1313+
List<String> remainingNames) =>
1314+
self.renderSimpleVariable(c, remainingNames, 'String'),
1315+
isNullValue: (CT_ c) => c.customInnerFooter == null,
1316+
renderValue:
1317+
(CT_ c, RendererBase<CT_> r, List<MustachioNode> ast) {
1318+
return renderSimple(c.customInnerFooter, ast, r.template,
1319+
parent: r);
1320+
},
1321+
),
12861322
'defaultPackage': Property(
12871323
getValue: (CT_ c) => c.defaultPackage,
12881324
renderVariable:

lib/templates/html/_footer.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88
{{/hasFooterVersion}}
99
</span>
1010

11-
{{! footer-text placeholder }}
11+
{{{ customInnerFooter }}}
1212
</footer>
1313

1414
{{! TODO(jdkoren): unwrap ^useBaseHref sections when the option is removed.}}
1515
<script src="{{^useBaseHref}}%%__HTMLBASE_dartdoc_internal__%%{{/useBaseHref}}static-assets/highlight.pack.js"></script>
1616
<script src="{{^useBaseHref}}%%__HTMLBASE_dartdoc_internal__%%{{/useBaseHref}}static-assets/script.js"></script>
1717

18-
{{! footer placeholder }}
18+
{{{ customFooter }}}
1919

2020
</body>
2121

lib/templates/html/_head.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
<link rel="stylesheet" href="{{^useBaseHref}}%%__HTMLBASE_dartdoc_internal__%%{{/useBaseHref}}static-assets/styles.css">
2929
<link rel="icon" href="{{^useBaseHref}}%%__HTMLBASE_dartdoc_internal__%%{{/useBaseHref}}static-assets/favicon.png">
3030

31-
{{! header placeholder }}
31+
{{{ customHeader }}}
3232
</head>
3333

3434
{{! We don't use <base href>, but we do lookup the htmlBase from javascript. }}

0 commit comments

Comments
 (0)