blob: da0dfb1fca0bb3026a47715b2f3096010ea50e31 [file] [log] [blame]
// Copyright (c) 2019, 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.
import 'dart:io';
import 'package:dartdoc/dartdoc.dart';
import 'package:dartdoc/src/generator_frontend.dart';
import 'package:dartdoc/src/generator_utils.dart' as generator_util;
import 'package:dartdoc/src/html/resource_loader.dart' as loader;
import 'package:dartdoc/src/html/resources.g.dart' as resources;
import 'package:dartdoc/src/html/template_data.dart';
import 'package:dartdoc/src/html/templates.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/model/package.dart';
import 'package:dartdoc/src/model/package_graph.dart';
import 'package:dartdoc/src/warnings.dart';
import 'package:mustache/mustache.dart';
import 'package:path/path.dart' as path;
/// Configuration options for the html backend.
class HtmlBackendOptions implements HtmlOptions {
@override
final String relCanonicalPrefix;
@override
final String toolVersion;
final String favicon;
final bool prettyIndexJson;
@override
final bool useBaseHref;
HtmlBackendOptions(
{this.relCanonicalPrefix,
this.toolVersion,
this.favicon,
this.prettyIndexJson = false,
this.useBaseHref = false});
}
/// GeneratorBackend for html output.
class HtmlGeneratorBackend implements GeneratorBackend {
final HtmlBackendOptions _options;
final Templates _templates;
static Future<HtmlGeneratorBackend> fromContext(
GeneratorContext context) async {
Templates templates = await Templates.fromContext(context);
// TODO(jcollins-g): Rationalize based on GeneratorContext all the way down
// through the generators.
HtmlOptions options = HtmlBackendOptions(
relCanonicalPrefix: context.relCanonicalPrefix,
toolVersion: dartdocVersion,
favicon: context.favicon,
prettyIndexJson: context.prettyIndexJson,
useBaseHref: context.useBaseHref,
);
return HtmlGeneratorBackend(options, templates);
}
HtmlGeneratorBackend(HtmlBackendOptions options, this._templates)
: this._options = (options ?? HtmlBackendOptions());
/// Helper method to bind template data and emit the content to the writer.
void _render(FileWriter writer, String filename, Template template,
TemplateData data) {
String content = template.renderString(data);
if (!_options.useBaseHref) {
content = content.replaceAll(HTMLBASE_PLACEHOLDER, data.htmlBase);
}
writer.write(filename, content,
element: data.self is Warnable ? data.self : null);
}
@override
void generateCategoryJson(
FileWriter writer, List<Categorization> categories) {
String json = generator_util.generateCategoryJson(
categories, _options.prettyIndexJson);
if (!_options.useBaseHref) {
json = json.replaceAll(HTMLBASE_PLACEHOLDER, '');
}
writer.write(path.join('categories.json'), '${json}\n');
}
@override
void generateSearchIndex(FileWriter writer, List<Indexable> indexedElements) {
String json = generator_util.generateSearchIndexJson(
indexedElements, _options.prettyIndexJson);
if (!_options.useBaseHref) {
json = json.replaceAll(HTMLBASE_PLACEHOLDER, '');
}
writer.write(path.join('index.json'), '${json}\n');
}
@override
void generatePackage(FileWriter writer, PackageGraph graph, Package package) {
TemplateData data = PackageTemplateData(_options, graph, package);
_render(writer, package.filePath, _templates.indexTemplate, data);
_render(writer, '__404error.html', _templates.errorTemplate, data);
}
@override
void generateCategory(
FileWriter writer, PackageGraph packageGraph, Category category) {
TemplateData data = CategoryTemplateData(_options, packageGraph, category);
_render(writer, category.filePath, _templates.categoryTemplate, data);
}
@override
void generateLibrary(
FileWriter writer, PackageGraph packageGraph, Library lib) {
TemplateData data = LibraryTemplateData(_options, packageGraph, lib);
_render(writer, lib.filePath, _templates.libraryTemplate, data);
}
@override
void generateClass(
FileWriter writer, PackageGraph packageGraph, Library lib, Class clazz) {
TemplateData data = ClassTemplateData(_options, packageGraph, lib, clazz);
_render(writer, clazz.filePath, _templates.classTemplate, data);
}
@override
void generateExtension(FileWriter writer, PackageGraph packageGraph,
Library lib, Extension extension) {
TemplateData data =
ExtensionTemplateData(_options, packageGraph, lib, extension);
_render(writer, extension.filePath, _templates.extensionTemplate, data);
}
@override
void generateMixin(
FileWriter writer, PackageGraph packageGraph, Library lib, Mixin mixin) {
TemplateData data = MixinTemplateData(_options, packageGraph, lib, mixin);
_render(writer, mixin.filePath, _templates.mixinTemplate, data);
}
@override
void generateConstructor(FileWriter writer, PackageGraph packageGraph,
Library lib, Class clazz, Constructor constructor) {
TemplateData data = ConstructorTemplateData(
_options, packageGraph, lib, clazz, constructor);
_render(writer, constructor.filePath, _templates.constructorTemplate, data);
}
@override
void generateEnum(
FileWriter writer, PackageGraph packageGraph, Library lib, Enum eNum) {
TemplateData data = EnumTemplateData(_options, packageGraph, lib, eNum);
_render(writer, eNum.filePath, _templates.enumTemplate, data);
}
@override
void generateFunction(FileWriter writer, PackageGraph packageGraph,
Library lib, ModelFunction function) {
TemplateData data =
FunctionTemplateData(_options, packageGraph, lib, function);
_render(writer, function.filePath, _templates.functionTemplate, data);
}
@override
void generateMethod(FileWriter writer, PackageGraph packageGraph, Library lib,
Container clazz, Method method) {
TemplateData data =
MethodTemplateData(_options, packageGraph, lib, clazz, method);
_render(writer, method.filePath, _templates.methodTemplate, data);
}
@override
void generateConstant(FileWriter writer, PackageGraph packageGraph,
Library lib, Container clazz, Field property) =>
generateProperty(writer, packageGraph, lib, clazz, property);
@override
void generateProperty(FileWriter writer, PackageGraph packageGraph,
Library lib, Container clazz, Field property) {
TemplateData data =
PropertyTemplateData(_options, packageGraph, lib, clazz, property);
_render(writer, property.filePath, _templates.propertyTemplate, data);
}
@override
void generateTopLevelProperty(FileWriter writer, PackageGraph packageGraph,
Library lib, TopLevelVariable property) {
TemplateData data =
TopLevelPropertyTemplateData(_options, packageGraph, lib, property);
_render(
writer, property.filePath, _templates.topLevelPropertyTemplate, data);
}
@override
void generateTopLevelConstant(FileWriter writer, PackageGraph packageGraph,
Library lib, TopLevelVariable property) =>
generateTopLevelProperty(writer, packageGraph, lib, property);
@override
void generateTypeDef(FileWriter writer, PackageGraph packageGraph,
Library lib, Typedef typeDef) {
TemplateData data =
TypedefTemplateData(_options, packageGraph, lib, typeDef);
_render(writer, typeDef.filePath, _templates.typeDefTemplate, data);
}
@override
void generateAdditionalFiles(FileWriter writer, PackageGraph graph) async {
await _copyResources(writer);
if (_options.favicon != null) {
// Allow overwrite of favicon.
var bytes = File(_options.favicon).readAsBytesSync();
writer.write(path.join('static-assets', 'favicon.png'), bytes,
allowOverwrite: true);
}
}
Future _copyResources(FileWriter writer) async {
final prefix = 'package:dartdoc/resources/';
for (String resourcePath in resources.resource_names) {
if (!resourcePath.startsWith(prefix)) {
throw StateError('Resource paths must start with $prefix, '
'encountered $resourcePath');
}
String destFileName = resourcePath.substring(prefix.length);
writer.write(path.join('static-assets', destFileName),
await loader.loadAsBytes(resourcePath));
}
}
}