Skip to content

Add markdown renderers and renderer factory #2152

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/src/model/package_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ class PackageBuilder {
if (config.topLevelPackageMeta.needsPubGet) {
config.topLevelPackageMeta.runPubGet();
}
// TODO(jdkoren): change factory for other formats based on config options
RendererFactory rendererFactory = HtmlRenderFactory();

RendererFactory rendererFactory = RendererFactory.forFormat(config.format);

PackageGraph newGraph = PackageGraph.UninitializedPackageGraph(
config, driver, sdk, hasEmbedderSdkFiles, rendererFactory);
Expand Down
16 changes: 15 additions & 1 deletion lib/src/render/category_renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class CategoryRendererHtml extends CategoryRenderer {

StringBuffer buf = StringBuffer();
buf.write('<span class="${spanClasses.join(' ')}" title="$spanTitle">');
buf.write(category.linkedName);
buf.write(renderLinkedName(category));
buf.write('</span>');
return buf.toString();
}
Expand All @@ -46,3 +46,17 @@ class CategoryRendererHtml extends CategoryRenderer {
}
}
}

class CategoryRendererMd extends CategoryRenderer {
@override
String renderCategoryLabel(Category category) => renderLinkedName(category);

@override
String renderLinkedName(Category category) {
String name = category.name;
if (category.isDocumented) {
return '[$name](${category.href})';
}
return name;
}
}
82 changes: 82 additions & 0 deletions lib/src/render/element_type_renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ abstract class ElementTypeRenderer<T extends ElementType> {
String renderNameWithGenerics(T elementType) => '';
}

// Html implementations

class FunctionTypeElementTypeRendererHtml
extends ElementTypeRenderer<FunctionTypeElementType> {
@override
Expand Down Expand Up @@ -93,3 +95,83 @@ class CallableElementTypeRendererHtml
return buf.toString();
}
}

// Markdown implementations

class FunctionTypeElementTypeRendererMd
extends ElementTypeRenderer<FunctionTypeElementType> {
@override
String renderLinkedName(FunctionTypeElementType elementType) {
StringBuffer buf = StringBuffer();
buf.write('${elementType.returnType.linkedName} ');
buf.write('${elementType.nameWithGenerics}');
buf.write('(');
buf.write(ParameterRendererMd().renderLinkedParams(elementType.parameters));
buf.write(')');
return buf.toString();
}

@override
String renderNameWithGenerics(FunctionTypeElementType elementType) {
StringBuffer buf = StringBuffer();
buf.write(elementType.name);
if (elementType.typeFormals.isNotEmpty) {
if (!elementType.typeFormals.every((t) => t.name == 'dynamic')) {
buf.write('<');
buf.writeAll(elementType.typeFormals.map((t) => t.name), ', ');
buf.write('>');
}
}
return buf.toString();
}
}

class ParameterizedElementTypeRendererMd
extends ElementTypeRenderer<ParameterizedElementType> {
@override
String renderLinkedName(ParameterizedElementType elementType) {
StringBuffer buf = StringBuffer();
buf.write(elementType.element.linkedName);
if (elementType.typeArguments.isNotEmpty &&
!elementType.typeArguments.every((t) => t.name == 'dynamic')) {
buf.write('<');
buf.writeAll(elementType.typeArguments.map((t) => t.linkedName), ', ');
buf.write('>');
}
return buf.toString();
}

@override
String renderNameWithGenerics(ParameterizedElementType elementType) {
StringBuffer buf = StringBuffer();
buf.write(elementType.element.name);
if (elementType.typeArguments.isNotEmpty &&
!elementType.typeArguments.every((t) => t.name == 'dynamic')) {
buf.write('<');
buf.writeAll(
elementType.typeArguments.map((t) => t.nameWithGenerics), ', ');
buf.write('>');
}
return buf.toString();
}
}

class CallableElementTypeRendererMd
extends ElementTypeRenderer<CallableElementType> {
@override
String renderLinkedName(CallableElementType elementType) {
if (elementType.name != null && elementType.name.isNotEmpty) {
return elementType.superLinkedName;
}

StringBuffer buf = StringBuffer();
buf.write(elementType.nameWithGenerics);
buf.write('(');
buf.write(ParameterRendererMd()
.renderLinkedParams(elementType.element.parameters, showNames: false)
.trim());
buf.write(') → ');
buf.write(elementType.returnType.linkedName);
return buf.toString();
}
}
11 changes: 11 additions & 0 deletions lib/src/render/enum_field_renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,14 @@ class EnumFieldRendererHtml extends EnumFieldRenderer {
}
}
}

