blob: 79052eccbc05c20b28727eee56cc0d062fa2ec55 [file] [log] [blame]
// Copyright (c) 2014, 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:async' show Future;
import 'dart:convert' show JsonEncoder;
import 'dart:io' show File;
import 'package:collection/collection.dart' show compareNatural;
import 'package:dartdoc/src/html/html_generator.dart' show HtmlGeneratorOptions;
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/logging.dart';
import 'package:dartdoc/src/model.dart';
import 'package:dartdoc/src/model_utils.dart';
import 'package:dartdoc/src/warnings.dart';
import 'package:mustache/mustache.dart';
import 'package:path/path.dart' as pathLib;
typedef void FileWriter(String path, Object content, {bool allowOverwrite});
class HtmlGeneratorInstance {
final HtmlGeneratorOptions _options;
final Templates _templates;
final PackageGraph _packageGraph;
final List<Indexable> _indexedElements = <Indexable>[];
final FileWriter _writer;
HtmlGeneratorInstance(
this._options, this._templates, this._packageGraph, this._writer);
Future generate() async {
if (_packageGraph != null) {
_generateDocs();
_generateSearchIndex();
_generateCategoryJson();
}
await _copyResources();
if (_options.faviconPath != null) {
var bytes = new File(_options.faviconPath).readAsBytesSync();
// Allow overwrite of favicon.
_writer(pathLib.join('static-assets', 'favicon.png'), bytes,
allowOverwrite: true);
}
}
void _generateCategoryJson() {
var encoder = new JsonEncoder.withIndent(' ');
final List<Map> indexItems = _categorizationItems.map((Categorization e) {
Map data = {
'name': e.name,
'qualifiedName': e.fullyQualifiedName,
'href': e.href,
'type': e.kind,
};
if (e.hasCategoryNames) data['categories'] = e.categoryNames;
if (e.hasSubCategoryNames) data['subcategories'] = e.subCategoryNames;
if (e.hasImage) data['image'] = e.image;
if (e.hasSamples) data['samples'] = e.samples;
return data;
}).toList();
indexItems.sort((a, b) {
var value = compareNatural(a['qualifiedName'], b['qualifiedName']);
if (value == 0) {
value = compareNatural(a['type'], b['type']);
}
return value;
});
String json = encoder.convert(indexItems);
_writer(pathLib.join('categories.json'), '${json}\n');
}
List<Categorization> _categorizationItems;
void _generateSearchIndex() {
var encoder = _options.prettyIndexJson
? new JsonEncoder.withIndent(' ')
: new JsonEncoder();
_categorizationItems = [];
final List<Map> indexItems = _indexedElements.map((Indexable e) {
if (e is Categorization && e.hasCategorization)
_categorizationItems.add(e);
Map data = {
'name': e.name,
'qualifiedName': e.fullyQualifiedName,
'href': e.href,
'type': e.kind,
'overriddenDepth': e.overriddenDepth,
};
if (e is EnclosedElement) {
EnclosedElement ee = e as EnclosedElement;
data['enclosedBy'] = {
'name': ee.enclosingElement.name,
'type': ee.enclosingElement.kind
};
data['qualifiedName'] = e.fullyQualifiedName;
}
return data;
}).toList();
indexItems.sort((a, b) {
var value = compareNatural(a['qualifiedName'], b['qualifiedName']);
if (value == 0) {
value = compareNatural(a['type'], b['type']);
}
return value;
});
String json = encoder.convert(indexItems);
_writer(pathLib.join('index.json'), '${json}\n');
}
void _generateDocs() {
if (_packageGraph == null) return;
generatePackage(_packageGraph, _packageGraph.defaultPackage);
for (var package in _packageGraph.localPackages) {
for (var category in filterNonDocumented(package.categories)) {
generateCategory(_packageGraph, category);
}
for (var lib in filterNonDocumented(package.libraries)) {
generateLibrary(_packageGraph, lib);
for (var clazz in filterNonDocumented(lib.allClasses)) {
generateClass(_packageGraph, lib, clazz);
for (var constructor in filterNonDocumented(clazz.constructors)) {
if (!constructor.isCanonical) continue;
generateConstructor(_packageGraph, lib, clazz, constructor);
}
for (var constant in filterNonDocumented(clazz.constants)) {
if (!constant.isCanonical) continue;
generateConstant(_packageGraph, lib, clazz, constant);
}
for (var property in filterNonDocumented(clazz.staticProperties)) {
if (!property.isCanonical) continue;
generateProperty(_packageGraph, lib, clazz, property);
}
for (var property in filterNonDocumented(clazz.allInstanceFields)) {
if (!property.isCanonical) continue;
generateProperty(_packageGraph, lib, clazz, property);
}
for (var method in filterNonDocumented(clazz.allInstanceMethods)) {
if (!method.isCanonical) continue;
generateMethod(_packageGraph, lib, clazz, method);
}
for (var operator in filterNonDocumented(clazz.allOperators)) {
if (!operator.isCanonical) continue;
generateMethod(_packageGraph, lib, clazz, operator);
}
for (var method in filterNonDocumented(clazz.staticMethods)) {
if (!method.isCanonical) continue;
generateMethod(_packageGraph, lib, clazz, method);
}
}
for (var mixin in filterNonDocumented(lib.mixins)) {
generateMixins(_packageGraph, lib, mixin);
for (var constructor in filterNonDocumented(mixin.constructors)) {
if (!constructor.isCanonical) continue;
generateConstructor(_packageGraph, lib, mixin, constructor);
}
for (var constant in filterNonDocumented(mixin.constants)) {
if (!constant.isCanonical) continue;
generateConstant(_packageGraph, lib, mixin, constant);
}
for (var property in filterNonDocumented(mixin.staticProperties)) {
if (!property.isCanonical) continue;
generateProperty(_packageGraph, lib, mixin, property);
}
for (var property in filterNonDocumented(mixin.allInstanceFields)) {
if (!property.isCanonical) continue;
generateProperty(_packageGraph, lib, mixin, property);
}
for (var method in filterNonDocumented(mixin.allInstanceMethods)) {
if (!method.isCanonical) continue;
generateMethod(_packageGraph, lib, mixin, method);
}
for (var operator in filterNonDocumented(mixin.allOperators)) {
if (!operator.isCanonical) continue;
generateMethod(_packageGraph, lib, mixin, operator);
}
for (var method in filterNonDocumented(mixin.staticMethods)) {
if (!method.isCanonical) continue;
generateMethod(_packageGraph, lib, mixin, method);
}
}
for (var eNum in filterNonDocumented(lib.enums)) {
generateEnum(_packageGraph, lib, eNum);
for (var property in filterNonDocumented(eNum.allInstanceFields)) {
generateProperty(_packageGraph, lib, eNum, property);
}
for (var operator in filterNonDocumented(eNum.allOperators)) {
generateMethod(_packageGraph, lib, eNum, operator);
}
for (var method in filterNonDocumented(eNum.allInstanceMethods)) {
generateMethod(_packageGraph, lib, eNum, method);
}
}
for (var constant in filterNonDocumented(lib.constants)) {
generateTopLevelConstant(_packageGraph, lib, constant);
}
for (var property in filterNonDocumented(lib.properties)) {
generateTopLevelProperty(_packageGraph, lib, property);
}
for (var function in filterNonDocumented(lib.functions)) {
generateFunction(_packageGraph, lib, function);
}
for (var typeDef in filterNonDocumented(lib.typedefs)) {
generateTypeDef(_packageGraph, lib, typeDef);
}
}
}
}
void generatePackage(PackageGraph packageGraph, Package package) {
TemplateData data =
new PackageTemplateData(_options, packageGraph, package);
logInfo('documenting ${package.name}');
_build('index.html', _templates.indexTemplate, data);
_build('__404error.html', _templates.errorTemplate, data);
}
void generateCategory(PackageGraph packageGraph, Category category) {
logInfo(
'Generating docs for category ${category.name} from ${category.package.fullyQualifiedName}...');
TemplateData data =
new CategoryTemplateData(_options, packageGraph, category);
_build(pathLib.joinAll(category.href.split('/')),
_templates.categoryTemplate, data);
}
void generateLibrary(PackageGraph packageGraph, Library lib) {
logInfo(
'Generating docs for library ${lib.name} from ${lib.element.source.uri}...');
if (!lib.isAnonymous && !lib.hasDocumentation) {
packageGraph.warnOnElement(lib, PackageWarning.noLibraryLevelDocs);
}
TemplateData data = new LibraryTemplateData(_options, packageGraph, lib);
_build(pathLib.join(lib.dirName, '${lib.fileName}'),
_templates.libraryTemplate, data);
}
void generateClass(PackageGraph packageGraph, Library lib, Class clazz) {
TemplateData data =
new ClassTemplateData(_options, packageGraph, lib, clazz);
_build(
pathLib.joinAll(clazz.href.split('/')), _templates.classTemplate, data);
}
void generateMixins(PackageGraph packageGraph, Library lib, Mixin mixin) {
TemplateData data =
new MixinTemplateData(_options, packageGraph, lib, mixin);
_build(
pathLib.joinAll(mixin.href.split('/')), _templates.mixinTemplate, data);
}
void generateConstructor(PackageGraph packageGraph, Library lib, Class clazz,
Constructor constructor) {
TemplateData data = new ConstructorTemplateData(
_options, packageGraph, lib, clazz, constructor);
_build(pathLib.joinAll(constructor.href.split('/')),
_templates.constructorTemplate, data);
}
void generateEnum(PackageGraph packageGraph, Library lib, Enum eNum) {
TemplateData data = new EnumTemplateData(_options, packageGraph, lib, eNum);
_build(
pathLib.joinAll(eNum.href.split('/')), _templates.enumTemplate, data);
}
void generateFunction(
PackageGraph packageGraph, Library lib, ModelFunction function) {
TemplateData data =
new FunctionTemplateData(_options, packageGraph, lib, function);
_build(pathLib.joinAll(function.href.split('/')),
_templates.functionTemplate, data);
}
void generateMethod(
PackageGraph packageGraph, Library lib, Class clazz, Method method) {
TemplateData data =
new MethodTemplateData(_options, packageGraph, lib, clazz, method);
_build(pathLib.joinAll(method.href.split('/')), _templates.methodTemplate,
data);
}
void generateConstant(
PackageGraph packageGraph, Library lib, Class clazz, Field property) {
TemplateData data =
new ConstantTemplateData(_options, packageGraph, lib, clazz, property);
_build(pathLib.joinAll(property.href.split('/')),
_templates.constantTemplate, data);
}
void generateProperty(
PackageGraph packageGraph, Library lib, Class clazz, Field property) {
TemplateData data =
new PropertyTemplateData(_options, packageGraph, lib, clazz, property);
_build(pathLib.joinAll(property.href.split('/')),
_templates.propertyTemplate, data);
}
void generateTopLevelProperty(
PackageGraph packageGraph, Library lib, TopLevelVariable property) {
TemplateData data =
new TopLevelPropertyTemplateData(_options, packageGraph, lib, property);
_build(pathLib.joinAll(property.href.split('/')),
_templates.topLevelPropertyTemplate, data);
}
void generateTopLevelConstant(
PackageGraph packageGraph, Library lib, TopLevelVariable property) {
TemplateData data =
new TopLevelConstTemplateData(_options, packageGraph, lib, property);
_build(pathLib.joinAll(property.href.split('/')),
_templates.topLevelConstantTemplate, data);
}
void generateTypeDef(
PackageGraph packageGraph, Library lib, Typedef typeDef) {
TemplateData data =
new TypedefTemplateData(_options, packageGraph, lib, typeDef);
_build(pathLib.joinAll(typeDef.href.split('/')), _templates.typeDefTemplate,
data);
}
// TODO: change this to use resource_loader
Future _copyResources() async {
final prefix = 'package:dartdoc/resources/';
for (String resourcePath in resources.resource_names) {
if (!resourcePath.startsWith(prefix)) {
throw new StateError('Resource paths must start with $prefix, '
'encountered $resourcePath');
}
String destFileName = resourcePath.substring(prefix.length);
_writer(pathLib.join('static-assets', destFileName),
await loader.loadAsBytes(resourcePath));
}
}
void _build(String filename, Template template, TemplateData data) {
String content = template.renderString(data);
_writer(filename, content);
if (data.self is Indexable) _indexedElements.add(data.self as Indexable);
}
}