| // 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/third_party/pkg/mustache4dart/lib/mustache4dart.dart'; |
| import 'package:dartdoc/src/warnings.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, TemplateRenderer template, TemplateData data) { |
| String content = template(data, |
| assumeNullNonExistingProperty: false, errorOnMissingProperty: true); |
| |
| _writer(filename, content); |
| if (data.self is Indexable) _indexedElements.add(data.self as Indexable); |
| } |
| } |