class EnumFieldRendererMd extends EnumFieldRenderer {
@override
String renderValue(EnumField field) {
if (field.name == 'values') {
return 'const List<${field.enclosingElement.name}>';
} else {
return 'const ${field.enclosingElement.name}(${field.index})';
}
}
}
14 changes: 14 additions & 0 deletions lib/src/render/model_element_renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,17 @@ class ModelElementRendererHtml extends ModelElementRenderer {
'''; // Must end at start of line, or following inline text will be indented.
}
}

class ModelElementRendererMd extends ModelElementRendererHtml {
@override
String renderLinkedName(ModelElement modelElement) {
if (modelElement.isDeprecated) {
return '[~~${modelElement.name}~~](${modelElement.href})';
}
return '[${modelElement.name}](${modelElement.href})';
}

@override
String renderExtendedDocLink(ModelElement modelElement) =>
'[...](${modelElement.href})';
}
29 changes: 29 additions & 0 deletions lib/src/render/parameter_renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,35 @@ class ParameterRendererHtml extends ParameterRenderer {
String required(String required) => '<span>$required</span>';
}

class ParameterRendererMd extends ParameterRenderer {
@override
String annotation(String annotation) => annotation;

@override
String covariant(String covariant) => covariant;

@override
String defaultValue(String defaultValue) => defaultValue;

@override
String listItem(String item) => item;

@override
String orderedList(String listItems) => listItems;

@override
String parameter(String parameter, String id) => parameter;

@override
String parameterName(String parameterName) => parameterName;

@override
String required(String required) => required;

@override
String typeName(String typeName) => typeName;
}

abstract class ParameterRenderer {
String listItem(String item);
String orderedList(String listItems);
Expand Down
58 changes: 58 additions & 0 deletions lib/src/render/renderer_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ import 'package:dartdoc/src/render/type_parameters_renderer.dart';
import 'package:dartdoc/src/render/typedef_renderer.dart';

abstract class RendererFactory {
static RendererFactory forFormat(String format) {
switch (format) {
case 'html':
return HtmlRenderFactory();
case 'md':
return MdRenderFactory();
default:
throw ArgumentError('Unsupported format: $format');
}
}

TemplateRenderer get templateRenderer;

CategoryRenderer get categoryRenderer;
Expand Down Expand Up @@ -86,3 +97,50 @@ class HtmlRenderFactory extends RendererFactory {
@override
TypedefRenderer get typedefRenderer => TypedefRendererHtml();
}

class MdRenderFactory extends RendererFactory {
@override
TemplateRenderer get templateRenderer => MdTemplateRenderer();

@override
CategoryRenderer get categoryRenderer => CategoryRendererMd();

// We render documentation as HTML for now.
// TODO(jdkoren): explore using documentation directly in the output file.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be a challenge, particularly when it comes to tools. I'm also curious as to whether tool execution works correctly end-to-end here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, @tool was one of the reasons I thought to leave documentation as html inside the markdown page. Same for @video.

We could in theory run the tools as they are and expect that whoever is running Dartdoc has taken the output format into account when they added the tools...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well... that's not completely unreasonable if we provide a way to tell the tool what sort of output to generate. Keeping things HTML for now though makes a lot of sense as if you want any sort of compatibility with what Flutter is doing we'll either have to extend those tools or make it safe for them to continue outputting HTML.

@override
DocumentationRenderer get documentationRenderer =>
DocumentationRendererHtml();

@override
ElementTypeRenderer<CallableElementType> get callableElementTypeRenderer =>
CallableElementTypeRendererMd();

@override
ElementTypeRenderer<FunctionTypeElementType>
get functionTypeElementTypeRenderer =>
FunctionTypeElementTypeRendererMd();

@override
ElementTypeRenderer<ParameterizedElementType>
get parameterizedElementTypeRenderer =>
ParameterizedElementTypeRendererMd();

@override
EnumFieldRenderer get enumFieldRenderer => EnumFieldRendererMd();

@override
ModelElementRenderer get modelElementRenderer => ModelElementRendererMd();

@override
ParameterRenderer get parameterRenderer => ParameterRendererMd();

@override
ParameterRenderer get parameterRendererDetailed => parameterRenderer;

@override
TypeParametersRenderer get typeParametersRenderer =>
TypeParametersRendererMd();

@override
TypedefRenderer get typedefRenderer => TypedefRendererMd();
}
11 changes: 11 additions & 0 deletions lib/src/render/template_renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,14 @@ class HtmlTemplateRenderer implements TemplateRenderer {
}
}
}

class MdTemplateRenderer implements TemplateRenderer {
@override
String composeLayoutTitle(String name, String kind, bool isDeprecated) {
if (isDeprecated) {
return '~~${name}~~ ${kind}';
} else {
return '${name} ${kind}';
}
}
}
20 changes: 20 additions & 0 deletions lib/src/render/type_parameters_renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:dartdoc/src/model/type_parameter.dart';

abstract class TypeParametersRenderer {
String renderGenericParameters(TypeParameters typeParameters);

String renderLinkedGenericParameters(TypeParameters typeParameters);
}

Expand All @@ -32,3 +33,22 @@ class TypeParametersRendererHtml extends TypeParametersRenderer {
return '<span class="signature">&lt;<wbr><span class="type-parameter">${joined}</span>&gt;</span>';
}
}

class TypeParametersRendererMd extends TypeParametersRenderer {
@override
String renderGenericParameters(TypeParameters typeParameters) =>
_compose(typeParameters.typeParameters, (t) => t.name);

@override
String renderLinkedGenericParameters(TypeParameters typeParameters) =>
_compose(typeParameters.typeParameters, (t) => t.linkedName);

String _compose(List<TypeParameter> typeParameters,
String Function(TypeParameter) mapfn) {
if (typeParameters.isEmpty) {
return '';
}
var joined = typeParameters.map(mapfn).join(', ');
return '<${joined}>';
}
}
11 changes: 11 additions & 0 deletions lib/src/render/typedef_renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,14 @@ class TypedefRendererHtml extends TypedefRenderer {
return '&lt;<wbr><span class="type-parameter">${joined}</span>&gt;';
}
}

class TypedefRendererMd extends TypedefRenderer {
@override
String renderGenericParameters(Typedef typedef) {
if (typedef.genericTypeParameters.isEmpty) {
return '';
}
var joined = typedef.genericTypeParameters.map((t) => t.name).join(', ');
return '<{$joined}>';
}
}
2 changes: 1 addition & 1 deletion lib/templates/md/_callable.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{{#isDeprecated}}~~{{/isDeprecated}}{{{linkedName}}}{{{linkedGenericParameters}}}({{{ linkedParamsNoMetadata }}}) {{{ linkedReturnType }}} {{>categorization}}{{#isDeprecated}}~~{{/isDeprecated}}
{{{linkedName}}}{{{linkedGenericParameters}}}({{{ linkedParamsNoMetadata }}}) {{{ linkedReturnType }}} {{>categorization}}
: {{{ oneLineDoc }}} {{{ extendedDocLink }}}
{{>features}}
2 changes: 1 addition & 1 deletion lib/templates/md/_class.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
{{#isDeprecated}}~~{{/isDeprecated}}{{{linkedName}}}{{{linkedGenericParameters}}} {{>categorization}}{{#isDeprecated}}~~{{/isDeprecated}}
{{{linkedName}}}{{{linkedGenericParameters}}} {{>categorization}}
: {{{ oneLineDoc }}} {{{ extendedDocLink }}}
2 changes: 1 addition & 1 deletion lib/templates/md/_constant.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{{#isDeprecated}}~~{{/isDeprecated}}{{{ linkedName }}} const {{{ linkedReturnType }}} {{>categorization}}{{#isDeprecated}}~~{{/isDeprecated}}
{{{ linkedName }}} const {{{ linkedReturnType }}} {{>categorization}}
: {{{ oneLineDoc }}} {{{ extendedDocLink }}}
{{>features}}
2 changes: 1 addition & 1 deletion lib/templates/md/_extension.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
{{#isDeprecated}}~~{{/isDeprecated}}{{{linkedName}}} {{>categorization}}{{#isDeprecated}}~~{{/isDeprecated}}
{{{linkedName}}} {{>categorization}}
: {{{ oneLineDoc }}} {{{ extendedDocLink }}}
2 changes: 1 addition & 1 deletion lib/templates/md/_mixin.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
{{#isDeprecated}}~~{{/isDeprecated}}{{{linkedName}}}{{{linkedGenericParameters}}} {{>categorization}}{{#isDeprecated}}~~{{/isDeprecated}}
{{{linkedName}}}{{{linkedGenericParameters}}} {{>categorization}}
: {{{ oneLineDoc }}} {{{ extendedDocLink }}}
2 changes: 1 addition & 1 deletion lib/templates/md/constructor.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
{{/hasAnnotations}}

{{#isConst}}const{{/isConst}}
{{#isDeprecated}}~~{{/isDeprecated}}{{{nameWithGenerics}}}({{#hasParameters}}{{{linkedParamsLines}}}{{/hasParameters}}){{#isDeprecated}}~~{{/isDeprecated}}
{{{nameWithGenerics}}}({{#hasParameters}}{{{linkedParamsLines}}}{{/hasParameters}})

{{>documentation}}

Expand Down
15 changes: 15 additions & 0 deletions test/model_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ void main() {
equals(utils.kTestPackagePublicLibraries - 5));
});

// TODO consider moving these to a separate suite
test('CategoryRendererHtml renders category label', () {
Category category = packageGraph.publicPackages.first.categories.first;
CategoryRendererHtml renderer = CategoryRendererHtml();
Expand All @@ -388,6 +389,20 @@ void main() {
expect(renderer.renderLinkedName(category),
'<a href="${HTMLBASE_PLACEHOLDER}topics/Superb-topic.html">Superb</a>');
});

test('CategoryRendererMd renders category label', () {
Category category = packageGraph.publicPackages.first.categories.first;
CategoryRendererMd renderer = CategoryRendererMd();
expect(renderer.renderCategoryLabel(category),
'[Superb](${HTMLBASE_PLACEHOLDER}topics/Superb-topic.html)');
});

test('CategoryRendererMd renders linkedName', () {
Category category = packageGraph.publicPackages.first.categories.first;
CategoryRendererMd renderer = CategoryRendererMd();
expect(renderer.renderLinkedName(category),
'[Superb](${HTMLBASE_PLACEHOLDER}topics/Superb-topic.html)');
});
});

group('LibraryContainer', () {
Expand Down
Loading