diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart index 66a8a8a8f0..721bcec079 100644 --- a/lib/dartdoc.dart +++ b/lib/dartdoc.dart @@ -44,10 +44,58 @@ class DartdocGeneratorOptionContext extends DartdocOptionContext : super(optionSet, dir); } +class DartdocFileWriter implements FileWriter { + final String outputDir; + final Map _fileElementMap = {}; + @override + final Set writtenFiles = Set(); + + DartdocFileWriter(this.outputDir); + + @override + void write(String filePath, Object content, + {bool allowOverwrite, Warnable element}) { + // Replace '/' separators with proper separators for the platform. + String outFile = path.joinAll(filePath.split('/')); + + allowOverwrite ??= false; + if (!allowOverwrite) { + if (_fileElementMap.containsKey(outFile)) { + assert(element != null, + 'Attempted overwrite of ${outFile} without corresponding element'); + Warnable originalElement = _fileElementMap[outFile]; + Iterable referredFrom = + originalElement != null ? [originalElement] : null; + element?.warn(PackageWarning.duplicateFile, + message: outFile, referredFrom: referredFrom); + } + } + _fileElementMap[outFile] = element; + + var file = File(path.join(outputDir, outFile)); + var parent = file.parent; + if (!parent.existsSync()) { + parent.createSync(recursive: true); + } + + if (content is String) { + file.writeAsStringSync(content); + } else if (content is List) { + file.writeAsBytesSync(content); + } else { + throw ArgumentError.value( + content, 'content', '`content` must be `String` or `List`.'); + } + + writtenFiles.add(outFile); + logProgress(outFile); + } +} + /// Generates Dart documentation for all public Dart libraries in the given /// directory. class Dartdoc extends PackageBuilder { - final List generators; + final Generator generator; final Set writtenFiles = Set(); Directory outputDir; @@ -55,29 +103,20 @@ class Dartdoc extends PackageBuilder { final StreamController _onCheckProgress = StreamController(sync: true); - Dartdoc._(DartdocOptionContext config, this.generators) : super(config) { + Dartdoc._(DartdocOptionContext config, this.generator) : super(config) { outputDir = Directory(config.output)..createSync(recursive: true); - generators.forEach((g) => g.onFileCreated.listen(logProgress)); } /// An asynchronous factory method that builds Dartdoc's file writers /// and returns a Dartdoc object with them. static Future withDefaultGenerators( DartdocGeneratorOptionContext config) async { - List generators = await initHtmlGenerators(config); - return Dartdoc._(config, generators); + return Dartdoc._(config, await initHtmlGenerator(config)); } /// An asynchronous factory method that builds static Future withEmptyGenerator(DartdocOptionContext config) async { - List generators = await initEmptyGenerators(config); - return Dartdoc._(config, generators); - } - - /// Basic synchronous factory that gives a stripped down Dartdoc that won't - /// use generators. Useful for testing. - factory Dartdoc.withoutGenerators(DartdocOptionContext config) { - return Dartdoc._(config, []); + return Dartdoc._(config, await initEmptyGenerator(config)); } Stream get onCheckProgress => _onCheckProgress.stream; @@ -94,19 +133,20 @@ class Dartdoc extends PackageBuilder { double seconds; packageGraph = await buildPackageGraph(); seconds = _stopwatch.elapsedMilliseconds / 1000.0; - logInfo( - "Initialized dartdoc with ${packageGraph.libraries.length} librar${packageGraph.libraries.length == 1 ? 'y' : 'ies'} " + int libs = packageGraph.libraries.length; + logInfo("Initialized dartdoc with ${libs} librar${libs == 1 ? 'y' : 'ies'} " "in ${seconds.toStringAsFixed(1)} seconds"); _stopwatch.reset(); - if (generators.isNotEmpty) { + final generator = this.generator; + if (generator != null) { // Create the out directory. if (!outputDir.existsSync()) outputDir.createSync(recursive: true); - for (var generator in generators) { - await generator.generate(packageGraph, outputDir.path); - writtenFiles.addAll(generator.writtenFiles.keys.map(path.normalize)); - } + DartdocFileWriter writer = DartdocFileWriter(outputDir.path); + await generator.generate(packageGraph, writer); + + writtenFiles.addAll(writer.writtenFiles); if (config.validateLinks && writtenFiles.isNotEmpty) { validateLinks(packageGraph, outputDir.path); } @@ -122,8 +162,8 @@ class Dartdoc extends PackageBuilder { } seconds = _stopwatch.elapsedMilliseconds / 1000.0; - logInfo( - "Documented ${packageGraph.localPublicLibraries.length} public librar${packageGraph.localPublicLibraries.length == 1 ? 'y' : 'ies'} " + libs = packageGraph.localPublicLibraries.length; + logInfo("Documented ${libs} public librar${libs == 1 ? 'y' : 'ies'} " "in ${seconds.toStringAsFixed(1)} seconds"); return DartdocResults(config.topLevelPackageMeta, packageGraph, outputDir); } diff --git a/lib/src/empty_generator.dart b/lib/src/empty_generator.dart index 666df064d7..595a666ba3 100644 --- a/lib/src/empty_generator.dart +++ b/lib/src/empty_generator.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'package:dartdoc/src/dartdoc_options.dart'; import 'package:dartdoc/src/generator.dart'; +import 'package:dartdoc/src/logging.dart'; import 'package:dartdoc/src/model/model.dart'; import 'package:dartdoc/src/model_utils.dart'; import 'package:dartdoc/src/warnings.dart'; @@ -13,34 +14,23 @@ import 'package:dartdoc/src/warnings.dart'; /// it were. class EmptyGenerator extends Generator { @override - Future generate(PackageGraph _packageGraph, String outputDirectoryPath) { - _onFileCreated.add(_packageGraph.defaultPackage.documentationAsHtml); + Future generate(PackageGraph _packageGraph, FileWriter writer) { + logProgress(_packageGraph.defaultPackage.documentationAsHtml); for (var package in Set.from([_packageGraph.defaultPackage]) ..addAll(_packageGraph.localPackages)) { for (var category in filterNonDocumented(package.categories)) { - _onFileCreated.add(category.documentationAsHtml); + logProgress(category.documentationAsHtml); } for (Library lib in filterNonDocumented(package.libraries)) { filterNonDocumented(lib.allModelElements) - .forEach((m) => _onFileCreated.add(m.documentationAsHtml)); + .forEach((m) => logProgress(m.documentationAsHtml)); } } return null; } - - final StreamController _onFileCreated = StreamController(sync: true); - - @override - - /// Implementation fires on each model element processed rather than - /// file creation. - Stream get onFileCreated => _onFileCreated.stream; - - @override - final Map writtenFiles = {}; } -Future> initEmptyGenerators(DartdocOptionContext config) async { - return [EmptyGenerator()]; +Future initEmptyGenerator(DartdocOptionContext config) async { + return EmptyGenerator(); } diff --git a/lib/src/generator.dart b/lib/src/generator.dart index 6ea61e18ab..28a0e8eb35 100644 --- a/lib/src/generator.dart +++ b/lib/src/generator.dart @@ -15,20 +15,23 @@ import 'package:dartdoc/src/package_meta.dart'; import 'package:dartdoc/src/warnings.dart'; import 'package:path/path.dart' as path; +abstract class FileWriter { + /// All filenames written by this generator. + Set get writtenFiles; + + /// Write [content] to a file at [filePath]. + void write(String filePath, Object content, + {bool allowOverwrite, Warnable element}); +} + /// An abstract class that defines a generator that generates documentation for /// a given package. /// /// Generators can generate documentation in different formats: html, json etc. abstract class Generator { - /// Generate the documentation for the given package in the specified - /// directory. Completes the returned future when done. - Future generate(PackageGraph packageGraph, String outputDirectoryPath); - - /// Fires when a file is created. - Stream get onFileCreated; - - /// Fetches all filenames written by this generator. - Map get writtenFiles; + /// Generate the documentation for the given package using the specified + /// writer. Completes the returned future when done. + Future generate(PackageGraph packageGraph, FileWriter writer); } /// Dartdoc options related to generators generally. diff --git a/lib/src/generator_frontend.dart b/lib/src/generator_frontend.dart new file mode 100644 index 0000000000..ff65f1e172 --- /dev/null +++ b/lib/src/generator_frontend.dart @@ -0,0 +1,343 @@ +// 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:async'; +import 'dart:io' show File; + +import 'package:dartdoc/src/generator.dart'; +import 'package:dartdoc/src/logging.dart'; +import 'package:dartdoc/src/model/model.dart'; +import 'package:dartdoc/src/model_utils.dart'; +import 'package:dartdoc/src/warnings.dart'; +import 'package:path/path.dart' as path; + +/// [Generator] that delegates rendering to a [GeneratorBackend] and delegates +/// file creation to a [FileWriter]. +class GeneratorFrontEnd implements Generator { + final GeneratorBackend _generatorBackend; + + GeneratorFrontEnd(this._generatorBackend); + + @override + Future generate(PackageGraph packageGraph, FileWriter writer) async { + List indexElements = []; + _generateDocs(packageGraph, writer, indexElements); + await _generatorBackend.generateAdditionalFiles(writer, packageGraph); + + List categories = indexElements + .whereType() + .where((e) => e.hasCategorization) + .toList(); + _generatorBackend.generateCategoryJson(writer, categories); + _generatorBackend.generateSearchIndex(writer, indexElements); + } + + // Traverses the package graph and collects elements for the search index. + void _generateDocs(PackageGraph packageGraph, FileWriter writer, + List indexAccumulator) { + if (packageGraph == null) return; + + logInfo('documenting ${packageGraph.defaultPackage.name}'); + _generatorBackend.generatePackage( + writer, packageGraph, packageGraph.defaultPackage); + + for (var package in packageGraph.localPackages) { + for (var category in filterNonDocumented(package.categories)) { + logInfo('Generating docs for category ${category.name} from ' + '${category.package.fullyQualifiedName}...'); + indexAccumulator.add(category); + _generatorBackend.generateCategory(writer, packageGraph, category); + } + + for (var lib in filterNonDocumented(package.libraries)) { + logInfo('Generating docs for library ${lib.name} from ' + '${lib.element.source.uri}...'); + if (!lib.isAnonymous && !lib.hasDocumentation) { + packageGraph.warnOnElement(lib, PackageWarning.noLibraryLevelDocs); + } + indexAccumulator.add(lib); + _generatorBackend.generateLibrary(writer, packageGraph, lib); + + for (var clazz in filterNonDocumented(lib.allClasses)) { + indexAccumulator.add(clazz); + _generatorBackend.generateClass(writer, packageGraph, lib, clazz); + + for (var constructor in filterNonDocumented(clazz.constructors)) { + if (!constructor.isCanonical) continue; + + indexAccumulator.add(constructor); + _generatorBackend.generateConstructor( + writer, packageGraph, lib, clazz, constructor); + } + + for (var constant in filterNonDocumented(clazz.constants)) { + if (!constant.isCanonical) continue; + + indexAccumulator.add(constant); + _generatorBackend.generateConstant( + writer, packageGraph, lib, clazz, constant); + } + + for (var property in filterNonDocumented(clazz.staticProperties)) { + if (!property.isCanonical) continue; + + indexAccumulator.add(property); + _generatorBackend.generateProperty( + writer, packageGraph, lib, clazz, property); + } + + for (var property in filterNonDocumented(clazz.allInstanceFields)) { + if (!property.isCanonical) continue; + + indexAccumulator.add(property); + _generatorBackend.generateProperty( + writer, packageGraph, lib, clazz, property); + } + + for (var method in filterNonDocumented(clazz.allInstanceMethods)) { + if (!method.isCanonical) continue; + + indexAccumulator.add(method); + _generatorBackend.generateMethod( + writer, packageGraph, lib, clazz, method); + } + + for (var operator in filterNonDocumented(clazz.allOperators)) { + if (!operator.isCanonical) continue; + + indexAccumulator.add(operator); + _generatorBackend.generateMethod( + writer, packageGraph, lib, clazz, operator); + } + + for (var method in filterNonDocumented(clazz.staticMethods)) { + if (!method.isCanonical) continue; + + indexAccumulator.add(method); + _generatorBackend.generateMethod( + writer, packageGraph, lib, clazz, method); + } + } + + for (var extension in filterNonDocumented(lib.extensions)) { + indexAccumulator.add(extension); + _generatorBackend.generateExtension( + writer, packageGraph, lib, extension); + + for (var constant in filterNonDocumented(extension.constants)) { + indexAccumulator.add(constant); + _generatorBackend.generateConstant( + writer, packageGraph, lib, extension, constant); + } + + for (var property + in filterNonDocumented(extension.staticProperties)) { + indexAccumulator.add(property); + _generatorBackend.generateProperty( + writer, packageGraph, lib, extension, property); + } + + for (var method + in filterNonDocumented(extension.allPublicInstanceMethods)) { + indexAccumulator.add(method); + _generatorBackend.generateMethod( + writer, packageGraph, lib, extension, method); + } + + for (var method in filterNonDocumented(extension.staticMethods)) { + indexAccumulator.add(method); + _generatorBackend.generateMethod( + writer, packageGraph, lib, extension, method); + } + + for (var operator in filterNonDocumented(extension.allOperators)) { + indexAccumulator.add(operator); + _generatorBackend.generateMethod( + writer, packageGraph, lib, extension, operator); + } + + for (var property + in filterNonDocumented(extension.allInstanceFields)) { + indexAccumulator.add(property); + _generatorBackend.generateProperty( + writer, packageGraph, lib, extension, property); + } + } + + for (var mixin in filterNonDocumented(lib.mixins)) { + indexAccumulator.add(mixin); + _generatorBackend.generateMixin(writer, packageGraph, lib, mixin); + + for (var constructor in filterNonDocumented(mixin.constructors)) { + if (!constructor.isCanonical) continue; + + indexAccumulator.add(constructor); + _generatorBackend.generateConstructor( + writer, packageGraph, lib, mixin, constructor); + } + + for (var constant in filterNonDocumented(mixin.constants)) { + if (!constant.isCanonical) continue; + indexAccumulator.add(constant); + _generatorBackend.generateConstant( + writer, packageGraph, lib, mixin, constant); + } + + for (var property in filterNonDocumented(mixin.staticProperties)) { + if (!property.isCanonical) continue; + + indexAccumulator.add(property); + _generatorBackend.generateConstant( + writer, packageGraph, lib, mixin, property); + } + + for (var property in filterNonDocumented(mixin.allInstanceFields)) { + if (!property.isCanonical) continue; + + indexAccumulator.add(property); + _generatorBackend.generateConstant( + writer, packageGraph, lib, mixin, property); + } + + for (var method in filterNonDocumented(mixin.allInstanceMethods)) { + if (!method.isCanonical) continue; + + indexAccumulator.add(method); + _generatorBackend.generateMethod( + writer, packageGraph, lib, mixin, method); + } + + for (var operator in filterNonDocumented(mixin.allOperators)) { + if (!operator.isCanonical) continue; + + indexAccumulator.add(operator); + _generatorBackend.generateMethod( + writer, packageGraph, lib, mixin, operator); + } + + for (var method in filterNonDocumented(mixin.staticMethods)) { + if (!method.isCanonical) continue; + + indexAccumulator.add(method); + _generatorBackend.generateMethod( + writer, packageGraph, lib, mixin, method); + } + } + + for (var eNum in filterNonDocumented(lib.enums)) { + indexAccumulator.add(eNum); + _generatorBackend.generateEnum(writer, packageGraph, lib, eNum); + + for (var property in filterNonDocumented(eNum.allInstanceFields)) { + indexAccumulator.add(property); + _generatorBackend.generateConstant( + writer, packageGraph, lib, eNum, property); + } + for (var operator in filterNonDocumented(eNum.allOperators)) { + indexAccumulator.add(operator); + _generatorBackend.generateMethod( + writer, packageGraph, lib, eNum, operator); + } + for (var method in filterNonDocumented(eNum.allInstanceMethods)) { + indexAccumulator.add(method); + _generatorBackend.generateMethod( + writer, packageGraph, lib, eNum, method); + } + } + + for (var constant in filterNonDocumented(lib.constants)) { + indexAccumulator.add(constant); + _generatorBackend.generateTopLevelConstant( + writer, packageGraph, lib, constant); + } + + for (var property in filterNonDocumented(lib.properties)) { + indexAccumulator.add(property); + _generatorBackend.generateTopLevelProperty( + writer, packageGraph, lib, property); + } + + for (var function in filterNonDocumented(lib.functions)) { + indexAccumulator.add(function); + _generatorBackend.generateFunction( + writer, packageGraph, lib, function); + } + + for (var typeDef in filterNonDocumented(lib.typedefs)) { + indexAccumulator.add(typeDef); + _generatorBackend.generateTypeDef(writer, packageGraph, lib, typeDef); + } + } + } + } +} + +abstract class GeneratorBackend { + /// Emit json describing the [categories] defined by the package. + void generateCategoryJson(FileWriter writer, List categories); + + /// Emit json catalog of [indexedElements] for use with a search index. + void generateSearchIndex(FileWriter writer, List indexedElements); + + /// Emit documentation content for the [package]. + void generatePackage(FileWriter writer, PackageGraph graph, Package package); + + /// Emit documentation content for the [category]. + void generateCategory( + FileWriter writer, PackageGraph graph, Category category); + + /// Emit documentation content for the [library]. + void generateLibrary(FileWriter writer, PackageGraph graph, Library library); + + /// Emit documentation content for the [clazz]. + void generateClass( + FileWriter writer, PackageGraph graph, Library library, Class clazz); + + /// Emit documentation content for the [eNum]. + void generateEnum( + FileWriter writer, PackageGraph graph, Library library, Enum eNum); + + /// Emit documentation content for the [mixin]. + void generateMixin( + FileWriter writer, PackageGraph graph, Library library, Mixin mixin); + + /// Emit documentation content for the [constructor]. + void generateConstructor(FileWriter writer, PackageGraph graph, + Library library, Class clazz, Constructor constructor); + + /// Emit documentation content for the [field]. + void generateConstant(FileWriter writer, PackageGraph graph, Library library, + Container clazz, Field field); + + /// Emit documentation content for the [field]. + void generateProperty(FileWriter writer, PackageGraph graph, Library library, + Container clazz, Field field); + + /// Emit documentation content for the [method]. + void generateMethod(FileWriter writer, PackageGraph graph, Library library, + Container clazz, Method method); + + /// Emit documentation content for the [extension]. + void generateExtension(FileWriter writer, PackageGraph graph, Library library, + Extension extension); + + /// Emit documentation content for the [function]. + void generateFunction(FileWriter writer, PackageGraph graph, Library library, + ModelFunction function); + + /// Emit documentation content for the [constant]. + void generateTopLevelConstant(FileWriter writer, PackageGraph graph, + Library library, TopLevelVariable constant); + + /// Emit documentation content for the [property]. + void generateTopLevelProperty(FileWriter writer, PackageGraph graph, + Library library, TopLevelVariable property); + + /// Emit documentation content for the [typedef]. + void generateTypeDef( + FileWriter writer, PackageGraph graph, Library library, Typedef typedef); + + /// Emit files not specific to a Dart language element. + void generateAdditionalFiles(FileWriter writer, PackageGraph graph); +} diff --git a/lib/src/generator_utils.dart b/lib/src/generator_utils.dart new file mode 100644 index 0000000000..59fa1289ac --- /dev/null +++ b/lib/src/generator_utils.dart @@ -0,0 +1,76 @@ +// 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:convert'; + +import 'package:collection/collection.dart'; +import 'package:dartdoc/src/model/categorization.dart'; +import 'package:dartdoc/src/model/enclosed_element.dart'; +import 'package:dartdoc/src/model/indexable.dart'; + +/// Convenience function to generate category JSON since different generators +/// will likely want the same content for this. +String generateCategoryJson(Iterable categories, bool pretty) { + var encoder = pretty ? JsonEncoder.withIndent(' ') : JsonEncoder(); + final List indexItems = categories.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; + }); + + return encoder.convert(indexItems); +} + +/// Convenience function to generate search index JSON since different +/// generators will likely want the same content for this. +String generateSearchIndexJson( + Iterable indexedElements, bool pretty) { + var encoder = pretty ? JsonEncoder.withIndent(' ') : JsonEncoder(); + final List indexItems = indexedElements.map((Indexable 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; + }); + + return encoder.convert(indexItems); +} diff --git a/lib/src/html/html_generator.dart b/lib/src/html/html_generator.dart index 29be73c776..1224005aae 100644 --- a/lib/src/html/html_generator.dart +++ b/lib/src/html/html_generator.dart @@ -4,173 +4,14 @@ library dartdoc.html_generator; -import 'dart:async' show Future, StreamController, Stream; -import 'dart:io' show Directory, File; -import 'dart:isolate'; +import 'dart:async' show Future; import 'package:dartdoc/dartdoc.dart'; -import 'package:dartdoc/src/empty_generator.dart'; import 'package:dartdoc/src/generator.dart'; -import 'package:dartdoc/src/html/html_generator_instance.dart'; -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/warnings.dart'; -import 'package:path/path.dart' as path; +import 'package:dartdoc/src/generator_frontend.dart'; +import 'package:dartdoc/src/html/html_generator_backend.dart'; -typedef Renderer = String Function(String input); - -// Generation order for libraries: -// constants -// typedefs -// properties -// functions -// enums -// classes -// exceptions -// -// Generation order for classes: -// constants -// static properties -// static methods -// properties -// constructors -// operators -// methods - -class HtmlGenerator extends Generator { - final Templates _templates; - final HtmlGeneratorOptions _options; - HtmlGeneratorInstance _instance; - - final StreamController _onFileCreated = StreamController(sync: true); - - @override - Stream get onFileCreated => _onFileCreated.stream; - - @override - final Map writtenFiles = {}; - - static Future create( - {HtmlGeneratorOptions options, - List headers, - List footers, - List footerTexts}) async { - var templates; - String dirname = options?.templatesDir; - if (dirname != null) { - Directory templateDir = Directory(dirname); - templates = await Templates.fromDirectory(templateDir, - headerPaths: headers, - footerPaths: footers, - footerTextPaths: footerTexts); - } else { - templates = await Templates.createDefault( - headerPaths: headers, - footerPaths: footers, - footerTextPaths: footerTexts); - } - - return HtmlGenerator._(options ?? HtmlGeneratorOptions(), templates); - } - - HtmlGenerator._(this._options, this._templates); - - @override - - /// Actually write out the documentation for [packageGraph]. - /// Stores the HtmlGeneratorInstance so we can access it in [writtenFiles]. - Future generate(PackageGraph packageGraph, String outputDirectoryPath) async { - assert(_instance == null); - - var enabled = true; - void write(String filePath, Object content, - {bool allowOverwrite, Warnable element}) { - allowOverwrite ??= false; - if (!enabled) { - throw StateError('`write` was called after `generate` completed.'); - } - if (!allowOverwrite) { - if (writtenFiles.containsKey(filePath)) { - assert(element != null, - 'Attempted overwrite of ${filePath} without corresponding element'); - Warnable originalElement = writtenFiles[filePath]; - Iterable referredFrom = - originalElement != null ? [originalElement] : null; - element?.warn(PackageWarning.duplicateFile, - message: filePath, referredFrom: referredFrom); - } - } - - var file = File(path.join(outputDirectoryPath, filePath)); - var parent = file.parent; - if (!parent.existsSync()) { - parent.createSync(recursive: true); - } - - if (content is String) { - file.writeAsStringSync(content); - } else if (content is List) { - file.writeAsBytesSync(content); - } else { - throw ArgumentError.value( - content, 'content', '`content` must be `String` or `List`.'); - } - _onFileCreated.add(file); - writtenFiles[filePath] = element; - } - - try { - _instance = - HtmlGeneratorInstance(_options, _templates, packageGraph, write); - await _instance.generate(); - } finally { - enabled = false; - } - } -} - -class HtmlGeneratorOptions implements HtmlOptions { - final String faviconPath; - final bool prettyIndexJson; - final String templatesDir; - - @override - final String relCanonicalPrefix; - - @override - final String toolVersion; - - @override - final bool useBaseHref; - - HtmlGeneratorOptions( - {this.relCanonicalPrefix, - this.faviconPath, - String toolVersion, - this.prettyIndexJson = false, - this.templatesDir, - this.useBaseHref = false}) - : this.toolVersion = toolVersion ?? 'unknown'; -} - -/// Initialize and setup the generators. -Future> initHtmlGenerators(GeneratorContext context) async { - // TODO(jcollins-g): Rationalize based on GeneratorContext all the way down - // through the generators. - HtmlGeneratorOptions options = HtmlGeneratorOptions( - relCanonicalPrefix: context.relCanonicalPrefix, - toolVersion: dartdocVersion, - faviconPath: context.favicon, - prettyIndexJson: context.prettyIndexJson, - templatesDir: context.templatesDir, - useBaseHref: context.useBaseHref); - return [ - await HtmlGenerator.create( - options: options, - headers: context.header, - footers: context.footer, - footerTexts: context.footerTextPaths, - ) - ]; +Future initHtmlGenerator(GeneratorContext context) async { + var backend = await HtmlGeneratorBackend.fromContext(context); + return GeneratorFrontEnd(backend); } diff --git a/lib/src/html/html_generator_backend.dart b/lib/src/html/html_generator_backend.dart new file mode 100644 index 0000000000..da0dfb1fca --- /dev/null +++ b/lib/src/html/html_generator_backend.dart @@ -0,0 +1,237 @@ +// 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 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 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 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)); + } + } +} diff --git a/lib/src/html/html_generator_instance.dart b/lib/src/html/html_generator_instance.dart deleted file mode 100644 index e82fe1bc42..0000000000 --- a/lib/src/html/html_generator_instance.dart +++ /dev/null @@ -1,413 +0,0 @@ -// 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/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 path; - -typedef FileWriter = void Function(String path, Object content, - {bool allowOverwrite, Warnable element}); - -class HtmlGeneratorInstance { - final HtmlGeneratorOptions _options; - final Templates _templates; - final PackageGraph _packageGraph; - final List _indexedElements = []; - 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 = File(_options.faviconPath).readAsBytesSync(); - // Allow overwrite of favicon. - _writer(path.join('static-assets', 'favicon.png'), bytes, - allowOverwrite: true); - } - } - - void _generateCategoryJson() { - var encoder = JsonEncoder.withIndent(' '); - final List 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); - if (!_options.useBaseHref) { - json = json.replaceAll(HTMLBASE_PLACEHOLDER, ''); - } - _writer(path.join('categories.json'), '${json}\n'); - } - - List _categorizationItems; - - void _generateSearchIndex() { - var encoder = - _options.prettyIndexJson ? JsonEncoder.withIndent(' ') : JsonEncoder(); - _categorizationItems = []; - - final List 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); - if (!_options.useBaseHref) { - json = json.replaceAll(HTMLBASE_PLACEHOLDER, ''); - } - _writer(path.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 extension in filterNonDocumented(lib.extensions)) { - generateExtension(_packageGraph, lib, extension); - - for (var constant in filterNonDocumented(extension.constants)) { - generateConstant(_packageGraph, lib, extension, constant); - } - - for (var property - in filterNonDocumented(extension.staticProperties)) { - generateProperty(_packageGraph, lib, extension, property); - } - - for (var method - in filterNonDocumented(extension.allPublicInstanceMethods)) { - generateMethod(_packageGraph, lib, extension, method); - } - - for (var method in filterNonDocumented(extension.staticMethods)) { - generateMethod(_packageGraph, lib, extension, method); - } - - for (var operator in filterNonDocumented(extension.allOperators)) { - generateMethod(_packageGraph, lib, extension, operator); - } - - for (var property - in filterNonDocumented(extension.allInstanceFields)) { - generateProperty(_packageGraph, lib, extension, property); - } - } - - 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 = PackageTemplateData(_options, packageGraph, package); - logInfo('documenting ${package.name}'); - - _build(package.filePath, _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 = CategoryTemplateData(_options, packageGraph, category); - - _build(category.filePath, _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 = LibraryTemplateData(_options, packageGraph, lib); - - _build(lib.filePath, _templates.libraryTemplate, data); - } - - void generateClass(PackageGraph packageGraph, Library lib, Class clazz) { - TemplateData data = ClassTemplateData(_options, packageGraph, lib, clazz); - _build(clazz.filePath, _templates.classTemplate, data); - } - - void generateExtension( - PackageGraph packageGraph, Library lib, Extension extension) { - TemplateData data = - ExtensionTemplateData(_options, packageGraph, lib, extension); - _build(extension.filePath, _templates.extensionTemplate, data); - } - - void generateMixins(PackageGraph packageGraph, Library lib, Mixin mixin) { - TemplateData data = MixinTemplateData(_options, packageGraph, lib, mixin); - _build(mixin.filePath, _templates.mixinTemplate, data); - } - - void generateConstructor(PackageGraph packageGraph, Library lib, Class clazz, - Constructor constructor) { - TemplateData data = ConstructorTemplateData( - _options, packageGraph, lib, clazz, constructor); - - _build(constructor.filePath, _templates.constructorTemplate, data); - } - - void generateEnum(PackageGraph packageGraph, Library lib, Enum eNum) { - TemplateData data = EnumTemplateData(_options, packageGraph, lib, eNum); - - _build(eNum.filePath, _templates.enumTemplate, data); - } - - void generateFunction( - PackageGraph packageGraph, Library lib, ModelFunction function) { - TemplateData data = - FunctionTemplateData(_options, packageGraph, lib, function); - - _build(function.filePath, _templates.functionTemplate, data); - } - - void generateMethod( - PackageGraph packageGraph, Library lib, Container clazz, Method method) { - TemplateData data = - MethodTemplateData(_options, packageGraph, lib, clazz, method); - - _build(method.filePath, _templates.methodTemplate, data); - } - - void generateConstant(PackageGraph packageGraph, Library lib, Container clazz, - Field property) => - generateProperty(packageGraph, lib, clazz, property); - - void generateProperty( - PackageGraph packageGraph, Library lib, Container clazz, Field property) { - TemplateData data = - PropertyTemplateData(_options, packageGraph, lib, clazz, property); - - _build(property.filePath, _templates.propertyTemplate, data); - } - - void generateTopLevelProperty( - PackageGraph packageGraph, Library lib, TopLevelVariable property) { - TemplateData data = - TopLevelPropertyTemplateData(_options, packageGraph, lib, property); - - _build(property.filePath, _templates.topLevelPropertyTemplate, data); - } - - void generateTopLevelConstant( - PackageGraph packageGraph, Library lib, TopLevelVariable property) => - generateTopLevelProperty(packageGraph, lib, property); - - void generateTypeDef( - PackageGraph packageGraph, Library lib, Typedef typeDef) { - TemplateData data = - TypedefTemplateData(_options, packageGraph, lib, typeDef); - - _build(typeDef.filePath, _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 StateError('Resource paths must start with $prefix, ' - 'encountered $resourcePath'); - } - String destFileName = resourcePath.substring(prefix.length); - _writer(path.join('static-assets', destFileName), - await loader.loadAsBytes(resourcePath)); - } - } - - void _build(String filename, Template template, TemplateData data) { - // Replaces '/' separators with proper separators for the platform. - String outFile = path.joinAll(filename.split('/')); - String content = template.renderString(data); - - if (!_options.useBaseHref) { - content = content.replaceAll(HTMLBASE_PLACEHOLDER, data.htmlBase); - } - _writer(outFile, content, - element: data.self is Warnable ? data.self : null); - if (data.self is Indexable) _indexedElements.add(data.self as Indexable); - } -} diff --git a/lib/src/html/templates.dart b/lib/src/html/templates.dart index a47efdebc0..2b929c3f6d 100644 --- a/lib/src/html/templates.dart +++ b/lib/src/html/templates.dart @@ -158,6 +158,21 @@ class Templates { final Template topLevelPropertyTemplate; final Template typeDefTemplate; + static Future fromContext(GeneratorContext context) { + String templatesDir = context.templatesDir; + if (templatesDir != null) { + return fromDirectory(Directory(templatesDir), + headerPaths: context.header, + footerPaths: context.footer, + footerTextPaths: context.footerTextPaths); + } else { + return createDefault( + headerPaths: context.header, + footerPaths: context.footer, + footerTextPaths: context.footerTextPaths); + } + } + static Future createDefault( {List headerPaths, List footerPaths, diff --git a/test/html_generator_test.dart b/test/html_generator_test.dart index 460c5dce19..db62526b3a 100644 --- a/test/html_generator_test.dart +++ b/test/html_generator_test.dart @@ -6,7 +6,9 @@ library dartdoc.html_generator_test; import 'dart:io' show File, Directory; -import 'package:dartdoc/src/html/html_generator.dart'; +import 'package:dartdoc/dartdoc.dart'; +import 'package:dartdoc/src/generator_frontend.dart'; +import 'package:dartdoc/src/html/html_generator_backend.dart'; import 'package:dartdoc/src/html/templates.dart'; import 'package:dartdoc/src/html/resources.g.dart'; import 'package:dartdoc/src/model/package_graph.dart'; @@ -16,6 +18,12 @@ import 'package:test/test.dart'; import 'src/utils.dart' as utils; +// Init a generator without a GeneratorContext and with the default file writer. +Future _initGeneratorForTest() async { + var backend = HtmlGeneratorBackend(null, await Templates.createDefault()); + return GeneratorFrontEnd(backend); +} + void main() { group('Templates', () { Templates templates; @@ -68,13 +76,15 @@ void main() { group('HtmlGenerator', () { // TODO: Run the HtmlGenerator and validate important constraints. group('for a null package', () { - HtmlGenerator generator; + Generator generator; Directory tempOutput; + FileWriter writer; setUp(() async { - generator = await HtmlGenerator.create(); + generator = await _initGeneratorForTest(); tempOutput = Directory.systemTemp.createTempSync('doc_test_temp'); - return generator.generate(null, tempOutput.path); + writer = DartdocFileWriter(tempOutput.path); + return generator.generate(null, writer); }); tearDown(() { @@ -96,15 +106,17 @@ void main() { }); group('for a package that causes duplicate files', () { - HtmlGenerator generator; + Generator generator; PackageGraph packageGraph; Directory tempOutput; + FileWriter writer; setUp(() async { - generator = await HtmlGenerator.create(); + generator = await _initGeneratorForTest(); packageGraph = await utils .bootBasicPackage(utils.testPackageDuplicateDir.path, []); tempOutput = await Directory.systemTemp.createTemp('doc_test_temp'); + writer = DartdocFileWriter(tempOutput.path); }); tearDown(() { @@ -114,7 +126,7 @@ void main() { }); test('run generator and verify duplicate file error', () async { - await generator.generate(packageGraph, tempOutput.path); + await generator.generate(packageGraph, writer); expect(generator, isNotNull); expect(tempOutput, isNotNull); String expectedPath =