mirror of
https://github.com/flutter/flutter
synced 2024-09-13 05:11:45 +00:00
Dartdoc snippet extension to inject full featured code snippets in to API docs. (#23281)
This creates a custom dartdoc tool that will generate snippet blocks in our API docs that allow the user to copy easily to the clipboard, and will also embed the snippet code into a template to show it in a larger context with an app. This PR adds the snippet tool, a template, and a couple of HTML skeleton files, one for snippets that are designed to be in an application setting, and one where it simply puts a nice container around existing snippets, making them easier to copy to the clipboard.
This commit is contained in:
parent
a3e0b0aee2
commit
65d3ddd5d1
9
dartdoc_options.yaml
Normal file
9
dartdoc_options.yaml
Normal file
|
@ -0,0 +1,9 @@
|
|||
# This file is used by dartdoc when generating API documentation for Flutter.
|
||||
dartdoc:
|
||||
tools:
|
||||
snippet:
|
||||
command: ["dev/snippets/lib/main.dart", "--type=application"]
|
||||
description: "Creates application sample code documentation output from embedded documentation samples."
|
||||
sample:
|
||||
command: ["dev/snippets/lib/main.dart", "--type=sample"]
|
||||
description: "Creates sample code documentation output from embedded documentation samples."
|
|
@ -266,6 +266,7 @@ Future<void> _verifyNoTestPackageImports(String workingDirectory) async {
|
|||
if (path.split(file.path).contains('test_driver') ||
|
||||
name.startsWith('dev/missing_dependency_tests/') ||
|
||||
name.startsWith('dev/automated_tests/') ||
|
||||
name.startsWith('dev/snippets/') ||
|
||||
name.startsWith('packages/flutter/test/engine/') ||
|
||||
name.startsWith('examples/layers/test/smoketests/raw/') ||
|
||||
name.startsWith('examples/layers/test/smoketests/rendering/') ||
|
||||
|
|
|
@ -182,6 +182,7 @@ Future<void> _runTests() async {
|
|||
await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol'));
|
||||
await _pubRunTest(path.join(flutterRoot, 'dev', 'bots'));
|
||||
await _pubRunTest(path.join(flutterRoot, 'dev', 'devicelab'));
|
||||
await _pubRunTest(path.join(flutterRoot, 'dev', 'snippets'));
|
||||
await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'));
|
||||
await _runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests'));
|
||||
await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool'));
|
||||
|
|
139
dev/docs/assets/overrides.css
Normal file
139
dev/docs/assets/overrides.css
Normal file
|
@ -0,0 +1,139 @@
|
|||
/* Overrides for dartdoc styles. */
|
||||
body {
|
||||
font-size: 15px;
|
||||
font-family: Roboto, sans-serif;
|
||||
line-height: 1.5;
|
||||
color: #111;
|
||||
background-color: #fdfdfd;
|
||||
font-weight: 300;
|
||||
-webkit-font-smoothing: auto;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: white;
|
||||
color: #424242;
|
||||
}
|
||||
|
||||
nav.navbar {
|
||||
min-height: 57px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
header.header-fixed nav.navbar-fixed-top {
|
||||
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.37);
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
h3, h4, h5, h6 {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 42px !important;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #111;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.markdown h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
section.summary h2 {
|
||||
font-size: 24px;
|
||||
color: inherit;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.sidebar ol,
|
||||
.sidebar ol li.section-title {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.sidebar-offcanvas-left.active {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-offcanvas-left ol {
|
||||
padding: 0 16px 16px 0;
|
||||
}
|
||||
|
||||
.sidebar-offcanvas-left h5 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
pre.prettyprint,
|
||||
pre > code {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
pre,
|
||||
pre.prettyprint {
|
||||
background: #f5f2f0;
|
||||
margin: 0 0 15px 0;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #cccccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: inherit;
|
||||
font-size: 1em; /* browsers default to smaller font for code */
|
||||
font-weight: 300;
|
||||
padding-left: 0; /* otherwise we get ragged left margins */
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
#search-box {
|
||||
color: #555;
|
||||
background-color: #fff;
|
||||
background-image: none;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
font-family: inherit;
|
||||
padding: 4px 6px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
input.form-control.typeahead {
|
||||
padding: 4px 7px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
dl.dl-horizontal dt {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Line the material icons up with their labels */
|
||||
i.material-icons.md-36,
|
||||
i.material-icons.md-48 {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
/* thinify the inherited names in lists */
|
||||
li.inherited a {
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
/* address a style issue with the background of code sections */
|
||||
code.hljs {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
footer {
|
||||
font-size: 13px;
|
||||
padding: 12px 20px;
|
||||
}
|
108
dev/docs/assets/snippets.css
Normal file
108
dev/docs/assets/snippets.css
Normal file
|
@ -0,0 +1,108 @@
|
|||
/* Styles for handling custom code snippets */
|
||||
.snippet-container {
|
||||
background-color: #45aae8;
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.snippet-container pre {
|
||||
max-height: 500px;
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.snippet-container ::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.snippet-container ::-webkit-scrollbar-thumb {
|
||||
width: 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.snippet {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.snippet-description {
|
||||
padding: 10px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.snippet-buttons button {
|
||||
background-color: #45aae8;
|
||||
border-style: none;
|
||||
color: white;
|
||||
padding: 10px 24px;
|
||||
cursor: pointer;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.snippet-buttons:after {
|
||||
content: "";
|
||||
clear: both;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.snippet-buttons button:focus { outline: none; }
|
||||
|
||||
.snippet-buttons button:hover {
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
.snippet-buttons :not([selected]) {
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.snippet-buttons [selected] {
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
.snippet-container [hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.snippet-create-command {
|
||||
text-align: end;
|
||||
font-size: smaller;
|
||||
font-style: normal;
|
||||
font-family: courier, lucidia;
|
||||
}
|
||||
|
||||
/* Styles for the copy-to-clipboard button */
|
||||
.copyable-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.copy-button-overlay {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 14px;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
transition: .3s ease;
|
||||
background-color: #45aae8;
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
border-style: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.copy-button :focus {
|
||||
outline: 0px;
|
||||
}
|
||||
|
||||
.copy-button :hover {
|
||||
transition: .3s ease;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.copy-image {
|
||||
opacity: 0.65;
|
||||
color: #45aae8;
|
||||
font-size: 28px;
|
||||
padding-top: 4px;
|
||||
}
|
93
dev/docs/assets/snippets.js
Normal file
93
dev/docs/assets/snippets.js
Normal file
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* Scripting for handling custom code snippets
|
||||
*/
|
||||
|
||||
const shortSnippet = 'shortSnippet';
|
||||
const longSnippet = 'longSnippet';
|
||||
var visibleSnippet = shortSnippet;
|
||||
|
||||
/**
|
||||
* Shows the requested snippet. Values for "name" can be "shortSnippet" or
|
||||
* "longSnippet".
|
||||
*/
|
||||
function showSnippet(name) {
|
||||
if (visibleSnippet == name) return;
|
||||
if (visibleSnippet != null) {
|
||||
var shown = document.getElementById(visibleSnippet);
|
||||
var attribute = document.createAttribute('hidden');
|
||||
if (shown != null) {
|
||||
shown.setAttributeNode(attribute);
|
||||
}
|
||||
var button = document.getElementById(visibleSnippet + 'Button');
|
||||
if (button != null) {
|
||||
button.removeAttribute('selected');
|
||||
}
|
||||
}
|
||||
if (name == null || name == '') {
|
||||
visibleSnippet = null;
|
||||
return;
|
||||
}
|
||||
var newlyVisible = document.getElementById(name);
|
||||
if (newlyVisible != null) {
|
||||
visibleSnippet = name;
|
||||
newlyVisible.removeAttribute('hidden');
|
||||
} else {
|
||||
visibleSnippet = null;
|
||||
}
|
||||
var button = document.getElementById(name + 'Button');
|
||||
var selectedAttribute = document.createAttribute('selected');
|
||||
if (button != null) {
|
||||
button.setAttributeNode(selectedAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
// Finds a sibling to given element with the given id.
|
||||
function findSiblingWithId(element, id) {
|
||||
var siblings = element.parentNode.children;
|
||||
var siblingWithId = null;
|
||||
for (var i = siblings.length; i--;) {
|
||||
if (siblings[i] == element) continue;
|
||||
if (siblings[i].id == id) {
|
||||
siblingWithId = siblings[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return siblingWithId;
|
||||
};
|
||||
|
||||
// Returns true if the browser supports the "copy" command.
|
||||
function supportsCopying() {
|
||||
return !!document.queryCommandSupported &&
|
||||
!!document.queryCommandSupported('copy');
|
||||
}
|
||||
|
||||
// Copies the text inside the currently visible snippet to the clipboard, or the
|
||||
// given element, if any.
|
||||
function copyTextToClipboard(element) {
|
||||
if (element == null) {
|
||||
var elementSelector = '#' + visibleSnippet + ' .language-dart';
|
||||
element = document.querySelector(elementSelector);
|
||||
if (element == null) {
|
||||
console.log(
|
||||
'copyTextToClipboard: Unable to find element for "' +
|
||||
elementSelector + '"');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!supportsCopying()) {
|
||||
alert('Unable to copy to clipboard (not supported by browser)');
|
||||
return;
|
||||
}
|
||||
|
||||
if (element.hasAttribute('contenteditable')) {
|
||||
element.focus();
|
||||
}
|
||||
|
||||
var selection = window.getSelection();
|
||||
var range = document.createRange();
|
||||
|
||||
range.selectNodeContents(element);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
document.execCommand('copy');
|
||||
}
|
3
dev/docs/snippets.html
Normal file
3
dev/docs/snippets.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<!-- Styles and scripting for handling custom code snippets -->
|
||||
<link href="../assets/snippets.css" rel="stylesheet" type="text/css">
|
||||
<script src="../assets/snippets.js"></script>
|
|
@ -1,148 +1,10 @@
|
|||
<!-- style overrides for dartdoc -->
|
||||
<style>
|
||||
@import 'https://fonts.googleapis.com/css?family=Roboto:500,400italic,300,400,100i';
|
||||
@import 'https://fonts.googleapis.com/css?family=Material+Icons';
|
||||
</style>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-size: 15px;
|
||||
font-family: Roboto, sans-serif;
|
||||
line-height: 1.5;
|
||||
color: #111;
|
||||
background-color: #fdfdfd;
|
||||
font-weight: 300;
|
||||
-webkit-font-smoothing: auto;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: white;
|
||||
color: #424242;
|
||||
}
|
||||
|
||||
nav.navbar {
|
||||
min-height: 57px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
header.header-fixed nav.navbar-fixed-top {
|
||||
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.37);
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
h3, h4, h5, h6 {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 42px !important;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #111;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.markdown h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
section.summary h2 {
|
||||
font-size: 24px;
|
||||
color: inherit;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.sidebar ol,
|
||||
.sidebar ol li.section-title {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.sidebar-offcanvas-left.active {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-offcanvas-left ol {
|
||||
padding: 0 16px 16px 0;
|
||||
}
|
||||
|
||||
.sidebar-offcanvas-left h5 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
pre.prettyprint,
|
||||
pre > code {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
pre,
|
||||
pre.prettyprint {
|
||||
background: #f5f2f0;
|
||||
margin: 0 0 15px 0;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #cccccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: inherit;
|
||||
font-size: 1em; /* browsers default to smaller font for code */
|
||||
font-weight: 300;
|
||||
padding-left: 0; /* otherwise we get ragged left margins */
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
#search-box {
|
||||
color: #555;
|
||||
background-color: #fff;
|
||||
background-image: none;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
font-family: inherit;
|
||||
padding: 4px 6px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
input.form-control.typeahead {
|
||||
padding: 4px 7px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
dl.dl-horizontal dt {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Line the material icons up with their labels */
|
||||
i.material-icons.md-36,
|
||||
i.material-icons.md-48 {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
/* thinify the inherited names in lists */
|
||||
li.inherited a {
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
/* address a style issue with the background of code sections */
|
||||
code.hljs {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
footer {
|
||||
font-size: 13px;
|
||||
padding: 12px 20px;
|
||||
}
|
||||
</style>
|
||||
<link href="../assets/overrides.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- The following rules are from http://google.github.io/material-design-icons/ -->
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
|
|
57
dev/snippets/README.md
Normal file
57
dev/snippets/README.md
Normal file
|
@ -0,0 +1,57 @@
|
|||
## Snippet Tool
|
||||
|
||||
This is a dartdoc extension tool that takes code snippets and expands how they
|
||||
are presented so that Flutter can have more interactive and useful code
|
||||
snippets.
|
||||
|
||||
This takes code in dartdocs, like this:
|
||||
|
||||
```dart
|
||||
/// The following is a skeleton of a stateless widget subclass called `GreenFrog`:
|
||||
/// {@tool snippet --template="stateless_widget"}
|
||||
/// class GreenFrog extends StatelessWidget {
|
||||
/// const GreenFrog({ Key key }) : super(key: key);
|
||||
///
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// return Container(color: const Color(0xFF2DBD3A));
|
||||
/// }
|
||||
/// }
|
||||
/// {@end-tool}
|
||||
```
|
||||
|
||||
And converts it into something which has a nice visual presentation, and
|
||||
a button to automatically copy the sample to the clipboard.
|
||||
|
||||
It does this by processing the source input and emitting HTML for output,
|
||||
which dartdoc places back into the documentation. Any options given to the
|
||||
`{@tool ...}` directive are passed on verbatim to the tool.
|
||||
|
||||
To render the above, the snippets tool needs to render the code in a combination
|
||||
of markdown and HTML, using the `{@inject-html}` dartdoc directive.
|
||||
|
||||
## Templates
|
||||
|
||||
In order to support showing an entire app when you click on the right tab of
|
||||
the code snippet UI, we have to be able to insert the snippet into the template
|
||||
and instantiate the right parts.
|
||||
|
||||
To do this, there is a [config/templates](config/templates) directory that
|
||||
contains a list of templates. These templates represent an entire app that the
|
||||
snippet can be placed into, basically a replacement for `lib/main.dart` in a
|
||||
flutter app package.
|
||||
|
||||
## Skeletons
|
||||
|
||||
A skeleton (in relation to this tool, in the [config/skeletons](config/skeletons)
|
||||
directory) is an HTML template into which the snippet Dart code and description
|
||||
are interpolated, in order to display it nicely.
|
||||
|
||||
There is currently one skeleton for
|
||||
[application](config/skeletons/application.html) snippets and one for
|
||||
[sample](config/skeletons/sample.html)
|
||||
snippets, but there could be more. It uses moustache notation (e.g. `{{code}}`)
|
||||
to mark where the components to be interpolated into the template should go.
|
||||
|
||||
(It doesn't actually use the moustache package, since the only things that need
|
||||
substituting are simple strings, but it uses the same syntax).
|
34
dev/snippets/config/skeletons/application.html
Normal file
34
dev/snippets/config/skeletons/application.html
Normal file
|
@ -0,0 +1,34 @@
|
|||
{@inject-html}
|
||||
<div class="snippet-buttons">
|
||||
<button id="shortSnippetButton" onclick="showSnippet(shortSnippet);" selected>Sample</button>
|
||||
<button id="longSnippetButton" onclick="showSnippet(longSnippet);">Sample in an App</button>
|
||||
</div>
|
||||
<div class="snippet-container">
|
||||
<div class="snippet" id="shortSnippet">
|
||||
<div class="snippet-description">
|
||||
{@end-inject-html}
|
||||
{{description}}
|
||||
{@inject-html}
|
||||
</div>
|
||||
<div class="copyable-container">
|
||||
<button class="copy-button-overlay copy-button" title="Copy to clipboard"
|
||||
onclick="copyTextToClipboard();">
|
||||
<i class="material-icons copy-image">assignment</i>
|
||||
</button>
|
||||
<pre class="language-dart"><code class="language-dart">{{code}}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="snippet" id="longSnippet" hidden>
|
||||
<div class="snippet-description">To create a sample project with this code snippet, run:<br/>
|
||||
<span class="snippet-create-command">flutter create --snippet={{id}} mysample</span>
|
||||
</div>
|
||||
<div class="copyable-container">
|
||||
<button class="copy-button-overlay copy-button" title="Copy to clipboard"
|
||||
onclick="copyTextToClipboard();">
|
||||
<i class="material-icons copy-image">assignment</i>
|
||||
</button>
|
||||
<pre class="language-dart"><code class="language-dart">{{app}}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{@end-inject-html}
|
20
dev/snippets/config/skeletons/sample.html
Normal file
20
dev/snippets/config/skeletons/sample.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{@inject-html}
|
||||
<div class="snippet-container">
|
||||
<div class="snippet">
|
||||
<div class="snippet-description">
|
||||
{@end-inject-html}
|
||||
{{description}}
|
||||
{@inject-html}
|
||||
</div>
|
||||
<div class="copyable-container">
|
||||
<button class="copy-button-overlay copy-button" title="Copy to clipboard"
|
||||
onclick="copyTextToClipboard(findSiblingWithId(this, 'sample-code'));">
|
||||
<i class="material-icons copy-image">assignment</i>
|
||||
</button>
|
||||
<pre class="language-dart" id="sample-code">
|
||||
<code class="language-dart">{{code}}</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{@end-inject-html}
|
56
dev/snippets/config/templates/README.md
Normal file
56
dev/snippets/config/templates/README.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
## Creating Code Snippets
|
||||
|
||||
In general, creating application snippets can be accomplished with the following
|
||||
syntax inside of the dartdoc comment for a Flutter class/variable/enum/etc.:
|
||||
|
||||
```dart
|
||||
/// {@tool snippet --template=stateful_widget}
|
||||
/// Any text outside of the code blocks will be accumulated and placed at the
|
||||
/// top of the snippet box as a description. Don't try and say "see the code
|
||||
/// above" or "see the code below", since the location of the description may
|
||||
/// change in the future. You can use dartdoc [Linking] in the description, and
|
||||
/// __Markdown__ too.
|
||||
/// ```dart preamble
|
||||
/// class Foo extends StatelessWidget {
|
||||
/// const Foo({this.value = ''});
|
||||
///
|
||||
/// String value;
|
||||
///
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// return Text(value);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// This will get tacked on to the end of the description above, and shown above
|
||||
/// the snippet. These two code blocks will be separated by `///...` in the
|
||||
/// short version of the snippet code sample.
|
||||
/// ```dart
|
||||
/// String myValue = 'Foo';
|
||||
///
|
||||
/// @override
|
||||
/// Widget build(BuildContext) {
|
||||
/// return const Foo(myValue);
|
||||
/// }
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
```
|
||||
|
||||
This will result in the template having the section that's inside "```dart"
|
||||
interpolated into the template's stateful widget's state object body.
|
||||
|
||||
All code within a code block in a snippet needs to be able to be run through
|
||||
dartfmt without errors, so it needs to be valid code (This shouldn't be an
|
||||
additional burden, since all code will also be compiled to be sure it compiles).
|
||||
|
||||
## Available Templates
|
||||
|
||||
The templates available for using as an argument to the snippets tool are as
|
||||
follows:
|
||||
|
||||
- __`stateful_widget`__ : Takes a `preamble` in addition to the default code
|
||||
block, which will be placed at the top level of the Dart file, so bare
|
||||
function calls are not allowed in the preamble. The default code block is
|
||||
placed as the body of a stateful widget, so you will need to implement the
|
||||
build() function, and any state variables.
|
||||
|
32
dev/snippets/config/templates/stateful_widget.tmpl
Normal file
32
dev/snippets/config/templates/stateful_widget.tmpl
Normal file
|
@ -0,0 +1,32 @@
|
|||
{{description}}
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() => runApp(new MyApp());
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new MaterialApp(
|
||||
title: 'Flutter Code Sample for {{id}}',
|
||||
theme: new ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: new MyHomePage(title: '{{id}} Sample'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
{{code-preamble}}
|
||||
|
||||
class MyHomePage extends StatelessWidget {
|
||||
MyHomePage({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_MyHomePageState createState() => new _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
{{code}}
|
||||
}
|
72
dev/snippets/lib/configuration.dart
Normal file
72
dev/snippets/lib/configuration.dart
Normal file
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:io' hide Platform;
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
/// What type of snippet to produce.
|
||||
enum SnippetType {
|
||||
/// Produces a snippet that includes the code interpolated into an application
|
||||
/// template.
|
||||
application,
|
||||
/// Produces a nicely formatted sample code, but no application.
|
||||
sample,
|
||||
}
|
||||
|
||||
/// Return the name of an enum item.
|
||||
String getEnumName(dynamic enumItem) {
|
||||
final String name = '$enumItem';
|
||||
final int index = name.indexOf('.');
|
||||
return index == -1 ? name : name.substring(index + 1);
|
||||
}
|
||||
|
||||
/// A class to compute the configuration of the snippets input and output
|
||||
/// locations based in the current location of the snippets main.dart.
|
||||
class Configuration {
|
||||
const Configuration({Platform platform}) : platform = platform ?? const LocalPlatform();
|
||||
|
||||
final Platform platform;
|
||||
|
||||
/// This is the configuration directory for the snippets system, containing
|
||||
/// the skeletons and templates.
|
||||
@visibleForTesting
|
||||
Directory getConfigDirectory(String kind) {
|
||||
final String platformScriptPath = path.dirname(platform.script.toFilePath());
|
||||
final String configPath =
|
||||
path.canonicalize(path.join(platformScriptPath, '..', 'config', kind));
|
||||
return Directory(configPath);
|
||||
}
|
||||
|
||||
/// This is where the snippets themselves will be written, in order to be
|
||||
/// uploaded to the docs site.
|
||||
Directory get outputDirectory {
|
||||
final String platformScriptPath = path.dirname(platform.script.toFilePath());
|
||||
final String docsDirectory =
|
||||
path.canonicalize(path.join(platformScriptPath, '..', '..', 'docs', 'doc', 'snippets'));
|
||||
return Directory(docsDirectory);
|
||||
}
|
||||
|
||||
/// This makes sure that the output directory exists.
|
||||
void createOutputDirectory() {
|
||||
if (!outputDirectory.existsSync()) {
|
||||
outputDirectory.createSync(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// The directory containing the HTML skeletons to be filled out with metadata
|
||||
/// and returned to dartdoc for insertion in the output.
|
||||
Directory get skeletonsDirectory => getConfigDirectory('skeletons');
|
||||
|
||||
/// The directory containing the code templates that can be referenced by the
|
||||
/// dartdoc.
|
||||
Directory get templatesDirectory => getConfigDirectory('templates');
|
||||
|
||||
/// Gets the skeleton file to use for the given [SnippetType].
|
||||
File getHtmlSkeletonFile(SnippetType type) {
|
||||
return File(path.join(skeletonsDirectory.path, '${getEnumName(type)}.html'));
|
||||
}
|
||||
}
|
122
dev/snippets/lib/main.dart
Normal file
122
dev/snippets/lib/main.dart
Normal file
|
@ -0,0 +1,122 @@
|
|||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:io' hide Platform;
|
||||
|
||||
import 'package:args/args.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
import 'configuration.dart';
|
||||
import 'snippets.dart';
|
||||
|
||||
const String _kElementOption = 'element';
|
||||
const String _kInputOption = 'input';
|
||||
const String _kLibraryOption = 'library';
|
||||
const String _kPackageOption = 'package';
|
||||
const String _kTemplateOption = 'template';
|
||||
const String _kTypeOption = 'type';
|
||||
|
||||
/// Generates snippet dartdoc output for a given input, and creates any sample
|
||||
/// applications needed by the snippet.
|
||||
void main(List<String> argList) {
|
||||
const Platform platform = LocalPlatform();
|
||||
final Map<String, String> environment = platform.environment;
|
||||
final ArgParser parser = ArgParser();
|
||||
final List<String> snippetTypes =
|
||||
SnippetType.values.map<String>((SnippetType type) => getEnumName(type)).toList();
|
||||
parser.addOption(
|
||||
_kTypeOption,
|
||||
defaultsTo: getEnumName(SnippetType.application),
|
||||
allowed: snippetTypes,
|
||||
allowedHelp: <String, String>{
|
||||
getEnumName(SnippetType.application):
|
||||
'Produce a code snippet complete with embedding the sample in an '
|
||||
'application template.',
|
||||
getEnumName(SnippetType.sample):
|
||||
'Produce a nicely formatted piece of sample code. Does not embed the '
|
||||
'sample into an application template.'
|
||||
},
|
||||
help: 'The type of snippet to produce.',
|
||||
);
|
||||
parser.addOption(
|
||||
_kTemplateOption,
|
||||
defaultsTo: null,
|
||||
help: 'The name of the template to inject the code into.',
|
||||
);
|
||||
parser.addOption(
|
||||
_kInputOption,
|
||||
defaultsTo: environment['INPUT'],
|
||||
help: 'The input file containing the snippet code to inject.',
|
||||
);
|
||||
parser.addOption(
|
||||
_kPackageOption,
|
||||
defaultsTo: environment['PACKAGE_NAME'],
|
||||
help: 'The name of the package that this snippet belongs to.',
|
||||
);
|
||||
parser.addOption(
|
||||
_kLibraryOption,
|
||||
defaultsTo: environment['LIBRARY_NAME'],
|
||||
help: 'The name of the library that this snippet belongs to.',
|
||||
);
|
||||
parser.addOption(
|
||||
_kElementOption,
|
||||
defaultsTo: environment['ELEMENT_NAME'],
|
||||
help: 'The name of the element that this snippet belongs to.',
|
||||
);
|
||||
|
||||
final ArgResults args = parser.parse(argList);
|
||||
|
||||
final SnippetType snippetType = SnippetType.values
|
||||
.firstWhere((SnippetType type) => getEnumName(type) == args[_kTypeOption], orElse: () => null);
|
||||
assert(snippetType != null, "Unable to find '${args[_kTypeOption]}' in SnippetType enum.");
|
||||
|
||||
if (args[_kInputOption] == null) {
|
||||
stderr.writeln(parser.usage);
|
||||
errorExit('The --$_kInputOption option must be specified, either on the command '
|
||||
'line, or in the INPUT environment variable.');
|
||||
}
|
||||
|
||||
final File input = File(args['input']);
|
||||
if (!input.existsSync()) {
|
||||
errorExit('The input file ${input.path} does not exist.');
|
||||
}
|
||||
|
||||
String template;
|
||||
if (snippetType == SnippetType.application) {
|
||||
if (args[_kTemplateOption] == null || args[_kTemplateOption].isEmpty) {
|
||||
stderr.writeln(parser.usage);
|
||||
errorExit('The --$_kTemplateOption option must be specified on the command '
|
||||
'line for application snippets.');
|
||||
}
|
||||
template = args[_kTemplateOption].toString().replaceAll(RegExp(r'.tmpl$'), '');
|
||||
}
|
||||
|
||||
final List<String> id = <String>[];
|
||||
if (args[_kPackageOption] != null &&
|
||||
args[_kPackageOption].isNotEmpty &&
|
||||
args[_kPackageOption] != 'flutter') {
|
||||
id.add(args[_kPackageOption]);
|
||||
}
|
||||
if (args[_kLibraryOption] != null && args[_kLibraryOption].isNotEmpty) {
|
||||
id.add(args[_kLibraryOption]);
|
||||
}
|
||||
if (args[_kElementOption] != null && args[_kElementOption].isNotEmpty) {
|
||||
id.add(args[_kElementOption]);
|
||||
}
|
||||
|
||||
if (id.isEmpty) {
|
||||
errorExit('Unable to determine ID. At least one of --$_kPackageOption, '
|
||||
'--$_kLibraryOption, --$_kElementOption, or the environment variables '
|
||||
'PACKAGE_NAME, LIBRARY_NAME, or ELEMENT_NAME must be non-empty.');
|
||||
}
|
||||
|
||||
final SnippetGenerator generator = SnippetGenerator();
|
||||
stdout.write(generator.generate(
|
||||
input,
|
||||
snippetType,
|
||||
template: template,
|
||||
id: id.join('.'),
|
||||
));
|
||||
exit(0);
|
||||
}
|
222
dev/snippets/lib/snippets.dart
Normal file
222
dev/snippets/lib/snippets.dart
Normal file
|
@ -0,0 +1,222 @@
|
|||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:dart_style/dart_style.dart';
|
||||
|
||||
import 'configuration.dart';
|
||||
|
||||
void errorExit(String message) {
|
||||
stderr.writeln(message);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// A Tuple containing the name and contents associated with a code block in a
|
||||
// snippet.
|
||||
class _ComponentTuple {
|
||||
_ComponentTuple(this.name, this.contents);
|
||||
final String name;
|
||||
final List<String> contents;
|
||||
String get mergedContent => contents.join('\n').trim();
|
||||
}
|
||||
|
||||
/// Generates the snippet HTML, as well as saving the output snippet main to
|
||||
/// the output directory.
|
||||
class SnippetGenerator {
|
||||
SnippetGenerator({Configuration configuration})
|
||||
: configuration = configuration ?? const Configuration() {
|
||||
this.configuration.createOutputDirectory();
|
||||
}
|
||||
|
||||
/// The configuration used to determine where to get/save data for the
|
||||
/// snippet.
|
||||
final Configuration configuration;
|
||||
|
||||
/// A Dart formatted used to format the snippet code and finished application
|
||||
/// code.
|
||||
static DartFormatter formatter = DartFormatter(pageWidth: 80, fixes: StyleFix.all);
|
||||
|
||||
/// This returns the output file for a given snippet ID. Only used for
|
||||
/// [SnippetType.application] snippets.
|
||||
File getOutputFile(String id) => File(path.join(configuration.outputDirectory.path, '$id.dart'));
|
||||
|
||||
/// Gets the path to the template file requested.
|
||||
File getTemplatePath(String templateName, {Directory templatesDir}) {
|
||||
final Directory templateDir = templatesDir ?? configuration.templatesDirectory;
|
||||
final File templateFile = File(path.join(templateDir.path, '$templateName.tmpl'));
|
||||
return templateFile.existsSync() ? templateFile : null;
|
||||
}
|
||||
|
||||
/// Injects the [injections] into the [template], and turning the
|
||||
/// "description" injection into a comment. Only used for
|
||||
/// [SnippetType.application] snippets.
|
||||
String interpolateTemplate(List<_ComponentTuple> injections, String template) {
|
||||
final String injectionMatches =
|
||||
injections.map<String>((_ComponentTuple tuple) => RegExp.escape(tuple.name)).join('|');
|
||||
final RegExp moustacheRegExp = RegExp('{{($injectionMatches)}}');
|
||||
return template.replaceAllMapped(moustacheRegExp, (Match match) {
|
||||
if (match[1] == 'description') {
|
||||
// Place the description into a comment.
|
||||
final List<String> description = injections
|
||||
.firstWhere((_ComponentTuple tuple) => tuple.name == match[1])
|
||||
.contents
|
||||
.map<String>((String line) => '// $line')
|
||||
.toList();
|
||||
// Remove any leading/trailing empty comment lines.
|
||||
// We don't want to remove ALL empty comment lines, only the ones at the
|
||||
// beginning and the end.
|
||||
while (description.last == '// ') {
|
||||
description.removeLast();
|
||||
}
|
||||
while (description.first == '// ') {
|
||||
description.removeAt(0);
|
||||
}
|
||||
return description.join('\n').trim();
|
||||
} else {
|
||||
return injections
|
||||
.firstWhere((_ComponentTuple tuple) => tuple.name == match[1])
|
||||
.mergedContent;
|
||||
}
|
||||
}).trim();
|
||||
}
|
||||
|
||||
/// Interpolates the [injections] into an HTML skeleton file.
|
||||
///
|
||||
/// Similar to interpolateTemplate, but we are only looking for `code-`
|
||||
/// components, and we care about the order of the injections.
|
||||
///
|
||||
/// Takes into account the [type] and doesn't substitute in the id and the app
|
||||
/// if not a [SnippetType.application] snippet.
|
||||
String interpolateSkeleton(SnippetType type, List<_ComponentTuple> injections, String skeleton) {
|
||||
final List<String> result = <String>[];
|
||||
for (_ComponentTuple injection in injections) {
|
||||
if (!injection.name.startsWith('code')) {
|
||||
continue;
|
||||
}
|
||||
result.addAll(injection.contents);
|
||||
result.addAll(<String>['', '// ...', '']);
|
||||
}
|
||||
if (result.length > 3) {
|
||||
result.removeRange(result.length - 3, result.length);
|
||||
}
|
||||
String formattedCode;
|
||||
try {
|
||||
formattedCode = formatter.format(result.join('\n'));
|
||||
} on FormatterException catch (exception) {
|
||||
errorExit('Unable to format snippet code: $exception');
|
||||
}
|
||||
final Map<String, String> substitutions = <String, String>{
|
||||
'description': injections
|
||||
.firstWhere((_ComponentTuple tuple) => tuple.name == 'description')
|
||||
.mergedContent,
|
||||
'code': formattedCode,
|
||||
}..addAll(type == SnippetType.application
|
||||
? <String, String>{
|
||||
'id':
|
||||
injections.firstWhere((_ComponentTuple tuple) => tuple.name == 'id').mergedContent,
|
||||
'app':
|
||||
injections.firstWhere((_ComponentTuple tuple) => tuple.name == 'app').mergedContent,
|
||||
}
|
||||
: <String, String>{'id': '', 'app': ''});
|
||||
return skeleton.replaceAllMapped(RegExp(r'{{(code|app|id|description)}}'), (Match match) {
|
||||
return substitutions[match[1]];
|
||||
});
|
||||
}
|
||||
|
||||
/// Parses the input for the various code and description segments, and
|
||||
/// returns them in the order found.
|
||||
List<_ComponentTuple> parseInput(String input) {
|
||||
bool inSnippet = false;
|
||||
input = input.trim();
|
||||
final List<String> description = <String>[];
|
||||
final List<_ComponentTuple> components = <_ComponentTuple>[];
|
||||
String currentComponent;
|
||||
for (String line in input.split('\n')) {
|
||||
final Match match = RegExp(r'^\s*```(dart|dart (\w+))?\s*$').firstMatch(line);
|
||||
if (match != null) {
|
||||
inSnippet = !inSnippet;
|
||||
if (match[1] != null) {
|
||||
currentComponent = match[1];
|
||||
if (match[2] != null) {
|
||||
components.add(_ComponentTuple('code-${match[2]}', <String>[]));
|
||||
} else {
|
||||
components.add(_ComponentTuple('code', <String>[]));
|
||||
}
|
||||
} else {
|
||||
currentComponent = null;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!inSnippet) {
|
||||
description.add(line);
|
||||
} else {
|
||||
assert(currentComponent != null);
|
||||
components.last.contents.add(line);
|
||||
}
|
||||
}
|
||||
return <_ComponentTuple>[
|
||||
_ComponentTuple('description', description),
|
||||
]..addAll(components);
|
||||
}
|
||||
|
||||
String _loadFileAsUtf8(File file) {
|
||||
return file.readAsStringSync(encoding: Encoding.getByName('utf-8'));
|
||||
}
|
||||
|
||||
/// The main routine for generating snippets.
|
||||
///
|
||||
/// The [input] is the file containing the dartdoc comments (minus the leading
|
||||
/// comment markers).
|
||||
///
|
||||
/// The [type] is the type of snippet to create: either a
|
||||
/// [SnippetType.application] or a [SnippetType.sample].
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// The [id] is a string ID to use for the output file, and to tell the user
|
||||
/// about in the `flutter create` hint. It must not be null if the [type] is
|
||||
/// [SnippetType.application].
|
||||
String generate(File input, SnippetType type, {String template, String id}) {
|
||||
assert(template != null || type != SnippetType.application);
|
||||
assert(id != null || type != SnippetType.application);
|
||||
assert(input != null);
|
||||
final List<_ComponentTuple> snippetData = parseInput(_loadFileAsUtf8(input));
|
||||
switch (type) {
|
||||
case SnippetType.application:
|
||||
final Directory templatesDir = configuration.templatesDirectory;
|
||||
if (templatesDir == null) {
|
||||
stderr.writeln('Unable to find the templates directory.');
|
||||
exit(1);
|
||||
}
|
||||
final File templateFile = getTemplatePath(template, templatesDir: templatesDir);
|
||||
if (templateFile == null) {
|
||||
stderr.writeln(
|
||||
'The template $template was not found in the templates directory ${templatesDir.path}');
|
||||
exit(1);
|
||||
}
|
||||
snippetData.add(_ComponentTuple('id', <String>[id]));
|
||||
final String templateContents = _loadFileAsUtf8(templateFile);
|
||||
String app = interpolateTemplate(snippetData, templateContents);
|
||||
|
||||
try {
|
||||
app = formatter.format(app);
|
||||
} on FormatterException catch (exception) {
|
||||
errorExit('Unable to format snippet app template: $exception');
|
||||
}
|
||||
|
||||
snippetData.add(_ComponentTuple('app', app.split('\n')));
|
||||
getOutputFile(id).writeAsStringSync(app);
|
||||
break;
|
||||
case SnippetType.sample:
|
||||
break;
|
||||
}
|
||||
final String skeleton = _loadFileAsUtf8(configuration.getHtmlSkeletonFile(type));
|
||||
return interpolateSkeleton(type, snippetData, skeleton);
|
||||
}
|
||||
}
|
101
dev/snippets/pubspec.yaml
Normal file
101
dev/snippets/pubspec.yaml
Normal file
|
@ -0,0 +1,101 @@
|
|||
name: snippets
|
||||
version: 0.1.0
|
||||
author: Flutter Team <flutter-dev@googlegroups.com>
|
||||
description: A code snippet dartdoc extension for Flutter API docs.
|
||||
homepage: https://github.com/flutter/flutter
|
||||
|
||||
environment:
|
||||
# The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite.
|
||||
sdk: ">=2.0.0-dev.68.0 <3.0.0"
|
||||
|
||||
dartdoc:
|
||||
# Exclude this package from the hosted API docs (Ironically...).
|
||||
nodoc: true
|
||||
|
||||
dependencies:
|
||||
args: 1.5.0
|
||||
dart_style: 1.2.0
|
||||
meta: 1.1.6
|
||||
platform: 2.2.0
|
||||
|
||||
analyzer: 0.33.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
async: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
convert: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
crypto: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
csslib: 0.14.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
front_end: 0.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
glob: 1.1.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
html: 0.13.3+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
kernel: 0.3.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
logging: 0.11.3+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
package_config: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
path: 1.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
plugin: 0.2.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
source_span: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
string_scanner: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
utf: 0.9.0+5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
watcher: 0.9.7+10 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
yaml: 2.1.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
|
||||
dev_dependencies:
|
||||
test: 1.3.4
|
||||
|
||||
boolean_selector: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
http: 0.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
http_multi_server: 2.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
http_parser: 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
io: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
json_rpc_2: 2.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
matcher: 0.12.3+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
mime: 0.9.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
multi_server_socket: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
node_preamble: 1.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
package_resolver: 1.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
pool: 1.3.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
pub_semver: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf: 0.7.3+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf_packages_handler: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf_static: 0.2.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf_web_socket: 0.2.2+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
source_map_stack_trace: 1.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
source_maps: 0.10.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
stack_trace: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
stream_channel: 1.6.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
term_glyph: 1.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
vm_service_client: 0.2.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
web_socket_channel: 1.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
|
||||
executables:
|
||||
snippets: null
|
||||
|
||||
boolean_selector: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
http: 0.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
http_multi_server: 2.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
http_parser: 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
io: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
json_rpc_2: 2.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
matcher: 0.12.3+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
mime: 0.9.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
multi_server_socket: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
node_preamble: 1.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
package_resolver: 1.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
pool: 1.3.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
pub_semver: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf: 0.7.3+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf_packages_handler: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf_static: 0.2.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf_web_socket: 0.2.2+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
source_map_stack_trace: 1.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
source_maps: 0.10.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
stack_trace: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
stream_channel: 1.6.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
term_glyph: 1.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
vm_service_client: 0.2.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
web_socket_channel: 1.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
|
||||
# PUBSPEC CHECKSUM: f478
|
45
dev/snippets/test/configuration_test.dart
Normal file
45
dev/snippets/test/configuration_test.dart
Normal file
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:platform/platform.dart' show FakePlatform;
|
||||
|
||||
import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
|
||||
|
||||
import 'package:snippets/configuration.dart';
|
||||
|
||||
void main() {
|
||||
group('Configuration', () {
|
||||
FakePlatform fakePlatform;
|
||||
Configuration config;
|
||||
|
||||
setUp(() {
|
||||
fakePlatform = FakePlatform(
|
||||
operatingSystem: 'linux',
|
||||
script: Uri.parse('file:///flutter/dev/snippets/lib/configuration_test.dart'));
|
||||
config = Configuration(platform: fakePlatform);
|
||||
});
|
||||
test('config directory is correct', () async {
|
||||
expect(config.getConfigDirectory('foo').path,
|
||||
matches(RegExp(r'[/\\]flutter[/\\]dev[/\\]snippets[/\\]config[/\\]foo')));
|
||||
});
|
||||
test('output directory is correct', () async {
|
||||
expect(config.outputDirectory.path,
|
||||
matches(RegExp(r'[/\\]flutter[/\\]dev[/\\]docs[/\\]doc[/\\]snippets')));
|
||||
});
|
||||
test('skeleton directory is correct', () async {
|
||||
expect(config.skeletonsDirectory.path,
|
||||
matches(RegExp(r'[/\\]flutter[/\\]dev[/\\]snippets[/\\]config[/\\]skeletons')));
|
||||
});
|
||||
test('templates directory is correct', () async {
|
||||
expect(config.templatesDirectory.path,
|
||||
matches(RegExp(r'[/\\]flutter[/\\]dev[/\\]snippets[/\\]config[/\\]templates')));
|
||||
});
|
||||
test('html skeleton file is correct', () async {
|
||||
expect(
|
||||
config.getHtmlSkeletonFile(SnippetType.application).path,
|
||||
matches(RegExp(
|
||||
r'[/\\]flutter[/\\]dev[/\\]snippets[/\\]config[/\\]skeletons[/\\]application.html')));
|
||||
});
|
||||
});
|
||||
}
|
118
dev/snippets/test/snippets_test.dart
Normal file
118
dev/snippets/test/snippets_test.dart
Normal file
|
@ -0,0 +1,118 @@
|
|||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:io' hide Platform;
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'package:platform/platform.dart' show FakePlatform;
|
||||
|
||||
import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
|
||||
|
||||
import 'package:snippets/configuration.dart';
|
||||
import 'package:snippets/snippets.dart';
|
||||
|
||||
void main() {
|
||||
group('Generator', () {
|
||||
FakePlatform fakePlatform;
|
||||
Configuration configuration;
|
||||
SnippetGenerator generator;
|
||||
Directory tmpDir;
|
||||
File template;
|
||||
|
||||
setUp(() {
|
||||
tmpDir = Directory.systemTemp.createTempSync('snippets_test');
|
||||
fakePlatform = FakePlatform(
|
||||
script: Uri.file(path.join(
|
||||
tmpDir.absolute.path, 'flutter', 'dev', 'snippets', 'lib', 'snippets_test.dart')));
|
||||
configuration = Configuration(platform: fakePlatform);
|
||||
configuration.createOutputDirectory();
|
||||
configuration.templatesDirectory.createSync(recursive: true);
|
||||
configuration.skeletonsDirectory.createSync(recursive: true);
|
||||
template = File(path.join(configuration.templatesDirectory.path, 'template.tmpl'));
|
||||
template.writeAsStringSync('''
|
||||
|
||||
{{description}}
|
||||
|
||||
{{code-preamble}}
|
||||
|
||||
main() {
|
||||
{{code}}
|
||||
}
|
||||
''');
|
||||
configuration.getHtmlSkeletonFile(SnippetType.application).writeAsStringSync('''
|
||||
<div>HTML Bits</div>
|
||||
{{description}}
|
||||
<pre>{{code}}</pre>
|
||||
<pre>{{app}}</pre>
|
||||
<div>More HTML Bits</div>
|
||||
''');
|
||||
configuration.getHtmlSkeletonFile(SnippetType.sample).writeAsStringSync('''
|
||||
<div>HTML Bits</div>
|
||||
{{description}}
|
||||
<pre>{{code}}</pre>
|
||||
<div>More HTML Bits</div>
|
||||
''');
|
||||
generator = SnippetGenerator(configuration: configuration);
|
||||
});
|
||||
tearDown(() {
|
||||
tmpDir.deleteSync(recursive: true);
|
||||
});
|
||||
|
||||
test('generates application 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.
|
||||
|
||||
```dart preamble
|
||||
const String name = 'snippet';
|
||||
```
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
print('The actual \$name.');
|
||||
}
|
||||
```
|
||||
''');
|
||||
|
||||
final String html =
|
||||
generator.generate(inputFile, SnippetType.application, template: 'template', id: 'id');
|
||||
expect(html, contains('<div>HTML Bits</div>'));
|
||||
expect(html, contains('<div>More HTML Bits</div>'));
|
||||
expect(html, contains("print('The actual \$name.');"));
|
||||
expect(html, contains('A description of the snippet.\n'));
|
||||
expect(
|
||||
html,
|
||||
contains('// A description of the snippet.\n'
|
||||
'//\n'
|
||||
'// On several lines.\n'));
|
||||
expect(html, contains('void main() {'));
|
||||
});
|
||||
|
||||
test('generates sample 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.sample);
|
||||
expect(html, contains('<div>HTML Bits</div>'));
|
||||
expect(html, contains('<div>More HTML Bits</div>'));
|
||||
expect(html, contains("print('The actual \$name.');"));
|
||||
expect(html, contains('A description of the snippet.\n\nOn several lines.\n'));
|
||||
expect(html, contains('main() {'));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -10,7 +10,8 @@ import 'package:args/args.dart';
|
|||
import 'package:intl/intl.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
const String kDocRoot = 'dev/docs/doc';
|
||||
const String kDocsRoot = 'dev/docs';
|
||||
const String kPublishRoot = '$kDocsRoot/doc';
|
||||
|
||||
/// This script expects to run with the cwd as the root of the flutter repo. It
|
||||
/// will generate documentation for the packages in `//packages/` and write the
|
||||
|
@ -57,17 +58,17 @@ Future<void> main(List<String> arguments) async {
|
|||
buf.writeln('dependency_overrides:');
|
||||
buf.writeln(' platform_integration:');
|
||||
buf.writeln(' path: platform_integration');
|
||||
File('dev/docs/pubspec.yaml').writeAsStringSync(buf.toString());
|
||||
File('$kDocsRoot/pubspec.yaml').writeAsStringSync(buf.toString());
|
||||
|
||||
// Create the library file.
|
||||
final Directory libDir = Directory('dev/docs/lib');
|
||||
final Directory libDir = Directory('$kDocsRoot/lib');
|
||||
libDir.createSync();
|
||||
|
||||
final StringBuffer contents = StringBuffer('library temp_doc;\n\n');
|
||||
for (String libraryRef in libraryRefs()) {
|
||||
contents.writeln('import \'package:$libraryRef\';');
|
||||
}
|
||||
File('dev/docs/lib/temp_doc.dart').writeAsStringSync(contents.toString());
|
||||
File('$kDocsRoot/lib/temp_doc.dart').writeAsStringSync(contents.toString());
|
||||
|
||||
final String flutterRoot = Directory.current.path;
|
||||
final Map<String, String> pubEnvironment = <String, String>{
|
||||
|
@ -86,7 +87,7 @@ Future<void> main(List<String> arguments) async {
|
|||
Process process = await Process.start(
|
||||
pubExecutable,
|
||||
<String>['get'],
|
||||
workingDirectory: 'dev/docs',
|
||||
workingDirectory: kDocsRoot,
|
||||
environment: pubEnvironment,
|
||||
);
|
||||
printStream(process.stdout, prefix: 'pub:stdout: ');
|
||||
|
@ -95,7 +96,9 @@ Future<void> main(List<String> arguments) async {
|
|||
if (code != 0)
|
||||
exit(code);
|
||||
|
||||
createFooter('dev/docs/lib/footer.html');
|
||||
createFooter('$kDocsRoot/lib/footer.html');
|
||||
copyAssets();
|
||||
cleanOutSnippets();
|
||||
|
||||
final List<String> dartdocBaseArgs = <String>['global', 'run'];
|
||||
if (args['checked']) {
|
||||
|
@ -107,7 +110,7 @@ Future<void> main(List<String> arguments) async {
|
|||
final ProcessResult result = Process.runSync(
|
||||
pubExecutable,
|
||||
<String>[]..addAll(dartdocBaseArgs)..add('--version'),
|
||||
workingDirectory: 'dev/docs',
|
||||
workingDirectory: kDocsRoot,
|
||||
environment: pubEnvironment,
|
||||
);
|
||||
print('\n${result.stdout}flutter version: $version\n');
|
||||
|
@ -124,26 +127,65 @@ Future<void> main(List<String> arguments) async {
|
|||
// We don't need to exclude flutter_tools in this list because it's not in the
|
||||
// recursive dependencies of the package defined at dev/docs/pubspec.yaml
|
||||
final List<String> dartdocArgs = <String>[]..addAll(dartdocBaseArgs)..addAll(<String>[
|
||||
'--inject-html',
|
||||
'--header', 'styles.html',
|
||||
'--header', 'analytics.html',
|
||||
'--header', 'survey.html',
|
||||
'--header', 'snippets.html',
|
||||
'--footer-text', 'lib/footer.html',
|
||||
'--exclude-packages',
|
||||
'analyzer,args,barback,cli_util,csslib,flutter_goldens,front_end,fuchsia_remote_debug_protocol,glob,html,http_multi_server,io,isolate,js,kernel,logging,mime,mockito,node_preamble,plugin,shelf,shelf_packages_handler,shelf_static,shelf_web_socket,utf,watcher,yaml',
|
||||
<String>[
|
||||
'analyzer',
|
||||
'args',
|
||||
'barback',
|
||||
'cli_util',
|
||||
'csslib',
|
||||
'flutter_goldens',
|
||||
'front_end',
|
||||
'fuchsia_remote_debug_protocol',
|
||||
'glob',
|
||||
'html',
|
||||
'http_multi_server',
|
||||
'io',
|
||||
'isolate',
|
||||
'js',
|
||||
'kernel',
|
||||
'logging',
|
||||
'mime',
|
||||
'mockito',
|
||||
'node_preamble',
|
||||
'plugin',
|
||||
'shelf',
|
||||
'shelf_packages_handler',
|
||||
'shelf_static',
|
||||
'shelf_web_socket',
|
||||
'utf',
|
||||
'watcher',
|
||||
'yaml',
|
||||
].join(','),
|
||||
'--exclude',
|
||||
'package:Flutter/temp_doc.dart,package:http/browser_client.dart,package:intl/intl_browser.dart,package:matcher/mirror_matchers.dart,package:quiver/mirrors.dart,package:quiver/io.dart,package:vm_service_client/vm_service_client.dart,package:web_socket_channel/html.dart',
|
||||
<String>[
|
||||
'package:Flutter/temp_doc.dart',
|
||||
'package:http/browser_client.dart',
|
||||
'package:intl/intl_browser.dart',
|
||||
'package:matcher/mirror_matchers.dart',
|
||||
'package:quiver/io.dart',
|
||||
'package:quiver/mirrors.dart',
|
||||
'package:vm_service_client/vm_service_client.dart',
|
||||
'package:web_socket_channel/html.dart',
|
||||
].join(','),
|
||||
'--favicon=favicon.ico',
|
||||
'--package-order', 'flutter,Dart,flutter_test,flutter_driver',
|
||||
'--auto-include-dependencies',
|
||||
]);
|
||||
|
||||
String quote(String arg) => arg.contains(' ') ? "'$arg'" : arg;
|
||||
print('Executing: (cd dev/docs ; $pubExecutable ${dartdocArgs.map<String>(quote).join(' ')})');
|
||||
print('Executing: (cd $kDocsRoot ; $pubExecutable ${dartdocArgs.map<String>(quote).join(' ')})');
|
||||
|
||||
process = await Process.start(
|
||||
pubExecutable,
|
||||
dartdocArgs,
|
||||
workingDirectory: 'dev/docs',
|
||||
workingDirectory: kDocsRoot,
|
||||
environment: pubEnvironment,
|
||||
);
|
||||
printStream(process.stdout, prefix: args['json'] ? '' : 'dartdoc:stdout: ',
|
||||
|
@ -211,16 +253,63 @@ void createFooter(String footerPath) {
|
|||
gitBranchOut].join(' '));
|
||||
}
|
||||
|
||||
/// Recursively copies `srcDir` to `destDir`, invoking [onFileCopied], if
|
||||
/// specified, for each source/destination file pair.
|
||||
///
|
||||
/// Creates `destDir` if needed.
|
||||
void copyDirectorySync(Directory srcDir, Directory destDir, [void onFileCopied(File srcFile, File destFile)]) {
|
||||
if (!srcDir.existsSync())
|
||||
throw Exception('Source directory "${srcDir.path}" does not exist, nothing to copy');
|
||||
|
||||
if (!destDir.existsSync())
|
||||
destDir.createSync(recursive: true);
|
||||
|
||||
for (FileSystemEntity entity in srcDir.listSync()) {
|
||||
final String newPath = path.join(destDir.path, path.basename(entity.path));
|
||||
if (entity is File) {
|
||||
final File newFile = File(newPath);
|
||||
entity.copySync(newPath);
|
||||
onFileCopied?.call(entity, newFile);
|
||||
} else if (entity is Directory) {
|
||||
copyDirectorySync(entity, Directory(newPath));
|
||||
} else {
|
||||
throw Exception('${entity.path} is neither File nor Directory');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void copyAssets() {
|
||||
final Directory assetsDir = Directory(path.join(kPublishRoot, 'assets'));
|
||||
if (assetsDir.existsSync()) {
|
||||
assetsDir.deleteSync(recursive: true);
|
||||
}
|
||||
copyDirectorySync(
|
||||
Directory(path.join(kDocsRoot, 'assets')),
|
||||
Directory(path.join(kPublishRoot, 'assets')),
|
||||
(File src, File dest) => print('Copied ${src.path} to ${dest.path}'));
|
||||
}
|
||||
|
||||
|
||||
void cleanOutSnippets() {
|
||||
final Directory snippetsDir = Directory(path.join(kPublishRoot, 'snippets'));
|
||||
if (snippetsDir.existsSync()) {
|
||||
snippetsDir
|
||||
..deleteSync(recursive: true)
|
||||
..createSync(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
void sanityCheckDocs() {
|
||||
final List<String> canaries = <String>[
|
||||
'$kDocRoot/api/dart-io/File-class.html',
|
||||
'$kDocRoot/api/dart-ui/Canvas-class.html',
|
||||
'$kDocRoot/api/dart-ui/Canvas/drawRect.html',
|
||||
'$kDocRoot/api/flutter_driver/FlutterDriver/FlutterDriver.connectedTo.html',
|
||||
'$kDocRoot/api/flutter_test/WidgetTester/pumpWidget.html',
|
||||
'$kDocRoot/api/material/Material-class.html',
|
||||
'$kDocRoot/api/material/Tooltip-class.html',
|
||||
'$kDocRoot/api/widgets/Widget-class.html',
|
||||
'$kPublishRoot/assets/overrides.css',
|
||||
'$kPublishRoot/api/dart-io/File-class.html',
|
||||
'$kPublishRoot/api/dart-ui/Canvas-class.html',
|
||||
'$kPublishRoot/api/dart-ui/Canvas/drawRect.html',
|
||||
'$kPublishRoot/api/flutter_driver/FlutterDriver/FlutterDriver.connectedTo.html',
|
||||
'$kPublishRoot/api/flutter_test/WidgetTester/pumpWidget.html',
|
||||
'$kPublishRoot/api/material/Material-class.html',
|
||||
'$kPublishRoot/api/material/Tooltip-class.html',
|
||||
'$kPublishRoot/api/widgets/Widget-class.html',
|
||||
];
|
||||
for (String canary in canaries) {
|
||||
if (!File(canary).existsSync())
|
||||
|
@ -231,7 +320,7 @@ void sanityCheckDocs() {
|
|||
/// Creates a custom index.html because we try to maintain old
|
||||
/// paths. Cleanup unused index.html files no longer needed.
|
||||
void createIndexAndCleanup() {
|
||||
print('\nCreating a custom index.html in $kDocRoot/index.html');
|
||||
print('\nCreating a custom index.html in $kPublishRoot/index.html');
|
||||
removeOldFlutterDocsDir();
|
||||
renameApiDir();
|
||||
copyIndexToRootOfDocs();
|
||||
|
@ -243,22 +332,22 @@ void createIndexAndCleanup() {
|
|||
|
||||
void removeOldFlutterDocsDir() {
|
||||
try {
|
||||
Directory('$kDocRoot/flutter').deleteSync(recursive: true);
|
||||
Directory('$kPublishRoot/flutter').deleteSync(recursive: true);
|
||||
} on FileSystemException {
|
||||
// If the directory does not exist, that's OK.
|
||||
}
|
||||
}
|
||||
|
||||
void renameApiDir() {
|
||||
Directory('$kDocRoot/api').renameSync('$kDocRoot/flutter');
|
||||
Directory('$kPublishRoot/api').renameSync('$kPublishRoot/flutter');
|
||||
}
|
||||
|
||||
void copyIndexToRootOfDocs() {
|
||||
File('$kDocRoot/flutter/index.html').copySync('$kDocRoot/index.html');
|
||||
File('$kPublishRoot/flutter/index.html').copySync('$kPublishRoot/index.html');
|
||||
}
|
||||
|
||||
void changePackageToSdkInTitlebar() {
|
||||
final File indexFile = File('$kDocRoot/index.html');
|
||||
final File indexFile = File('$kPublishRoot/index.html');
|
||||
String indexContents = indexFile.readAsStringSync();
|
||||
indexContents = indexContents.replaceFirst(
|
||||
'<li><a href="https://flutter.io">Flutter package</a></li>',
|
||||
|
@ -269,7 +358,7 @@ void changePackageToSdkInTitlebar() {
|
|||
}
|
||||
|
||||
void addHtmlBaseToIndex() {
|
||||
final File indexFile = File('$kDocRoot/index.html');
|
||||
final File indexFile = File('$kPublishRoot/index.html');
|
||||
String indexContents = indexFile.readAsStringSync();
|
||||
indexContents = indexContents.replaceFirst(
|
||||
'</title>\n',
|
||||
|
@ -289,7 +378,7 @@ void addHtmlBaseToIndex() {
|
|||
|
||||
void putRedirectInOldIndexLocation() {
|
||||
const String metaTag = '<meta http-equiv="refresh" content="0;URL=../index.html">';
|
||||
File('$kDocRoot/flutter/index.html').writeAsStringSync(metaTag);
|
||||
File('$kPublishRoot/flutter/index.html').writeAsStringSync(metaTag);
|
||||
}
|
||||
|
||||
List<String> findPackageNames() {
|
||||
|
|
|
@ -142,11 +142,13 @@ abstract class DeletableChipAttributes {
|
|||
///
|
||||
/// The chip will not automatically remove itself: this just tells the app
|
||||
/// that the user tapped the delete button. In order to delete the chip, you
|
||||
/// have to do something like the following:
|
||||
/// have to do something similar to the following sample:
|
||||
///
|
||||
/// ## Sample code
|
||||
/// {@tool snippet --template=stateful_widget}
|
||||
/// This sample shows how to use [onDeleted] to remove an entry when the
|
||||
/// delete button is tapped.
|
||||
///
|
||||
/// ```dart
|
||||
/// ```dart preamble
|
||||
/// class Actor {
|
||||
/// const Actor(this.name, this.initials);
|
||||
/// final String name;
|
||||
|
@ -193,6 +195,14 @@ abstract class DeletableChipAttributes {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ```dart
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// return CastList();
|
||||
/// }
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
VoidCallback get onDeleted;
|
||||
|
||||
/// The [Color] for the delete icon. The default is based on the ambient
|
||||
|
@ -247,7 +257,9 @@ abstract class SelectableChipAttributes {
|
|||
/// The [onSelected] and [TappableChipAttributes.onPressed] callbacks must not
|
||||
/// both be specified at the same time.
|
||||
///
|
||||
/// ## Sample code
|
||||
/// {@tool sample}
|
||||
///
|
||||
/// A [StatefulWidget] that illustrates use of onSelected in an [InputChip].
|
||||
///
|
||||
/// ```dart
|
||||
/// class Wood extends StatefulWidget {
|
||||
|
@ -272,6 +284,7 @@ abstract class SelectableChipAttributes {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
ValueChanged<bool> get onSelected;
|
||||
|
||||
/// Elevation to be applied on the chip during the press motion.
|
||||
|
|
Loading…
Reference in a new issue