blob: 32f20b55fc503357635e0687ddd30db4ac45c8d5 [file] [log] [blame]
// 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.
library dartdoc.html_generator;
import 'dart:async' show Future;
import 'dart:io' show Directory, File;
import 'dart:convert' show JSON;
import 'dart:typed_data' show Uint8List;
import 'package:mustache4dart/mustache4dart.dart';
import 'package:path/path.dart' as path;
import 'model.dart';
import 'package_meta.dart';
import 'resources.g.dart' as resources;
import '../generator.dart';
import '../markdown_processor.dart';
import '../resource_loader.dart' as loader;
import 'io_utils.dart' show createOutputFile;
String dartdocVersion = 'unknown';
typedef String Renderer(String input);
typedef String TemplateRenderer(context,
{bool assumeNullNonExistingProperty, bool errorOnMissingProperty});
// 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 Templates {
TemplateRenderer indexTemplate;
TemplateRenderer libraryTemplate;
TemplateRenderer classTemplate;
TemplateRenderer functionTemplate;
TemplateRenderer methodTemplate;
TemplateRenderer constructorTemplate;
TemplateRenderer propertyTemplate;
TemplateRenderer constantTemplate;
TemplateRenderer topLevelConstantTemplate;
TemplateRenderer topLevelPropertyTemplate;
TemplateRenderer typeDefTemplate;
final Map<String, String> _partialTemplates = <String, String>{};
final String _footer;
final String _header;
Templates(this._header, this._footer);
Future init() async {
if (_partialTemplates.isNotEmpty) return;
indexTemplate = await _loadTemplate('index.html');
libraryTemplate = await _loadTemplate('library.html');
classTemplate = await _loadTemplate('class.html');
functionTemplate = await _loadTemplate('function.html');
methodTemplate = await _loadTemplate('method.html');
constructorTemplate = await _loadTemplate('constructor.html');
propertyTemplate = await _loadTemplate('property.html');
constantTemplate = await _loadTemplate('constant.html');
topLevelConstantTemplate = await _loadTemplate('top_level_constant.html');
topLevelPropertyTemplate = await _loadTemplate('top_level_property.html');
typeDefTemplate = await _loadTemplate('typedef.html');
// TODO: if we can ever enumerate the contents of a package, we
// won't need this.
List<String> partials = [
'callable',
'callable_multiline',
'constant',
'footer',
'head',
'property',
'styles_and_scripts',
'readable_writable',
'documentation',
'name_summary',
'sidebar_for_class',
'source_code',
'sidebar_for_library',
'name_link',
'has_more_docs'
];
for (String partial in partials) {
_partialTemplates[partial] = await _loadPartial('_$partial.html');
}
}
String _partial(String name) {
String partial = _partialTemplates[name];
if (partial == null || partial.isEmpty) {
throw new StateError('Did not find partial "$name"');
}
return partial;
}
Future<TemplateRenderer> _loadTemplate(String templatePath) async {
String templateContents = await _getTemplateFile(templatePath);
return compile(templateContents, partial: _partial) as TemplateRenderer;
}
Future<String> _getTemplateFile(String templatePath) {
return loader.loadAsString('package:dartdoc/templates/$templatePath');
}
Future<String> _loadPartial(String templatePath) async {
String template = await _getTemplateFile(templatePath);
// TODO: revisit, not sure this is the right place for this logic
if (templatePath.contains('_footer') && _footer != null) {
String footerValue = await new File(_footer).readAsString();
template =
template.replaceAll('<!-- Footer Placeholder -->', footerValue);
}
if (templatePath.contains('_head') && _header != null) {
String headerValue = await new File(_header).readAsString();
template =
template.replaceAll('<!-- Header Placeholder -->', headerValue);
}
return template;
}
}
class HtmlGenerator extends Generator {
/// Optional URL for where the docs will be hosted.
final String url;
final Templates _templates;
/// [url] can be null.
// TODO: make url an optional parameter
HtmlGenerator(this.url, {String header, String footer})
: _templates = new Templates(header, footer) {}
Future generate(Package package, Directory out) {
return new HtmlGeneratorInstance(url, _templates, package, out).generate();
}
}
class HtmlGeneratorInstance {
final String url;
final Templates _templates;
final Package package;
final Directory out;
final List<ModelElement> documentedElements = [];
HtmlGeneratorInstance(this.url, this._templates, this.package, this.out);
Future generate() async {
await _templates.init();
if (!out.existsSync()) out.createSync();
if (package != null) {
_generateDocs();
_generateSearchIndex();
// TODO: generate sitemap
}
await _copyResources();
}
void _generateSearchIndex() {
File jsonFile = createOutputFile(out, 'index.json');
String json = JSON.encode(documentedElements.map((ModelElement e) {
// TODO: find a better string for type
Map data = {'name': e.name, 'href': e.href, 'type': e.kind};
if (e is EnclosedElement) {
EnclosedElement ee = e as EnclosedElement;
data['enclosedBy'] = {
'name': ee.enclosingElement.name,
'type': ee.enclosingElement.kind
};
}
return data;
}).toList());
jsonFile.writeAsStringSync(json);
}
void _generateDocs() {
if (package == null) return;
generatePackage();
package.libraries.forEach((Library lib) {
generateLibrary(package, lib);
lib.allClasses.forEach((Class clazz) {
generateClass(package, lib, clazz);
clazz.constructors.forEach((constructor) {
generateConstructor(package, lib, clazz, constructor);
});
clazz.constants.forEach((constant) {
generateConstant(package, lib, clazz, constant);
});
clazz.staticProperties.forEach((property) {
generateProperty(package, lib, clazz, property);
});
clazz.instanceProperties.forEach((property) {
generateProperty(package, lib, clazz, property);
});
clazz.instanceMethods.forEach((method) {
generateMethod(package, lib, clazz, method);
});
clazz.operators.forEach((operator) {
generateMethod(package, lib, clazz, operator);
});
clazz.staticMethods.forEach((method) {
generateMethod(package, lib, clazz, method);
});
});
lib.enums.forEach((eNum) {
generateEnum(package, lib, eNum);
});
lib.constants.forEach((constant) {
generateTopLevelConstant(package, lib, constant);
});
lib.properties.forEach((property) {
generateTopLevelProperty(package, lib, property);
});
lib.functions.forEach((function) {
generateFunction(package, lib, function);
});
lib.typedefs.forEach((typeDef) {
generateTypeDef(package, lib, typeDef);
});
});
}
void generatePackage() {
Renderer markdown = renderMarkdown;
if (package.hasDocumentationFile) {
FileContents readme = package.documentationFile;
markdown = readme.isMarkdown ? renderMarkdown : renderPlainText;
}
TemplateData data = new PackageTemplateData(package, markdown: markdown);
_build('index.html', _templates.indexTemplate, data);
}
void generateLibrary(Package package, Library lib) {
print('generating docs for library ${lib.name} from ${lib.path}...');
if (!lib.isAnonymous && !lib.hasDocumentation) {
print(" warning: library '${lib.name}' has no documentation");
}
TemplateData data = new LibraryTemplateData(package, lib);
_build(path.join(lib.dirName, '${lib.fileName}'),
_templates.libraryTemplate, data);
}
void generateClass(Package package, Library lib, Class clazz) {
TemplateData data = new ClassTemplateData(package, lib, clazz);
_build(path.joinAll(clazz.href.split('/')), _templates.classTemplate, data);
}
void generateConstructor(
Package package, Library lib, Class clazz, Constructor constructor) {
TemplateData data =
new ConstructorTemplateData(package, lib, clazz, constructor);
_build(path.joinAll(constructor.href.split('/')),
_templates.constructorTemplate, data);
}
void generateEnum(Package package, Library lib, Class eNum) {
TemplateData data = new EnumTemplateData(package, lib, eNum);
_build(path.joinAll(eNum.href.split('/')), _templates.classTemplate, data);
}
void generateFunction(Package package, Library lib, ModelFunction function) {
TemplateData data = new FunctionTemplateData(package, lib, function);
_build(path.joinAll(function.href.split('/')), _templates.functionTemplate,
data);
}
void generateMethod(
Package package, Library lib, Class clazz, Method method) {
TemplateData data = new MethodTemplateData(package, lib, clazz, method);
_build(
path.joinAll(method.href.split('/')), _templates.methodTemplate, data);
}
void generateConstant(
Package package, Library lib, Class clazz, Field property) {
TemplateData data = new ConstantTemplateData(package, lib, clazz, property);
_build(path.joinAll(property.href.split('/')), _templates.constantTemplate,
data);
}
void generateProperty(
Package package, Library lib, Class clazz, Field property) {
TemplateData data = new PropertyTemplateData(package, lib, clazz, property);
_build(path.joinAll(property.href.split('/')), _templates.propertyTemplate,
data);
}
void generateTopLevelProperty(
Package package, Library lib, TopLevelVariable property) {
TemplateData data =
new TopLevelPropertyTemplateData(package, lib, property);
_build(path.joinAll(property.href.split('/')),
_templates.topLevelPropertyTemplate, data);
}
void generateTopLevelConstant(
Package package, Library lib, TopLevelVariable property) {
TemplateData data = new TopLevelConstTemplateData(package, lib, property);
_build(path.joinAll(property.href.split('/')),
_templates.topLevelConstantTemplate, data);
}
void generateTypeDef(Package package, Library lib, Typedef typeDef) {
TemplateData data = new TypedefTemplateData(package, lib, typeDef);
_build(path.joinAll(typeDef.href.split('/')), _templates.typeDefTemplate,
data);
}
// TODO: change this to use resource_loader
Future _copyResources() async {
final prefix = 'package:dartdoc/resources/';
for (String resourcePath in resources.resource_names) {
if (!resourcePath.startsWith(prefix)) {
throw new StateError(
'Resource paths must start with $prefix, encountered $resourcePath');
}
String destFileName = resourcePath.substring(prefix.length);
File destFile =
new File(path.join(out.path, 'static-assets', destFileName))
..createSync(recursive: true);
Uint8List resourceBytes = await loader.loadAsBytes(resourcePath);
destFile.writeAsBytesSync(resourceBytes);
}
}
void _build(String filename, TemplateRenderer template, TemplateData data) {
String content = template(data,
assumeNullNonExistingProperty: false, errorOnMissingProperty: true);
_writeFile(filename, content);
if (data.self is ModelElement) documentedElements.add(data.self);
}
void _writeFile(String filename, String content) {
File f = createOutputFile(out, filename);
f.writeAsStringSync(content);
}
}
class Subnav {
final String name;
final String href;
Subnav(this.name, this.href);
String toString() => name;
}
/// Converts a markdown formatted string into HTML, and removes any script tags.
/// Returns the HTML as a string.
String renderMarkdown(String markdown) => renderMarkdownToHtml(markdown);
/// Convert the given plain text into HTML.
String renderPlainText(String text) {
if (text == null) return '';
return "<code class='fixed'>${text.trim()}</code>";
}
abstract class TemplateData {
Package _package;
Function _markdown = renderMarkdown;
TemplateData(this._package, {Renderer markdown}) : _markdown = markdown;
Package get package => _package;
String get documentation => self.documentation;
String get oneLineDoc => self.oneLineDoc;
String get title;
String get layoutTitle;
String get metaDescription;
List get navLinks;
List<Subnav> get subnavItems;
String get htmlBase;
dynamic get self;
String get version => dartdocVersion;
Function get markdown => _markdown;
String _layoutTitle(String name, String kind, bool isDeprecated) {
if (kind.isEmpty) kind =
'&nbsp;'; // Ugly. fixes https://github.com/dart-lang/dartdoc/issues/695
String str = '<div class="kind">$kind</div>';
if (!isDeprecated) return '${str} ${name}';
return '${str} <span class="deprecated">$name</span>';
}
List<Subnav> _gatherSubnavForInvokable(ModelElement element) {
if (element is SourceCodeMixin &&
(element as SourceCodeMixin).hasSourceCode) {
return [new Subnav('Source', '${element.href}#source')];
} else {
return [];
}
}
}
class PackageTemplateData extends TemplateData {
PackageTemplateData(Package package, {Renderer markdown})
: super(package, markdown: markdown);
List get navLinks => [];
String get title => '${package.name} - Dart API docs';
Package get self => package;
String get layoutTitle =>
_layoutTitle(package.name, package.isSdk ? '' : 'package', false);
String get metaDescription =>
'${package.name} API docs, for the Dart programming language.';
List<Subnav> get subnavItems =>
[new Subnav('Libraries', '${package.href}#libraries')];
String get htmlBase => '.';
}
class LibraryTemplateData extends TemplateData {
Library _library;
LibraryTemplateData(Package package, this._library) : super(package);
String get title => '${_library.name} library - Dart API';
Library get library => _library;
String get documentation => _library.documentation;
String get htmlBase => '..';
String get metaDescription =>
'${_library.name} library API docs, for the Dart programming language.';
List get navLinks => [package];
List<Subnav> get subnavItems {
List<Subnav> navs = [];
if (_library.hasConstants) navs
.add(new Subnav('Constants', '${_library.href}#constants'));
if (_library.hasTypedefs) navs
.add(new Subnav('Typedefs', '${_library.href}#typedefs'));
if (_library.hasProperties) navs
.add(new Subnav('Properties', '${_library.href}#properties'));
if (_library.hasFunctions) navs
.add(new Subnav('Functions', '${_library.href}#functions'));
if (_library.hasEnums) navs
.add(new Subnav('Enums', '${_library.href}#enums'));
if (_library.hasClasses) navs
.add(new Subnav('Classes', '${_library.href}#classes'));
if (_library.hasExceptions) navs
.add(new Subnav('Exceptions', '${_library.href}#exceptions'));
return navs;
}
String get layoutTitle =>
_layoutTitle(_library.name, 'library', _library.isDeprecated);
Library get self => _library;
}
class ClassTemplateData extends TemplateData {
Class _clazz;
Library _library;
Class _objectType;
ClassTemplateData(Package package, this._library, this._clazz)
: super(package);
Class get self => _clazz;
Library get library => _library;
Class get clazz => _clazz;
String get linkedObjectType =>
objectType == null ? 'Object' : objectType.linkedName;
String get title =>
'${clazz.name} ${clazz.kind} - ${_library.name} library - Dart API';
String get metaDescription =>
'API docs for the ${clazz.name} ${clazz.kind} from the ${_library.name} library, for the Dart programming language.';
String get layoutTitle =>
_layoutTitle(clazz.nameWithGenerics, clazz.kind, clazz.isDeprecated);
List get navLinks => [package, _library];
String get htmlBase => '..';
List<Subnav> get subnavItems {
List<Subnav> navs = [];
if (clazz.hasConstants) navs
.add(new Subnav('Constants', '${clazz.href}#constants'));
if (clazz.hasStaticProperties) navs.add(
new Subnav('Static Properties', '${clazz.href}#static-properties'));
if (clazz.hasStaticMethods) navs
.add(new Subnav('Static Methods', '${clazz.href}#static-methods'));
if (clazz.hasInstanceProperties) navs
.add(new Subnav('Properties', '${clazz.href}#instance-properties'));
if (clazz.hasConstructors) navs
.add(new Subnav('Constructors', '${clazz.href}#constructors'));
if (clazz.hasOperators) navs
.add(new Subnav('Operators', '${clazz.href}#operators'));
if (clazz.hasInstanceMethods) navs
.add(new Subnav('Methods', '${clazz.href}#instance-methods'));
return navs;
}
Class get objectType {
if (_objectType != null) {
return _objectType;
}
Library dc = package.libraries
.firstWhere((it) => it.name == "dart:core", orElse: () => null);
if (dc == null) {
return _objectType = null;
}
return _objectType = dc.getClassByName("Object");
}
}
class ConstructorTemplateData extends TemplateData {
Library _library;
Class _class;
Constructor _constructor;
ConstructorTemplateData(
Package package, this._library, this._class, this._constructor)
: super(package);
Constructor get self => _constructor;
Library get library => _library;
Class get clazz => _class;
Constructor get constructor => _constructor;
String get layoutTitle =>
_layoutTitle(constructor.name, 'constructor', constructor.isDeprecated);
List get navLinks => [package, _library, clazz];
List<Subnav> get subnavItems => _gatherSubnavForInvokable(constructor);
String get htmlBase => '../..';
String get title =>
'${constructor.name} constructor - ${clazz.name} class - ${_library.name} library - Dart API';
String get metaDescription =>
'API docs for the ${constructor.name} constructor from the ${clazz} class from the ${_library.name} library, for the Dart programming language.';
}
class EnumTemplateData extends TemplateData {
Library _library;
Enum _enum;
EnumTemplateData(Package package, this._library, this._enum) : super(package);
Library get library => _library;
Enum get clazz => _enum;
Enum get self => _enum;
String get layoutTitle =>
_layoutTitle(_enum.name, 'enum', _enum.isDeprecated);
String get title => '${self.name} enum - ${_library.name} library - Dart API';
String get metaDescription =>
'API docs for the ${_enum.name} enum from the ${_library.name} library, for the Dart programming language.';
List get navLinks => [package, _library];
String get htmlBase => '..';
List<Subnav> get subnavItems {
return [
new Subnav('Constants', '${_enum.href}#constants'),
new Subnav('Properties', '${_enum.href}#properties')
];
}
}
class FunctionTemplateData extends TemplateData {
ModelFunction _function;
Library _library;
FunctionTemplateData(Package package, this._library, this._function)
: super(package);
ModelFunction get self => _function;
ModelFunction get function => _function;
Library get library => _library;
String get title =>
'${function.name} function - ${_library.name} library - Dart API';
String get layoutTitle =>
_layoutTitle(function.name, 'function', function.isDeprecated);
String get metaDescription =>
'API docs for the ${function.name} function from the ${_library.name} library, for the Dart programming language.';
List get navLinks => [package, _library];
List<Subnav> get subnavItems => _gatherSubnavForInvokable(function);
String get htmlBase => '..';
}
class MethodTemplateData extends TemplateData {
Library _library;
Method _method;
Class _class;
MethodTemplateData(Package package, this._library, this._class, this._method)
: super(package);
Library get library => _library;
Class get clazz => _class;
Method get self => _method;
Method get method => _method;
String get title =>
'${method.name} method - ${clazz.name} class - ${_library.name} library - Dart API';
String get layoutTitle =>
_layoutTitle(method.name, 'method', method.isDeprecated);
String get metaDescription =>
'API docs for the ${method.name} method from the ${clazz.name} class, for the Dart programming language.';
List get navLinks => [package, _library, clazz];
List<Subnav> get subnavItems => _gatherSubnavForInvokable(method);
String get htmlBase => '../..';
}
class PropertyTemplateData extends TemplateData {
Library _library;
Class _class;
Field _property;
PropertyTemplateData(
Package package, this._library, this._class, this._property)
: super(package);
Library get library => _library;
Class get clazz => _class;
Field get self => _property;
Field get property => _property;
String get title =>
'${property.name} $type - ${clazz.name} class - ${_library.name} library - Dart API';
String get layoutTitle =>
_layoutTitle(property.name, type, property.isDeprecated);
String get metaDescription =>
'API docs for the ${property.name} $type from the ${clazz.name} class, for the Dart programming language.';
List get navLinks => [package, _library, clazz];
List get subnavItems => [];
String get htmlBase => '../..';
String get type => 'property';
}
class ConstantTemplateData extends PropertyTemplateData {
ConstantTemplateData(
Package package, Library library, Class clazz, Field property)
: super(package, library, clazz, property);
String get type => 'constant';
}
class TypedefTemplateData extends TemplateData {
Library _library;
Typedef _typeDef;
TypedefTemplateData(Package package, this._library, this._typeDef)
: super(package);
Library get library => _library;
Typedef get self => _typeDef;
Typedef get typeDef => _typeDef;
String get title =>
'${typeDef.name} typedef - ${library.name} library - Dart API';
String get layoutTitle =>
_layoutTitle(typeDef.name, 'typedef', typeDef.isDeprecated);
String get metaDescription =>
'API docs for the ${typeDef.name} property from the ${library.name} library, for the Dart programming language.';
List get navLinks => [package, library];
String get htmlBase => '..';
List get subnavItems => [];
}
class TopLevelPropertyTemplateData extends TemplateData {
Library _library;
TopLevelVariable _property;
TopLevelPropertyTemplateData(Package package, this._library, this._property)
: super(package);
Library get library => _library;
TopLevelVariable get self => _property;
TopLevelVariable get property => _property;
String get title =>
'${property.name} $_type - ${library.name} library - Dart API';
String get layoutTitle =>
_layoutTitle(property.name, _type, property.isDeprecated);
String get metaDescription =>
'API docs for the ${property.name} $_type from the ${library.name} library, for the Dart programming language.';
List get navLinks => [package, library];
String get htmlBase => '..';
List get subnavItems => [];
String get _type => 'property';
}
class TopLevelConstTemplateData extends TopLevelPropertyTemplateData {
TopLevelConstTemplateData(
Package package, Library library, TopLevelVariable property)
: super(package, library, property);
String get _type => 'constant';
}