Refactor html generator into frontend and backend (#2115)
* Create generator frontend
* Start html generator backend
* rename file
* Init single generator instead of list
* Wire up html backend
* Generator frontend/backend fixes
* Change FileWriter back to typedef
* dartfmt and misc fixes
* Fix typo in parameter name
* Use whereType
* Restructure FileWriter usage
Make FileWriter an abstract class and give it the responsibility for
recording written filenames. This removes the FileWriter wrapping in
GeneratorFrontEnd.
* Fix duplicate file test
diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart
index 66a8a8a..721bcec 100644
--- a/lib/dartdoc.dart
+++ b/lib/dartdoc.dart
@@ -44,10 +44,58 @@
: super(optionSet, dir);
}
+class DartdocFileWriter implements FileWriter {
+ final String outputDir;
+ final Map<String, Warnable> _fileElementMap = {};
+ @override
+ final Set<String> 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<Warnable> 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<int>) {
+ file.writeAsBytesSync(content);
+ } else {
+ throw ArgumentError.value(
+ content, 'content', '`content` must be `String` or `List<int>`.');
+ }
+
+ writtenFiles.add(outFile);
+ logProgress(outFile);
+ }
+}
+
/// Generates Dart documentation for all public Dart libraries in the given
/// directory.
class Dartdoc extends PackageBuilder {
- final List<Generator> generators;
+ final Generator generator;
final Set<String> writtenFiles = Set();
Directory outputDir;
@@ -55,29 +103,20 @@
final StreamController<String> _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<Dartdoc> withDefaultGenerators(
DartdocGeneratorOptionContext config) async {
- List<Generator> generators = await initHtmlGenerators(config);
- return Dartdoc._(config, generators);
+ return Dartdoc._(config, await initHtmlGenerator(config));
}
/// An asynchronous factory method that builds
static Future<Dartdoc> withEmptyGenerator(DartdocOptionContext config) async {
- List<Generator> 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<String> get onCheckProgress => _onCheckProgress.stream;
@@ -94,19 +133,20 @@
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 @@
}
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 666df06..595a666 100644
--- a/lib/src/empty_generator.dart
+++ b/lib/src/empty_generator.dart
@@ -4,6 +4,7 @@
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 @@
/// 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<void> _onFileCreated = StreamController(sync: true);
-
- @override
-
- /// Implementation fires on each model element processed rather than
- /// file creation.
- Stream<void> get onFileCreated => _onFileCreated.stream;
-
- @override
- final Map<String, Warnable> writtenFiles = {};
}
-Future<List<Generator>> initEmptyGenerators(DartdocOptionContext config) async {
- return [EmptyGenerator()];
+Future<Generator> initEmptyGenerator(DartdocOptionContext config) async {
+ return EmptyGenerator();
}
diff --git a/lib/src/generator.dart b/lib/src/generator.dart
index 6ea61e1..28a0e8e 100644
--- a/lib/src/generator.dart
+++ b/lib/src/generator.dart
@@ -15,20 +15,23 @@
import 'package:dartdoc/src/warnings.dart';
import 'package:path/path.dart' as path;
+abstract class FileWriter {
+ /// All filenames written by this generator.
+ Set<String> 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<void> get onFileCreated;
-
- /// Fetches all filenames written by this generator.
- Map<String, Warnable> 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 0000000..ff65f1e
--- /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<Indexable> indexElements = <Indexable>[];
+ _generateDocs(packageGraph, writer, indexElements);
+ await _generatorBackend.generateAdditionalFiles(writer, packageGraph);
+
+ List<Categorization> categories = indexElements
+ .whereType<Categorization>()
+ .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<Indexable> 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<Categorization> categories);
+
+ /// Emit json catalog of [indexedElements] for use with a search index.
+ void generateSearchIndex(FileWriter writer, List<Indexable> 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 0000000..59fa128
--- /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<Categorization> categories, bool pretty) {
+ var encoder = pretty ? JsonEncoder.withIndent(' ') : JsonEncoder();
+ final List<Map> 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<Indexable> indexedElements, bool pretty) {
+ var encoder = pretty ? JsonEncoder.withIndent(' ') : JsonEncoder();
+ final List<Map> 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 29be73c..1224005 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<void> _onFileCreated = StreamController(sync: true);
-
- @override
- Stream<void> get onFileCreated => _onFileCreated.stream;
-
- @override
- final Map<String, Warnable> writtenFiles = {};
-
- static Future<HtmlGenerator> create(
- {HtmlGeneratorOptions options,
- List<String> headers,
- List<String> footers,
- List<String> 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<Warnable> 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<int>) {
- file.writeAsBytesSync(content);
- } else {
- throw ArgumentError.value(
- content, 'content', '`content` must be `String` or `List<int>`.');
- }
- _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<List<Generator>> 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<Generator> 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 0000000..da0dfb1
--- /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<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));
+ }
+ }
+}
diff --git a/lib/src/html/html_generator_instance.dart b/lib/src/html/html_generator_instance.dart
deleted file mode 100644
index e82fe1b..0000000
--- 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<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 = 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<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);
- if (!_options.useBaseHref) {
- json = json.replaceAll(HTMLBASE_PLACEHOLDER, '');
- }
- _writer(path.join('categories.json'), '${json}\n');
- }
-
- List<Categorization> _categorizationItems;
-
- void _generateSearchIndex() {
- var encoder =
- _options.prettyIndexJson ? JsonEncoder.withIndent(' ') : 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);
- 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 a47efde..2b929c3 100644
--- a/lib/src/html/templates.dart
+++ b/lib/src/html/templates.dart
@@ -158,6 +158,21 @@
final Template topLevelPropertyTemplate;
final Template typeDefTemplate;
+ static Future<Templates> 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<Templates> createDefault(
{List<String> headerPaths,
List<String> footerPaths,
diff --git a/test/html_generator_test.dart b/test/html_generator_test.dart
index 460c5dc..db62526 100644
--- a/test/html_generator_test.dart
+++ b/test/html_generator_test.dart
@@ -6,7 +6,9 @@
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 'src/utils.dart' as utils;
+// Init a generator without a GeneratorContext and with the default file writer.
+Future<Generator> _initGeneratorForTest() async {
+ var backend = HtmlGeneratorBackend(null, await Templates.createDefault());
+ return GeneratorFrontEnd(backend);
+}
+
void main() {
group('Templates', () {
Templates templates;
@@ -68,13 +76,15 @@
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 @@
});
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 @@
});
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 =