Skip to content

Commit 16b6c84

Browse files
gspencergoogjcollins-g
authored andcommitted
Add support for injecting HTML (#1792)
* Add support for injecting HTML * Update docs
1 parent b02d1dc commit 16b6c84

File tree

131 files changed

+2322
-11
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

131 files changed

+2322
-11
lines changed

lib/src/model.dart

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import 'package:analyzer/src/dart/element/member.dart'
3939
import 'package:analyzer/src/dart/analysis/driver.dart';
4040
import 'package:args/args.dart';
4141
import 'package:collection/collection.dart';
42+
import 'package:crypto/crypto.dart';
4243
import 'package:dartdoc/src/dartdoc_options.dart';
4344
import 'package:dartdoc/src/element_type.dart';
4445
import 'package:dartdoc/src/io_utils.dart';
@@ -3142,6 +3143,7 @@ abstract class ModelElement extends Canonicalization
31423143
_rawDocs = _injectExamples(_rawDocs);
31433144
_rawDocs = _injectAnimations(_rawDocs);
31443145
_rawDocs = _stripMacroTemplatesAndAddToIndex(_rawDocs);
3146+
_rawDocs = _stripHtmlAndAddToIndex(_rawDocs);
31453147
}
31463148
return _rawDocs;
31473149
}
@@ -3287,8 +3289,13 @@ abstract class ModelElement extends Canonicalization
32873289
return false;
32883290
}
32893291

3292+
String _htmlDocumentation;
32903293
@override
3291-
String get documentationAsHtml => _documentation.asHtml;
3294+
String get documentationAsHtml {
3295+
if (_htmlDocumentation != null) return _htmlDocumentation;
3296+
_htmlDocumentation = _injectHtmlFragments(_documentation.asHtml);
3297+
return _htmlDocumentation;
3298+
}
32923299

32933300
@override
32943301
Element get element => _element;
@@ -4047,6 +4054,51 @@ abstract class ModelElement extends Canonicalization
40474054
});
40484055
}
40494056

4057+
/// Replace &lt;<dartdoc-html>[digest]</dartdoc-html>&gt; in API comments with
4058+
/// the contents of the HTML fragment earlier defined by the
4059+
/// &#123;@inject-html&#125; directive. The [digest] is a SHA1 of the contents
4060+
/// of the HTML fragment, automatically generated upon parsing the
4061+
/// &#123;@inject-html&#125; directive.
4062+
///
4063+
/// This markup is generated and inserted by [_stripHtmlAndAddToIndex] when it
4064+
/// removes the HTML fragment in preparation for markdown processing. It isn't
4065+
/// meant to be used at a user level.
4066+
///
4067+
/// Example:
4068+
///
4069+
/// You place the fragment in a dartdoc comment:
4070+
///
4071+
/// Some comments
4072+
/// &#123;@inject-html&#125;
4073+
/// &lt;p&gt;[HTML contents!]&lt;/p&gt;
4074+
/// &#123;@endtemplate&#125;
4075+
/// More comments
4076+
///
4077+
/// and [_stripHtmlAndAddToIndex] will replace your HTML fragment with this:
4078+
///
4079+
/// Some comments
4080+
/// &lt;dartdoc-html&gt;4cc02f877240bf69855b4c7291aba8a16e5acce0&lt;/dartdoc-html&gt;
4081+
/// More comments
4082+
///
4083+
/// Which will render in the final HTML output as:
4084+
///
4085+
/// Some comments
4086+
/// &lt;p&gt;[HTML contents!]&lt;/p&gt;
4087+
/// More comments
4088+
///
4089+
/// And the HTML fragment will not have been processed or changed by Markdown,
4090+
/// but just injected verbatim.
4091+
String _injectHtmlFragments(String rawDocs) {
4092+
final macroRegExp = new RegExp(r'<dartdoc-html>([a-f0-9]+)</dartdoc-html>');
4093+
return rawDocs.replaceAllMapped(macroRegExp, (match) {
4094+
String fragment = packageGraph.getHtmlFragment(match[1]);
4095+
if (fragment == null) {
4096+
warn(PackageWarning.unknownHtmlFragment, message: match[1]);
4097+
}
4098+
return fragment;
4099+
});
4100+
}
4101+
40504102
/// Replace &#123;@macro ...&#125; in API comments with the contents of the macro
40514103
///
40524104
/// Syntax:
@@ -4084,7 +4136,8 @@ abstract class ModelElement extends Canonicalization
40844136
});
40854137
}
40864138

4087-
/// Parse &#123;@template ...&#125; in API comments and store them in the index on the package.
4139+
/// Parse and remove &#123;@template ...&#125; in API comments and store them
4140+
/// in the index on the package.
40884141
///
40894142
/// Syntax:
40904143
///
@@ -4102,6 +4155,31 @@ abstract class ModelElement extends Canonicalization
41024155
});
41034156
}
41044157

