From fd41f4d04333e577fb2dc0cd0745d717110d4a23 Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Thu, 19 Dec 2019 15:54:02 -0800 Subject: [PATCH 01/12] Create generator frontend --- lib/src/generator_fe.dart | 331 +++++++++++++++++++++++++++++++ lib/src/generator_utils.dart | 76 +++++++ lib/src/html/html_generator.dart | 24 +-- 3 files changed, 408 insertions(+), 23 deletions(-) create mode 100644 lib/src/generator_fe.dart create mode 100644 lib/src/generator_utils.dart diff --git a/lib/src/generator_fe.dart b/lib/src/generator_fe.dart new file mode 100644 index 0000000000..5335962c0f --- /dev/null +++ b/lib/src/generator_fe.dart @@ -0,0 +1,331 @@ +// 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:path/path.dart' as path; + +abstract class FileWriter { + File write(String filePath, Object content, [bool allowOverwrite = false]); +} + +class GeneratorFrontEnd implements Generator, FileWriter { + final GeneratorBackend _generatorBackend; + final FileWriter _writer; + + final StreamController _onFileCreated = StreamController(sync: true); + + @override + Stream get onFileCreated => _onFileCreated.stream; + + @override + final Set writtenFiles = Set(); + + GeneratorFrontEnd(this._generatorBackend, this._writer); + + @override + File write(String filePath, Object content, [bool allowOverwrite = false]) { + // Replaces '/' separators with proper separators for the platform. + String outFile = path.joinAll(filePath.split('/')); + File file = _writer.write(outFile, content, allowOverwrite); + _onFileCreated.add(file); + writtenFiles.add(outFile); + return file; + } + + @override + Future generate(PackageGraph packageGraph, String outputDirectoryPath) async { + List indexElements = []; + _generateDocs(packageGraph, indexElements); + _generatorBackend.generateAdditionalFiles(this, packageGraph); + + Iterable categories = indexElements + .where((e) => e is Categorization && e.hasCategorization); + _generatorBackend.generateCategoryJson(this, categories); + _generatorBackend.generateSearchIndex(this, indexElements); + } + + void _generateDocs( + PackageGraph packageGraph, List indexAccumulator) { + if (packageGraph == null) return; + + logInfo('documenting ${packageGraph.defaultPackage.name}'); + _generatorBackend.generatePackage(this, 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(this, packageGraph, category); + } + + for (var lib in filterNonDocumented(package.libraries)) { + logInfo('Generating docs for library ${lib.name} from ' + '${lib.element.source.uri}...'); + indexAccumulator.add(lib); + _generatorBackend.generateLibrary(this, packageGraph, lib); + + for (var clazz in filterNonDocumented(lib.allClasses)) { + indexAccumulator.add(clazz); + _generatorBackend.generateClass(this, packageGraph, lib, clazz); + + for (var constructor in filterNonDocumented(clazz.constructors)) { + if (!constructor.isCanonical) continue; + + indexAccumulator.add(constructor); + _generatorBackend.generateConstructor(this, packageGraph, lib, clazz, constructor); + } + + for (var constant in filterNonDocumented(clazz.constants)) { + if (!constant.isCanonical) continue; + + indexAccumulator.add(constant); + _generatorBackend.generateConstant(this, packageGraph, lib, clazz, constant); + } + + for (var property in filterNonDocumented(clazz.staticProperties)) { + if (!property.isCanonical) continue; + + indexAccumulator.add(property); + _generatorBackend.generateProperty(this, packageGraph, lib, clazz, property); + } + + for (var property in filterNonDocumented(clazz.allInstanceFields)) { + if (!property.isCanonical) continue; + + indexAccumulator.add(property); + _generatorBackend.generateProperty(this, packageGraph, lib, clazz, property); + } + + for (var method in filterNonDocumented(clazz.allInstanceMethods)) { + if (!method.isCanonical) continue; + + indexAccumulator.add(method); + _generatorBackend.generateMethod(this, packageGraph, lib, clazz, method); + } + + for (var operator in filterNonDocumented(clazz.allOperators)) { + if (!operator.isCanonical) continue; + + indexAccumulator.add(operator); + _generatorBackend.generateMethod(this, packageGraph, lib, clazz, operator); + } + + for (var method in filterNonDocumented(clazz.staticMethods)) { + if (!method.isCanonical) continue; + + indexAccumulator.add(method); + _generatorBackend.generateMethod(this, packageGraph, lib, clazz, method); + } + } + + for (var extension in filterNonDocumented(lib.extensions)) { + indexAccumulator.add(extension); + _generatorBackend.generateExtension(this, packageGraph, lib, extension); + + for (var constant in filterNonDocumented(extension.constants)) { + indexAccumulator.add(constant); + _generatorBackend.generateConstant(this, packageGraph, lib, extension, constant); + } + + for (var property + in filterNonDocumented(extension.staticProperties)) { + indexAccumulator.add(property); + _generatorBackend.generateProperty(this, + packageGraph, lib, extension, property); + } + + for (var method + in filterNonDocumented(extension.allPublicInstanceMethods)) { + indexAccumulator.add(method); + _generatorBackend.generateMethod(this, + packageGraph, lib, extension, method); + } + + for (var method in filterNonDocumented(extension.staticMethods)) { + indexAccumulator.add(method); + _generatorBackend.generateMethod(this, + packageGraph, lib, extension, method); + } + + for (var operator in filterNonDocumented(extension.allOperators)) { + indexAccumulator.add(operator); + _generatorBackend.generateMethod(this, + packageGraph, lib, extension, operator); + } + + for (var property + in filterNonDocumented(extension.allInstanceFields)) { + indexAccumulator.add(property); + _generatorBackend.generateProperty(this, + packageGraph, lib, extension, property); + } + } + + for (var mixin in filterNonDocumented(lib.mixins)) { + indexAccumulator.add(mixin); + _generatorBackend.generateMixin(this, packageGraph, lib, mixin); + + for (var constructor in filterNonDocumented(mixin.constructors)) { + if (!constructor.isCanonical) continue; + + indexAccumulator.add(constructor); + _generatorBackend.generateConstructor(this, + packageGraph, lib, mixin, constructor); + } + + for (var constant in filterNonDocumented(mixin.constants)) { + if (!constant.isCanonical) continue; + indexAccumulator.add(constant); + _generatorBackend.generateConstant(this, + packageGraph, lib, mixin, constant); + } + + for (var property in filterNonDocumented(mixin.staticProperties)) { + if (!property.isCanonical) continue; + + indexAccumulator.add(property); + _generatorBackend.generateConstant(this, packageGraph, lib, mixin, property); + } + + for (var property in filterNonDocumented(mixin.allInstanceFields)) { + if (!property.isCanonical) continue; + + indexAccumulator.add(property); + _generatorBackend.generateConstant(this, packageGraph, lib, mixin, property); + } + + for (var method in filterNonDocumented(mixin.allInstanceMethods)) { + if (!method.isCanonical) continue; + + indexAccumulator.add(method); + _generatorBackend.generateMethod(this, packageGraph, lib, mixin, method); + } + + for (var operator in filterNonDocumented(mixin.allOperators)) { + if (!operator.isCanonical) continue; + + indexAccumulator.add(operator); + _generatorBackend.generateMethod(this, packageGraph, lib, mixin, operator); + } + + for (var method in filterNonDocumented(mixin.staticMethods)) { + if (!method.isCanonical) continue; + + indexAccumulator.add(method); + _generatorBackend.generateMethod(this, packageGraph, lib, mixin, method); + } + } + + for (var eNum in filterNonDocumented(lib.enums)) { + indexAccumulator.add(eNum); + _generatorBackend.generateEnum(this, packageGraph, lib, eNum); + + for (var property in filterNonDocumented(eNum.allInstanceFields)) { + indexAccumulator.add(property); + _generatorBackend.generateConstant(this, packageGraph, lib, eNum, property); + } + for (var operator in filterNonDocumented(eNum.allOperators)) { + indexAccumulator.add(operator); + _generatorBackend.generateMethod(this, packageGraph, lib, eNum, operator); + } + for (var method in filterNonDocumented(eNum.allInstanceMethods)) { + indexAccumulator.add(method); + _generatorBackend.generateMethod(this, packageGraph, lib, eNum, method); + } + } + + for (var constant in filterNonDocumented(lib.constants)) { + indexAccumulator.add(constant); + _generatorBackend.generateTopLevelConstant(this, packageGraph, lib, constant); + } + + for (var property in filterNonDocumented(lib.properties)) { + indexAccumulator.add(property); + _generatorBackend.generateTopLevelProperty(this, packageGraph, lib, property); + } + + for (var function in filterNonDocumented(lib.functions)) { + indexAccumulator.add(function); + _generatorBackend.generateFunction(this, packageGraph, lib, function); + } + + for (var typeDef in filterNonDocumented(lib.typedefs)) { + indexAccumulator.add(typeDef); + _generatorBackend.generateTypeDef(this, packageGraph, lib, typeDef); + } + } + } + } +} + +abstract class GeneratorBackend { + /// Emit json describing the [categories] defined by the package. + void generateCategoryJson(FileWriter writer, List categories); + + /// Emit json cataloging [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..9c838eef5f --- /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 cateories) { + var encoder = JsonEncoder.withIndent(' '); + final List indexItems = cateories.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..346899f274 100644 --- a/lib/src/html/html_generator.dart +++ b/lib/src/html/html_generator.dart @@ -6,10 +6,8 @@ library dartdoc.html_generator; import 'dart:async' show Future, StreamController, Stream; import 'dart:io' show Directory, File; -import 'dart:isolate'; 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'; @@ -18,26 +16,6 @@ import 'package:dartdoc/src/model/model.dart'; import 'package:dartdoc/src/warnings.dart'; import 'package:path/path.dart' as path; -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; @@ -76,10 +54,10 @@ class HtmlGenerator extends Generator { HtmlGenerator._(this._options, this._templates); - @override /// Actually write out the documentation for [packageGraph]. /// Stores the HtmlGeneratorInstance so we can access it in [writtenFiles]. + @override Future generate(PackageGraph packageGraph, String outputDirectoryPath) async { assert(_instance == null); From d6e8c3b214a8cb0b0560834dc6cfeb1bd5d06bbd Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Fri, 20 Dec 2019 11:13:46 -0800 Subject: [PATCH 02/12] Start html generator backend --- lib/src/generator_fe.dart | 143 ++++++++++-------- lib/src/html/html_generator_backend.dart | 176 +++++++++++++++++++++++ 2 files changed, 262 insertions(+), 57 deletions(-) create mode 100644 lib/src/html/html_generator_backend.dart diff --git a/lib/src/generator_fe.dart b/lib/src/generator_fe.dart index 5335962c0f..74b64f0e39 100644 --- a/lib/src/generator_fe.dart +++ b/lib/src/generator_fe.dart @@ -9,6 +9,7 @@ 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; abstract class FileWriter { @@ -45,8 +46,8 @@ class GeneratorFrontEnd implements Generator, FileWriter { _generateDocs(packageGraph, indexElements); _generatorBackend.generateAdditionalFiles(this, packageGraph); - Iterable categories = indexElements - .where((e) => e is Categorization && e.hasCategorization); + Iterable categories = + indexElements.where((e) => e is Categorization && e.hasCategorization); _generatorBackend.generateCategoryJson(this, categories); _generatorBackend.generateSearchIndex(this, indexElements); } @@ -56,7 +57,8 @@ class GeneratorFrontEnd implements Generator, FileWriter { if (packageGraph == null) return; logInfo('documenting ${packageGraph.defaultPackage.name}'); - _generatorBackend.generatePackage(this, packageGraph, packageGraph.defaultPackage); + _generatorBackend.generatePackage( + this, packageGraph, packageGraph.defaultPackage); for (var package in packageGraph.localPackages) { for (var category in filterNonDocumented(package.categories)) { @@ -69,6 +71,9 @@ class GeneratorFrontEnd implements Generator, FileWriter { 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(this, packageGraph, lib); @@ -80,92 +85,101 @@ class GeneratorFrontEnd implements Generator, FileWriter { if (!constructor.isCanonical) continue; indexAccumulator.add(constructor); - _generatorBackend.generateConstructor(this, packageGraph, lib, clazz, constructor); + _generatorBackend.generateConstructor( + this, packageGraph, lib, clazz, constructor); } for (var constant in filterNonDocumented(clazz.constants)) { if (!constant.isCanonical) continue; indexAccumulator.add(constant); - _generatorBackend.generateConstant(this, packageGraph, lib, clazz, constant); + _generatorBackend.generateConstant( + this, packageGraph, lib, clazz, constant); } for (var property in filterNonDocumented(clazz.staticProperties)) { if (!property.isCanonical) continue; indexAccumulator.add(property); - _generatorBackend.generateProperty(this, packageGraph, lib, clazz, property); + _generatorBackend.generateProperty( + this, packageGraph, lib, clazz, property); } for (var property in filterNonDocumented(clazz.allInstanceFields)) { if (!property.isCanonical) continue; indexAccumulator.add(property); - _generatorBackend.generateProperty(this, packageGraph, lib, clazz, property); + _generatorBackend.generateProperty( + this, packageGraph, lib, clazz, property); } for (var method in filterNonDocumented(clazz.allInstanceMethods)) { if (!method.isCanonical) continue; indexAccumulator.add(method); - _generatorBackend.generateMethod(this, packageGraph, lib, clazz, method); + _generatorBackend.generateMethod( + this, packageGraph, lib, clazz, method); } for (var operator in filterNonDocumented(clazz.allOperators)) { if (!operator.isCanonical) continue; indexAccumulator.add(operator); - _generatorBackend.generateMethod(this, packageGraph, lib, clazz, operator); + _generatorBackend.generateMethod( + this, packageGraph, lib, clazz, operator); } for (var method in filterNonDocumented(clazz.staticMethods)) { if (!method.isCanonical) continue; indexAccumulator.add(method); - _generatorBackend.generateMethod(this, packageGraph, lib, clazz, method); + _generatorBackend.generateMethod( + this, packageGraph, lib, clazz, method); } } for (var extension in filterNonDocumented(lib.extensions)) { indexAccumulator.add(extension); - _generatorBackend.generateExtension(this, packageGraph, lib, extension); + _generatorBackend.generateExtension( + this, packageGraph, lib, extension); for (var constant in filterNonDocumented(extension.constants)) { indexAccumulator.add(constant); - _generatorBackend.generateConstant(this, packageGraph, lib, extension, constant); + _generatorBackend.generateConstant( + this, packageGraph, lib, extension, constant); } for (var property in filterNonDocumented(extension.staticProperties)) { indexAccumulator.add(property); - _generatorBackend.generateProperty(this, - packageGraph, lib, extension, property); + _generatorBackend.generateProperty( + this, packageGraph, lib, extension, property); } for (var method in filterNonDocumented(extension.allPublicInstanceMethods)) { indexAccumulator.add(method); - _generatorBackend.generateMethod(this, - packageGraph, lib, extension, method); + _generatorBackend.generateMethod( + this, packageGraph, lib, extension, method); } for (var method in filterNonDocumented(extension.staticMethods)) { indexAccumulator.add(method); - _generatorBackend.generateMethod(this, - packageGraph, lib, extension, method); + _generatorBackend.generateMethod( + this, packageGraph, lib, extension, method); } for (var operator in filterNonDocumented(extension.allOperators)) { indexAccumulator.add(operator); - _generatorBackend.generateMethod(this, - packageGraph, lib, extension, operator); + _generatorBackend.generateMethod( + this, packageGraph, lib, extension, operator); } for (var property in filterNonDocumented(extension.allInstanceFields)) { indexAccumulator.add(property); - _generatorBackend.generateProperty(this, - packageGraph, lib, extension, property); + _generatorBackend.generateProperty( + this, packageGraph, lib, extension, property); } } @@ -177,50 +191,55 @@ class GeneratorFrontEnd implements Generator, FileWriter { if (!constructor.isCanonical) continue; indexAccumulator.add(constructor); - _generatorBackend.generateConstructor(this, - packageGraph, lib, mixin, constructor); + _generatorBackend.generateConstructor( + this, packageGraph, lib, mixin, constructor); } for (var constant in filterNonDocumented(mixin.constants)) { if (!constant.isCanonical) continue; indexAccumulator.add(constant); - _generatorBackend.generateConstant(this, - packageGraph, lib, mixin, constant); + _generatorBackend.generateConstant( + this, packageGraph, lib, mixin, constant); } for (var property in filterNonDocumented(mixin.staticProperties)) { if (!property.isCanonical) continue; indexAccumulator.add(property); - _generatorBackend.generateConstant(this, packageGraph, lib, mixin, property); + _generatorBackend.generateConstant( + this, packageGraph, lib, mixin, property); } for (var property in filterNonDocumented(mixin.allInstanceFields)) { if (!property.isCanonical) continue; indexAccumulator.add(property); - _generatorBackend.generateConstant(this, packageGraph, lib, mixin, property); + _generatorBackend.generateConstant( + this, packageGraph, lib, mixin, property); } for (var method in filterNonDocumented(mixin.allInstanceMethods)) { if (!method.isCanonical) continue; indexAccumulator.add(method); - _generatorBackend.generateMethod(this, packageGraph, lib, mixin, method); + _generatorBackend.generateMethod( + this, packageGraph, lib, mixin, method); } for (var operator in filterNonDocumented(mixin.allOperators)) { if (!operator.isCanonical) continue; indexAccumulator.add(operator); - _generatorBackend.generateMethod(this, packageGraph, lib, mixin, operator); + _generatorBackend.generateMethod( + this, packageGraph, lib, mixin, operator); } for (var method in filterNonDocumented(mixin.staticMethods)) { if (!method.isCanonical) continue; indexAccumulator.add(method); - _generatorBackend.generateMethod(this, packageGraph, lib, mixin, method); + _generatorBackend.generateMethod( + this, packageGraph, lib, mixin, method); } } @@ -230,26 +249,31 @@ class GeneratorFrontEnd implements Generator, FileWriter { for (var property in filterNonDocumented(eNum.allInstanceFields)) { indexAccumulator.add(property); - _generatorBackend.generateConstant(this, packageGraph, lib, eNum, property); + _generatorBackend.generateConstant( + this, packageGraph, lib, eNum, property); } for (var operator in filterNonDocumented(eNum.allOperators)) { indexAccumulator.add(operator); - _generatorBackend.generateMethod(this, packageGraph, lib, eNum, operator); + _generatorBackend.generateMethod( + this, packageGraph, lib, eNum, operator); } for (var method in filterNonDocumented(eNum.allInstanceMethods)) { indexAccumulator.add(method); - _generatorBackend.generateMethod(this, packageGraph, lib, eNum, method); + _generatorBackend.generateMethod( + this, packageGraph, lib, eNum, method); } } for (var constant in filterNonDocumented(lib.constants)) { indexAccumulator.add(constant); - _generatorBackend.generateTopLevelConstant(this, packageGraph, lib, constant); + _generatorBackend.generateTopLevelConstant( + this, packageGraph, lib, constant); } for (var property in filterNonDocumented(lib.properties)) { indexAccumulator.add(property); - _generatorBackend.generateTopLevelProperty(this, packageGraph, lib, property); + _generatorBackend.generateTopLevelProperty( + this, packageGraph, lib, property); } for (var function in filterNonDocumented(lib.functions)) { @@ -277,54 +301,59 @@ abstract class GeneratorBackend { void generatePackage(FileWriter writer, PackageGraph graph, Package package); /// Emit documentation content for the [category]. - void generateCategory(FileWriter writer, PackageGraph graph, Category 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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/html/html_generator_backend.dart b/lib/src/html/html_generator_backend.dart new file mode 100644 index 0000000000..1eaaa7e601 --- /dev/null +++ b/lib/src/html/html_generator_backend.dart @@ -0,0 +1,176 @@ +// 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 'package:dartdoc/src/generator_fe.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:mustache/mustache.dart'; +import 'package:path/path.dart' as path; + +class HtmlGeneratorBackend implements GeneratorBackend { + final HtmlOptions _options; + final Templates _templates; + + HtmlGeneratorBackend(this._options, this._templates); + + void _write(FileWriter writer, String filename, Template template, + TemplateData data) { + String content = template.renderString(data); + content = content.replaceAll(HTMLBASE_PLACEHOLDER, data.htmlBase); + writer.write(filename, content); + } + + @override + void generateCategoryJson( + FileWriter writer, List categories) { + String json = generator_util.generateCategoryJson(categories); + json = json.replaceAll(HTMLBASE_PLACEHOLDER, ''); + writer.write(path.join('categories.json'), '${json}\n'); + } + + @override + void generateSearchIndex(FileWriter writer, List indexedElements) { + // TODO pretty-index-json from generator options + String json = + generator_util.generateSearchIndexJson(indexedElements, false); + json = json.replaceAll(HTMLBASE_PLACEHOLDER, ''); + writer.write(path.join('categories.json'), '${json}\n'); + } + + @override + void generatePackage(FileWriter writer, PackageGraph graph, Package package) { + TemplateData data = PackageTemplateData(_options, graph, package); + _write(writer, package.filePath, _templates.indexTemplate, data); + _write(writer, '__404error.html', _templates.errorTemplate, data); + } + + @override + void generateCategory( + FileWriter writer, PackageGraph packageGraph, Category category) { + TemplateData data = CategoryTemplateData(_options, packageGraph, category); + _write(writer, category.filePath, _templates.categoryTemplate, data); + } + + @override + void generateLibrary( + FileWriter writer, PackageGraph packageGraph, Library lib) { + TemplateData data = LibraryTemplateData(_options, packageGraph, lib); + _write(writer, lib.filePath, _templates.libraryTemplate, data); + } + + @override + void generateClass( + FileWriter writer, PackageGraph packageGraph, Library lib, Class clazz) { + TemplateData data = ClassTemplateData(_options, packageGraph, lib, clazz); + _write(writer, clazz.filePath, _templates.classTemplate, data); + } + + @override + void generateExtension(FileWriter writer, PackageGraph packageGraph, + Library lib, Extension extension) { + TemplateData data = + ExtensionTemplateData(_options, packageGraph, lib, extension); + _write(writer, extension.filePath, _templates.extensionTemplate, data); + } + + @override + void generateMixin( + FileWriter writer, PackageGraph packageGraph, Library lib, Mixin mixin) { + TemplateData data = MixinTemplateData(_options, packageGraph, lib, mixin); + _write(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); + + _write(writer, constructor.filePath, _templates.constructorTemplate, data); + } + + @override + void generateEnum( + FileWriter writer, PackageGraph packageGraph, Library lib, Enum eNum) { + TemplateData data = EnumTemplateData(_options, packageGraph, lib, eNum); + + _write(writer, eNum.filePath, _templates.enumTemplate, data); + } + + @override + void generateFunction(FileWriter writer, PackageGraph packageGraph, + Library lib, ModelFunction function) { + TemplateData data = + FunctionTemplateData(_options, packageGraph, lib, function); + + _write(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); + + _write(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); + + _write(writer, property.filePath, _templates.propertyTemplate, data); + } + + @override + void generateTopLevelProperty(FileWriter writer, PackageGraph packageGraph, + Library lib, TopLevelVariable property) { + TemplateData data = + TopLevelPropertyTemplateData(_options, packageGraph, lib, property); + + _write( + 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); + + _write(writer, typeDef.filePath, _templates.typeDefTemplate, data); + } + + @override + void generateAdditionalFiles(FileWriter writer, PackageGraph graph) 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)); + } + } +} From 544e11caeddfc670796a5444e6dfb454c7ab79ec Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Fri, 20 Dec 2019 11:53:00 -0800 Subject: [PATCH 03/12] rename file --- lib/src/{generator_fe.dart => generator_frontend.dart} | 0 lib/src/html/html_generator_backend.dart | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/src/{generator_fe.dart => generator_frontend.dart} (100%) diff --git a/lib/src/generator_fe.dart b/lib/src/generator_frontend.dart similarity index 100% rename from lib/src/generator_fe.dart rename to lib/src/generator_frontend.dart diff --git a/lib/src/html/html_generator_backend.dart b/lib/src/html/html_generator_backend.dart index 1eaaa7e601..6cca775deb 100644 --- a/lib/src/html/html_generator_backend.dart +++ b/lib/src/html/html_generator_backend.dart @@ -2,7 +2,7 @@ // 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 'package:dartdoc/src/generator_fe.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; From b47afd96f1bd9bc2e3932fae694d88d786356d0c Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Fri, 20 Dec 2019 14:42:35 -0800 Subject: [PATCH 04/12] Init single generator instead of list --- lib/dartdoc.dart | 34 +++++++++++--------------------- lib/src/empty_generator.dart | 4 ++-- lib/src/html/html_generator.dart | 18 ++++++++--------- 3 files changed, 22 insertions(+), 34 deletions(-) diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart index 66a8a8a8f0..c7fa287c51 100644 --- a/lib/dartdoc.dart +++ b/lib/dartdoc.dart @@ -47,7 +47,7 @@ class DartdocGeneratorOptionContext extends DartdocOptionContext /// 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 +55,21 @@ 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)); + generator?.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 initHtmlGenerators(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 +86,17 @@ 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) { + 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)); - } + await generator.generate(packageGraph, outputDir.path); + writtenFiles.addAll(generator.writtenFiles.keys.map(path.normalize)); if (config.validateLinks && writtenFiles.isNotEmpty) { validateLinks(packageGraph, outputDir.path); } @@ -122,8 +112,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..58b5a65a85 100644 --- a/lib/src/empty_generator.dart +++ b/lib/src/empty_generator.dart @@ -41,6 +41,6 @@ class EmptyGenerator extends Generator { final Map writtenFiles = {}; } -Future> initEmptyGenerators(DartdocOptionContext config) async { - return [EmptyGenerator()]; +Future initEmptyGenerator(DartdocOptionContext config) async { + return EmptyGenerator(); } diff --git a/lib/src/html/html_generator.dart b/lib/src/html/html_generator.dart index 346899f274..405e76c6a7 100644 --- a/lib/src/html/html_generator.dart +++ b/lib/src/html/html_generator.dart @@ -54,7 +54,6 @@ class HtmlGenerator extends Generator { HtmlGenerator._(this._options, this._templates); - /// Actually write out the documentation for [packageGraph]. /// Stores the HtmlGeneratorInstance so we can access it in [writtenFiles]. @override @@ -133,7 +132,7 @@ class HtmlGeneratorOptions implements HtmlOptions { } /// Initialize and setup the generators. -Future> initHtmlGenerators(GeneratorContext context) async { +Future initHtmlGenerators(GeneratorContext context) async { // TODO(jcollins-g): Rationalize based on GeneratorContext all the way down // through the generators. HtmlGeneratorOptions options = HtmlGeneratorOptions( @@ -143,12 +142,11 @@ Future> initHtmlGenerators(GeneratorContext context) async { prettyIndexJson: context.prettyIndexJson, templatesDir: context.templatesDir, useBaseHref: context.useBaseHref); - return [ - await HtmlGenerator.create( - options: options, - headers: context.header, - footers: context.footer, - footerTexts: context.footerTextPaths, - ) - ]; + + return HtmlGenerator.create( + options: options, + headers: context.header, + footers: context.footer, + footerTexts: context.footerTextPaths, + ); } From c76e0efd535888bb581067d37b2e0d412f7a219d Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Fri, 20 Dec 2019 15:47:56 -0800 Subject: [PATCH 05/12] Wire up html backend --- lib/dartdoc.dart | 48 ++- lib/src/empty_generator.dart | 3 - lib/src/generator.dart | 4 - lib/src/generator_frontend.dart | 43 ++- lib/src/html/html_generator.dart | 148 +------- lib/src/html/html_generator_backend.dart | 88 +++-- lib/src/html/html_generator_instance.dart | 413 ---------------------- lib/src/html/templates.dart | 15 + test/html_generator_test.dart | 10 +- 9 files changed, 169 insertions(+), 603 deletions(-) delete mode 100644 lib/src/html/html_generator_instance.dart diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart index c7fa287c51..a399914519 100644 --- a/lib/dartdoc.dart +++ b/lib/dartdoc.dart @@ -15,6 +15,7 @@ import 'dart:io'; import 'package:dartdoc/src/dartdoc_options.dart'; import 'package:dartdoc/src/empty_generator.dart'; import 'package:dartdoc/src/generator.dart'; +import 'package:dartdoc/src/generator_frontend.dart'; import 'package:dartdoc/src/html/html_generator.dart'; import 'package:dartdoc/src/logging.dart'; import 'package:dartdoc/src/model/model.dart'; @@ -44,18 +45,55 @@ class DartdocGeneratorOptionContext extends DartdocOptionContext : super(optionSet, dir); } +class DartdocFileWriter extends FileWriter { + @override + File write(String filePath, Object content, + {bool allowOverwrite, Warnable element}) { + allowOverwrite ??= false; + 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(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`.'); + } + writtenFiles[filePath] = element; + return file; + } +} + /// Generates Dart documentation for all public Dart libraries in the given /// directory. class Dartdoc extends PackageBuilder { final Generator generator; final Set writtenFiles = Set(); + final FileWriter writer; Directory outputDir; // Fires when the self checks make progress. final StreamController _onCheckProgress = StreamController(sync: true); - Dartdoc._(DartdocOptionContext config, this.generator) : super(config) { + Dartdoc._(DartdocOptionContext config, this.generator, this.writer) + : super(config) { outputDir = Directory(config.output)..createSync(recursive: true); generator?.onFileCreated?.listen(logProgress); } @@ -64,12 +102,14 @@ class Dartdoc extends PackageBuilder { /// and returns a Dartdoc object with them. static Future withDefaultGenerators( DartdocGeneratorOptionContext config) async { - return Dartdoc._(config, await initHtmlGenerators(config)); + FileWriter writer = DartdocFileWriter(); + return Dartdoc._(config, await initHtmlGenerator(config, writer), writer); } /// An asynchronous factory method that builds static Future withEmptyGenerator(DartdocOptionContext config) async { - return Dartdoc._(config, await initEmptyGenerator(config)); + return Dartdoc._( + config, await initEmptyGenerator(config), DartdocFileWriter()); } Stream get onCheckProgress => _onCheckProgress.stream; @@ -96,7 +136,7 @@ class Dartdoc extends PackageBuilder { if (!outputDir.existsSync()) outputDir.createSync(recursive: true); await generator.generate(packageGraph, outputDir.path); - writtenFiles.addAll(generator.writtenFiles.keys.map(path.normalize)); + writtenFiles.addAll(writer.writtenFiles.keys.map(path.normalize)); if (config.validateLinks && writtenFiles.isNotEmpty) { validateLinks(packageGraph, outputDir.path); } diff --git a/lib/src/empty_generator.dart b/lib/src/empty_generator.dart index 58b5a65a85..7fe92d8403 100644 --- a/lib/src/empty_generator.dart +++ b/lib/src/empty_generator.dart @@ -36,9 +36,6 @@ class EmptyGenerator extends Generator { /// Implementation fires on each model element processed rather than /// file creation. Stream get onFileCreated => _onFileCreated.stream; - - @override - final Map writtenFiles = {}; } Future initEmptyGenerator(DartdocOptionContext config) async { diff --git a/lib/src/generator.dart b/lib/src/generator.dart index 6ea61e18ab..5cf77063ab 100644 --- a/lib/src/generator.dart +++ b/lib/src/generator.dart @@ -12,7 +12,6 @@ import 'dart:isolate'; import 'package:dartdoc/src/dartdoc_options.dart'; import 'package:dartdoc/src/model/model.dart' show PackageGraph; import 'package:dartdoc/src/package_meta.dart'; -import 'package:dartdoc/src/warnings.dart'; import 'package:path/path.dart' as path; /// An abstract class that defines a generator that generates documentation for @@ -26,9 +25,6 @@ abstract class Generator { /// Fires when a file is created. Stream get onFileCreated; - - /// Fetches all filenames written by this generator. - Map get writtenFiles; } /// Dartdoc options related to generators generally. diff --git a/lib/src/generator_frontend.dart b/lib/src/generator_frontend.dart index 74b64f0e39..3422bac9da 100644 --- a/lib/src/generator_frontend.dart +++ b/lib/src/generator_frontend.dart @@ -13,43 +13,58 @@ import 'package:dartdoc/src/warnings.dart'; import 'package:path/path.dart' as path; abstract class FileWriter { - File write(String filePath, Object content, [bool allowOverwrite = false]); + final Map writtenFiles = {}; + + File write(String filePath, Object content, + {bool allowOverwrite, Warnable element}); } class GeneratorFrontEnd implements Generator, FileWriter { final GeneratorBackend _generatorBackend; final FileWriter _writer; + // Used in write(). This being not null signals the generator is active. + String _outputDirectory; + final StreamController _onFileCreated = StreamController(sync: true); @override Stream get onFileCreated => _onFileCreated.stream; @override - final Set writtenFiles = Set(); + final Map writtenFiles = {}; GeneratorFrontEnd(this._generatorBackend, this._writer); @override - File write(String filePath, Object content, [bool allowOverwrite = false]) { + File write(String filePath, Object content, {bool allowOverwrite, Warnable element}) { + assert(_outputDirectory != null); // Replaces '/' separators with proper separators for the platform. String outFile = path.joinAll(filePath.split('/')); - File file = _writer.write(outFile, content, allowOverwrite); + outFile = path.join(_outputDirectory, outFile); + File file = _writer.write(outFile, content, allowOverwrite: allowOverwrite, element: element); _onFileCreated.add(file); - writtenFiles.add(outFile); + writtenFiles[filePath] = element; return file; } @override - Future generate(PackageGraph packageGraph, String outputDirectoryPath) async { - List indexElements = []; - _generateDocs(packageGraph, indexElements); - _generatorBackend.generateAdditionalFiles(this, packageGraph); - - Iterable categories = - indexElements.where((e) => e is Categorization && e.hasCategorization); - _generatorBackend.generateCategoryJson(this, categories); - _generatorBackend.generateSearchIndex(this, indexElements); + Future generate(PackageGraph packageGraph, String outputDirectory) async { + _outputDirectory = outputDirectory; + try { + List indexElements = []; + _generateDocs(packageGraph, indexElements); + _generatorBackend.generateAdditionalFiles(this, packageGraph); + + List categories = indexElements + .where((e) => e is Categorization && e.hasCategorization) + .map((e) => e as Categorization) + .toList(); + _generatorBackend.generateCategoryJson(this, categories); + _generatorBackend.generateSearchIndex(this, indexElements); + } finally { + _outputDirectory = null; + } } void _generateDocs( diff --git a/lib/src/html/html_generator.dart b/lib/src/html/html_generator.dart index 405e76c6a7..fd66bb6e50 100644 --- a/lib/src/html/html_generator.dart +++ b/lib/src/html/html_generator.dart @@ -4,149 +4,15 @@ library dartdoc.html_generator; -import 'dart:async' show Future, StreamController, Stream; -import 'dart:io' show Directory, File; +import 'dart:async' show Future; import 'package:dartdoc/dartdoc.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'; -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); - - /// Actually write out the documentation for [packageGraph]. - /// Stores the HtmlGeneratorInstance so we can access it in [writtenFiles]. - @override - 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 HtmlGenerator.create( - options: options, - headers: context.header, - footers: context.footer, - footerTexts: context.footerTextPaths, - ); +Future initHtmlGenerator( + GeneratorContext context, FileWriter writer) async { + var backend = await HtmlGeneratorBackend.fromContext(context); + return GeneratorFrontEnd(backend, writer); } diff --git a/lib/src/html/html_generator_backend.dart b/lib/src/html/html_generator_backend.dart index 6cca775deb..c3ff75666a 100644 --- a/lib/src/html/html_generator_backend.dart +++ b/lib/src/html/html_generator_backend.dart @@ -2,6 +2,9 @@ // 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; @@ -14,16 +17,45 @@ import 'package:dartdoc/src/model/package_graph.dart'; import 'package:mustache/mustache.dart'; import 'package:path/path.dart' as path; +class HtmlBackendOptions implements HtmlOptions { + @override + final String relCanonicalPrefix; + @override + final String toolVersion; + + final String favicon; + + @override + final bool useBaseHref; + + HtmlBackendOptions({this.relCanonicalPrefix, this.toolVersion, this.favicon, this.useBaseHref}); +} + class HtmlGeneratorBackend implements GeneratorBackend { - final HtmlOptions _options; + final HtmlBackendOptions _options; final Templates _templates; - HtmlGeneratorBackend(this._options, this._templates); + static Future fromContext( + GeneratorContext context) async { + Templates templates = await Templates.fromContext(context); + HtmlOptions options = HtmlBackendOptions( + relCanonicalPrefix: context.relCanonicalPrefix, + toolVersion: dartdocVersion, + favicon: context.favicon, + ); + return HtmlGeneratorBackend(options, templates); + } + + HtmlGeneratorBackend(HtmlBackendOptions options, this._templates) + : this._options = (options ?? HtmlBackendOptions()); - void _write(FileWriter writer, String filename, Template template, + /// 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); - content = content.replaceAll(HTMLBASE_PLACEHOLDER, data.htmlBase); + if (!_options.useBaseHref) { + content = content.replaceAll(HTMLBASE_PLACEHOLDER, data.htmlBase); + } writer.write(filename, content); } @@ -31,7 +63,9 @@ class HtmlGeneratorBackend implements GeneratorBackend { void generateCategoryJson( FileWriter writer, List categories) { String json = generator_util.generateCategoryJson(categories); - json = json.replaceAll(HTMLBASE_PLACEHOLDER, ''); + if (!_options.useBaseHref) { + json = json.replaceAll(HTMLBASE_PLACEHOLDER, ''); + } writer.write(path.join('categories.json'), '${json}\n'); } @@ -40,36 +74,38 @@ class HtmlGeneratorBackend implements GeneratorBackend { // TODO pretty-index-json from generator options String json = generator_util.generateSearchIndexJson(indexedElements, false); - json = json.replaceAll(HTMLBASE_PLACEHOLDER, ''); - writer.write(path.join('categories.json'), '${json}\n'); + 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); - _write(writer, package.filePath, _templates.indexTemplate, data); - _write(writer, '__404error.html', _templates.errorTemplate, data); + _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); - _write(writer, category.filePath, _templates.categoryTemplate, data); + _render(writer, category.filePath, _templates.categoryTemplate, data); } @override void generateLibrary( FileWriter writer, PackageGraph packageGraph, Library lib) { TemplateData data = LibraryTemplateData(_options, packageGraph, lib); - _write(writer, lib.filePath, _templates.libraryTemplate, data); + _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); - _write(writer, clazz.filePath, _templates.classTemplate, data); + _render(writer, clazz.filePath, _templates.classTemplate, data); } @override @@ -77,14 +113,14 @@ class HtmlGeneratorBackend implements GeneratorBackend { Library lib, Extension extension) { TemplateData data = ExtensionTemplateData(_options, packageGraph, lib, extension); - _write(writer, extension.filePath, _templates.extensionTemplate, data); + _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); - _write(writer, mixin.filePath, _templates.mixinTemplate, data); + _render(writer, mixin.filePath, _templates.mixinTemplate, data); } @override @@ -93,7 +129,7 @@ class HtmlGeneratorBackend implements GeneratorBackend { TemplateData data = ConstructorTemplateData( _options, packageGraph, lib, clazz, constructor); - _write(writer, constructor.filePath, _templates.constructorTemplate, data); + _render(writer, constructor.filePath, _templates.constructorTemplate, data); } @override @@ -101,7 +137,7 @@ class HtmlGeneratorBackend implements GeneratorBackend { FileWriter writer, PackageGraph packageGraph, Library lib, Enum eNum) { TemplateData data = EnumTemplateData(_options, packageGraph, lib, eNum); - _write(writer, eNum.filePath, _templates.enumTemplate, data); + _render(writer, eNum.filePath, _templates.enumTemplate, data); } @override @@ -110,7 +146,7 @@ class HtmlGeneratorBackend implements GeneratorBackend { TemplateData data = FunctionTemplateData(_options, packageGraph, lib, function); - _write(writer, function.filePath, _templates.functionTemplate, data); + _render(writer, function.filePath, _templates.functionTemplate, data); } @override @@ -119,7 +155,7 @@ class HtmlGeneratorBackend implements GeneratorBackend { TemplateData data = MethodTemplateData(_options, packageGraph, lib, clazz, method); - _write(writer, method.filePath, _templates.methodTemplate, data); + _render(writer, method.filePath, _templates.methodTemplate, data); } @override @@ -133,7 +169,7 @@ class HtmlGeneratorBackend implements GeneratorBackend { TemplateData data = PropertyTemplateData(_options, packageGraph, lib, clazz, property); - _write(writer, property.filePath, _templates.propertyTemplate, data); + _render(writer, property.filePath, _templates.propertyTemplate, data); } @override @@ -142,7 +178,7 @@ class HtmlGeneratorBackend implements GeneratorBackend { TemplateData data = TopLevelPropertyTemplateData(_options, packageGraph, lib, property); - _write( + _render( writer, property.filePath, _templates.topLevelPropertyTemplate, data); } @@ -157,11 +193,21 @@ class HtmlGeneratorBackend implements GeneratorBackend { TemplateData data = TypedefTemplateData(_options, packageGraph, lib, typeDef); - _write(writer, typeDef.filePath, _templates.typeDefTemplate, data); + _render(writer, typeDef.filePath, _templates.typeDefTemplate, data); } @override void generateAdditionalFiles(FileWriter writer, PackageGraph graph) async { + if (_options.favicon != null) { + var bytes = File(_options.favicon).readAsBytesSync(); + // Allow overwrite of favicon. + writer.write(path.join('static-assets', 'favicon.png'), bytes, allowOverwrite: true); + } + await _copyResources(writer); + } + + // TODO: change this to use resource_loader + Future _copyResources(FileWriter writer) async { final prefix = 'package:dartdoc/resources/'; for (String resourcePath in resources.resource_names) { if (!resourcePath.startsWith(prefix)) { 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 6cfdee0dd8..db3af7fd04 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'; @@ -68,11 +70,13 @@ void main() { group('HtmlGenerator', () { // TODO: Run the HtmlGenerator and validate important constraints. group('for a null package', () { - HtmlGenerator generator; + Generator generator; Directory tempOutput; setUp(() async { - generator = await HtmlGenerator.create(); + HtmlGeneratorBackend backend = + HtmlGeneratorBackend(null, await Templates.createDefault()); + generator = GeneratorFrontEnd(backend, DartdocFileWriter()); tempOutput = Directory.systemTemp.createTempSync('doc_test_temp'); return generator.generate(null, tempOutput.path); }); From 87a14b2e8314ef1ebbd4a482b4fe084b4dfa8b50 Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Mon, 23 Dec 2019 10:11:00 -0800 Subject: [PATCH 06/12] Generator frontend/backend fixes --- lib/dartdoc.dart | 14 +++++++------- lib/src/empty_generator.dart | 6 ++++-- lib/src/generator.dart | 3 +++ lib/src/generator_frontend.dart | 6 ++---- lib/src/html/html_generator_backend.dart | 5 +++-- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart index a399914519..05ecc2e685 100644 --- a/lib/dartdoc.dart +++ b/lib/dartdoc.dart @@ -46,6 +46,8 @@ class DartdocGeneratorOptionContext extends DartdocOptionContext } class DartdocFileWriter extends FileWriter { + final Map writtenFiles = {}; + @override File write(String filePath, Object content, {bool allowOverwrite, Warnable element}) { @@ -85,14 +87,13 @@ class DartdocFileWriter extends FileWriter { class Dartdoc extends PackageBuilder { final Generator generator; final Set writtenFiles = Set(); - final FileWriter writer; Directory outputDir; // Fires when the self checks make progress. final StreamController _onCheckProgress = StreamController(sync: true); - Dartdoc._(DartdocOptionContext config, this.generator, this.writer) + Dartdoc._(DartdocOptionContext config, this.generator) : super(config) { outputDir = Directory(config.output)..createSync(recursive: true); generator?.onFileCreated?.listen(logProgress); @@ -102,14 +103,13 @@ class Dartdoc extends PackageBuilder { /// and returns a Dartdoc object with them. static Future withDefaultGenerators( DartdocGeneratorOptionContext config) async { - FileWriter writer = DartdocFileWriter(); - return Dartdoc._(config, await initHtmlGenerator(config, writer), writer); + return Dartdoc._( + config, await initHtmlGenerator(config, DartdocFileWriter())); } /// An asynchronous factory method that builds static Future withEmptyGenerator(DartdocOptionContext config) async { - return Dartdoc._( - config, await initEmptyGenerator(config), DartdocFileWriter()); + return Dartdoc._(config, await initEmptyGenerator(config)); } Stream get onCheckProgress => _onCheckProgress.stream; @@ -136,7 +136,7 @@ class Dartdoc extends PackageBuilder { if (!outputDir.existsSync()) outputDir.createSync(recursive: true); await generator.generate(packageGraph, outputDir.path); - writtenFiles.addAll(writer.writtenFiles.keys.map(path.normalize)); + writtenFiles.addAll(generator.writtenFiles.keys.map(path.normalize)); if (config.validateLinks && writtenFiles.isNotEmpty) { validateLinks(packageGraph, outputDir.path); } diff --git a/lib/src/empty_generator.dart b/lib/src/empty_generator.dart index 7fe92d8403..d6f2cb3aa8 100644 --- a/lib/src/empty_generator.dart +++ b/lib/src/empty_generator.dart @@ -31,11 +31,13 @@ class EmptyGenerator extends Generator { final StreamController _onFileCreated = StreamController(sync: true); - @override - /// Implementation fires on each model element processed rather than /// file creation. + @override Stream get onFileCreated => _onFileCreated.stream; + + @override + final Map writtenFiles = {}; } Future initEmptyGenerator(DartdocOptionContext config) async { diff --git a/lib/src/generator.dart b/lib/src/generator.dart index 5cf77063ab..6484f3eb4e 100644 --- a/lib/src/generator.dart +++ b/lib/src/generator.dart @@ -12,6 +12,7 @@ import 'dart:isolate'; import 'package:dartdoc/src/dartdoc_options.dart'; import 'package:dartdoc/src/model/model.dart' show PackageGraph; import 'package:dartdoc/src/package_meta.dart'; +import 'package:dartdoc/src/warnings.dart'; import 'package:path/path.dart' as path; /// An abstract class that defines a generator that generates documentation for @@ -25,6 +26,8 @@ abstract class Generator { /// Fires when a file is created. Stream get onFileCreated; + + final Map writtenFiles = {}; } /// Dartdoc options related to generators generally. diff --git a/lib/src/generator_frontend.dart b/lib/src/generator_frontend.dart index 3422bac9da..e1bca8e7ca 100644 --- a/lib/src/generator_frontend.dart +++ b/lib/src/generator_frontend.dart @@ -13,8 +13,6 @@ import 'package:dartdoc/src/warnings.dart'; import 'package:path/path.dart' as path; abstract class FileWriter { - final Map writtenFiles = {}; - File write(String filePath, Object content, {bool allowOverwrite, Warnable element}); } @@ -44,7 +42,7 @@ class GeneratorFrontEnd implements Generator, FileWriter { outFile = path.join(_outputDirectory, outFile); File file = _writer.write(outFile, content, allowOverwrite: allowOverwrite, element: element); _onFileCreated.add(file); - writtenFiles[filePath] = element; + writtenFiles[file.path] = element; return file; } @@ -54,7 +52,7 @@ class GeneratorFrontEnd implements Generator, FileWriter { try { List indexElements = []; _generateDocs(packageGraph, indexElements); - _generatorBackend.generateAdditionalFiles(this, packageGraph); + await _generatorBackend.generateAdditionalFiles(this, packageGraph); List categories = indexElements .where((e) => e is Categorization && e.hasCategorization) diff --git a/lib/src/html/html_generator_backend.dart b/lib/src/html/html_generator_backend.dart index c3ff75666a..68994c1dc2 100644 --- a/lib/src/html/html_generator_backend.dart +++ b/lib/src/html/html_generator_backend.dart @@ -198,12 +198,13 @@ class HtmlGeneratorBackend implements GeneratorBackend { @override void generateAdditionalFiles(FileWriter writer, PackageGraph graph) async { + await _copyResources(writer); if (_options.favicon != null) { var bytes = File(_options.favicon).readAsBytesSync(); // Allow overwrite of favicon. - writer.write(path.join('static-assets', 'favicon.png'), bytes, allowOverwrite: true); + writer.write(path.join('static-assets', 'favicon.png'), bytes, + allowOverwrite: true); } - await _copyResources(writer); } // TODO: change this to use resource_loader From c096cd23f3945e6307ecd0779de37b256e4cdf17 Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Mon, 23 Dec 2019 12:54:34 -0800 Subject: [PATCH 07/12] Change FileWriter back to typedef --- lib/dartdoc.dart | 10 ++- lib/src/generator_frontend.dart | 87 ++++++++++++------------ lib/src/html/html_generator_backend.dart | 10 +-- test/html_generator_test.dart | 14 ++-- 4 files changed, 61 insertions(+), 60 deletions(-) diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart index 05ecc2e685..49b2f04d08 100644 --- a/lib/dartdoc.dart +++ b/lib/dartdoc.dart @@ -15,7 +15,6 @@ import 'dart:io'; import 'package:dartdoc/src/dartdoc_options.dart'; import 'package:dartdoc/src/empty_generator.dart'; import 'package:dartdoc/src/generator.dart'; -import 'package:dartdoc/src/generator_frontend.dart'; import 'package:dartdoc/src/html/html_generator.dart'; import 'package:dartdoc/src/logging.dart'; import 'package:dartdoc/src/model/model.dart'; @@ -45,10 +44,10 @@ class DartdocGeneratorOptionContext extends DartdocOptionContext : super(optionSet, dir); } -class DartdocFileWriter extends FileWriter { +class DartdocFileWriter { + // Track written files so we don't need to use File.existsSync(). final Map writtenFiles = {}; - @override File write(String filePath, Object content, {bool allowOverwrite, Warnable element}) { allowOverwrite ??= false; @@ -93,8 +92,7 @@ class Dartdoc extends PackageBuilder { final StreamController _onCheckProgress = StreamController(sync: true); - Dartdoc._(DartdocOptionContext config, this.generator) - : super(config) { + Dartdoc._(DartdocOptionContext config, this.generator) : super(config) { outputDir = Directory(config.output)..createSync(recursive: true); generator?.onFileCreated?.listen(logProgress); } @@ -104,7 +102,7 @@ class Dartdoc extends PackageBuilder { static Future withDefaultGenerators( DartdocGeneratorOptionContext config) async { return Dartdoc._( - config, await initHtmlGenerator(config, DartdocFileWriter())); + config, await initHtmlGenerator(config, DartdocFileWriter().write)); } /// An asynchronous factory method that builds diff --git a/lib/src/generator_frontend.dart b/lib/src/generator_frontend.dart index e1bca8e7ca..b5ddfb711c 100644 --- a/lib/src/generator_frontend.dart +++ b/lib/src/generator_frontend.dart @@ -12,12 +12,10 @@ import 'package:dartdoc/src/model_utils.dart'; import 'package:dartdoc/src/warnings.dart'; import 'package:path/path.dart' as path; -abstract class FileWriter { - File write(String filePath, Object content, - {bool allowOverwrite, Warnable element}); -} +typedef FileWriter = File Function(String filePath, Object content, + {bool allowOverwrite, Warnable element}); -class GeneratorFrontEnd implements Generator, FileWriter { +class GeneratorFrontEnd implements Generator { final GeneratorBackend _generatorBackend; final FileWriter _writer; @@ -34,13 +32,13 @@ class GeneratorFrontEnd implements Generator, FileWriter { GeneratorFrontEnd(this._generatorBackend, this._writer); - @override + // Implement FileWriter so we can wrap the one given to us. File write(String filePath, Object content, {bool allowOverwrite, Warnable element}) { assert(_outputDirectory != null); // Replaces '/' separators with proper separators for the platform. String outFile = path.joinAll(filePath.split('/')); outFile = path.join(_outputDirectory, outFile); - File file = _writer.write(outFile, content, allowOverwrite: allowOverwrite, element: element); + File file = _writer(outFile, content, allowOverwrite: allowOverwrite, element: element); _onFileCreated.add(file); writtenFiles[file.path] = element; return file; @@ -52,14 +50,14 @@ class GeneratorFrontEnd implements Generator, FileWriter { try { List indexElements = []; _generateDocs(packageGraph, indexElements); - await _generatorBackend.generateAdditionalFiles(this, packageGraph); + await _generatorBackend.generateAdditionalFiles(write, packageGraph); List categories = indexElements .where((e) => e is Categorization && e.hasCategorization) .map((e) => e as Categorization) .toList(); - _generatorBackend.generateCategoryJson(this, categories); - _generatorBackend.generateSearchIndex(this, indexElements); + _generatorBackend.generateCategoryJson(write, categories); + _generatorBackend.generateSearchIndex(write, indexElements); } finally { _outputDirectory = null; } @@ -71,14 +69,14 @@ class GeneratorFrontEnd implements Generator, FileWriter { logInfo('documenting ${packageGraph.defaultPackage.name}'); _generatorBackend.generatePackage( - this, packageGraph, packageGraph.defaultPackage); + write, 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(this, packageGraph, category); + _generatorBackend.generateCategory(write, packageGraph, category); } for (var lib in filterNonDocumented(package.libraries)) { @@ -88,18 +86,18 @@ class GeneratorFrontEnd implements Generator, FileWriter { packageGraph.warnOnElement(lib, PackageWarning.noLibraryLevelDocs); } indexAccumulator.add(lib); - _generatorBackend.generateLibrary(this, packageGraph, lib); + _generatorBackend.generateLibrary(write, packageGraph, lib); for (var clazz in filterNonDocumented(lib.allClasses)) { indexAccumulator.add(clazz); - _generatorBackend.generateClass(this, packageGraph, lib, clazz); + _generatorBackend.generateClass(write, packageGraph, lib, clazz); for (var constructor in filterNonDocumented(clazz.constructors)) { if (!constructor.isCanonical) continue; indexAccumulator.add(constructor); _generatorBackend.generateConstructor( - this, packageGraph, lib, clazz, constructor); + write, packageGraph, lib, clazz, constructor); } for (var constant in filterNonDocumented(clazz.constants)) { @@ -107,7 +105,7 @@ class GeneratorFrontEnd implements Generator, FileWriter { indexAccumulator.add(constant); _generatorBackend.generateConstant( - this, packageGraph, lib, clazz, constant); + write, packageGraph, lib, clazz, constant); } for (var property in filterNonDocumented(clazz.staticProperties)) { @@ -115,7 +113,7 @@ class GeneratorFrontEnd implements Generator, FileWriter { indexAccumulator.add(property); _generatorBackend.generateProperty( - this, packageGraph, lib, clazz, property); + write, packageGraph, lib, clazz, property); } for (var property in filterNonDocumented(clazz.allInstanceFields)) { @@ -123,7 +121,7 @@ class GeneratorFrontEnd implements Generator, FileWriter { indexAccumulator.add(property); _generatorBackend.generateProperty( - this, packageGraph, lib, clazz, property); + write, packageGraph, lib, clazz, property); } for (var method in filterNonDocumented(clazz.allInstanceMethods)) { @@ -131,7 +129,7 @@ class GeneratorFrontEnd implements Generator, FileWriter { indexAccumulator.add(method); _generatorBackend.generateMethod( - this, packageGraph, lib, clazz, method); + write, packageGraph, lib, clazz, method); } for (var operator in filterNonDocumented(clazz.allOperators)) { @@ -139,7 +137,7 @@ class GeneratorFrontEnd implements Generator, FileWriter { indexAccumulator.add(operator); _generatorBackend.generateMethod( - this, packageGraph, lib, clazz, operator); + write, packageGraph, lib, clazz, operator); } for (var method in filterNonDocumented(clazz.staticMethods)) { @@ -147,72 +145,72 @@ class GeneratorFrontEnd implements Generator, FileWriter { indexAccumulator.add(method); _generatorBackend.generateMethod( - this, packageGraph, lib, clazz, method); + write, packageGraph, lib, clazz, method); } } for (var extension in filterNonDocumented(lib.extensions)) { indexAccumulator.add(extension); _generatorBackend.generateExtension( - this, packageGraph, lib, extension); + write, packageGraph, lib, extension); for (var constant in filterNonDocumented(extension.constants)) { indexAccumulator.add(constant); _generatorBackend.generateConstant( - this, packageGraph, lib, extension, constant); + write, packageGraph, lib, extension, constant); } for (var property in filterNonDocumented(extension.staticProperties)) { indexAccumulator.add(property); _generatorBackend.generateProperty( - this, packageGraph, lib, extension, property); + write, packageGraph, lib, extension, property); } for (var method in filterNonDocumented(extension.allPublicInstanceMethods)) { indexAccumulator.add(method); _generatorBackend.generateMethod( - this, packageGraph, lib, extension, method); + write, packageGraph, lib, extension, method); } for (var method in filterNonDocumented(extension.staticMethods)) { indexAccumulator.add(method); _generatorBackend.generateMethod( - this, packageGraph, lib, extension, method); + write, packageGraph, lib, extension, method); } for (var operator in filterNonDocumented(extension.allOperators)) { indexAccumulator.add(operator); _generatorBackend.generateMethod( - this, packageGraph, lib, extension, operator); + write, packageGraph, lib, extension, operator); } for (var property in filterNonDocumented(extension.allInstanceFields)) { indexAccumulator.add(property); _generatorBackend.generateProperty( - this, packageGraph, lib, extension, property); + write, packageGraph, lib, extension, property); } } for (var mixin in filterNonDocumented(lib.mixins)) { indexAccumulator.add(mixin); - _generatorBackend.generateMixin(this, packageGraph, lib, mixin); + _generatorBackend.generateMixin(write, packageGraph, lib, mixin); for (var constructor in filterNonDocumented(mixin.constructors)) { if (!constructor.isCanonical) continue; indexAccumulator.add(constructor); _generatorBackend.generateConstructor( - this, packageGraph, lib, mixin, constructor); + write, packageGraph, lib, mixin, constructor); } for (var constant in filterNonDocumented(mixin.constants)) { if (!constant.isCanonical) continue; indexAccumulator.add(constant); _generatorBackend.generateConstant( - this, packageGraph, lib, mixin, constant); + write, packageGraph, lib, mixin, constant); } for (var property in filterNonDocumented(mixin.staticProperties)) { @@ -220,7 +218,7 @@ class GeneratorFrontEnd implements Generator, FileWriter { indexAccumulator.add(property); _generatorBackend.generateConstant( - this, packageGraph, lib, mixin, property); + write, packageGraph, lib, mixin, property); } for (var property in filterNonDocumented(mixin.allInstanceFields)) { @@ -228,7 +226,7 @@ class GeneratorFrontEnd implements Generator, FileWriter { indexAccumulator.add(property); _generatorBackend.generateConstant( - this, packageGraph, lib, mixin, property); + write, packageGraph, lib, mixin, property); } for (var method in filterNonDocumented(mixin.allInstanceMethods)) { @@ -236,7 +234,7 @@ class GeneratorFrontEnd implements Generator, FileWriter { indexAccumulator.add(method); _generatorBackend.generateMethod( - this, packageGraph, lib, mixin, method); + write, packageGraph, lib, mixin, method); } for (var operator in filterNonDocumented(mixin.allOperators)) { @@ -244,7 +242,7 @@ class GeneratorFrontEnd implements Generator, FileWriter { indexAccumulator.add(operator); _generatorBackend.generateMethod( - this, packageGraph, lib, mixin, operator); + write, packageGraph, lib, mixin, operator); } for (var method in filterNonDocumented(mixin.staticMethods)) { @@ -252,51 +250,52 @@ class GeneratorFrontEnd implements Generator, FileWriter { indexAccumulator.add(method); _generatorBackend.generateMethod( - this, packageGraph, lib, mixin, method); + write, packageGraph, lib, mixin, method); } } for (var eNum in filterNonDocumented(lib.enums)) { indexAccumulator.add(eNum); - _generatorBackend.generateEnum(this, packageGraph, lib, eNum); + _generatorBackend.generateEnum(write, packageGraph, lib, eNum); for (var property in filterNonDocumented(eNum.allInstanceFields)) { indexAccumulator.add(property); _generatorBackend.generateConstant( - this, packageGraph, lib, eNum, property); + write, packageGraph, lib, eNum, property); } for (var operator in filterNonDocumented(eNum.allOperators)) { indexAccumulator.add(operator); _generatorBackend.generateMethod( - this, packageGraph, lib, eNum, operator); + write, packageGraph, lib, eNum, operator); } for (var method in filterNonDocumented(eNum.allInstanceMethods)) { indexAccumulator.add(method); _generatorBackend.generateMethod( - this, packageGraph, lib, eNum, method); + write, packageGraph, lib, eNum, method); } } for (var constant in filterNonDocumented(lib.constants)) { indexAccumulator.add(constant); _generatorBackend.generateTopLevelConstant( - this, packageGraph, lib, constant); + write, packageGraph, lib, constant); } for (var property in filterNonDocumented(lib.properties)) { indexAccumulator.add(property); _generatorBackend.generateTopLevelProperty( - this, packageGraph, lib, property); + write, packageGraph, lib, property); } for (var function in filterNonDocumented(lib.functions)) { indexAccumulator.add(function); - _generatorBackend.generateFunction(this, packageGraph, lib, function); + _generatorBackend.generateFunction( + write, packageGraph, lib, function); } for (var typeDef in filterNonDocumented(lib.typedefs)) { indexAccumulator.add(typeDef); - _generatorBackend.generateTypeDef(this, packageGraph, lib, typeDef); + _generatorBackend.generateTypeDef(write, packageGraph, lib, typeDef); } } } diff --git a/lib/src/html/html_generator_backend.dart b/lib/src/html/html_generator_backend.dart index 68994c1dc2..903b595c97 100644 --- a/lib/src/html/html_generator_backend.dart +++ b/lib/src/html/html_generator_backend.dart @@ -56,7 +56,7 @@ class HtmlGeneratorBackend implements GeneratorBackend { if (!_options.useBaseHref) { content = content.replaceAll(HTMLBASE_PLACEHOLDER, data.htmlBase); } - writer.write(filename, content); + writer(filename, content); } @override @@ -66,7 +66,7 @@ class HtmlGeneratorBackend implements GeneratorBackend { if (!_options.useBaseHref) { json = json.replaceAll(HTMLBASE_PLACEHOLDER, ''); } - writer.write(path.join('categories.json'), '${json}\n'); + writer(path.join('categories.json'), '${json}\n'); } @override @@ -77,7 +77,7 @@ class HtmlGeneratorBackend implements GeneratorBackend { if (!_options.useBaseHref) { json = json.replaceAll(HTMLBASE_PLACEHOLDER, ''); } - writer.write(path.join('index.json'), '${json}\n'); + writer(path.join('index.json'), '${json}\n'); } @override @@ -202,7 +202,7 @@ class HtmlGeneratorBackend implements GeneratorBackend { if (_options.favicon != null) { var bytes = File(_options.favicon).readAsBytesSync(); // Allow overwrite of favicon. - writer.write(path.join('static-assets', 'favicon.png'), bytes, + writer(path.join('static-assets', 'favicon.png'), bytes, allowOverwrite: true); } } @@ -216,7 +216,7 @@ class HtmlGeneratorBackend implements GeneratorBackend { 'encountered $resourcePath'); } String destFileName = resourcePath.substring(prefix.length); - writer.write(path.join('static-assets', destFileName), + writer(path.join('static-assets', destFileName), await loader.loadAsBytes(resourcePath)); } } diff --git a/test/html_generator_test.dart b/test/html_generator_test.dart index db3af7fd04..da862ae6c2 100644 --- a/test/html_generator_test.dart +++ b/test/html_generator_test.dart @@ -18,6 +18,12 @@ import 'package:test/test.dart'; import 'src/utils.dart' as utils; +// Helper to init a generator without a GeneratorContext +Future initHtmlGenerator(FileWriter writer) async { + var backend = HtmlGeneratorBackend(null, await Templates.createDefault()); + return GeneratorFrontEnd(backend, writer); +} + void main() { group('Templates', () { Templates templates; @@ -74,9 +80,7 @@ void main() { Directory tempOutput; setUp(() async { - HtmlGeneratorBackend backend = - HtmlGeneratorBackend(null, await Templates.createDefault()); - generator = GeneratorFrontEnd(backend, DartdocFileWriter()); + generator = await initHtmlGenerator(DartdocFileWriter().write); tempOutput = Directory.systemTemp.createTempSync('doc_test_temp'); return generator.generate(null, tempOutput.path); }); @@ -100,12 +104,12 @@ void main() { }); group('for a package that causes duplicate files', () { - HtmlGenerator generator; + Generator generator; PackageGraph packageGraph; Directory tempOutput; setUp(() async { - generator = await HtmlGenerator.create(); + generator = await initHtmlGenerator(DartdocFileWriter().write); packageGraph = await utils .bootBasicPackage(utils.testPackageDuplicateDir.path, []); tempOutput = await Directory.systemTemp.createTemp('doc_test_temp'); From cab3e575b17d5a320781672252579202a1ba86b4 Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Tue, 7 Jan 2020 12:56:02 -0800 Subject: [PATCH 08/12] dartfmt and misc fixes --- lib/dartdoc.dart | 15 ++++++------ lib/src/generator.dart | 3 ++- lib/src/generator_frontend.dart | 15 ++++++++---- lib/src/generator_utils.dart | 4 ++-- lib/src/html/html_generator_backend.dart | 30 +++++++++++++++++------- test/html_generator_test.dart | 12 +++++----- 6 files changed, 50 insertions(+), 29 deletions(-) diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart index 49b2f04d08..43971a619c 100644 --- a/lib/dartdoc.dart +++ b/lib/dartdoc.dart @@ -45,18 +45,18 @@ class DartdocGeneratorOptionContext extends DartdocOptionContext } class DartdocFileWriter { - // Track written files so we don't need to use File.existsSync(). - final Map writtenFiles = {}; + final Map _writtenFiles = {}; File write(String filePath, Object content, {bool allowOverwrite, Warnable element}) { allowOverwrite ??= false; if (!allowOverwrite) { - if (writtenFiles.containsKey(filePath)) { + if (_writtenFiles.containsKey(filePath)) { assert(element != null, 'Attempted overwrite of ${filePath} without corresponding element'); - Warnable originalElement = writtenFiles[filePath]; - Iterable referredFrom = originalElement != null ? [originalElement] : null; + Warnable originalElement = _writtenFiles[filePath]; + Iterable referredFrom = + originalElement != null ? [originalElement] : null; element?.warn(PackageWarning.duplicateFile, message: filePath, referredFrom: referredFrom); } @@ -76,7 +76,7 @@ class DartdocFileWriter { throw ArgumentError.value( content, 'content', '`content` must be `String` or `List`.'); } - writtenFiles[filePath] = element; + _writtenFiles[filePath] = element; return file; } } @@ -94,7 +94,6 @@ class Dartdoc extends PackageBuilder { Dartdoc._(DartdocOptionContext config, this.generator) : super(config) { outputDir = Directory(config.output)..createSync(recursive: true); - generator?.onFileCreated?.listen(logProgress); } /// An asynchronous factory method that builds Dartdoc's file writers @@ -129,7 +128,9 @@ class Dartdoc extends PackageBuilder { "in ${seconds.toStringAsFixed(1)} seconds"); _stopwatch.reset(); + final generator = this.generator; if (generator != null) { + generator.onFileCreated.listen(logProgress); // Create the out directory. if (!outputDir.existsSync()) outputDir.createSync(recursive: true); diff --git a/lib/src/generator.dart b/lib/src/generator.dart index 6484f3eb4e..6ea61e18ab 100644 --- a/lib/src/generator.dart +++ b/lib/src/generator.dart @@ -27,7 +27,8 @@ abstract class Generator { /// Fires when a file is created. Stream get onFileCreated; - final Map writtenFiles = {}; + /// Fetches all filenames written by this generator. + Map get writtenFiles; } /// Dartdoc options related to generators generally. diff --git a/lib/src/generator_frontend.dart b/lib/src/generator_frontend.dart index b5ddfb711c..69d2caa8fc 100644 --- a/lib/src/generator_frontend.dart +++ b/lib/src/generator_frontend.dart @@ -15,6 +15,8 @@ import 'package:path/path.dart' as path; typedef FileWriter = File Function(String filePath, Object content, {bool allowOverwrite, Warnable element}); +/// [Generator] that delegates rendering to a [GeneratorBackend] and delegates +/// file creation to a [FileWriter]. class GeneratorFrontEnd implements Generator { final GeneratorBackend _generatorBackend; final FileWriter _writer; @@ -33,14 +35,16 @@ class GeneratorFrontEnd implements Generator { GeneratorFrontEnd(this._generatorBackend, this._writer); // Implement FileWriter so we can wrap the one given to us. - File write(String filePath, Object content, {bool allowOverwrite, Warnable element}) { + File write(String filePath, Object content, + {bool allowOverwrite, Warnable element}) { assert(_outputDirectory != null); - // Replaces '/' separators with proper separators for the platform. + // Replace '/' separators with proper separators for the platform. String outFile = path.joinAll(filePath.split('/')); outFile = path.join(_outputDirectory, outFile); - File file = _writer(outFile, content, allowOverwrite: allowOverwrite, element: element); + File file = _writer(outFile, content, + allowOverwrite: allowOverwrite, element: element); + writtenFiles[outFile] = element; _onFileCreated.add(file); - writtenFiles[file.path] = element; return file; } @@ -63,6 +67,7 @@ class GeneratorFrontEnd implements Generator { } } + // Traverses the package graph and collects elements for the search index. void _generateDocs( PackageGraph packageGraph, List indexAccumulator) { if (packageGraph == null) return; @@ -306,7 +311,7 @@ abstract class GeneratorBackend { /// Emit json describing the [categories] defined by the package. void generateCategoryJson(FileWriter writer, List categories); - /// Emit json cataloging [indexedElements] for use with a search index. + /// Emit json catalog of [indexedElements] for use with a search index. void generateSearchIndex(FileWriter writer, List indexedElements); /// Emit documentation content for the [package]. diff --git a/lib/src/generator_utils.dart b/lib/src/generator_utils.dart index 9c838eef5f..43ec129fa3 100644 --- a/lib/src/generator_utils.dart +++ b/lib/src/generator_utils.dart @@ -11,8 +11,8 @@ 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 cateories) { - var encoder = JsonEncoder.withIndent(' '); +String generateCategoryJson(Iterable cateories, bool pretty) { + var encoder = pretty ? JsonEncoder.withIndent(' ') : JsonEncoder(); final List indexItems = cateories.map((Categorization e) { Map data = { 'name': e.name, diff --git a/lib/src/html/html_generator_backend.dart b/lib/src/html/html_generator_backend.dart index 903b595c97..f72c04aada 100644 --- a/lib/src/html/html_generator_backend.dart +++ b/lib/src/html/html_generator_backend.dart @@ -14,9 +14,11 @@ 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; @@ -25,12 +27,20 @@ class HtmlBackendOptions implements HtmlOptions { final String favicon; + final bool prettyIndexJson; + @override final bool useBaseHref; - HtmlBackendOptions({this.relCanonicalPrefix, this.toolVersion, this.favicon, this.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; @@ -38,10 +48,14 @@ class HtmlGeneratorBackend implements GeneratorBackend { 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); } @@ -56,13 +70,15 @@ class HtmlGeneratorBackend implements GeneratorBackend { if (!_options.useBaseHref) { content = content.replaceAll(HTMLBASE_PLACEHOLDER, data.htmlBase); } - writer(filename, content); + writer(filename, content, + element: data.self is Warnable ? data.self : null); } @override void generateCategoryJson( FileWriter writer, List categories) { - String json = generator_util.generateCategoryJson(categories); + String json = generator_util.generateCategoryJson( + categories, _options.prettyIndexJson); if (!_options.useBaseHref) { json = json.replaceAll(HTMLBASE_PLACEHOLDER, ''); } @@ -71,9 +87,8 @@ class HtmlGeneratorBackend implements GeneratorBackend { @override void generateSearchIndex(FileWriter writer, List indexedElements) { - // TODO pretty-index-json from generator options - String json = - generator_util.generateSearchIndexJson(indexedElements, false); + String json = generator_util.generateSearchIndexJson( + indexedElements, _options.prettyIndexJson); if (!_options.useBaseHref) { json = json.replaceAll(HTMLBASE_PLACEHOLDER, ''); } @@ -200,14 +215,13 @@ class HtmlGeneratorBackend implements GeneratorBackend { void generateAdditionalFiles(FileWriter writer, PackageGraph graph) async { await _copyResources(writer); if (_options.favicon != null) { - var bytes = File(_options.favicon).readAsBytesSync(); // Allow overwrite of favicon. + var bytes = File(_options.favicon).readAsBytesSync(); writer(path.join('static-assets', 'favicon.png'), bytes, allowOverwrite: true); } } - // TODO: change this to use resource_loader Future _copyResources(FileWriter writer) async { final prefix = 'package:dartdoc/resources/'; for (String resourcePath in resources.resource_names) { diff --git a/test/html_generator_test.dart b/test/html_generator_test.dart index da862ae6c2..93c4bd7daf 100644 --- a/test/html_generator_test.dart +++ b/test/html_generator_test.dart @@ -18,10 +18,10 @@ import 'package:test/test.dart'; import 'src/utils.dart' as utils; -// Helper to init a generator without a GeneratorContext -Future initHtmlGenerator(FileWriter writer) async { +// 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, writer); + return GeneratorFrontEnd(backend, DartdocFileWriter().write); } void main() { @@ -80,7 +80,7 @@ void main() { Directory tempOutput; setUp(() async { - generator = await initHtmlGenerator(DartdocFileWriter().write); + generator = await _initGeneratorForTest(); tempOutput = Directory.systemTemp.createTempSync('doc_test_temp'); return generator.generate(null, tempOutput.path); }); @@ -109,7 +109,7 @@ void main() { Directory tempOutput; setUp(() async { - generator = await initHtmlGenerator(DartdocFileWriter().write); + generator = await _initGeneratorForTest(); packageGraph = await utils .bootBasicPackage(utils.testPackageDuplicateDir.path, []); tempOutput = await Directory.systemTemp.createTemp('doc_test_temp'); @@ -126,7 +126,7 @@ void main() { expect(generator, isNotNull); expect(tempOutput, isNotNull); String expectedPath = - path.join('aDuplicate', 'aDuplicate-library.html'); + path.join(tempOutput.path, 'aDuplicate', 'aDuplicate-library.html'); expect( packageGraph.localPublicLibraries, anyElement((l) => packageGraph.packageWarningCounter From a80abc492df1eda17552316c026739f17da7a6c1 Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Wed, 8 Jan 2020 09:23:40 -0800 Subject: [PATCH 09/12] Fix typo in parameter name --- lib/src/generator_utils.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/generator_utils.dart b/lib/src/generator_utils.dart index 43ec129fa3..59fa1289ac 100644 --- a/lib/src/generator_utils.dart +++ b/lib/src/generator_utils.dart @@ -11,9 +11,9 @@ 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 cateories, bool pretty) { +String generateCategoryJson(Iterable categories, bool pretty) { var encoder = pretty ? JsonEncoder.withIndent(' ') : JsonEncoder(); - final List indexItems = cateories.map((Categorization e) { + final List indexItems = categories.map((Categorization e) { Map data = { 'name': e.name, 'qualifiedName': e.fullyQualifiedName, From 5626b1bc872bb7e05121d6a2b40501b6af8e3b9a Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Wed, 8 Jan 2020 10:19:51 -0800 Subject: [PATCH 10/12] Use whereType --- lib/src/generator_frontend.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/generator_frontend.dart b/lib/src/generator_frontend.dart index 69d2caa8fc..77f93b79e5 100644 --- a/lib/src/generator_frontend.dart +++ b/lib/src/generator_frontend.dart @@ -57,8 +57,8 @@ class GeneratorFrontEnd implements Generator { await _generatorBackend.generateAdditionalFiles(write, packageGraph); List categories = indexElements - .where((e) => e is Categorization && e.hasCategorization) - .map((e) => e as Categorization) + .whereType() + .where((e) => e.hasCategorization) .toList(); _generatorBackend.generateCategoryJson(write, categories); _generatorBackend.generateSearchIndex(write, indexElements); From be7530f26b22dd9529515c3575452df7c237120f Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Thu, 9 Jan 2020 07:55:41 -0800 Subject: [PATCH 11/12] Restructure FileWriter usage Make FileWriter an abstract class and give it the responsibility for recording written filenames. This removes the FileWriter wrapping in GeneratorFrontEnd. --- lib/dartdoc.dart | 41 ++++--- lib/src/empty_generator.dart | 19 +--- lib/src/generator.dart | 21 ++-- lib/src/generator_frontend.dart | 130 +++++++++-------------- lib/src/html/html_generator.dart | 5 +- lib/src/html/html_generator_backend.dart | 10 +- test/html_generator_test.dart | 10 +- 7 files changed, 105 insertions(+), 131 deletions(-) diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart index 43971a619c..721bcec079 100644 --- a/lib/dartdoc.dart +++ b/lib/dartdoc.dart @@ -44,25 +44,35 @@ class DartdocGeneratorOptionContext extends DartdocOptionContext : super(optionSet, dir); } -class DartdocFileWriter { - final Map _writtenFiles = {}; +class DartdocFileWriter implements FileWriter { + final String outputDir; + final Map _fileElementMap = {}; + @override + final Set writtenFiles = Set(); - File write(String filePath, Object content, + 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 (_writtenFiles.containsKey(filePath)) { + if (_fileElementMap.containsKey(outFile)) { assert(element != null, - 'Attempted overwrite of ${filePath} without corresponding element'); - Warnable originalElement = _writtenFiles[filePath]; + 'Attempted overwrite of ${outFile} without corresponding element'); + Warnable originalElement = _fileElementMap[outFile]; Iterable referredFrom = originalElement != null ? [originalElement] : null; element?.warn(PackageWarning.duplicateFile, - message: filePath, referredFrom: referredFrom); + message: outFile, referredFrom: referredFrom); } } + _fileElementMap[outFile] = element; - var file = File(filePath); + var file = File(path.join(outputDir, outFile)); var parent = file.parent; if (!parent.existsSync()) { parent.createSync(recursive: true); @@ -76,8 +86,9 @@ class DartdocFileWriter { throw ArgumentError.value( content, 'content', '`content` must be `String` or `List`.'); } - _writtenFiles[filePath] = element; - return file; + + writtenFiles.add(outFile); + logProgress(outFile); } } @@ -100,8 +111,7 @@ class Dartdoc extends PackageBuilder { /// and returns a Dartdoc object with them. static Future withDefaultGenerators( DartdocGeneratorOptionContext config) async { - return Dartdoc._( - config, await initHtmlGenerator(config, DartdocFileWriter().write)); + return Dartdoc._(config, await initHtmlGenerator(config)); } /// An asynchronous factory method that builds @@ -130,12 +140,13 @@ class Dartdoc extends PackageBuilder { final generator = this.generator; if (generator != null) { - generator.onFileCreated.listen(logProgress); // Create the out directory. if (!outputDir.existsSync()) outputDir.createSync(recursive: true); - 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); } diff --git a/lib/src/empty_generator.dart b/lib/src/empty_generator.dart index d6f2cb3aa8..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,31 +14,21 @@ 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); - - /// Implementation fires on each model element processed rather than - /// file creation. - @override - Stream get onFileCreated => _onFileCreated.stream; - - @override - final Map writtenFiles = {}; } Future initEmptyGenerator(DartdocOptionContext config) async { 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 index 77f93b79e5..ff65f1e172 100644 --- a/lib/src/generator_frontend.dart +++ b/lib/src/generator_frontend.dart @@ -12,76 +12,42 @@ import 'package:dartdoc/src/model_utils.dart'; import 'package:dartdoc/src/warnings.dart'; import 'package:path/path.dart' as path; -typedef FileWriter = File Function(String filePath, Object content, - {bool allowOverwrite, Warnable element}); - /// [Generator] that delegates rendering to a [GeneratorBackend] and delegates /// file creation to a [FileWriter]. class GeneratorFrontEnd implements Generator { final GeneratorBackend _generatorBackend; - final FileWriter _writer; - - // Used in write(). This being not null signals the generator is active. - String _outputDirectory; - - final StreamController _onFileCreated = StreamController(sync: true); - - @override - Stream get onFileCreated => _onFileCreated.stream; - @override - final Map writtenFiles = {}; - - GeneratorFrontEnd(this._generatorBackend, this._writer); - - // Implement FileWriter so we can wrap the one given to us. - File write(String filePath, Object content, - {bool allowOverwrite, Warnable element}) { - assert(_outputDirectory != null); - // Replace '/' separators with proper separators for the platform. - String outFile = path.joinAll(filePath.split('/')); - outFile = path.join(_outputDirectory, outFile); - File file = _writer(outFile, content, - allowOverwrite: allowOverwrite, element: element); - writtenFiles[outFile] = element; - _onFileCreated.add(file); - return file; - } + GeneratorFrontEnd(this._generatorBackend); @override - Future generate(PackageGraph packageGraph, String outputDirectory) async { - _outputDirectory = outputDirectory; - try { - List indexElements = []; - _generateDocs(packageGraph, indexElements); - await _generatorBackend.generateAdditionalFiles(write, packageGraph); - - List categories = indexElements - .whereType() - .where((e) => e.hasCategorization) - .toList(); - _generatorBackend.generateCategoryJson(write, categories); - _generatorBackend.generateSearchIndex(write, indexElements); - } finally { - _outputDirectory = null; - } + 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, List indexAccumulator) { + void _generateDocs(PackageGraph packageGraph, FileWriter writer, + List indexAccumulator) { if (packageGraph == null) return; logInfo('documenting ${packageGraph.defaultPackage.name}'); _generatorBackend.generatePackage( - write, packageGraph, packageGraph.defaultPackage); + 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(write, packageGraph, category); + _generatorBackend.generateCategory(writer, packageGraph, category); } for (var lib in filterNonDocumented(package.libraries)) { @@ -91,18 +57,18 @@ class GeneratorFrontEnd implements Generator { packageGraph.warnOnElement(lib, PackageWarning.noLibraryLevelDocs); } indexAccumulator.add(lib); - _generatorBackend.generateLibrary(write, packageGraph, lib); + _generatorBackend.generateLibrary(writer, packageGraph, lib); for (var clazz in filterNonDocumented(lib.allClasses)) { indexAccumulator.add(clazz); - _generatorBackend.generateClass(write, packageGraph, lib, clazz); + _generatorBackend.generateClass(writer, packageGraph, lib, clazz); for (var constructor in filterNonDocumented(clazz.constructors)) { if (!constructor.isCanonical) continue; indexAccumulator.add(constructor); _generatorBackend.generateConstructor( - write, packageGraph, lib, clazz, constructor); + writer, packageGraph, lib, clazz, constructor); } for (var constant in filterNonDocumented(clazz.constants)) { @@ -110,7 +76,7 @@ class GeneratorFrontEnd implements Generator { indexAccumulator.add(constant); _generatorBackend.generateConstant( - write, packageGraph, lib, clazz, constant); + writer, packageGraph, lib, clazz, constant); } for (var property in filterNonDocumented(clazz.staticProperties)) { @@ -118,7 +84,7 @@ class GeneratorFrontEnd implements Generator { indexAccumulator.add(property); _generatorBackend.generateProperty( - write, packageGraph, lib, clazz, property); + writer, packageGraph, lib, clazz, property); } for (var property in filterNonDocumented(clazz.allInstanceFields)) { @@ -126,7 +92,7 @@ class GeneratorFrontEnd implements Generator { indexAccumulator.add(property); _generatorBackend.generateProperty( - write, packageGraph, lib, clazz, property); + writer, packageGraph, lib, clazz, property); } for (var method in filterNonDocumented(clazz.allInstanceMethods)) { @@ -134,7 +100,7 @@ class GeneratorFrontEnd implements Generator { indexAccumulator.add(method); _generatorBackend.generateMethod( - write, packageGraph, lib, clazz, method); + writer, packageGraph, lib, clazz, method); } for (var operator in filterNonDocumented(clazz.allOperators)) { @@ -142,7 +108,7 @@ class GeneratorFrontEnd implements Generator { indexAccumulator.add(operator); _generatorBackend.generateMethod( - write, packageGraph, lib, clazz, operator); + writer, packageGraph, lib, clazz, operator); } for (var method in filterNonDocumented(clazz.staticMethods)) { @@ -150,72 +116,72 @@ class GeneratorFrontEnd implements Generator { indexAccumulator.add(method); _generatorBackend.generateMethod( - write, packageGraph, lib, clazz, method); + writer, packageGraph, lib, clazz, method); } } for (var extension in filterNonDocumented(lib.extensions)) { indexAccumulator.add(extension); _generatorBackend.generateExtension( - write, packageGraph, lib, extension); + writer, packageGraph, lib, extension); for (var constant in filterNonDocumented(extension.constants)) { indexAccumulator.add(constant); _generatorBackend.generateConstant( - write, packageGraph, lib, extension, constant); + writer, packageGraph, lib, extension, constant); } for (var property in filterNonDocumented(extension.staticProperties)) { indexAccumulator.add(property); _generatorBackend.generateProperty( - write, packageGraph, lib, extension, property); + writer, packageGraph, lib, extension, property); } for (var method in filterNonDocumented(extension.allPublicInstanceMethods)) { indexAccumulator.add(method); _generatorBackend.generateMethod( - write, packageGraph, lib, extension, method); + writer, packageGraph, lib, extension, method); } for (var method in filterNonDocumented(extension.staticMethods)) { indexAccumulator.add(method); _generatorBackend.generateMethod( - write, packageGraph, lib, extension, method); + writer, packageGraph, lib, extension, method); } for (var operator in filterNonDocumented(extension.allOperators)) { indexAccumulator.add(operator); _generatorBackend.generateMethod( - write, packageGraph, lib, extension, operator); + writer, packageGraph, lib, extension, operator); } for (var property in filterNonDocumented(extension.allInstanceFields)) { indexAccumulator.add(property); _generatorBackend.generateProperty( - write, packageGraph, lib, extension, property); + writer, packageGraph, lib, extension, property); } } for (var mixin in filterNonDocumented(lib.mixins)) { indexAccumulator.add(mixin); - _generatorBackend.generateMixin(write, packageGraph, lib, mixin); + _generatorBackend.generateMixin(writer, packageGraph, lib, mixin); for (var constructor in filterNonDocumented(mixin.constructors)) { if (!constructor.isCanonical) continue; indexAccumulator.add(constructor); _generatorBackend.generateConstructor( - write, packageGraph, lib, mixin, constructor); + writer, packageGraph, lib, mixin, constructor); } for (var constant in filterNonDocumented(mixin.constants)) { if (!constant.isCanonical) continue; indexAccumulator.add(constant); _generatorBackend.generateConstant( - write, packageGraph, lib, mixin, constant); + writer, packageGraph, lib, mixin, constant); } for (var property in filterNonDocumented(mixin.staticProperties)) { @@ -223,7 +189,7 @@ class GeneratorFrontEnd implements Generator { indexAccumulator.add(property); _generatorBackend.generateConstant( - write, packageGraph, lib, mixin, property); + writer, packageGraph, lib, mixin, property); } for (var property in filterNonDocumented(mixin.allInstanceFields)) { @@ -231,7 +197,7 @@ class GeneratorFrontEnd implements Generator { indexAccumulator.add(property); _generatorBackend.generateConstant( - write, packageGraph, lib, mixin, property); + writer, packageGraph, lib, mixin, property); } for (var method in filterNonDocumented(mixin.allInstanceMethods)) { @@ -239,7 +205,7 @@ class GeneratorFrontEnd implements Generator { indexAccumulator.add(method); _generatorBackend.generateMethod( - write, packageGraph, lib, mixin, method); + writer, packageGraph, lib, mixin, method); } for (var operator in filterNonDocumented(mixin.allOperators)) { @@ -247,7 +213,7 @@ class GeneratorFrontEnd implements Generator { indexAccumulator.add(operator); _generatorBackend.generateMethod( - write, packageGraph, lib, mixin, operator); + writer, packageGraph, lib, mixin, operator); } for (var method in filterNonDocumented(mixin.staticMethods)) { @@ -255,52 +221,52 @@ class GeneratorFrontEnd implements Generator { indexAccumulator.add(method); _generatorBackend.generateMethod( - write, packageGraph, lib, mixin, method); + writer, packageGraph, lib, mixin, method); } } for (var eNum in filterNonDocumented(lib.enums)) { indexAccumulator.add(eNum); - _generatorBackend.generateEnum(write, packageGraph, lib, eNum); + _generatorBackend.generateEnum(writer, packageGraph, lib, eNum); for (var property in filterNonDocumented(eNum.allInstanceFields)) { indexAccumulator.add(property); _generatorBackend.generateConstant( - write, packageGraph, lib, eNum, property); + writer, packageGraph, lib, eNum, property); } for (var operator in filterNonDocumented(eNum.allOperators)) { indexAccumulator.add(operator); _generatorBackend.generateMethod( - write, packageGraph, lib, eNum, operator); + writer, packageGraph, lib, eNum, operator); } for (var method in filterNonDocumented(eNum.allInstanceMethods)) { indexAccumulator.add(method); _generatorBackend.generateMethod( - write, packageGraph, lib, eNum, method); + writer, packageGraph, lib, eNum, method); } } for (var constant in filterNonDocumented(lib.constants)) { indexAccumulator.add(constant); _generatorBackend.generateTopLevelConstant( - write, packageGraph, lib, constant); + writer, packageGraph, lib, constant); } for (var property in filterNonDocumented(lib.properties)) { indexAccumulator.add(property); _generatorBackend.generateTopLevelProperty( - write, packageGraph, lib, property); + writer, packageGraph, lib, property); } for (var function in filterNonDocumented(lib.functions)) { indexAccumulator.add(function); _generatorBackend.generateFunction( - write, packageGraph, lib, function); + writer, packageGraph, lib, function); } for (var typeDef in filterNonDocumented(lib.typedefs)) { indexAccumulator.add(typeDef); - _generatorBackend.generateTypeDef(write, packageGraph, lib, typeDef); + _generatorBackend.generateTypeDef(writer, packageGraph, lib, typeDef); } } } diff --git a/lib/src/html/html_generator.dart b/lib/src/html/html_generator.dart index fd66bb6e50..1224005aae 100644 --- a/lib/src/html/html_generator.dart +++ b/lib/src/html/html_generator.dart @@ -11,8 +11,7 @@ import 'package:dartdoc/src/generator.dart'; import 'package:dartdoc/src/generator_frontend.dart'; import 'package:dartdoc/src/html/html_generator_backend.dart'; -Future initHtmlGenerator( - GeneratorContext context, FileWriter writer) async { +Future initHtmlGenerator(GeneratorContext context) async { var backend = await HtmlGeneratorBackend.fromContext(context); - return GeneratorFrontEnd(backend, writer); + return GeneratorFrontEnd(backend); } diff --git a/lib/src/html/html_generator_backend.dart b/lib/src/html/html_generator_backend.dart index f72c04aada..da0dfb1fca 100644 --- a/lib/src/html/html_generator_backend.dart +++ b/lib/src/html/html_generator_backend.dart @@ -70,7 +70,7 @@ class HtmlGeneratorBackend implements GeneratorBackend { if (!_options.useBaseHref) { content = content.replaceAll(HTMLBASE_PLACEHOLDER, data.htmlBase); } - writer(filename, content, + writer.write(filename, content, element: data.self is Warnable ? data.self : null); } @@ -82,7 +82,7 @@ class HtmlGeneratorBackend implements GeneratorBackend { if (!_options.useBaseHref) { json = json.replaceAll(HTMLBASE_PLACEHOLDER, ''); } - writer(path.join('categories.json'), '${json}\n'); + writer.write(path.join('categories.json'), '${json}\n'); } @override @@ -92,7 +92,7 @@ class HtmlGeneratorBackend implements GeneratorBackend { if (!_options.useBaseHref) { json = json.replaceAll(HTMLBASE_PLACEHOLDER, ''); } - writer(path.join('index.json'), '${json}\n'); + writer.write(path.join('index.json'), '${json}\n'); } @override @@ -217,7 +217,7 @@ class HtmlGeneratorBackend implements GeneratorBackend { if (_options.favicon != null) { // Allow overwrite of favicon. var bytes = File(_options.favicon).readAsBytesSync(); - writer(path.join('static-assets', 'favicon.png'), bytes, + writer.write(path.join('static-assets', 'favicon.png'), bytes, allowOverwrite: true); } } @@ -230,7 +230,7 @@ class HtmlGeneratorBackend implements GeneratorBackend { 'encountered $resourcePath'); } String destFileName = resourcePath.substring(prefix.length); - writer(path.join('static-assets', destFileName), + writer.write(path.join('static-assets', destFileName), await loader.loadAsBytes(resourcePath)); } } diff --git a/test/html_generator_test.dart b/test/html_generator_test.dart index 93c4bd7daf..bbdb4d8726 100644 --- a/test/html_generator_test.dart +++ b/test/html_generator_test.dart @@ -21,7 +21,7 @@ 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, DartdocFileWriter().write); + return GeneratorFrontEnd(backend); } void main() { @@ -78,11 +78,13 @@ void main() { group('for a null package', () { Generator generator; Directory tempOutput; + FileWriter writer; setUp(() async { 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(() { @@ -107,12 +109,14 @@ void main() { Generator generator; PackageGraph packageGraph; Directory tempOutput; + FileWriter writer; setUp(() async { generator = await _initGeneratorForTest(); packageGraph = await utils .bootBasicPackage(utils.testPackageDuplicateDir.path, []); tempOutput = await Directory.systemTemp.createTemp('doc_test_temp'); + writer = DartdocFileWriter(tempOutput.path); }); tearDown(() { @@ -122,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 = From 948c4042ebad2d1fa230a23452c9b263194d9f57 Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Thu, 9 Jan 2020 08:35:46 -0800 Subject: [PATCH 12/12] Fix duplicate file test --- test/html_generator_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/html_generator_test.dart b/test/html_generator_test.dart index bbdb4d8726..9d29cbd01b 100644 --- a/test/html_generator_test.dart +++ b/test/html_generator_test.dart @@ -130,7 +130,7 @@ void main() { expect(generator, isNotNull); expect(tempOutput, isNotNull); String expectedPath = - path.join(tempOutput.path, 'aDuplicate', 'aDuplicate-library.html'); + path.join('aDuplicate', 'aDuplicate-library.html'); expect( packageGraph.localPublicLibraries, anyElement((l) => packageGraph.packageWarningCounter