blob: 17cc49661819e071e6cd0ecbae9dbeb377fe8dc7 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// TODO(srawlins): Add Renderer annotations for more types as the mustachio
// implementation matures.
@Renderer(#renderIndex, Context<PackageTemplateData>())
library dartdoc.templates;
import 'package:analyzer/file_system/file_system.dart';
import 'package:dartdoc/dartdoc.dart';
import 'package:dartdoc/src/generator/resource_loader.dart';
import 'package:dartdoc/src/generator/template_data.dart';
import 'package:dartdoc/src/mustachio/annotations.dart';
import 'package:meta/meta.dart';
import 'package:mustache/mustache.dart';
import 'package:path/path.dart' as path;
// resource_loader and the Resource API doesn't support viewing resources like
// a directory listing, so we have to explicitly list the partials.
const _partials_html = <String>[
'callable',
'callable_multiline',
'categorization',
'class',
'constant',
'extension',
'feature_set',
'footer',
'head',
'library',
'mixin',
'packages',
'property',
'features',
'documentation',
'name_summary',
'search_sidebar',
'sidebar_for_category',
'sidebar_for_container',
'source_code',
'source_link',
'sidebar_for_library',
'accessor_getter',
'accessor_setter',
];
const _partials_md = <String>[
'accessor_getter',
'accessor_setter',
'callable',
'callable_multiline',
'categorization',
'class',
'constant',
'documentation',
'extension',
'features',
'feature_set',
'footer',
'head',
'library',
'mixin',
'name_summary',
'property',
'source_code',
'source_link',
];
const String _headerPlaceholder = '{{! header placeholder }}';
const String _footerPlaceholder = '{{! footer placeholder }}';
const String _footerTextPlaceholder = '{{! footer-text placeholder }}';
Future<Map<String, String>> _loadPartials(
_TemplatesLoader templatesLoader,
List<String> headerPaths,
List<String> footerPaths,
List<String> footerTextPaths) async {
var partials = await templatesLoader.loadPartials();
void replacePlaceholder(String key, String placeholder, List<String> paths) {
var template = partials[key];
if (template != null && paths != null && paths.isNotEmpty) {
var replacement = paths
.map((p) =>
templatesLoader.loader.provider.getFile(p).readAsStringSync())
.join('\n');
template = template.replaceAll(placeholder, replacement);
partials[key] = template;
}
}
replacePlaceholder('head', _headerPlaceholder, headerPaths);
replacePlaceholder('footer', _footerPlaceholder, footerPaths);
replacePlaceholder('footer', _footerTextPlaceholder, footerTextPaths);
return partials;
}
abstract class _TemplatesLoader {
ResourceLoader get loader;
Future<Map<String, String>> loadPartials();
Future<String> loadTemplate(String name);
}
/// Loads default templates included in the Dartdoc program.
class _DefaultTemplatesLoader extends _TemplatesLoader {
final String _format;
final List<String> _partials;
@override
final ResourceLoader loader;
factory _DefaultTemplatesLoader.create(String format, ResourceLoader loader) {
List<String> partials;
switch (format) {
case 'html':
partials = _partials_html;
break;
case 'md':
partials = _partials_md;
break;
default:
partials = [];
}
return _DefaultTemplatesLoader(format, partials, loader);
}
_DefaultTemplatesLoader(this._format, this._partials, this.loader);
@override
Future<Map<String, String>> loadPartials() async {
var templates = <String, String>{};
for (var name in _partials) {
var uri = 'package:dartdoc/templates/$_format/_$name.$_format';
templates[name] = await loader.loadAsString(uri);
}
return templates;
}
@override
Future<String> loadTemplate(String name) =>
loader.loadAsString('package:dartdoc/templates/$_format/$name.$_format');
}
/// Loads templates from a specified Directory.
class _DirectoryTemplatesLoader extends _TemplatesLoader {
final Folder _directory;
final String _format;
@override
final ResourceLoader loader;
_DirectoryTemplatesLoader(this._directory, this._format, this.loader);
path.Context get pathContext => _directory.provider.pathContext;
@override
Future<Map<String, String>> loadPartials() async {
var partials = <String, String>{};
for (var file in _directory.getChildren().whereType<File>()) {
var basename = pathContext.basename(file.path);
if (basename.startsWith('_') && basename.endsWith('.$_format')) {
var content = file.readAsStringSync();
var partialName = basename.substring(1, basename.lastIndexOf('.'));
partials[partialName] = content;
}
}
return partials;
}
@override
Future<String> loadTemplate(String name) async {
var file = _directory.getChildAssumingFile('$name.$_format');
if (!file.exists) {
throw DartdocFailure('Missing required template file: $name.$_format');
}
return file.readAsStringSync();
}
}
class Templates {
final Template categoryTemplate;
final Template classTemplate;
final Template extensionTemplate;
final Template enumTemplate;
final Template constructorTemplate;
final Template errorTemplate;
final Template functionTemplate;
final Template indexTemplate;
final Template libraryTemplate;
final Template methodTemplate;
final Template mixinTemplate;
final Template propertyTemplate;
final Template sidebarContainerTemplate;
final Template sidebarLibraryTemplate;
final Template topLevelPropertyTemplate;
final Template typeDefTemplate;
static Future<Templates> fromContext(
DartdocGeneratorOptionContext context) async {
var templatesDir = context.templatesDir;
var format = context.format;
var footerTextPaths = context.footerText;
var resourceLoader = ResourceLoader(context.resourceProvider);
if (templatesDir != null) {
return _fromDirectory(
context.resourceProvider.getFolder(templatesDir), format,
loader: resourceLoader,
headerPaths: context.header,
footerPaths: context.footer,
footerTextPaths: footerTextPaths);
} else {
return createDefault(format,
loader: resourceLoader,
headerPaths: context.header,
footerPaths: context.footer,
footerTextPaths: footerTextPaths);
}
}
@visibleForTesting
static Future<Templates> createDefault(String format,
{@required ResourceLoader loader,
List<String> headerPaths = const <String>[],
List<String> footerPaths = const <String>[],
List<String> footerTextPaths = const <String>[]}) async {
return _create(_DefaultTemplatesLoader.create(format, loader),
headerPaths: headerPaths,
footerPaths: footerPaths,
footerTextPaths: footerTextPaths);
}
static Future<Templates> _fromDirectory(Folder dir, String format,
{@required ResourceLoader loader,
@required List<String> headerPaths,
@required List<String> footerPaths,
@required List<String> footerTextPaths}) async {
return _create(_DirectoryTemplatesLoader(dir, format, loader),
headerPaths: headerPaths,
footerPaths: footerPaths,
footerTextPaths: footerTextPaths);
}
static Future<Templates> _create(_TemplatesLoader templatesLoader,
{@required List<String> headerPaths,
@required List<String> footerPaths,
@required List<String> footerTextPaths}) async {
var partials = await _loadPartials(
templatesLoader, headerPaths, footerPaths, footerTextPaths);
Template _partial(String name) {
var partial = partials[name];
if (partial == null || partial.isEmpty) {
throw StateError('Did not find partial "$name"');
}
return Template(partial);
}
Future<Template> _loadTemplate(String templatePath) async {
var templateContents = await templatesLoader.loadTemplate(templatePath);
return Template(templateContents, partialResolver: _partial);
}
var indexTemplate = await _loadTemplate('index');
var libraryTemplate = await _loadTemplate('library');
var sidebarContainerTemplate =
await _loadTemplate('_sidebar_for_container');
var sidebarLibraryTemplate = await _loadTemplate('_sidebar_for_library');
var categoryTemplate = await _loadTemplate('category');
var classTemplate = await _loadTemplate('class');
var extensionTemplate = await _loadTemplate('extension');
var enumTemplate = await _loadTemplate('enum');
var functionTemplate = await _loadTemplate('function');
var methodTemplate = await _loadTemplate('method');
var constructorTemplate = await _loadTemplate('constructor');
var errorTemplate = await _loadTemplate('404error');
var propertyTemplate = await _loadTemplate('property');
var topLevelPropertyTemplate = await _loadTemplate('top_level_property');
var typeDefTemplate = await _loadTemplate('typedef');
var mixinTemplate = await _loadTemplate('mixin');
return Templates._(
indexTemplate,
categoryTemplate,
libraryTemplate,
sidebarContainerTemplate,
sidebarLibraryTemplate,
classTemplate,
extensionTemplate,
enumTemplate,
functionTemplate,
methodTemplate,
constructorTemplate,
errorTemplate,
propertyTemplate,
topLevelPropertyTemplate,
typeDefTemplate,
mixinTemplate);
}
Templates._(
this.indexTemplate,
this.categoryTemplate,
this.libraryTemplate,
this.sidebarContainerTemplate,
this.sidebarLibraryTemplate,
this.classTemplate,
this.extensionTemplate,
this.enumTemplate,
this.functionTemplate,
this.methodTemplate,
this.constructorTemplate,
this.errorTemplate,
this.propertyTemplate,
this.topLevelPropertyTemplate,
this.typeDefTemplate,
this.mixinTemplate);
}