4158+
/// Parse and remove &#123;@inject-html ...&#125; in API comments and store
4159+
/// them in the index on the package, replacing them with a SHA1 hash of the
4160+
/// contents, where the HTML will be re-injected after Markdown processing of
4161+
/// the rest of the text is complete.
4162+
///
4163+
/// Syntax:
4164+
///
4165+
/// &#123;@inject-html&#125;
4166+
/// <p>The HTML to inject.</p>
4167+
/// &#123;@end-inject-html&#125;
4168+
///
4169+
String _stripHtmlAndAddToIndex(String rawDocs) {
4170+
final templateRegExp = new RegExp(
4171+
r'[ ]*{@inject-html\s*}([\s\S]+?){@end-inject-html}[ ]*\n?',
4172+
multiLine: true);
4173+
return rawDocs.replaceAllMapped(templateRegExp, (match) {
4174+
String fragment = match[1];
4175+
String digest = sha1.convert(fragment.codeUnits).toString();
4176+
packageGraph._addHtmlFragment(digest, fragment);
4177+
// The newlines are so that Markdown will pass this through without
4178+
// touching it.
4179+
return '\n<dartdoc-html>$digest</dartdoc-html>\n';
4180+
});
4181+
}
4182+
41054183
/// Helper to process arguments given as a (possibly quoted) string.
41064184
///
41074185
/// First, this will split the given [argsAsString] into separate arguments,
@@ -4434,7 +4512,7 @@ class PackageGraph {
44344512
// Go through docs of every ModelElement in package to pre-build the macros
44354513
// index.
44364514
allLocalModelElements.forEach((m) => m.documentationLocal);
4437-
_macrosAdded = true;
4515+
_localDocumentationBuilt = true;
44384516

44394517
// Scan all model elements to insure that interceptor and other special
44404518
// objects are found.
@@ -4539,8 +4617,9 @@ class PackageGraph {
45394617

45404618
final Map<Element, Library> _elementToLibrary = {};
45414619
final Map<String, String> _macros = {};
4620+
final Map<String, String> _htmlFragments = {};
45424621
bool allLibrariesAdded = false;
4543-
bool _macrosAdded = false;
4622+
bool _localDocumentationBuilt = false;
45444623

45454624
/// Returns true if there's at least one library documented in the package
45464625
/// that has the same package path as the library for the given element.
@@ -4685,6 +4764,9 @@ class PackageGraph {
46854764
case PackageWarning.unknownMacro:
46864765
warningMessage = "undefined macro [${message}]";
46874766
break;
4767+
case PackageWarning.unknownHtmlFragment:
4768+
warningMessage = "undefined HTML fragment identifier [${message}]";
4769+
break;
46884770
case PackageWarning.brokenLink:
46894771
warningMessage = 'dartdoc generated a broken link to: ${message}';
46904772
warnablePrefix = 'to element';
@@ -5165,14 +5247,24 @@ class PackageGraph {
51655247
}
51665248

51675249
String getMacro(String name) {
5168-
assert(_macrosAdded);
5250+
assert(_localDocumentationBuilt);
51695251
return _macros[name];
51705252
}
51715253

51725254
void _addMacro(String name, String content) {
5173-
assert(!_macrosAdded);
5255+
assert(!_localDocumentationBuilt);
51745256
_macros[name] = content;
51755257
}
5258+
5259+
String getHtmlFragment(String name) {
5260+
assert(_localDocumentationBuilt);
5261+
return _htmlFragments[name];
5262+
}
5263+
5264+
void _addHtmlFragment(String name, String content) {
5265+
assert(!_localDocumentationBuilt);
5266+
_htmlFragments[name] = content;
5267+
}
51765268
}
51775269

51785270
/// A set of [Class]es, [Enum]s, [TopLevelVariable]s, [ModelFunction]s,
@@ -5887,8 +5979,8 @@ abstract class SourceCodeMixin implements Documentable {
58875979

58885980
String get _crossdartPath {
58895981
var node = element.computeNode();
5890-
if (node is Declaration && node.element != null) {
5891-
var source = node.element.source;
5982+
if (node is Declaration && node.declaredElement != null) {
5983+
var source = node.declaredElement.source;
58925984
var filePath = source.fullName;
58935985
var uri = source.uri.toString();
58945986
var packageMeta = library.packageGraph.packageMeta;

lib/src/warnings.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ enum PackageWarning {
136136
reexportedPrivateApiAcrossPackages,
137137
unresolvedDocReference,
138138
unknownMacro,
139+
unknownHtmlFragment,
139140
brokenLink,
140141
orphanedFile,
141142
unknownFile,

pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ packages:
128128
source: hosted
129129
version: "2.0.2"
130130
crypto:
131-
dependency: transitive
131+
dependency: "direct main"
132132
description:
133133
name: crypto
134134
url: "https://pub.dartlang.org"

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ dependencies:
1111
analyzer: ^0.33.0
1212
args: '>=1.4.1 <2.0.0'
1313
collection: ^1.2.0
14+
crypto: ^2.0.6
1415
html: '>=0.12.1 <0.14.0'
1516
# We don't use http_parser directly; this dep exists to ensure that we get at
1617
# least version 3.0.3 to work around an issue with 3.0.2.

test/model_test.dart

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,25 @@ void main() {
116116
});
117117
});
118118

119+
group('HTML Injection', () {
120+
Class htmlInjection;
121+
Method injectSimpleHtml;
122+
123+
setUp(() {
124+
htmlInjection = exLibrary.classes.firstWhere((c) => c.name == 'HtmlInjection');
125+
injectSimpleHtml =
126+
htmlInjection.allInstanceMethods.firstWhere((m) => m.name == 'injectSimpleHtml');
127+
packageGraph.allLocalModelElements.forEach((m) => m.documentation);
128+
});
129+
test("can inject HTML", () {
130+
expect(
131+
injectSimpleHtml.documentation,
132+
contains('\n<dartdoc-html>bad2bbdd4a5cf9efb3212afff4449904756851aa</dartdoc-html>\n'));
133+
expect(injectSimpleHtml.documentationAsHtml,
134+
contains(' <div style="opacity: 0.5;">[HtmlInjection]</div>'));
135+
});
136+
});
137+
119138
group('Missing and Remote', () {
120139
test('Verify that SDK libraries are not canonical when missing', () {
121140
expect(
@@ -1424,7 +1443,7 @@ void main() {
14241443
});
14251444

14261445
test('correctly finds all the classes', () {
1427-
expect(classes, hasLength(29));
1446+
expect(classes, hasLength(30));
14281447
});
14291448

14301449
test('abstract', () {

test/tool_runner_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ void main() {
7575
);
7676
expect(errors, isNotEmpty);
7777
expect(
78-
errors[0], contains('Tool "drill" returned non-zero exit code (1)'));
78+
errors[0], contains('Tool "drill" returned non-zero exit code'));
7979
expect(result, isEmpty);
8080
});
8181
test("fails if tool in tool map doesn't exist", () {

testing/test_package/lib/example.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,3 +598,11 @@ abstract class ToolUser {
598598
/// {@end-tool}
599599
void invokeToolNoInput();
600600
}
601+
602+
abstract class HtmlInjection {
603+
/// Injects some HTML.
604+
/// {@inject-html}
605+
/// <div style="opacity: 0.5;">[HtmlInjection]</div>
606+
/// {@end-inject-html}
607+
void injectSimpleHtml();
608+
}

testing/test_package_docs/ex/Animal-class.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ <h5>ex library</h5>
5555
<li><a href="ex/ForAnnotation-class.html">ForAnnotation</a></li>
5656
<li><a href="ex/HasAnnotation-class.html">HasAnnotation</a></li>
5757
<li><a href="ex/Helper-class.html">Helper</a></li>
58+
<li><a href="ex/HtmlInjection-class.html">HtmlInjection</a></li>
5859
<li><a href="ex/Klass-class.html">Klass</a></li>
5960
<li><a href="ex/ParameterizedClass-class.html">ParameterizedClass</a></li>
6061
<li><a href="ex/PublicClassExtendsPrivateClass-class.html">PublicClassExtendsPrivateClass</a></li>

testing/test_package_docs/ex/AnotherParameterizedClass-class.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ <h5>ex library</h5>
5555
<li><a href="ex/ForAnnotation-class.html">ForAnnotation</a></li>
5656
<li><a href="ex/HasAnnotation-class.html">HasAnnotation</a></li>
5757
<li><a href="ex/Helper-class.html">Helper</a></li>
58+
<li><a href="ex/HtmlInjection-class.html">HtmlInjection</a></li>
5859
<li><a href="ex/Klass-class.html">Klass</a></li>
5960
<li><a href="ex/ParameterizedClass-class.html">ParameterizedClass</a></li>
6061
<li><a href="ex/PublicClassExtendsPrivateClass-class.html">PublicClassExtendsPrivateClass</a></li>

testing/test_package_docs/ex/Apple-class.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ <h5>ex library</h5>
5555
<li><a href="ex/ForAnnotation-class.html">ForAnnotation</a></li>
5656
<li><a href="ex/HasAnnotation-class.html">HasAnnotation</a></li>
5757
<li><a href="ex/Helper-class.html">Helper</a></li>
58+
<li><a href="ex/HtmlInjection-class.html">HtmlInjection</a></li>
5859
<li><a href="ex/Klass-class.html">Klass</a></li>
5960
<li><a href="ex/ParameterizedClass-class.html">ParameterizedClass</a></li>
6061
<li><a href="ex/PublicClassExtendsPrivateClass-class.html">PublicClassExtendsPrivateClass</a></li>

0 commit comments

Comments
 (0)