diff --git a/dartdoc_options.yaml b/dartdoc_options.yaml index 4a68e2f8424758..90c431695ae721 100644 --- a/dartdoc_options.yaml +++ b/dartdoc_options.yaml @@ -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." diff --git a/dev/docs/assets/snippets.css b/dev/docs/assets/snippets.css index e5ee48f170b0fb..02f2b1360ac82a 100644 --- a/dev/docs/assets/snippets.css +++ b/dev/docs/assets/snippets.css @@ -83,6 +83,11 @@ font-family: courier, lucidia; } +.snippet-dartpad { + width: 100%; + height: 500px; +} + .anchor-container { position: relative; } diff --git a/dev/snippets/config/skeletons/dartpad-application.html b/dev/snippets/config/skeletons/dartpad-application.html new file mode 100644 index 00000000000000..1fa16f7d0209f8 --- /dev/null +++ b/dev/snippets/config/skeletons/dartpad-application.html @@ -0,0 +1,38 @@ +{@inject-html} + +
+ + link + +
+
+ + + +
+
+
+ +
+ +
+{@end-inject-html} diff --git a/dev/snippets/lib/configuration.dart b/dev/snippets/lib/configuration.dart index 1f401b1bfd9a17..e1f65fd9e858a7 100644 --- a/dev/snippets/lib/configuration.dart +++ b/dev/snippets/lib/configuration.dart @@ -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)); } } diff --git a/dev/snippets/lib/main.dart b/dev/snippets/lib/main.dart index 996b186c9bbd9e..6af06617ed8c1b 100644 --- a/dev/snippets/lib/main.dart +++ b/dev/snippets/lib/main.dart @@ -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. @@ -87,6 +88,14 @@ void main(List 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); @@ -99,6 +108,11 @@ void main(List 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 ' @@ -151,6 +165,7 @@ void main(List argList) { stdout.write(generator.generate( input, snippetType, + showDartPad: args[_kShowDartPad], template: template, output: args[_kOutputOption] != null ? File(args[_kOutputOption]) : null, metadata: { diff --git a/dev/snippets/lib/snippets.dart b/dev/snippets/lib/snippets.dart index 05d54f4c16979a..22721060522101 100644 --- a/dev/snippets/lib/snippets.dart +++ b/dev/snippets/lib/snippets.dart @@ -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. @@ -212,6 +217,7 @@ class SnippetGenerator { String generate( File input, SnippetType type, { + bool showDartPad = false, String template, File output, @required Map metadata, @@ -219,6 +225,8 @@ class SnippetGenerator { 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: @@ -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); } } diff --git a/dev/snippets/test/configuration_test.dart b/dev/snippets/test/configuration_test.dart index 41e7d2f7df6cec..f4879d751095bd 100644 --- a/dev/snippets/test/configuration_test.dart +++ b/dev/snippets/test/configuration_test.dart @@ -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'))); + }); }); } diff --git a/dev/snippets/test/snippets_test.dart b/dev/snippets/test/snippets_test.dart index f3cb758a3cc2de..73ca67ea4c207d 100644 --- a/dev/snippets/test/snippets_test.dart +++ b/dev/snippets/test/snippets_test.dart @@ -48,6 +48,11 @@ main() { {{description}}
{{code}}
More HTML Bits
+'''); + configuration.getHtmlSkeletonFile(SnippetType.application, showDartPad: true).writeAsStringSync(''' +
HTML Bits (DartPad-style)
+ +
More HTML Bits
'''); generator = SnippetGenerator(configuration: configuration); }); @@ -109,7 +114,11 @@ void main() { ``` '''); - final String html = generator.generate(inputFile, SnippetType.sample, metadata: {'id': 'id'}); + final String html = generator.generate( + inputFile, + SnippetType.sample, + metadata: {'id': 'id'}, + ); expect(html, contains('
HTML Bits
')); expect(html, contains('
More HTML Bits
')); expect(html, contains(' print('The actual \$name.');')); @@ -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: {'id': 'id'}, + ); + expect(html, contains('
HTML Bits (DartPad-style)
')); + expect(html, contains('
More HTML Bits
')); + expect(html, contains('')); + }); + test('generates snippet application metadata', () async { final File inputFile = File(path.join(tmpDir.absolute.path, 'snippet_in.txt')) ..createSync(recursive: true) diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index c4a47127a1d958..bad79ac93a4552 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -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.