Move templates.dart to use ResourceProvider. (#2481)
Move templates.dart to use ResourceProvider.
* **Breaking Change**: Templates.fromDirectory is made private and now requires
a ResourceLoader.
* Templates.createDefault is made `@visibleForTesting` and now requires a
ResourceLoader.
This includes a few minor cleanups as well.
diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart
index cfc2a0e..d1f1002 100644
--- a/lib/dartdoc.dart
+++ b/lib/dartdoc.dart
@@ -50,7 +50,8 @@
class DartdocFileWriter implements FileWriter {
final String outputDir;
- ResourceProvider resourceProvider;
+ @override
+ final ResourceProvider resourceProvider;
final Map<String, Warnable> _fileElementMap = {};
@override
final Set<String> writtenFiles = {};
diff --git a/lib/src/generator/generator.dart b/lib/src/generator/generator.dart
index 4258342..79258ec 100644
--- a/lib/src/generator/generator.dart
+++ b/lib/src/generator/generator.dart
@@ -5,12 +5,15 @@
/// A library containing an abstract documentation generator.
library dartdoc.generator;
+import 'package:analyzer/file_system/file_system.dart';
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';
abstract class FileWriter {
+ ResourceProvider get resourceProvider;
+
/// All filenames written by this generator.
Set<String> get writtenFiles;
diff --git a/lib/src/generator/html_generator.dart b/lib/src/generator/html_generator.dart
index d13c86c..17be599 100644
--- a/lib/src/generator/html_generator.dart
+++ b/lib/src/generator/html_generator.dart
@@ -9,10 +9,9 @@
import 'package:dartdoc/src/generator/generator.dart';
import 'package:dartdoc/src/generator/generator_frontend.dart';
import 'package:dartdoc/src/generator/html_resources.g.dart' as resources;
-import 'package:dartdoc/src/generator/resource_loader.dart' as resource_loader;
+import 'package:dartdoc/src/generator/resource_loader.dart';
import 'package:dartdoc/src/generator/template_data.dart';
import 'package:dartdoc/src/generator/templates.dart';
-import 'package:path/path.dart' as path;
Future<Generator> initHtmlGenerator(
DartdocGeneratorOptionContext context) async {
@@ -43,21 +42,28 @@
// Allow overwrite of favicon.
var bytes =
graph.resourceProvider.getFile(options.favicon).readAsBytesSync();
- writer.write(path.join('static-assets', 'favicon.png'), bytes,
+ writer.write(
+ graph.resourceProvider.pathContext
+ .join('static-assets', 'favicon.png'),
+ bytes,
allowOverwrite: true);
}
}
Future<void> _copyResources(FileWriter writer) async {
- final prefix = 'package:dartdoc/resources/';
+ var resourceLoader = ResourceLoader(writer.resourceProvider);
for (var resourcePath in resources.resource_names) {
- if (!resourcePath.startsWith(prefix)) {
- throw StateError('Resource paths must start with $prefix, '
- 'encountered $resourcePath');
+ if (!resourcePath.startsWith(_dartdocResourcePrefix)) {
+ throw StateError('Resource paths must start with '
+ '$_dartdocResourcePrefix, encountered $resourcePath');
}
- var destFileName = resourcePath.substring(prefix.length);
- writer.write(path.join('static-assets', destFileName),
- await resource_loader.loadAsBytes(resourcePath));
+ var destFileName = resourcePath.substring(_dartdocResourcePrefix.length);
+ var destFilePath = writer.resourceProvider.pathContext
+ .join('static-assets', destFileName);
+ writer.write(
+ destFilePath, await resourceLoader.loadAsBytes(resourcePath));
}
}
+
+ static const _dartdocResourcePrefix = 'package:dartdoc/resources/';
}
diff --git a/lib/src/generator/resource_loader.dart b/lib/src/generator/resource_loader.dart
index 2e4990a..2959699 100644
--- a/lib/src/generator/resource_loader.dart
+++ b/lib/src/generator/resource_loader.dart
@@ -6,35 +6,43 @@
library dartdoc.resource_loader;
import 'dart:convert' show utf8;
-import 'dart:io' show File;
import 'dart:isolate' show Isolate;
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:meta/meta.dart';
-/// Loads a `package:` resource as a String.
-Future<String> loadAsString(String path) async {
- var bytes = await loadAsBytes(path);
+class ResourceLoader {
+ final ResourceProvider provider;
- return utf8.decode(bytes);
-}
+ ResourceLoader(this.provider);
-/// Loads a `package:` resource as an [List<int>].
-Future<List<int>> loadAsBytes(String path) async {
- if (!path.startsWith('package:')) {
- throw ArgumentError('path must begin with package:');
+ /// Loads a `package:` resource as a String.
+ Future<String> loadAsString(String path) async {
+ var bytes = await loadAsBytes(path);
+
+ return utf8.decode(bytes);
}
- var uri = await _resolveUri(Uri.parse(path));
- return File.fromUri(uri).readAsBytes();
-}
+ /// Loads a `package:` resource as an [List<int>].
+ Future<List<int>> loadAsBytes(String path) async {
+ if (!path.startsWith('package:')) {
+ throw ArgumentError('path must begin with package:');
+ }
-/// Helper function for resolving to a non-relative, non-package URI.
-Future<Uri> _resolveUri(Uri uri) {
- if (uri.scheme == 'package') {
- return Isolate.resolvePackageUri(uri).then((resolvedUri) {
- if (resolvedUri == null) {
- throw ArgumentError.value(uri, 'uri', 'Unknown package');
- }
- return resolvedUri;
- });
+ var uri = await resolveUri(Uri.parse(path));
+ return provider.getFile(uri.toFilePath()).readAsBytesSync();
}
- return Future<Uri>.value(Uri.base.resolveUri(uri));
+
+ /// Helper function for resolving to a non-relative, non-package URI.
+ @visibleForTesting
+ Future<Uri> resolveUri(Uri uri) {
+ if (uri.scheme == 'package') {
+ return Isolate.resolvePackageUri(uri).then((resolvedUri) {
+ if (resolvedUri == null) {
+ throw ArgumentError.value(uri, 'uri', 'Unknown package');
+ }
+ return resolvedUri;
+ });
+ }
+ return Future<Uri>.value(Uri.base.resolveUri(uri));
+ }
}
diff --git a/lib/src/generator/templates.dart b/lib/src/generator/templates.dart
index 572edb1..17cc496 100644
--- a/lib/src/generator/templates.dart
+++ b/lib/src/generator/templates.dart
@@ -7,12 +7,12 @@
@Renderer(#renderIndex, Context<PackageTemplateData>())
library dartdoc.templates;
-import 'dart:io' show File, Directory;
-
+import 'package:analyzer/file_system/file_system.dart';
import 'package:dartdoc/dartdoc.dart';
-import 'package:dartdoc/src/generator/resource_loader.dart' as loader;
+import 'package:dartdoc/src/generator/resource_loader.dart';
import 'package:dartdoc/src/generator/template_data.dart';
import 'package:dartdoc/src/mustachio/annotations.dart';
+import 'package:meta/meta.dart';
import 'package:mustache/mustache.dart';
import 'package:path/path.dart' as path;
@@ -58,7 +58,6 @@
'features',
'feature_set',
'footer',
- 'footer',
'head',
'library',
'mixin',
@@ -77,16 +76,15 @@
List<String> headerPaths,
List<String> footerPaths,
List<String> footerTextPaths) async {
- headerPaths ??= [];
- footerPaths ??= [];
- footerTextPaths ??= [];
-
var partials = await templatesLoader.loadPartials();
void replacePlaceholder(String key, String placeholder, List<String> paths) {
var template = partials[key];
if (template != null && paths != null && paths.isNotEmpty) {
- var replacement = paths.map((p) => File(p).readAsStringSync()).join('\n');
+ var replacement = paths
+ .map((p) =>
+ templatesLoader.loader.provider.getFile(p).readAsStringSync())
+ .join('\n');
template = template.replaceAll(placeholder, replacement);
partials[key] = template;
}
@@ -100,6 +98,8 @@
}
abstract class _TemplatesLoader {
+ ResourceLoader get loader;
+
Future<Map<String, String>> loadPartials();
Future<String> loadTemplate(String name);
@@ -110,7 +110,10 @@
final String _format;
final List<String> _partials;
- factory _DefaultTemplatesLoader.create(String format) {
+ @override
+ final ResourceLoader loader;
+
+ factory _DefaultTemplatesLoader.create(String format, ResourceLoader loader) {
List<String> partials;
switch (format) {
case 'html':
@@ -122,10 +125,10 @@
default:
partials = [];
}
- return _DefaultTemplatesLoader(format, partials);
+ return _DefaultTemplatesLoader(format, partials, loader);
}
- _DefaultTemplatesLoader(this._format, this._partials);
+ _DefaultTemplatesLoader(this._format, this._partials, this.loader);
@override
Future<Map<String, String>> loadPartials() async {
@@ -144,33 +147,38 @@
/// Loads templates from a specified Directory.
class _DirectoryTemplatesLoader extends _TemplatesLoader {
- final Directory _directory;
+ final Folder _directory;
final String _format;
- _DirectoryTemplatesLoader(this._directory, this._format);
+ @override
+ final ResourceLoader loader;
+
+ _DirectoryTemplatesLoader(this._directory, this._format, this.loader);
+
+ path.Context get pathContext => _directory.provider.pathContext;
@override
Future<Map<String, String>> loadPartials() async {
var partials = <String, String>{};
- for (var file in _directory.listSync().whereType<File>()) {
- var basename = path.basename(file.path);
+ for (var file in _directory.getChildren().whereType<File>()) {
+ var basename = pathContext.basename(file.path);
if (basename.startsWith('_') && basename.endsWith('.$_format')) {
- var content = file.readAsString();
+ var content = file.readAsStringSync();
var partialName = basename.substring(1, basename.lastIndexOf('.'));
- partials[partialName] = await content;
+ partials[partialName] = content;
}
}
return partials;
}
@override
- Future<String> loadTemplate(String name) {
- var file = File(path.join(_directory.path, '$name.$_format'));
- if (!file.existsSync()) {
+ Future<String> loadTemplate(String name) async {
+ var file = _directory.getChildAssumingFile('$name.$_format');
+ if (!file.exists) {
throw DartdocFailure('Missing required template file: $name.$_format');
}
- return file.readAsString();
+ return file.readAsStringSync();
}
}
@@ -197,44 +205,51 @@
var templatesDir = context.templatesDir;
var format = context.format;
var footerTextPaths = context.footerText;
+ var resourceLoader = ResourceLoader(context.resourceProvider);
if (templatesDir != null) {
- return fromDirectory(Directory(templatesDir), format,
+ return _fromDirectory(
+ context.resourceProvider.getFolder(templatesDir), format,
+ loader: resourceLoader,
headerPaths: context.header,
footerPaths: context.footer,
footerTextPaths: footerTextPaths);
} else {
return createDefault(format,
+ loader: resourceLoader,
headerPaths: context.header,
footerPaths: context.footer,
footerTextPaths: footerTextPaths);
}
}
+ @visibleForTesting
static Future<Templates> createDefault(String format,
- {List<String> headerPaths,
- List<String> footerPaths,
- List<String> footerTextPaths}) async {
- return _create(_DefaultTemplatesLoader.create(format),
+ {@required ResourceLoader loader,
+ List<String> headerPaths = const <String>[],
+ List<String> footerPaths = const <String>[],
+ List<String> footerTextPaths = const <String>[]}) async {
+ return _create(_DefaultTemplatesLoader.create(format, loader),
headerPaths: headerPaths,
footerPaths: footerPaths,
footerTextPaths: footerTextPaths);
}
- static Future<Templates> fromDirectory(Directory dir, String format,
- {List<String> headerPaths,
- List<String> footerPaths,
- List<String> footerTextPaths}) async {
- return _create(_DirectoryTemplatesLoader(dir, format),
+ static Future<Templates> _fromDirectory(Folder dir, String format,
+ {@required ResourceLoader loader,
+ @required List<String> headerPaths,
+ @required List<String> footerPaths,
+ @required List<String> footerTextPaths}) async {
+ return _create(_DirectoryTemplatesLoader(dir, format, loader),
headerPaths: headerPaths,
footerPaths: footerPaths,
footerTextPaths: footerTextPaths);
}
static Future<Templates> _create(_TemplatesLoader templatesLoader,
- {List<String> headerPaths,
- List<String> footerPaths,
- List<String> footerTextPaths}) async {
+ {@required List<String> headerPaths,
+ @required List<String> footerPaths,
+ @required List<String> footerTextPaths}) async {
var partials = await _loadPartials(
templatesLoader, headerPaths, footerPaths, footerTextPaths);
diff --git a/test/html_generator_test.dart b/test/html_generator_test.dart
index e95f44f..5b0d8f8 100644
--- a/test/html_generator_test.dart
+++ b/test/html_generator_test.dart
@@ -8,6 +8,7 @@
import 'package:dartdoc/src/generator/generator_frontend.dart';
import 'package:dartdoc/src/generator/html_generator.dart';
import 'package:dartdoc/src/generator/html_resources.g.dart';
+import 'package:dartdoc/src/generator/resource_loader.dart';
import 'package:dartdoc/src/generator/templates.dart';
import 'package:dartdoc/src/package_config_provider.dart';
import 'package:dartdoc/src/package_meta.dart';
@@ -37,8 +38,66 @@
pathContext = resourceProvider.pathContext;
packageConfigProvider = utils
.getTestPackageConfigProvider(packageMetaProvider.defaultSdkDir.path);
+ var resourceLoader = ResourceLoader(resourceProvider);
+ for (var template in [
+ '_accessor_getter',
+ '_accessor_setter',
+ '_callable',
+ '_callable_multiline',
+ '_categorization',
+ '_class',
+ '_constant',
+ '_documentation',
+ '_extension',
+ '_features',
+ '_feature_set',
+ '_footer',
+ '_head',
+ '_library',
+ '_mixin',
+ '_name_summary',
+ '_packages',
+ '_property',
+ '_search_sidebar',
+ '_sidebar_for_category',
+ '_sidebar_for_container',
+ '_sidebar_for_library',
+ '_source_code',
+ '_source_link',
+ '404error',
+ 'category',
+ 'class',
+ 'constructor',
+ 'enum',
+ 'extension',
+ 'function',
+ 'index',
+ 'library',
+ 'method',
+ 'mixin',
+ 'property',
+ 'top_level_property',
+ 'typedef',
+ ]) {
+ await resourceLoader.writeDartdocResource(
+ 'templates/html/$template.html', 'CONTENT');
+ }
- templates = await Templates.createDefault('html');
+ for (var resource in [
+ 'favicon.png',
+ 'github.css',
+ 'highlight.pack.js',
+ 'play_button.svg',
+ 'readme.md',
+ 'script.js',
+ 'styles.css',
+ 'typeahead.bundle.min.js',
+ ]) {
+ await resourceLoader.writeDartdocResource(
+ 'resources/$resource', 'CONTENT');
+ }
+
+ templates = await Templates.createDefault('html', loader: resourceLoader);
generator = GeneratorFrontEnd(HtmlGeneratorBackend(null, templates));
projectRoot = utils.writePackage(
@@ -127,3 +186,12 @@
}
}
}
+
+/// Extension methods just for tests.
+extension on ResourceLoader {
+ Future<void> writeDartdocResource(String path, String content) async {
+ var filePath =
+ (await resolveUri(Uri.parse('package:dartdoc/$path'))).toFilePath();
+ provider.getFile(filePath).writeAsStringSync(content);
+ }
+}
diff --git a/test/resource_loader_test.dart b/test/resource_loader_test.dart
index 1670aab..6607f22 100644
--- a/test/resource_loader_test.dart
+++ b/test/resource_loader_test.dart
@@ -4,11 +4,18 @@
library dartdoc.resource_loader_test;
-import 'package:dartdoc/src/generator/resource_loader.dart' as loader;
+import 'package:analyzer/file_system/physical_file_system.dart';
+import 'package:dartdoc/src/generator/resource_loader.dart';
import 'package:test/test.dart';
void main() {
group('Resource Loader', () {
+ ResourceLoader loader;
+
+ setUp(() {
+ loader = ResourceLoader(PhysicalResourceProvider());
+ });
+
test('load from packages', () async {
var contents = await loader
.loadAsString('package:dartdoc/templates/html/index.html');