Skip to content

Commit

Permalink
Adds DartPad option to the DartDoc snippet generator. (#39924)
Browse files Browse the repository at this point in the history
  • Loading branch information
RedBrogdon committed Sep 10, 2019
1 parent 440a911 commit 6919777
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 7 deletions.
3 changes: 3 additions & 0 deletions dartdoc_options.yaml
Expand Up @@ -9,3 +9,6 @@ dartdoc:
sample:
command: ["dev/snippets/lib/main.dart", "--type=sample"]
description: "Creates sample code documentation output from embedded documentation samples."
dartpad:
command: ["dev/snippets/lib/main.dart", "--type=application", "--dartpad"]
description: "Creates sample code documentation output from embedded documentation samples and displays it in an embedded DartPad."
5 changes: 5 additions & 0 deletions dev/docs/assets/snippets.css
Expand Up @@ -83,6 +83,11 @@
font-family: courier, lucidia;
}

.snippet-dartpad {
width: 100%;
height: 500px;
}

.anchor-container {
position: relative;
}
Expand Down
38 changes: 38 additions & 0 deletions dev/snippets/config/skeletons/dartpad-application.html
@@ -0,0 +1,38 @@
{@inject-html}
<a name="{{id}}"></a>
<div class="anchor-container">
<a class="anchor-button-overlay anchor-button" title="Copy link to clipboard"
onmouseenter="fixHref(this, '{{id}}');"
onclick="fixHref(this, '{{id}}'); copyStringToClipboard(this.href);"
href="#">
<i class="material-icons copy-image">link</i>
</a>
</div>
<div class="snippet-buttons">
<script>var visibleSnippet{{serial}} = "longSnippet{{serial}}";</script>
<button id="longSnippet{{serial}}Button"
onclick="visibleSnippet{{serial}} = showSnippet('longSnippet{{serial}}', visibleSnippet{{serial}});"
selected>
Interactive App
</button>
<button id="shortSnippet{{serial}}Button"
onclick="visibleSnippet{{serial}} = showSnippet('shortSnippet{{serial}}', visibleSnippet{{serial}});">
Sample code
</button>
</div>
<div class="snippet-container">
<div class="snippet" id="longSnippet{{serial}}">
<iframe class="snippet-dartpad" src="https://dartpad.dev/embed-flutter.html?split=60&run=true&sample_id={{id}}"></iframe>
</div>
<div class="snippet" id="shortSnippet{{serial}}" hidden>
{{description}}
<div class="copyable-container">
<button class="copy-button-overlay copy-button" title="Copy to clipboard"
onclick="copyTextToClipboard(visibleSnippet{{serial}});">
<i class="material-icons copy-image">assignment</i>
</button>
<pre class="language-{{language}}"><code class="language-{{language}}">{{code}}</code></pre>
</div>
</div>
</div>
{@end-inject-html}
10 changes: 7 additions & 3 deletions dev/snippets/lib/configuration.dart
Expand Up @@ -67,8 +67,12 @@ class Configuration {
/// dartdoc.
Directory get templatesDirectory => Directory(path.join(configDirectory.path, 'templates'));

/// Gets the skeleton file to use for the given [SnippetType].
File getHtmlSkeletonFile(SnippetType type) {
return File(path.join(skeletonsDirectory.path, '${getEnumName(type)}.html'));
/// Gets the skeleton file to use for the given [SnippetType] and DartPad preference.
File getHtmlSkeletonFile(SnippetType type, {bool showDartPad = false}) {
assert(!showDartPad || type == SnippetType.application,
'Only application snippets work with dartpad.');
final String filename =
'${showDartPad ? 'dartpad-' : ''}${getEnumName(type)}.html';
return File(path.join(skeletonsDirectory.path, filename));
}
}
15 changes: 15 additions & 0 deletions dev/snippets/lib/main.dart
Expand Up @@ -20,6 +20,7 @@ const String _kOutputOption = 'output';
const String _kPackageOption = 'package';
const String _kTemplateOption = 'template';
const String _kTypeOption = 'type';
const String _kShowDartPad = 'dartpad';

/// Generates snippet dartdoc output for a given input, and creates any sample
/// applications needed by the snippet.
Expand Down Expand Up @@ -87,6 +88,14 @@ void main(List<String> argList) {
negatable: false,
help: 'Prints help documentation for this command',
);
parser.addFlag(
_kShowDartPad,
defaultsTo: false,
negatable: false,
help: 'Indicates whether DartPad should be included in the snippet\'s '
'final HTML output. This flag only applies when the type parameter is '
'"application".',
);

final ArgResults args = parser.parse(argList);

Expand All @@ -99,6 +108,11 @@ void main(List<String> argList) {
.firstWhere((SnippetType type) => getEnumName(type) == args[_kTypeOption], orElse: () => null);
assert(snippetType != null, "Unable to find '${args[_kTypeOption]}' in SnippetType enum.");

if (args[_kShowDartPad] == true && snippetType != SnippetType.application) {
errorExit('${args[_kTypeOption]} was selected, but the --dartpad flag is only valid '
'for application snippets.');
}

if (args[_kInputOption] == null) {
stderr.writeln(parser.usage);
errorExit('The --$_kInputOption option must be specified, either on the command '
Expand Down Expand Up @@ -151,6 +165,7 @@ void main(List<String> argList) {
stdout.write(generator.generate(
input,
snippetType,
showDartPad: args[_kShowDartPad],
template: template,
output: args[_kOutputOption] != null ? File(args[_kOutputOption]) : null,
metadata: <String, Object>{
Expand Down
11 changes: 10 additions & 1 deletion dev/snippets/lib/snippets.dart
Expand Up @@ -202,6 +202,11 @@ class SnippetGenerator {
/// The [type] is the type of snippet to create: either a
/// [SnippetType.application] or a [SnippetType.sample].
///
/// [showDartPad] indicates whether DartPad should be shown where possible.
/// Currently, this value only has an effect if [type] is
/// [SnippetType.application], in which case an alternate skeleton file is
/// used to create the final HTML output.
///
/// The [template] must not be null if the [type] is
/// [SnippetType.application], and specifies the name of the template to use
/// for the application code.
Expand All @@ -212,13 +217,16 @@ class SnippetGenerator {
String generate(
File input,
SnippetType type, {
bool showDartPad = false,
String template,
File output,
@required Map<String, Object> metadata,
}) {
assert(template != null || type != SnippetType.application);
assert(metadata != null && metadata['id'] != null);
assert(input != null);
assert(!showDartPad || type == SnippetType.application,
'Only application snippets work with dartpad.');
final List<_ComponentTuple> snippetData = parseInput(_loadFileAsUtf8(input));
switch (type) {
case SnippetType.application:
Expand Down Expand Up @@ -266,7 +274,8 @@ class SnippetGenerator {
case SnippetType.sample:
break;
}
final String skeleton = _loadFileAsUtf8(configuration.getHtmlSkeletonFile(type));
final String skeleton =
_loadFileAsUtf8(configuration.getHtmlSkeletonFile(type, showDartPad: showDartPad));
return interpolateSkeleton(type, snippetData, skeleton, metadata);
}
}
14 changes: 13 additions & 1 deletion dev/snippets/test/configuration_test.dart
Expand Up @@ -31,11 +31,23 @@ void main() {
expect(config.templatesDirectory.path,
matches(RegExp(r'[/\\]flutter sdk[/\\]dev[/\\]snippets[/\\]config[/\\]templates')));
});
test('html skeleton file is correct', () async {
test('html skeleton file for sample is correct', () async {
expect(
config.getHtmlSkeletonFile(SnippetType.sample).path,
matches(RegExp(
r'[/\\]flutter sdk[/\\]dev[/\\]snippets[/\\]config[/\\]skeletons[/\\]sample.html')));
});
test('html skeleton file for app with no dartpad is correct', () async {
expect(
config.getHtmlSkeletonFile(SnippetType.application).path,
matches(RegExp(
r'[/\\]flutter sdk[/\\]dev[/\\]snippets[/\\]config[/\\]skeletons[/\\]application.html')));
});
test('html skeleton file for app with dartpad is correct', () async {
expect(
config.getHtmlSkeletonFile(SnippetType.application, showDartPad: true).path,
matches(RegExp(
r'[/\\]flutter sdk[/\\]dev[/\\]snippets[/\\]config[/\\]skeletons[/\\]dartpad-application.html')));
});
});
}
38 changes: 37 additions & 1 deletion dev/snippets/test/snippets_test.dart
Expand Up @@ -48,6 +48,11 @@ main() {
{{description}}
<pre>{{code}}</pre>
<div>More HTML Bits</div>
''');
configuration.getHtmlSkeletonFile(SnippetType.application, showDartPad: true).writeAsStringSync('''
<div>HTML Bits (DartPad-style)</div>
<iframe class="snippet-dartpad" src="https://dartpad.dev/embed-flutter.html?split=60&run=true&sample_id={{id}}"></iframe>
<div>More HTML Bits</div>
''');
generator = SnippetGenerator(configuration: configuration);
});
Expand Down Expand Up @@ -109,7 +114,11 @@ void main() {
```
''');

final String html = generator.generate(inputFile, SnippetType.sample, metadata: <String, Object>{'id': 'id'});
final String html = generator.generate(
inputFile,
SnippetType.sample,
metadata: <String, Object>{'id': 'id'},
);
expect(html, contains('<div>HTML Bits</div>'));
expect(html, contains('<div>More HTML Bits</div>'));
expect(html, contains(' print(&#39;The actual \$name.&#39;);'));
Expand All @@ -119,6 +128,33 @@ void main() {
expect(html, contains('main() {'));
});

test('generates dartpad snippets', () async {
final File inputFile = File(path.join(tmpDir.absolute.path, 'snippet_in.txt'))
..createSync(recursive: true)
..writeAsStringSync('''
A description of the snippet.
On several lines.
```code
void main() {
print('The actual \$name.');
}
```
''');

final String html = generator.generate(
inputFile,
SnippetType.application,
showDartPad: true,
template: 'template',
metadata: <String, Object>{'id': 'id'},
);
expect(html, contains('<div>HTML Bits (DartPad-style)</div>'));
expect(html, contains('<div>More HTML Bits</div>'));
expect(html, contains('<iframe class="snippet-dartpad" src="https://dartpad.dev/embed-flutter.html?split=60&run=true&sample_id=id"></iframe>'));
});

test('generates snippet application metadata', () async {
final File inputFile = File(path.join(tmpDir.absolute.path, 'snippet_in.txt'))
..createSync(recursive: true)
Expand Down
2 changes: 1 addition & 1 deletion packages/flutter/lib/src/material/app_bar.dart
Expand Up @@ -89,7 +89,7 @@ class _ToolbarContainerLayout extends SingleChildLayoutDelegate {
/// to false. In that case a null leading widget will result in the middle/title widget
/// stretching to start.
///
/// {@tool snippet --template=stateless_widget_material}
/// {@tool dartpad --template=stateless_widget_material}
///
/// This sample shows an [AppBar] with two simple actions. The first action
/// opens a [SnackBar], while the second action navigates to a new page.
Expand Down

0 comments on commit 6919777

Please sign in to comment.