Remove dartdoc.
R=alanknight@google.com, efortuna@google.com Review URL: https://codereview.chromium.org//140303009 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@32388 260f80e4-7a28-3924-810f-c04153c831b5
6
sdk/lib/_internal/dartdoc/.gitignore
vendored
|
@ -1,6 +0,0 @@
|
|||
# Generated output.
|
||||
/docs/
|
||||
|
||||
# Compiled .js output.
|
||||
static/client-static.js
|
||||
static/client-live-nav.js
|
|
@ -1,84 +0,0 @@
|
|||
Dartdoc generates static HTML documentation from Dart code.
|
||||
|
||||
To use it, from this directory, run:
|
||||
|
||||
$ dartdoc <path to .dart file>
|
||||
|
||||
This will create a "docs" directory with the docs for your libraries.
|
||||
|
||||
|
||||
How docs are generated
|
||||
----------------------
|
||||
|
||||
To make beautiful docs from your library, dartdoc parses it and every library it
|
||||
imports (recursively). From each library, it parses all classes and members,
|
||||
finds the associated doc comments and builds crosslinked docs from them.
|
||||
|
||||
"Doc comments" can be in one of a few forms:
|
||||
|
||||
/**
|
||||
* JavaDoc style block comments.
|
||||
*/
|
||||
|
||||
/** Which can also be single line. */
|
||||
|
||||
/// Triple-slash line comments.
|
||||
/// Which can be multiple lines.
|
||||
|
||||
The body of a doc comment will be parsed as markdown which means you can apply
|
||||
most of the formatting and structuring you want while still having docs that
|
||||
look nice in plain text. For example:
|
||||
|
||||
/// This is a doc comment. This is the first paragraph in the comment. It
|
||||
/// can span multiple lines.
|
||||
///
|
||||
/// A blank line starts a new paragraph like this one.
|
||||
///
|
||||
/// * Unordered lists start with `*` or `-` or `+`.
|
||||
/// * And can have multiple items.
|
||||
/// 1. You can nest lists.
|
||||
/// 2. Like this numbered one.
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// Three dashes, underscores, or tildes on a line by themselves create a
|
||||
/// horizontal rule.
|
||||
///
|
||||
/// to.get(a.block + of.code) {
|
||||
/// indent(it, 4.spaces);
|
||||
/// like(this);
|
||||
/// }
|
||||
///
|
||||
/// There are a few inline styles you can apply: *emphasis*, **strong**,
|
||||
/// and `inline code`. You can also use underscores for _emphasis_ and
|
||||
/// __strong__.
|
||||
///
|
||||
/// An H1 header using equals on the next line
|
||||
/// ==========================================
|
||||
///
|
||||
/// And an H2 in that style using hyphens
|
||||
/// -------------------------------------
|
||||
///
|
||||
/// # Or an H1 - H6 using leading hashes
|
||||
/// ## H2
|
||||
/// ### H3
|
||||
/// #### H4 you can also have hashes at then end: ###
|
||||
/// ##### H5
|
||||
/// ###### H6
|
||||
|
||||
There is also an extension to markdown specific to dartdoc: A name inside
|
||||
square brackets that is not a markdown link (i.e. doesn't have square brackets
|
||||
or parentheses following it) like:
|
||||
|
||||
Calls [someMethod], passing in [arg].
|
||||
|
||||
is understood to be the name of some member or type that's in the scope of the
|
||||
member where that comment appears. Dartdoc will automatically figure out what
|
||||
the name refers to and generate an approriate link to that member or type.
|
||||
|
||||
|
||||
Attribution
|
||||
-----------
|
||||
|
||||
dartdoc uses the delightful Silk icon set by Mark James.
|
||||
http://www.famfamfam.com/lab/icons/silk/
|
|
@ -1,285 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* To generate docs for a library, run this script with the path to an
|
||||
* entrypoint .dart file, like:
|
||||
*
|
||||
* $ dart dartdoc.dart foo.dart
|
||||
*
|
||||
* This will create a "docs" directory with the docs for your libraries. To
|
||||
* create these beautiful docs, dartdoc parses your library and every library
|
||||
* it imports (recursively). From each library, it parses all classes and
|
||||
* members, finds the associated doc comments and builds crosslinked docs from
|
||||
* them.
|
||||
*/
|
||||
library dartdoc;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import '../lib/dartdoc.dart';
|
||||
import '../lib/src/dartdoc/utils.dart';
|
||||
import 'package:args/args.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
/**
|
||||
* Run this from the `lib/_internal/dartdoc` directory.
|
||||
*/
|
||||
main(List<String> arguments) {
|
||||
// Need this because ArgParser.getUsage doesn't show command invocation.
|
||||
final USAGE = 'Usage dartdoc [options] <entrypoint(s)>\n[options] include:';
|
||||
|
||||
final dartdoc = new Dartdoc();
|
||||
|
||||
final argParser = new ArgParser();
|
||||
|
||||
String libPath = path.join(scriptDir, '..', '..', '..', '..');
|
||||
|
||||
String packageRoot;
|
||||
|
||||
argParser.addFlag('no-code',
|
||||
help: 'Do not include source code in the documentation.',
|
||||
defaultsTo: false, negatable: false,
|
||||
callback: (noCode) => dartdoc.includeSource = !noCode);
|
||||
|
||||
argParser.addOption('mode', abbr: 'm',
|
||||
help: 'Define how HTML pages are generated.',
|
||||
allowed: ['static', 'live-nav'], allowedHelp: {
|
||||
'static': 'Generates completely static HTML containing\n'
|
||||
'everything you need to browse the docs. The only\n'
|
||||
'client side behavior is trivial stuff like syntax\n'
|
||||
'highlighting code, and the find-as-you-type search\n'
|
||||
'box.',
|
||||
'live-nav': '(Default) Generated docs do not included baked HTML\n'
|
||||
'navigation. Instead a single `nav.json` file is\n'
|
||||
'created and the appropriate navigation is generated\n'
|
||||
'client-side by parsing that and building HTML.\n'
|
||||
'\tThis dramatically reduces the generated size of\n'
|
||||
'the HTML since a large fraction of each static page\n'
|
||||
'is just redundant navigation links.\n'
|
||||
'\tIn this mode, the browser will do a XHR for\n'
|
||||
'nav.json which means that to preview docs locallly,\n'
|
||||
'you will need to enable requesting file:// links in\n'
|
||||
'your browser or run a little local server like\n'
|
||||
'`python -m SimpleHTTPServer`.'},
|
||||
defaultsTo: 'live-nav',
|
||||
callback: (genMode) {
|
||||
dartdoc.mode = (genMode == 'static' ? MODE_STATIC : MODE_LIVE_NAV);
|
||||
});
|
||||
|
||||
argParser.addFlag('generate-app-cache',
|
||||
help: 'Generates the App Cache manifest file, enabling\n'
|
||||
'offline doc viewing.',
|
||||
defaultsTo: false, negatable: false,
|
||||
callback: (generate) => dartdoc.generateAppCache = generate);
|
||||
|
||||
argParser.addFlag('omit-generation-time',
|
||||
help: 'Omits generation timestamp from output.',
|
||||
defaultsTo: false, negatable: false,
|
||||
callback: (genTimestamp) => dartdoc.omitGenerationTime = genTimestamp);
|
||||
|
||||
argParser.addFlag('verbose', abbr: 'v',
|
||||
help: 'Print verbose information during generation.',
|
||||
defaultsTo: false, negatable: false,
|
||||
callback: (verb) => dartdoc.verbose = verb);
|
||||
|
||||
argParser.addFlag('include-api',
|
||||
help: 'Include the used API libraries in the generated\n'
|
||||
'documentation. If the --link-api option is used,\n'
|
||||
'this option is ignored.',
|
||||
defaultsTo: false, negatable: false,
|
||||
callback: (incApi) => dartdoc.includeApi = incApi);
|
||||
|
||||
argParser.addFlag('link-api',
|
||||
help: 'Link to the online language API in the generated\n'
|
||||
'documentation. The option overrides inclusion\n'
|
||||
'through --include-api or --include-lib.',
|
||||
defaultsTo: false, negatable: false,
|
||||
callback: (linkApi) => dartdoc.linkToApi = linkApi);
|
||||
|
||||
argParser.addFlag('show-private',
|
||||
help: 'Document private types and members.',
|
||||
defaultsTo: false,
|
||||
callback: (showPrivate) => dartdoc.showPrivate = showPrivate);
|
||||
|
||||
argParser.addFlag('inherit-from-object',
|
||||
help: 'Show members inherited from Object.',
|
||||
defaultsTo: false, negatable: false,
|
||||
callback: (inherit) => dartdoc.inheritFromObject = inherit);
|
||||
|
||||
argParser.addFlag('enable-diagnostic-colors', negatable: false);
|
||||
|
||||
argParser.addOption('out',
|
||||
help: 'Generates files into directory specified. If\n'
|
||||
'omitted the files are generated into ./docs/',
|
||||
callback: (outDir) {
|
||||
if(outDir != null) {
|
||||
dartdoc.outputDir = outDir;
|
||||
}
|
||||
});
|
||||
|
||||
argParser.addOption('include-lib',
|
||||
help: 'Use this option to explicitly specify which\n'
|
||||
'libraries to include in the documentation. If\n'
|
||||
'omitted, all used libraries are included by\n'
|
||||
'default. Specify a comma-separated list of\n'
|
||||
'library names, or call this option multiple times.',
|
||||
callback: (incLibs) {
|
||||
if(!incLibs.isEmpty) {
|
||||
List<String> allLibs = new List<String>();
|
||||
for(final lst in incLibs) {
|
||||
var someLibs = lst.split(',');
|
||||
for(final lib in someLibs) {
|
||||
allLibs.add(lib);
|
||||
}
|
||||
}
|
||||
dartdoc.includedLibraries = allLibs;
|
||||
}
|
||||
}, allowMultiple: true);
|
||||
|
||||
argParser.addOption('exclude-lib',
|
||||
help: 'Use this option to explicitly specify which\n'
|
||||
'libraries to exclude from the documentation. If\n'
|
||||
'omitted, no libraries are excluded. Specify a\n'
|
||||
'comma-separated list of library names, or call\n'
|
||||
'this option multiple times.',
|
||||
callback: (excLibs) {
|
||||
if(!excLibs.isEmpty) {
|
||||
List<String> allLibs = new List<String>();
|
||||
for(final lst in excLibs) {
|
||||
var someLibs = lst.split(',');
|
||||
for(final lib in someLibs) {
|
||||
allLibs.add(lib);
|
||||
}
|
||||
}
|
||||
dartdoc.excludedLibraries = allLibs;
|
||||
}
|
||||
}, allowMultiple: true);
|
||||
|
||||
argParser.addOption('package-root',
|
||||
help: 'Sets the package directory to the specified directory.\n'
|
||||
'If omitted the package directory is the closest packages directory to'
|
||||
' the entrypoint.',
|
||||
callback: (packageDir) {
|
||||
if(packageDir != null) {
|
||||
packageRoot = packageDir;
|
||||
}
|
||||
});
|
||||
|
||||
argParser.addOption('library-root',
|
||||
help: 'Sets the library root directory to the specified directory.',
|
||||
callback: (libraryRoot) {
|
||||
if (libraryRoot != null) {
|
||||
libPath = libraryRoot;
|
||||
}
|
||||
});
|
||||
|
||||
// TODO(amouravski): This method is deprecated. Remove on April 22.
|
||||
argParser.addOption('pkg',
|
||||
help: 'Deprecated: same as --package-root.',
|
||||
callback: (packageDir) {
|
||||
if(packageDir != null) {
|
||||
packageRoot = packageDir;
|
||||
}
|
||||
});
|
||||
|
||||
dartdoc.dartdocPath = path.join(libPath, 'lib', '_internal', 'dartdoc');
|
||||
|
||||
if (arguments.isEmpty) {
|
||||
print('No arguments provided.');
|
||||
print(USAGE);
|
||||
print(argParser.getUsage());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
final entrypoints = <Uri>[];
|
||||
try {
|
||||
final option = argParser.parse(arguments, allowTrailingOptions: true);
|
||||
|
||||
// This checks to see if the root of all entrypoints is the same.
|
||||
// If it is not, then we display a warning, as package imports might fail.
|
||||
var entrypointRoot;
|
||||
for (final entrypoint in option.rest) {
|
||||
var uri = Uri.parse(entrypoint);
|
||||
|
||||
// If it looks like it was a file path (no scheme, or a one letter scheme
|
||||
// which is likely a drive letter on Windows), turn it into a file URL.
|
||||
if (uri.scheme == '' || uri.scheme.length == 1) {
|
||||
uri = path.toUri(entrypoint);
|
||||
}
|
||||
|
||||
entrypoints.add(uri);
|
||||
|
||||
if (uri.scheme != 'file') continue;
|
||||
if (entrypointRoot == null) {
|
||||
entrypointRoot = path.dirname(entrypoint);
|
||||
} else if (entrypointRoot != path.dirname(entrypoint)) {
|
||||
print('Warning: entrypoints are at different directories. "package:"'
|
||||
' imports may fail.');
|
||||
}
|
||||
}
|
||||
} on FormatException catch (e) {
|
||||
print(e.message);
|
||||
print(USAGE);
|
||||
print(argParser.getUsage());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (entrypoints.isEmpty) {
|
||||
print('No entrypoints provided.');
|
||||
print(argParser.getUsage());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (packageRoot == null) packageRoot = _getPackageRoot(entrypoints);
|
||||
|
||||
cleanOutputDirectory(dartdoc.outputDir);
|
||||
|
||||
// Start the analysis and documentation.
|
||||
dartdoc.documentLibraries(entrypoints, libPath, packageRoot)
|
||||
// Prepare the dart2js script code and copy static resources.
|
||||
// TODO(amouravski): move compileScript out and pre-generate the client
|
||||
// scripts. This takes a long time and the js hardly ever changes.
|
||||
.then((_) => compileScript(dartdoc.mode, dartdoc.outputDir, libPath,
|
||||
dartdoc.tmpPath))
|
||||
.then((_) => copyDirectory(
|
||||
path.join(libPath, 'lib', '_internal', 'dartdoc', 'static'),
|
||||
dartdoc.outputDir))
|
||||
.then((_) {
|
||||
print(dartdoc.status);
|
||||
if (dartdoc.totals == 0) {
|
||||
exit(1);
|
||||
}
|
||||
})
|
||||
.catchError((e, trace) {
|
||||
print('Error: generation failed: ${e}');
|
||||
if (trace != null) print("StackTrace: $trace");
|
||||
dartdoc.cleanup();
|
||||
exit(1);
|
||||
})
|
||||
.whenComplete(() => dartdoc.cleanup());
|
||||
}
|
||||
|
||||
String _getPackageRoot(List<Uri> entrypoints) {
|
||||
// Check if there's a `packages` directory in the entry point directory.
|
||||
var fileEntrypoint = entrypoints.firstWhere(
|
||||
(entrypoint) => entrypoint.scheme == 'file',
|
||||
orElse: () => null);
|
||||
if (fileEntrypoint == null) return;
|
||||
|
||||
var script = path.normalize(path.absolute(path.fromUri(fileEntrypoint)));
|
||||
var dir = path.join(path.dirname(script), 'packages/');
|
||||
if (new Directory(dir).existsSync()) return dir;
|
||||
|
||||
// If there is not, then check if the entrypoint is somewhere in a `lib`
|
||||
// directory.
|
||||
var parts = path.split(path.dirname(script));
|
||||
var libDir = parts.lastIndexOf('lib');
|
||||
if (libDir > 0) {
|
||||
return path.join(path.joinAll(parts.take(libDir)), 'packages');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
# Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
# for details. All rights reserved. Use of this source code is governed by a
|
||||
# BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
test/markdown_test: Pass
|
||||
test/dartdoc_test: Pass, Slow # Issue 16311
|
||||
test/dartdoc_search_test: Pass, Skip
|
||||
|
||||
# Dartdoc only runs on the VM, so just rule out all compilers.
|
||||
[ $compiler == dart2js || $compiler == dart2dart ]
|
||||
*: Skip
|
||||
|
||||
# Dartdoc only runs on the standalone VM, not in dartium.
|
||||
[ $runtime == drt || $runtime == dartium ]
|
||||
*: Skip
|
|
@ -1,206 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library classify;
|
||||
|
||||
import '../../compiler/implementation/scanner/scannerlib.dart';
|
||||
// TODO(rnystrom): Use "package:" URL (#4968).
|
||||
import 'markdown.dart' as md;
|
||||
|
||||
/**
|
||||
* Kinds of tokens that we care to highlight differently. The values of the
|
||||
* fields here will be used as CSS class names for the generated spans.
|
||||
*/
|
||||
class Classification {
|
||||
static const NONE = null;
|
||||
static const ERROR = "e";
|
||||
static const COMMENT = "c";
|
||||
static const IDENTIFIER = "i";
|
||||
static const KEYWORD = "k";
|
||||
static const OPERATOR = "o";
|
||||
static const STRING = "s";
|
||||
static const NUMBER = "n";
|
||||
static const PUNCTUATION = "p";
|
||||
|
||||
// A few things that are nice to make different:
|
||||
static const TYPE_IDENTIFIER = "t";
|
||||
|
||||
// Between a keyword and an identifier
|
||||
static const SPECIAL_IDENTIFIER = "r";
|
||||
|
||||
static const ARROW_OPERATOR = "a";
|
||||
|
||||
static const STRING_INTERPOLATION = 'si';
|
||||
}
|
||||
|
||||
/// Returns a marked up HTML string. If the code does not appear to be valid
|
||||
/// Dart code, returns the original [text].
|
||||
String classifySource(String text) {
|
||||
try {
|
||||
var html = new StringBuffer();
|
||||
var tokenizer = new StringScanner.fromString(text, includeComments: true);
|
||||
|
||||
var whitespaceOffset = 0;
|
||||
var token = tokenizer.tokenize();
|
||||
var inString = false;
|
||||
while (token.kind != EOF_TOKEN) {
|
||||
html.write(text.substring(whitespaceOffset, token.charOffset));
|
||||
whitespaceOffset = token.charOffset + token.slowCharCount;
|
||||
|
||||
// Track whether or not we're in a string.
|
||||
switch (token.kind) {
|
||||
case STRING_TOKEN:
|
||||
case STRING_INTERPOLATION_TOKEN:
|
||||
inString = true;
|
||||
break;
|
||||
}
|
||||
|
||||
final kind = classify(token);
|
||||
final escapedText = md.escapeHtml(token.value);
|
||||
if (kind != null) {
|
||||
// Add a secondary class to tokens appearing within a string so that
|
||||
// we can highlight tokens in an interpolation specially.
|
||||
var stringClass = inString ? Classification.STRING_INTERPOLATION : '';
|
||||
html.write('<span class="$kind $stringClass">$escapedText</span>');
|
||||
} else {
|
||||
html.write(escapedText);
|
||||
}
|
||||
|
||||
// Track whether or not we're in a string.
|
||||
if (token.kind == STRING_TOKEN) {
|
||||
inString = false;
|
||||
}
|
||||
token = token.next;
|
||||
}
|
||||
return html.toString();
|
||||
} catch (e) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
bool _looksLikeType(String name) {
|
||||
// If the name looks like an UppercaseName, assume it's a type.
|
||||
return _looksLikePublicType(name) || _looksLikePrivateType(name);
|
||||
}
|
||||
|
||||
bool _looksLikePublicType(String name) {
|
||||
// If the name looks like an UppercaseName, assume it's a type.
|
||||
return name.length >= 2 && isUpper(name[0]) && isLower(name[1]);
|
||||
}
|
||||
|
||||
bool _looksLikePrivateType(String name) {
|
||||
// If the name looks like an _UppercaseName, assume it's a type.
|
||||
return (name.length >= 3 && name[0] == '_' && isUpper(name[1])
|
||||
&& isLower(name[2]));
|
||||
}
|
||||
|
||||
// These ensure that they don't return "true" if the string only has symbols.
|
||||
bool isUpper(String s) => s.toLowerCase() != s;
|
||||
bool isLower(String s) => s.toUpperCase() != s;
|
||||
|
||||
String classify(Token token) {
|
||||
switch (token.kind) {
|
||||
case IDENTIFIER_TOKEN:
|
||||
// Special case for names that look like types.
|
||||
final text = token.value;
|
||||
if (_looksLikeType(text)
|
||||
|| text == 'num'
|
||||
|| text == 'bool'
|
||||
|| text == 'int'
|
||||
|| text == 'double') {
|
||||
return Classification.TYPE_IDENTIFIER;
|
||||
}
|
||||
return Classification.IDENTIFIER;
|
||||
|
||||
case STRING_TOKEN:
|
||||
case STRING_INTERPOLATION_TOKEN:
|
||||
return Classification.STRING;
|
||||
|
||||
case INT_TOKEN:
|
||||
case HEXADECIMAL_TOKEN:
|
||||
case DOUBLE_TOKEN:
|
||||
return Classification.NUMBER;
|
||||
|
||||
case COMMENT_TOKEN:
|
||||
return Classification.COMMENT;
|
||||
|
||||
// => is so awesome it is in a class of its own.
|
||||
case FUNCTION_TOKEN:
|
||||
return Classification.ARROW_OPERATOR;
|
||||
|
||||
case OPEN_PAREN_TOKEN:
|
||||
case CLOSE_PAREN_TOKEN:
|
||||
case OPEN_SQUARE_BRACKET_TOKEN:
|
||||
case CLOSE_SQUARE_BRACKET_TOKEN:
|
||||
case OPEN_CURLY_BRACKET_TOKEN:
|
||||
case CLOSE_CURLY_BRACKET_TOKEN:
|
||||
case COLON_TOKEN:
|
||||
case SEMICOLON_TOKEN:
|
||||
case COMMA_TOKEN:
|
||||
case PERIOD_TOKEN:
|
||||
case PERIOD_PERIOD_TOKEN:
|
||||
return Classification.PUNCTUATION;
|
||||
|
||||
case PLUS_PLUS_TOKEN:
|
||||
case MINUS_MINUS_TOKEN:
|
||||
case TILDE_TOKEN:
|
||||
case BANG_TOKEN:
|
||||
case EQ_TOKEN:
|
||||
case BAR_EQ_TOKEN:
|
||||
case CARET_EQ_TOKEN:
|
||||
case AMPERSAND_EQ_TOKEN:
|
||||
case LT_LT_EQ_TOKEN:
|
||||
case GT_GT_EQ_TOKEN:
|
||||
case PLUS_EQ_TOKEN:
|
||||
case MINUS_EQ_TOKEN:
|
||||
case STAR_EQ_TOKEN:
|
||||
case SLASH_EQ_TOKEN:
|
||||
case TILDE_SLASH_EQ_TOKEN:
|
||||
case PERCENT_EQ_TOKEN:
|
||||
case QUESTION_TOKEN:
|
||||
case BAR_BAR_TOKEN:
|
||||
case AMPERSAND_AMPERSAND_TOKEN:
|
||||
case BAR_TOKEN:
|
||||
case CARET_TOKEN:
|
||||
case AMPERSAND_TOKEN:
|
||||
case LT_LT_TOKEN:
|
||||
case GT_GT_TOKEN:
|
||||
case PLUS_TOKEN:
|
||||
case MINUS_TOKEN:
|
||||
case STAR_TOKEN:
|
||||
case SLASH_TOKEN:
|
||||
case TILDE_SLASH_TOKEN:
|
||||
case PERCENT_TOKEN:
|
||||
case EQ_EQ_TOKEN:
|
||||
case BANG_EQ_TOKEN:
|
||||
case EQ_EQ_EQ_TOKEN:
|
||||
case BANG_EQ_EQ_TOKEN:
|
||||
case LT_TOKEN:
|
||||
case GT_TOKEN:
|
||||
case LT_EQ_TOKEN:
|
||||
case GT_EQ_TOKEN:
|
||||
case INDEX_TOKEN:
|
||||
case INDEX_EQ_TOKEN:
|
||||
return Classification.OPERATOR;
|
||||
|
||||
// Color keyword token. Most are colored as keywords.
|
||||
case HASH_TOKEN:
|
||||
case KEYWORD_TOKEN:
|
||||
if (token.stringValue == 'void') {
|
||||
// Color "void" as a type.
|
||||
return Classification.TYPE_IDENTIFIER;
|
||||
}
|
||||
if (token.stringValue == 'this' || token.stringValue == 'super') {
|
||||
// Color "this" and "super" as identifiers.
|
||||
return Classification.SPECIAL_IDENTIFIER;
|
||||
}
|
||||
return Classification.KEYWORD;
|
||||
|
||||
case EOF_TOKEN:
|
||||
return Classification.NONE;
|
||||
|
||||
default:
|
||||
return Classification.NONE;
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/// Parses text in a markdown-like format and renders to HTML.
|
||||
library markdown;
|
||||
|
||||
// TODO(rnystrom): Use "package:" URL (#4968).
|
||||
part 'src/markdown/ast.dart';
|
||||
part 'src/markdown/block_parser.dart';
|
||||
part 'src/markdown/html_renderer.dart';
|
||||
part 'src/markdown/inline_parser.dart';
|
||||
|
||||
typedef Node Resolver(String name);
|
||||
|
||||
/// Converts the given string of markdown to HTML.
|
||||
String markdownToHtml(String markdown, {inlineSyntaxes, linkResolver}) {
|
||||
final document = new Document(inlineSyntaxes: inlineSyntaxes,
|
||||
linkResolver: linkResolver);
|
||||
|
||||
// Replace windows line endings with unix line endings, and split.
|
||||
final lines = markdown.replaceAll('\r\n','\n').split('\n');
|
||||
document.parseRefLinks(lines);
|
||||
final blocks = document.parseLines(lines);
|
||||
return renderToHtml(blocks);
|
||||
}
|
||||
|
||||
/// Replaces `<`, `&`, and `>`, with their HTML entity equivalents.
|
||||
String escapeHtml(String html) {
|
||||
return html.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>');
|
||||
}
|
||||
|
||||
/// Maintains the context needed to parse a markdown document.
|
||||
class Document {
|
||||
final Map<String, Link> refLinks;
|
||||
List<InlineSyntax> inlineSyntaxes;
|
||||
Resolver linkResolver;
|
||||
|
||||
Document({this.inlineSyntaxes, this.linkResolver})
|
||||
: refLinks = <String, Link>{};
|
||||
|
||||
parseRefLinks(List<String> lines) {
|
||||
// This is a hideous regex. It matches:
|
||||
// [id]: http:foo.com "some title"
|
||||
// Where there may whitespace in there, and where the title may be in
|
||||
// single quotes, double quotes, or parentheses.
|
||||
final indent = r'^[ ]{0,3}'; // Leading indentation.
|
||||
final id = r'\[([^\]]+)\]'; // Reference id in [brackets].
|
||||
final quote = r'"[^"]+"'; // Title in "double quotes".
|
||||
final apos = r"'[^']+'"; // Title in 'single quotes'.
|
||||
final paren = r"\([^)]+\)"; // Title in (parentheses).
|
||||
final pattern = new RegExp(
|
||||
'$indent$id:\\s+(\\S+)\\s*($quote|$apos|$paren|)\\s*\$');
|
||||
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
final match = pattern.firstMatch(lines[i]);
|
||||
if (match != null) {
|
||||
// Parse the link.
|
||||
var id = match[1];
|
||||
var url = match[2];
|
||||
var title = match[3];
|
||||
|
||||
if (title == '') {
|
||||
// No title.
|
||||
title = null;
|
||||
} else {
|
||||
// Remove "", '', or ().
|
||||
title = title.substring(1, title.length - 1);
|
||||
}
|
||||
|
||||
// References are case-insensitive.
|
||||
id = id.toLowerCase();
|
||||
|
||||
refLinks[id] = new Link(id, url, title);
|
||||
|
||||
// Remove it from the output. We replace it with a blank line which will
|
||||
// get consumed by later processing.
|
||||
lines[i] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the given [lines] of markdown to a series of AST nodes.
|
||||
List<Node> parseLines(List<String> lines) {
|
||||
final parser = new BlockParser(lines, this);
|
||||
|
||||
final blocks = [];
|
||||
while (!parser.isDone) {
|
||||
for (final syntax in BlockSyntax.syntaxes) {
|
||||
if (syntax.canParse(parser)) {
|
||||
final block = syntax.parse(parser);
|
||||
if (block != null) blocks.add(block);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/// Takes a string of raw text and processes all inline markdown tags,
|
||||
/// returning a list of AST nodes. For example, given ``"*this **is** a*
|
||||
/// `markdown`"``, returns:
|
||||
/// `<em>this <strong>is</strong> a</em> <code>markdown</code>`.
|
||||
List<Node> parseInline(String text) => new InlineParser(text, this).parse();
|
||||
}
|
||||
|
||||
class Link {
|
||||
final String id;
|
||||
final String url;
|
||||
final String title;
|
||||
Link(this.id, this.url, this.title);
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/** Provides client-side behavior for generated docs. */
|
||||
library client;
|
||||
|
||||
import 'dart:html';
|
||||
import 'dart:convert';
|
||||
// TODO(rnystrom): Use "package:" URL (#4968).
|
||||
import '../../classify.dart';
|
||||
import '../../markdown.dart' as md;
|
||||
import '../dartdoc/nav.dart';
|
||||
import 'dropdown.dart';
|
||||
import 'search.dart';
|
||||
import 'client-shared.dart';
|
||||
|
||||
main() {
|
||||
setup();
|
||||
|
||||
// Request the navigation data so we can build the HTML for it.
|
||||
HttpRequest.getString('${prefix}nav.json').then((text) {
|
||||
var json = JSON.decode(text);
|
||||
buildNavigation(json);
|
||||
setupSearch(json);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Takes [libraries], a JSON array representing a set of libraries and builds
|
||||
* the appropriate navigation DOM for it relative to the current library and
|
||||
* type.
|
||||
*/
|
||||
buildNavigation(List libraries) {
|
||||
final html = new StringBuffer();
|
||||
for (Map libraryInfo in libraries) {
|
||||
String libraryName = libraryInfo[NAME];
|
||||
html.write('<h2><div class="icon-library"></div>');
|
||||
if (currentLibrary == libraryName && currentType == null) {
|
||||
html.write('<strong>${md.escapeHtml(libraryName)}</strong>');
|
||||
} else {
|
||||
final url = getLibraryUrl(libraryName);
|
||||
html.write('<a href="$url">${md.escapeHtml(libraryName)}</a>');
|
||||
}
|
||||
html.write('</h2>');
|
||||
|
||||
// Only list the types for the current library.
|
||||
if (currentLibrary == libraryName && libraryInfo.containsKey(TYPES)) {
|
||||
buildLibraryNavigation(html, libraryInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// Insert it into the DOM.
|
||||
final navElement = document.query('.nav');
|
||||
navElement.innerHtml = html.toString();
|
||||
}
|
||||
|
||||
/** Writes the navigation for the types contained by [library] to [html]. */
|
||||
buildLibraryNavigation(StringBuffer html, Map libraryInfo) {
|
||||
// Show the exception types separately.
|
||||
final types = [];
|
||||
final exceptions = [];
|
||||
|
||||
for (Map typeInfo in libraryInfo[TYPES]) {
|
||||
var name = typeInfo[NAME];
|
||||
if (name.endsWith('Exception') || name.endsWith('Error')) {
|
||||
exceptions.add(typeInfo);
|
||||
} else {
|
||||
types.add(typeInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (types.length == 0 && exceptions.length == 0) return;
|
||||
|
||||
writeType(String icon, Map typeInfo) {
|
||||
html.write('<li>');
|
||||
if (currentType == typeInfo[NAME]) {
|
||||
html.write(
|
||||
'<div class="icon-$icon"></div><strong>${getTypeName(typeInfo)}</strong>');
|
||||
} else {
|
||||
html.write(
|
||||
'''
|
||||
<a href="${getTypeUrl(currentLibrary, typeInfo)}">
|
||||
<div class="icon-$icon"></div>${getTypeName(typeInfo)}
|
||||
</a>
|
||||
''');
|
||||
}
|
||||
html.write('</li>');
|
||||
}
|
||||
|
||||
html.write('<ul class="icon">');
|
||||
types.forEach((typeInfo) =>
|
||||
writeType(kindToString(typeInfo[KIND]), typeInfo));
|
||||
exceptions.forEach((typeInfo) => writeType('exception', typeInfo));
|
||||
html.write('</ul>');
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library client_shared;
|
||||
|
||||
import 'dart:html';
|
||||
import 'dropdown.dart';
|
||||
import '../../classify.dart';
|
||||
import '../dartdoc/nav.dart';
|
||||
|
||||
|
||||
// Code shared between the different client-side libraries.
|
||||
|
||||
// The names of the library and type that this page documents.
|
||||
String currentLibrary = null;
|
||||
String currentType = null;
|
||||
|
||||
// What we need to prefix relative URLs with to get them to work.
|
||||
String prefix = '';
|
||||
|
||||
void setup() {
|
||||
setupLocation();
|
||||
setupShortcuts();
|
||||
enableCodeBlocks();
|
||||
enableShowHideInherited();
|
||||
}
|
||||
|
||||
void setupLocation() {
|
||||
// Figure out where we are.
|
||||
final body = document.query('body');
|
||||
currentLibrary = body.dataset['library'];
|
||||
currentType = body.dataset['type'];
|
||||
prefix = (currentType != null) ? '../' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all code blocks and makes them toggleable. Syntax highlights each
|
||||
* code block the first time it's shown.
|
||||
*/
|
||||
enableCodeBlocks() {
|
||||
for (var elem in document.queryAll('.method, .field')) {
|
||||
var showCode = elem.query('.show-code');
|
||||
|
||||
// Skip it if we don't have a code link. Will happen if source code is
|
||||
// disabled.
|
||||
if (showCode == null) continue;
|
||||
|
||||
var preList = elem.queryAll('pre.source');
|
||||
|
||||
showCode.onClick.listen((e) {
|
||||
for (final pre in preList) {
|
||||
if (pre.classes.contains('expanded')) {
|
||||
pre.classes.remove('expanded');
|
||||
} else {
|
||||
// Syntax highlight.
|
||||
if (!pre.classes.contains('formatted')) {
|
||||
pre.innerHtml = classifySource(pre.text);
|
||||
pre.classes.add('formatted');
|
||||
};
|
||||
pre.classes.add('expanded');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables show/hide functionality for inherited members and comments.
|
||||
*/
|
||||
void enableShowHideInherited() {
|
||||
var showInherited = document.query('#show-inherited');
|
||||
if (showInherited == null) return;
|
||||
showInherited.dataset.putIfAbsent('show-inherited', () => 'block');
|
||||
showInherited.onClick.listen((e) {
|
||||
String display = showInherited.dataset['show-inherited'];
|
||||
if (display == 'block') {
|
||||
display = 'none';
|
||||
showInherited.innerHtml = 'Show inherited';
|
||||
} else {
|
||||
display = 'block';
|
||||
showInherited.innerHtml = 'Hide inherited';
|
||||
}
|
||||
showInherited.dataset['show-inherited'] = display;
|
||||
for (var elem in document.queryAll('.inherited')) {
|
||||
elem.style.display = display;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/** Turns [name] into something that's safe to use as a file name. */
|
||||
String sanitize(String name) => name.replaceAll(':', '_').replaceAll('/', '_');
|
||||
|
||||
String getTypeName(Map typeInfo) =>
|
||||
typeInfo.containsKey('args')
|
||||
? '${typeInfo[NAME]}<${typeInfo[NAME]}>'
|
||||
: typeInfo[NAME];
|
||||
|
||||
String getLibraryUrl(String libraryName) =>
|
||||
'$prefix${sanitize(libraryName)}.html';
|
||||
|
||||
String getTypeUrl(String libraryName, Map typeInfo) =>
|
||||
'$prefix${sanitize(libraryName)}/${sanitize(typeInfo[NAME])}.html';
|
||||
|
||||
String getLibraryMemberUrl(String libraryName, Map memberInfo) =>
|
||||
'$prefix${sanitize(libraryName)}.html#${getMemberAnchor(memberInfo)}';
|
||||
|
||||
String getTypeMemberUrl(String libraryName, String typeName, Map memberInfo) =>
|
||||
'$prefix${sanitize(libraryName)}/${sanitize(typeName)}.html#'
|
||||
'${getMemberAnchor(memberInfo)}';
|
||||
|
||||
String getMemberAnchor(Map memberInfo) => memberInfo.containsKey(LINK_NAME)
|
||||
? memberInfo[LINK_NAME] : memberInfo[NAME];
|
|
@ -1,363 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library dropdown;
|
||||
|
||||
import 'dart:html';
|
||||
import 'search.dart';
|
||||
import 'client-shared.dart';
|
||||
import '../dartdoc/nav.dart';
|
||||
|
||||
List libraryList;
|
||||
InputElement searchInput;
|
||||
DivElement dropdown;
|
||||
|
||||
/**
|
||||
* Update the search drop down based on the current search text.
|
||||
*/
|
||||
updateDropDown(Event event) {
|
||||
if (libraryList == null) return;
|
||||
if (searchInput == null) return;
|
||||
if (dropdown == null) return;
|
||||
|
||||
var results = <Result>[];
|
||||
String text = searchInput.value;
|
||||
if (text == currentSearchText) {
|
||||
return;
|
||||
}
|
||||
if (text.isEmpty) {
|
||||
updateResults(text, results);
|
||||
hideDropDown();
|
||||
return;
|
||||
}
|
||||
if (text.contains('.')) {
|
||||
// Search type members.
|
||||
String typeText = text.substring(0, text.indexOf('.'));
|
||||
String memberText = text.substring(text.indexOf('.') + 1);
|
||||
|
||||
if (typeText.isEmpty && memberText.isEmpty) {
|
||||
// Don't search on '.'.
|
||||
} else if (typeText.isEmpty) {
|
||||
// Search text is of the form '.id' => Look up members.
|
||||
matchAllMembers(results, memberText);
|
||||
} else if (memberText.isEmpty) {
|
||||
// Search text is of the form 'Type.' => Look up members in 'Type'.
|
||||
matchAllMembersInType(results, typeText, memberText);
|
||||
} else {
|
||||
// Search text is of the form 'Type.id' => Look up member 'id' in 'Type'.
|
||||
matchMembersInType(results, text, typeText, memberText);
|
||||
}
|
||||
} else {
|
||||
// Search all entities.
|
||||
var searchText = new SearchText(text);
|
||||
for (Map<String,dynamic> library in libraryList) {
|
||||
matchLibrary(results, searchText, library);
|
||||
matchLibraryMembers(results, searchText, library);
|
||||
matchTypes(results, searchText, library);
|
||||
}
|
||||
}
|
||||
var elements = <Element>[];
|
||||
var table = new TableElement();
|
||||
table.classes.add('drop-down-table');
|
||||
elements.add(table);
|
||||
|
||||
if (results.isEmpty) {
|
||||
var row = table.insertRow(0);
|
||||
row.innerHtml = "<tr><td>No matches found for '$text'.</td></tr>";
|
||||
} else {
|
||||
results.sort(resultComparator);
|
||||
|
||||
var count = 0;
|
||||
for (Result result in results) {
|
||||
result.addRow(table);
|
||||
if (++count >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (results.length >= 10) {
|
||||
var row = table.insertRow(table.rows.length);
|
||||
row.innerHtml = '<tr><td>+ ${results.length-10} more.</td></tr>';
|
||||
results = results.sublist(0, 10);
|
||||
}
|
||||
}
|
||||
dropdown.children = elements;
|
||||
updateResults(text, results);
|
||||
showDropDown();
|
||||
}
|
||||
|
||||
void matchAllMembers(List<Result> results, String memberText) {
|
||||
var searchText = new SearchText(memberText);
|
||||
for (Map<String,dynamic> library in libraryList) {
|
||||
String libraryName = library[NAME];
|
||||
if (library.containsKey(TYPES)) {
|
||||
for (Map<String,dynamic> type in library[TYPES]) {
|
||||
String typeName = type[NAME];
|
||||
if (type.containsKey(MEMBERS)) {
|
||||
for (Map<String,dynamic> member in type[MEMBERS]) {
|
||||
StringMatch memberMatch = obtainMatch(searchText, member[NAME]);
|
||||
if (memberMatch != null) {
|
||||
results.add(new Result(memberMatch, member[KIND],
|
||||
getTypeMemberUrl(libraryName, typeName, member),
|
||||
library: libraryName, type: typeName, args: type[ARGS],
|
||||
noargs: member[NO_PARAMS]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void matchAllMembersInType(List<Result> results,
|
||||
String typeText, String memberText) {
|
||||
var searchText = new SearchText(typeText);
|
||||
var emptyText = new SearchText(memberText);
|
||||
for (Map<String,dynamic> library in libraryList) {
|
||||
String libraryName = library[NAME];
|
||||
if (library.containsKey(TYPES)) {
|
||||
for (Map<String,dynamic> type in library[TYPES]) {
|
||||
String typeName = type[NAME];
|
||||
StringMatch typeMatch = obtainMatch(searchText, typeName);
|
||||
if (typeMatch != null) {
|
||||
if (type.containsKey(MEMBERS)) {
|
||||
for (Map<String,dynamic> member in type[MEMBERS]) {
|
||||
StringMatch memberMatch = obtainMatch(emptyText,
|
||||
member[NAME]);
|
||||
results.add(new Result(memberMatch, member[KIND],
|
||||
getTypeMemberUrl(libraryName, typeName, member),
|
||||
library: libraryName, prefix: typeMatch,
|
||||
noargs: member[NO_PARAMS]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void matchMembersInType(List<Result> results,
|
||||
String text, String typeText, String memberText) {
|
||||
var searchText = new SearchText(text);
|
||||
var typeSearchText = new SearchText(typeText);
|
||||
var memberSearchText = new SearchText(memberText);
|
||||
for (Map<String,dynamic> library in libraryList) {
|
||||
String libraryName = library[NAME];
|
||||
if (library.containsKey(TYPES)) {
|
||||
for (Map<String,dynamic> type in library[TYPES]) {
|
||||
String typeName = type[NAME];
|
||||
StringMatch typeMatch = obtainMatch(typeSearchText, typeName);
|
||||
if (typeMatch != null) {
|
||||
if (type.containsKey(MEMBERS)) {
|
||||
for (Map<String,dynamic> member in type[MEMBERS]) {
|
||||
// Check for constructor match.
|
||||
StringMatch constructorMatch = obtainMatch(searchText,
|
||||
member[NAME]);
|
||||
if (constructorMatch != null) {
|
||||
results.add(new Result(constructorMatch, member[KIND],
|
||||
getTypeMemberUrl(libraryName, typeName, member),
|
||||
library: libraryName, noargs: member[NO_PARAMS]));
|
||||
} else {
|
||||
// Try member match.
|
||||
StringMatch memberMatch = obtainMatch(memberSearchText,
|
||||
member[NAME]);
|
||||
if (memberMatch != null) {
|
||||
results.add(new Result(memberMatch, member[KIND],
|
||||
getTypeMemberUrl(libraryName, typeName, member),
|
||||
library: libraryName, prefix: typeMatch,
|
||||
args: type[ARGS], noargs: member[NO_PARAMS]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void matchLibrary(List<Result> results, SearchText searchText, Map library) {
|
||||
String libraryName = library[NAME];
|
||||
StringMatch libraryMatch = obtainMatch(searchText, libraryName);
|
||||
if (libraryMatch != null) {
|
||||
results.add(new Result(libraryMatch, LIBRARY,
|
||||
getLibraryUrl(libraryName)));
|
||||
}
|
||||
}
|
||||
|
||||
void matchLibraryMembers(List<Result> results, SearchText searchText,
|
||||
Map library) {
|
||||
if (library.containsKey(MEMBERS)) {
|
||||
String libraryName = library[NAME];
|
||||
for (Map<String,dynamic> member in library[MEMBERS]) {
|
||||
StringMatch memberMatch = obtainMatch(searchText, member[NAME]);
|
||||
if (memberMatch != null) {
|
||||
results.add(new Result(memberMatch, member[KIND],
|
||||
getLibraryMemberUrl(libraryName, member),
|
||||
library: libraryName, noargs: member[NO_PARAMS]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void matchTypes(List<Result> results, SearchText searchText,
|
||||
Map library) {
|
||||
if (library.containsKey(TYPES)) {
|
||||
String libraryName = library[NAME];
|
||||
for (Map<String,dynamic> type in library[TYPES]) {
|
||||
String typeName = type[NAME];
|
||||
matchType(results, searchText, libraryName, type);
|
||||
matchTypeMembers(results, searchText, libraryName, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void matchType(List<Result> results, SearchText searchText,
|
||||
String libraryName, Map type) {
|
||||
String typeName = type[NAME];
|
||||
StringMatch typeMatch = obtainMatch(searchText, typeName);
|
||||
if (typeMatch != null) {
|
||||
results.add(new Result(typeMatch, type[KIND],
|
||||
getTypeUrl(libraryName, type),
|
||||
library: libraryName, args: type[ARGS]));
|
||||
}
|
||||
}
|
||||
|
||||
void matchTypeMembers(List<Result> results, SearchText searchText,
|
||||
String libraryName, Map type) {
|
||||
if (type.containsKey(MEMBERS)) {
|
||||
String typeName = type[NAME];
|
||||
for (Map<String,dynamic> member in type[MEMBERS]) {
|
||||
StringMatch memberMatch = obtainMatch(searchText, member[NAME]);
|
||||
if (memberMatch != null) {
|
||||
results.add(new Result(memberMatch, member[KIND],
|
||||
getTypeMemberUrl(libraryName, typeName, member),
|
||||
library: libraryName, type: typeName, args: type[ARGS],
|
||||
noargs: member[NO_PARAMS]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String currentSearchText;
|
||||
Result _currentResult;
|
||||
List<Result> currentResults = const <Result>[];
|
||||
|
||||
void updateResults(String searchText, List<Result> results) {
|
||||
currentSearchText = searchText;
|
||||
currentResults = results;
|
||||
if (currentResults.isEmpty) {
|
||||
_currentResultIndex = -1;
|
||||
currentResult = null;
|
||||
} else {
|
||||
_currentResultIndex = 0;
|
||||
currentResult = currentResults[0];
|
||||
}
|
||||
}
|
||||
|
||||
int _currentResultIndex;
|
||||
|
||||
void set currentResultIndex(int index) {
|
||||
if (index < -1) {
|
||||
return;
|
||||
}
|
||||
if (index >= currentResults.length) {
|
||||
return;
|
||||
}
|
||||
if (index != _currentResultIndex) {
|
||||
_currentResultIndex = index;
|
||||
if (index >= 0) {
|
||||
currentResult = currentResults[_currentResultIndex];
|
||||
} else {
|
||||
currentResult = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int get currentResultIndex => _currentResultIndex;
|
||||
|
||||
void set currentResult(Result result) {
|
||||
if (_currentResult != result) {
|
||||
if (_currentResult != null) {
|
||||
_currentResult.row.classes.remove('drop-down-link-select');
|
||||
}
|
||||
_currentResult = result;
|
||||
if (_currentResult != null) {
|
||||
_currentResult.row.classes.add('drop-down-link-select');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result get currentResult => _currentResult;
|
||||
|
||||
/**
|
||||
* Navigate the search drop down using up/down inside the search field. Follow
|
||||
* the result link on enter.
|
||||
*/
|
||||
void handleUpDown(KeyboardEvent event) {
|
||||
if (event.keyCode == KeyCode.UP) {
|
||||
currentResultIndex--;
|
||||
event.preventDefault();
|
||||
} else if (event.keyCode == KeyCode.DOWN) {
|
||||
currentResultIndex++;
|
||||
event.preventDefault();
|
||||
} else if (event.keyCode == KeyCode.ENTER) {
|
||||
if (currentResult != null) {
|
||||
window.location.href = currentResult.url;
|
||||
event.preventDefault();
|
||||
hideDropDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Show the search drop down unless there are no current results. */
|
||||
void showDropDown() {
|
||||
if (currentResults.isEmpty) {
|
||||
hideDropDown();
|
||||
} else {
|
||||
dropdown.style.visibility = 'visible';
|
||||
}
|
||||
}
|
||||
|
||||
/** Used to prevent hiding the drop down when it is clicked. */
|
||||
bool hideDropDownSuspend = false;
|
||||
|
||||
/** Hide the search drop down unless suspended. */
|
||||
void hideDropDown() {
|
||||
if (hideDropDownSuspend) return;
|
||||
|
||||
dropdown.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
/** Activate search on Ctrl+3 and S. */
|
||||
void shortcutHandler(KeyboardEvent event) {
|
||||
if (event.keyCode == KeyCode.THREE && event.ctrlKey) {
|
||||
searchInput.focus();
|
||||
event.preventDefault();
|
||||
} else if (event.target != searchInput && event.keyCode == KeyCode.S) {
|
||||
// Allow writing 's' in the search input.
|
||||
searchInput.focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup window shortcuts.
|
||||
*/
|
||||
void setupShortcuts() {
|
||||
window.onKeyDown.listen(shortcutHandler);
|
||||
}
|
||||
|
||||
/** Setup search hooks. */
|
||||
void setupSearch(var libraries) {
|
||||
libraryList = libraries;
|
||||
searchInput = query('#q');
|
||||
dropdown = query('#drop-down');
|
||||
|
||||
searchInput.onKeyDown.listen(handleUpDown);
|
||||
searchInput.onKeyUp.listen(updateDropDown);
|
||||
searchInput.onChange.listen(updateDropDown);
|
||||
searchInput.onReset.listen(updateDropDown);
|
||||
searchInput.onFocus.listen((event) => showDropDown());
|
||||
searchInput.onBlur.listen((event) => hideDropDown());
|
||||
}
|
|
@ -1,230 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library search;
|
||||
|
||||
import 'dart:html';
|
||||
import 'dropdown.dart';
|
||||
import '../dartdoc/nav.dart';
|
||||
|
||||
/**
|
||||
* [SearchText] represent the search field text. The text is viewed in three
|
||||
* ways: [text] holds the original search text, used for performing
|
||||
* case-sensitive matches, [lowerCase] holds the lower-case search text, used
|
||||
* for performing case-insenstive matches, [camelCase] holds a camel-case
|
||||
* interpretation of the search text, used to order matches in camel-case.
|
||||
*/
|
||||
class SearchText {
|
||||
final String text;
|
||||
final String lowerCase;
|
||||
final String camelCase;
|
||||
|
||||
SearchText(String searchText)
|
||||
: text = searchText,
|
||||
lowerCase = searchText.toLowerCase(),
|
||||
camelCase = searchText.isEmpty ? ''
|
||||
: '${searchText.substring(0, 1).toUpperCase()}'
|
||||
'${searchText.substring(1)}';
|
||||
|
||||
int get length => text.length;
|
||||
|
||||
bool get isEmpty => length == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* [StringMatch] represents the case-insensitive matching of [searchText] as a
|
||||
* substring within a [text].
|
||||
*/
|
||||
class StringMatch {
|
||||
final SearchText searchText;
|
||||
final String text;
|
||||
final int matchOffset;
|
||||
final int matchEnd;
|
||||
|
||||
StringMatch(this.searchText,
|
||||
this.text, this.matchOffset, this.matchEnd);
|
||||
|
||||
/**
|
||||
* Returns the HTML representation of the match.
|
||||
*/
|
||||
String toHtml() {
|
||||
return '${text.substring(0, matchOffset)}'
|
||||
'<span class="drop-down-link-highlight">$matchText</span>'
|
||||
'${text.substring(matchEnd)}';
|
||||
}
|
||||
|
||||
String get matchText =>
|
||||
text.substring(matchOffset, matchEnd);
|
||||
|
||||
/**
|
||||
* Is [:true:] iff [searchText] matches the full [text] case-sensitively.
|
||||
*/
|
||||
bool get isFullMatch => text == searchText.text;
|
||||
|
||||
/**
|
||||
* Is [:true:] iff [searchText] matches a substring of [text]
|
||||
* case-sensitively.
|
||||
*/
|
||||
bool get isExactMatch => matchText == searchText.text;
|
||||
|
||||
/**
|
||||
* Is [:true:] iff [searchText] matches a substring of [text] when
|
||||
* [searchText] is interpreted as camel case.
|
||||
*/
|
||||
bool get isCamelCaseMatch => matchText == searchText.camelCase;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Result] represents a match of the search text on a library, type or member.
|
||||
*/
|
||||
class Result {
|
||||
final StringMatch prefix;
|
||||
final StringMatch match;
|
||||
|
||||
final String library;
|
||||
final String type;
|
||||
final String args;
|
||||
final String kind;
|
||||
final String url;
|
||||
final bool noargs;
|
||||
|
||||
TableRowElement row;
|
||||
|
||||
Result(this.match, this.kind, this.url,
|
||||
{this.library: null, this.type: null, String args: null,
|
||||
this.prefix: null, this.noargs: false})
|
||||
: this.args = args != null ? '<$args>' : '';
|
||||
|
||||
bool get isTopLevel => prefix == null && type == null;
|
||||
|
||||
void addRow(TableElement table) {
|
||||
if (row != null) return;
|
||||
|
||||
clickHandler(Event event) {
|
||||
window.location.href = url;
|
||||
hideDropDown();
|
||||
}
|
||||
|
||||
row = table.insertRow(table.rows.length);
|
||||
row.classes.add('drop-down-link-tr');
|
||||
row.onMouseDown.listen((event) => hideDropDownSuspend = true);
|
||||
row.onClick.listen(clickHandler);
|
||||
row.onMouseUp.listen((event) => hideDropDownSuspend = false);
|
||||
var sb = new StringBuffer();
|
||||
sb.write('<td class="drop-down-link-td">');
|
||||
sb.write('<table class="drop-down-table"><tr><td colspan="2">');
|
||||
if (kind == GETTER) {
|
||||
sb.write('get ');
|
||||
} else if (kind == SETTER) {
|
||||
sb.write('set ');
|
||||
}
|
||||
sb.write(match.toHtml());
|
||||
if (kind == CLASS || kind == TYPEDEF) {
|
||||
sb.write(args);
|
||||
} else if (kind == CONSTRUCTOR || kind == METHOD) {
|
||||
if (noargs) {
|
||||
sb.write("()");
|
||||
} else {
|
||||
sb.write('(...)');
|
||||
}
|
||||
}
|
||||
sb.write('</td></tr><tr><td class="drop-down-link-kind">');
|
||||
sb.write(kindToString(kind));
|
||||
if (prefix != null) {
|
||||
sb.write(' in ');
|
||||
sb.write(prefix.toHtml());
|
||||
sb.write(args);
|
||||
} else if (type != null) {
|
||||
sb.write(' in ');
|
||||
sb.write(type);
|
||||
sb.write(args);
|
||||
}
|
||||
|
||||
sb.write('</td><td class="drop-down-link-library">');
|
||||
if (library != null) {
|
||||
sb.write('library $library');
|
||||
}
|
||||
sb.write('</td></tr></table></td>');
|
||||
row.innerHtml = sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [StringMatch] object for [text] if a substring matches
|
||||
* [searchText], or returns [: null :] if no match is found.
|
||||
*/
|
||||
StringMatch obtainMatch(SearchText searchText, String text) {
|
||||
if (searchText.isEmpty) {
|
||||
return new StringMatch(searchText, text, 0, 0);
|
||||
}
|
||||
int offset = text.toLowerCase().indexOf(searchText.lowerCase);
|
||||
if (offset != -1) {
|
||||
return new StringMatch(searchText, text,
|
||||
offset, offset + searchText.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares [a] and [b], regarding [:true:] smaller than [:false:].
|
||||
*
|
||||
* [:null:]-values are not handled.
|
||||
*/
|
||||
int compareBools(bool a, bool b) {
|
||||
if (a == b) return 0;
|
||||
return a ? -1 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to sort the search results heuristically to show the more relevant match
|
||||
* in the top of the dropdown.
|
||||
*/
|
||||
int resultComparator(Result a, Result b) {
|
||||
// Favor top level entities.
|
||||
int result = compareBools(a.isTopLevel, b.isTopLevel);
|
||||
if (result != 0) return result;
|
||||
|
||||
if (a.prefix != null && b.prefix != null) {
|
||||
// Favor full prefix matches.
|
||||
result = compareBools(a.prefix.isFullMatch, b.prefix.isFullMatch);
|
||||
if (result != 0) return result;
|
||||
}
|
||||
|
||||
// Favor matches in the start.
|
||||
result = compareBools(a.match.matchOffset == 0,
|
||||
b.match.matchOffset == 0);
|
||||
if (result != 0) return result;
|
||||
|
||||
// Favor matches to the end. For example, prefer 'cancel' over 'cancelable'
|
||||
result = compareBools(a.match.matchEnd == a.match.text.length,
|
||||
b.match.matchEnd == b.match.text.length);
|
||||
if (result != 0) return result;
|
||||
|
||||
// Favor exact case-sensitive matches.
|
||||
result = compareBools(a.match.isExactMatch, b.match.isExactMatch);
|
||||
if (result != 0) return result;
|
||||
|
||||
// Favor matches that do not break camel-case.
|
||||
result = compareBools(a.match.isCamelCaseMatch, b.match.isCamelCaseMatch);
|
||||
if (result != 0) return result;
|
||||
|
||||
// Favor matches close to the begining.
|
||||
result = a.match.matchOffset.compareTo(b.match.matchOffset);
|
||||
if (result != 0) return result;
|
||||
|
||||
if (a.type != null && b.type != null) {
|
||||
// Favor short type names over long.
|
||||
result = a.type.length.compareTo(b.type.length);
|
||||
if (result != 0) return result;
|
||||
|
||||
// Sort type alphabetically.
|
||||
// TODO(4805): Use [:type.compareToIgnoreCase] when supported.
|
||||
result = a.type.toLowerCase().compareTo(b.type.toLowerCase());
|
||||
if (result != 0) return result;
|
||||
}
|
||||
|
||||
// Sort match alphabetically.
|
||||
// TODO(4805): Use [:text.compareToIgnoreCase] when supported.
|
||||
return a.match.text.toLowerCase().compareTo(b.match.text.toLowerCase());
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library dart2js_util;
|
||||
|
||||
import 'dart:async' show Future;
|
||||
|
||||
import '../../../compiler/compiler.dart' as api;
|
||||
import '../../../compiler/implementation/mirrors/analyze.dart' as dart2js;
|
||||
import '../../../compiler/implementation/mirrors/source_mirrors.dart'
|
||||
show MirrorSystem;
|
||||
import '../../../compiler/implementation/source_file_provider.dart'
|
||||
show FormattingDiagnosticHandler, SourceFileProvider,
|
||||
CompilerSourceFileProvider;
|
||||
import '../../../compiler/implementation/filenames.dart'
|
||||
show appendSlash, currentDirectory;
|
||||
|
||||
// TODO(johnniwinther): Support client configurable providers.
|
||||
|
||||
/**
|
||||
* Returns a future that completes to a non-null String when [script]
|
||||
* has been successfully compiled.
|
||||
*/
|
||||
// TODO(amouravski): Remove this method and call dart2js via a process instead.
|
||||
Future<String> compile(String script,
|
||||
String libraryRoot,
|
||||
{String packageRoot,
|
||||
List<String> options: const <String>[],
|
||||
api.DiagnosticHandler diagnosticHandler}) {
|
||||
SourceFileProvider provider = new CompilerSourceFileProvider();
|
||||
if (diagnosticHandler == null) {
|
||||
diagnosticHandler =
|
||||
new FormattingDiagnosticHandler(provider).diagnosticHandler;
|
||||
}
|
||||
Uri scriptUri = currentDirectory.resolve(script.toString());
|
||||
Uri libraryUri = currentDirectory.resolve(appendSlash('$libraryRoot'));
|
||||
Uri packageUri = null;
|
||||
if (packageRoot != null) {
|
||||
packageUri = currentDirectory.resolve(appendSlash('$packageRoot'));
|
||||
}
|
||||
return api.compile(scriptUri, libraryUri, packageUri,
|
||||
provider.readStringFromUri, diagnosticHandler, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes set of libraries and provides a mirror system which can be used for
|
||||
* static inspection of the source code.
|
||||
*/
|
||||
Future<MirrorSystem> analyze(List<String> libraries,
|
||||
String libraryRoot,
|
||||
{String packageRoot,
|
||||
List<String> options: const <String>[],
|
||||
api.DiagnosticHandler diagnosticHandler}) {
|
||||
SourceFileProvider provider = new CompilerSourceFileProvider();
|
||||
if (diagnosticHandler == null) {
|
||||
diagnosticHandler =
|
||||
new FormattingDiagnosticHandler(provider).diagnosticHandler;
|
||||
}
|
||||
Uri libraryUri = currentDirectory.resolve(appendSlash('$libraryRoot'));
|
||||
Uri packageUri = null;
|
||||
if (packageRoot != null) {
|
||||
packageUri = currentDirectory.resolve(appendSlash('$packageRoot'));
|
||||
}
|
||||
List<Uri> librariesUri = <Uri>[];
|
||||
for (String library in libraries) {
|
||||
librariesUri.add(currentDirectory.resolve(library));
|
||||
}
|
||||
return dart2js.analyze(librariesUri, libraryUri, packageUri,
|
||||
provider.readStringFromUri, diagnosticHandler,
|
||||
options);
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library dartdoc_nav;
|
||||
|
||||
/*
|
||||
* Constant values used for encoding navigation info.
|
||||
*
|
||||
* The generated JSON data is a list of LibraryInfo maps, defined as follows:
|
||||
*
|
||||
* LibraryInfo = {
|
||||
* String NAME, // Library name.
|
||||
* List<TypeInfo> TYPES, // Library types.
|
||||
* List<MemberInfo> MEMBERS, // Library functions and variables.
|
||||
* };
|
||||
* TypeInfo = {
|
||||
* String NAME, // Type name.
|
||||
* String ARGS, // Type variables, e.g. "<K,V>". Optional.
|
||||
* String KIND, // One of CLASS or TYPEDEF.
|
||||
* List<MemberInfo> MEMBERS, // Type fields and methods.
|
||||
* };
|
||||
* MemberInfo = {
|
||||
* String NAME, // Member name.
|
||||
* String KIND, // One of FIELD, CONSTRUCTOR, METHOD, GETTER, or SETTER.
|
||||
* String LINK_NAME, // Anchor name for the member if different from
|
||||
* // NAME.
|
||||
* bool NO_PARAMS, // Is true if member takes no arguments?
|
||||
* };
|
||||
*
|
||||
*
|
||||
* TODO(johnniwinther): Shorten the string values to reduce JSON output size.
|
||||
*/
|
||||
|
||||
const String LIBRARY = 'library';
|
||||
const String CLASS = 'class';
|
||||
const String TYPEDEF = 'typedef';
|
||||
const String MEMBERS = 'members';
|
||||
const String TYPES = 'types';
|
||||
const String ARGS = 'args';
|
||||
const String NAME = 'name';
|
||||
const String KIND = 'kind';
|
||||
const String FIELD = 'field';
|
||||
const String CONSTRUCTOR = 'constructor';
|
||||
const String METHOD = 'method';
|
||||
const String NO_PARAMS = 'noparams';
|
||||
const String GETTER = 'getter';
|
||||
const String SETTER = 'setter';
|
||||
const String LINK_NAME = 'link_name';
|
||||
|
||||
/**
|
||||
* Translation of const values to strings. Used to facilitate shortening of
|
||||
* constant value strings.
|
||||
*/
|
||||
String kindToString(String kind) {
|
||||
if (kind == LIBRARY) {
|
||||
return 'library';
|
||||
} else if (kind == CLASS) {
|
||||
return 'class';
|
||||
} else if (kind == TYPEDEF) {
|
||||
return 'typedef';
|
||||
} else if (kind == FIELD) {
|
||||
return 'field';
|
||||
} else if (kind == CONSTRUCTOR) {
|
||||
return 'constructor';
|
||||
} else if (kind == METHOD) {
|
||||
return 'method';
|
||||
} else if (kind == GETTER) {
|
||||
return 'getter';
|
||||
} else if (kind == SETTER) {
|
||||
return 'setter';
|
||||
}
|
||||
return '';
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// Generic utility functions.
|
||||
library utils;
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:path/path.dart' as pathos;
|
||||
|
||||
import '../../../../compiler/implementation/mirrors/source_mirrors.dart';
|
||||
|
||||
import '../export_map.dart';
|
||||
|
||||
/** Turns [name] into something that's safe to use as a file name. */
|
||||
String sanitize(String name) => name.replaceAll(':', '_').replaceAll('/', '_');
|
||||
|
||||
/** Returns the number of times [search] occurs in [text]. */
|
||||
int countOccurrences(String text, String search) {
|
||||
int start = 0;
|
||||
int count = 0;
|
||||
|
||||
while (true) {
|
||||
start = text.indexOf(search, start);
|
||||
if (start == -1) break;
|
||||
count++;
|
||||
// Offsetting by search length means overlapping results are not counted.
|
||||
start += search.length;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/** Repeats [text] [count] times, separated by [separator] if given. */
|
||||
String repeat(String text, int count, {String separator}) {
|
||||
// TODO(rnystrom): Should be in corelib.
|
||||
final buffer = new StringBuffer();
|
||||
for (int i = 0; i < count; i++) {
|
||||
buffer.write(text);
|
||||
if ((i < count - 1) && (separator != null)) buffer.write(separator);
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** Removes up to [indentation] leading whitespace characters from [text]. */
|
||||
String unindent(String text, int indentation) {
|
||||
var start;
|
||||
for (start = 0; start < math.min(indentation, text.length); start++) {
|
||||
// Stop if we hit a non-whitespace character.
|
||||
if (text[start] != ' ') break;
|
||||
}
|
||||
|
||||
return text.substring(start);
|
||||
}
|
||||
|
||||
/** Sorts the map by the key, doing a case-insensitive comparison. */
|
||||
List<Mirror> orderByName(Iterable<DeclarationMirror> list) {
|
||||
final elements = new List<Mirror>.from(list);
|
||||
elements.sort((a,b) {
|
||||
String aName = MirrorSystem.getName(a.simpleName).toLowerCase();
|
||||
String bName = MirrorSystem.getName(b.simpleName).toLowerCase();
|
||||
bool doma = aName.startsWith(r"$dom");
|
||||
bool domb = bName.startsWith(r"$dom");
|
||||
return doma == domb ? aName.compareTo(bName) : doma ? 1 : -1;
|
||||
});
|
||||
return elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins [items] into a single, comma-separated string using [conjunction].
|
||||
* E.g. `['A', 'B', 'C']` becomes `"A, B, and C"`.
|
||||
*/
|
||||
String joinWithCommas(List<String> items, [String conjunction = 'and']) {
|
||||
if (items.length == 1) return items[0];
|
||||
if (items.length == 2) return "${items[0]} $conjunction ${items[1]}";
|
||||
return '${items.take(items.length - 1).join(', ')}'
|
||||
', $conjunction ${items[items.length - 1]}';
|
||||
}
|
||||
|
||||
void writeString(File file, String text) {
|
||||
var randomAccessFile = file.openSync(mode: FileMode.WRITE);
|
||||
randomAccessFile.writeStringSync(text);
|
||||
randomAccessFile.closeSync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts [uri], which should come from a Dart import or export, to a local
|
||||
* filesystem path. [basePath] is the base directory to use when converting
|
||||
* relative URIs; without it, relative URIs will not be converted. [packageRoot]
|
||||
* is the `packages` directory to use when converting `package:` URIs; without
|
||||
* it, `package:` URIs will not be converted.
|
||||
*
|
||||
* If a URI cannot be converted, this will return `null`.
|
||||
*/
|
||||
String importUriToPath(Uri uri, {String basePath, String packageRoot}) {
|
||||
if (uri.scheme == 'file') return pathos.fromUri(uri);
|
||||
|
||||
if (basePath != null && uri.scheme == '') {
|
||||
return pathos.normalize(pathos.absolute(pathos.join(basePath, uri.path)));
|
||||
}
|
||||
|
||||
if (packageRoot != null && uri.scheme == 'package') {
|
||||
return pathos.normalize(pathos.absolute(
|
||||
pathos.join(packageRoot, uri.path)));
|
||||
}
|
||||
|
||||
// Ignore unsupported schemes.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If [map] contains an [Export] under [key], this merges that with [export].
|
||||
* Otherwise, it sets [key] to [export].
|
||||
*/
|
||||
void addOrMergeExport(Map<LibraryMirror, Export> map,
|
||||
LibraryMirror key, Export export) {
|
||||
if (map.containsKey(key)) {
|
||||
map[key] = map[key].merge(export);
|
||||
} else {
|
||||
map[key] = export;
|
||||
}
|
||||
}
|
||||
|
||||
/// A pair of values.
|
||||
class Pair<E, F> {
|
||||
E first;
|
||||
F last;
|
||||
|
||||
Pair(this.first, this.last);
|
||||
|
||||
String toString() => '($first, $last)';
|
||||
|
||||
bool operator==(other) {
|
||||
if (other is! Pair) return false;
|
||||
return other.first == first && other.last == last;
|
||||
}
|
||||
|
||||
int get hashCode => first.hashCode ^ last.hashCode;
|
||||
}
|
|
@ -1,233 +0,0 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/// This library uses the Dart analyzer to find the exports for a set of
|
||||
/// libraries. It stores these exports in an [ExportMap]. This is used to
|
||||
/// display exported members as part of the exporting library, since dart2js
|
||||
/// doesn't provide this information itself.
|
||||
library export_map;
|
||||
|
||||
import '../../../compiler/implementation/mirrors/source_mirrors.dart';
|
||||
import '../../../compiler/implementation/mirrors/mirrors_util.dart';
|
||||
|
||||
/// A class that tracks which libraries export which other libraries.
|
||||
class ExportMap {
|
||||
/// A map from libraries to their [Export]s.
|
||||
///
|
||||
/// Each key is a library and each value is a list of [Export]s for that
|
||||
/// library. There's guaranteed to be only one [Export] of a given library
|
||||
/// in a given list.
|
||||
final Map<LibraryMirror, List<Export>> exports = {};
|
||||
|
||||
/// A cache of the transitive exports for each library. The values are maps
|
||||
/// from the exported libraries to the [Export] objects, to make it easier to
|
||||
/// merge multiple exports of the same library.
|
||||
Map<LibraryMirror, Map<LibraryMirror, Export>> _transitiveExports = {};
|
||||
|
||||
ExportMap(MirrorSystem mirrors) {
|
||||
mirrors.libraries.values.where((lib) => !_isDartLibrary(lib))
|
||||
.forEach(_computeExports);
|
||||
}
|
||||
|
||||
bool _isDartLibrary(LibraryMirror lib) => lib.uri.scheme == 'dart';
|
||||
|
||||
/// Compute all non-dart: exports in [library].
|
||||
void _computeExports(LibrarySourceMirror library) {
|
||||
var exportMap = {};
|
||||
library.libraryDependencies
|
||||
.where((mirror) =>
|
||||
mirror.isExport && !_isDartLibrary(mirror.targetLibrary))
|
||||
.map((mirror) => new Export.fromMirror(mirror))
|
||||
.forEach((export) {
|
||||
var target = export.exported;
|
||||
if (exportMap.containsKey(target)) {
|
||||
exportMap[target] = exportMap[target].merge(export);
|
||||
} else {
|
||||
exportMap[target] = export;
|
||||
}
|
||||
});
|
||||
exports[library] = exportMap.values.toList();
|
||||
}
|
||||
|
||||
/// Returns a list of all exports that [library] transitively exports. This
|
||||
/// means that if [library] exports another library that in turn exports a
|
||||
/// third, the third library will be included in the returned list.
|
||||
///
|
||||
/// This will automatically handle nested `hide` and `show` directives on the
|
||||
/// exports, as well as merging multiple exports of the same library.
|
||||
List<Export> transitiveExports(LibraryMirror library) {
|
||||
Map<LibraryMirror, Export> _getTransitiveExports(LibraryMirror library) {
|
||||
if (_transitiveExports.containsKey(library)) {
|
||||
return _transitiveExports[library];
|
||||
}
|
||||
|
||||
var exportsByPath = <LibraryMirror, Export>{};
|
||||
_transitiveExports[library] = exportsByPath;
|
||||
if (exports[library] == null) return exportsByPath;
|
||||
|
||||
for (var export in exports[library]) {
|
||||
exportsByPath[export.exported] = export;
|
||||
}
|
||||
|
||||
for (var export in exports[library]) {
|
||||
for (var subExport in _getTransitiveExports(export.exported).values) {
|
||||
subExport = export.compose(subExport);
|
||||
if (exportsByPath.containsKey(subExport.exported)) {
|
||||
subExport = subExport.merge(exportsByPath[subExport.exported]);
|
||||
}
|
||||
exportsByPath[subExport.exported] = subExport;
|
||||
}
|
||||
}
|
||||
return exportsByPath;
|
||||
}
|
||||
|
||||
return _getTransitiveExports(library).values.toList();
|
||||
}
|
||||
}
|
||||
|
||||
/// A class that represents one library exporting another.
|
||||
class Export {
|
||||
/// The library that contains this export.
|
||||
final LibraryMirror exporter;
|
||||
|
||||
/// The library being exported.
|
||||
final LibraryMirror exported;
|
||||
|
||||
/// The set of identifiers that are explicitly being exported. If this is
|
||||
/// non-empty, no identifiers other than these will be visible.
|
||||
///
|
||||
/// One or both of [show] and [hide] will always be empty.
|
||||
Set<String> get show => _show;
|
||||
Set<String> _show;
|
||||
|
||||
/// The set of identifiers that are not exported.
|
||||
///
|
||||
/// One or both of [show] and [hide] will always be empty.
|
||||
Set<String> get hide => _hide;
|
||||
Set<String> _hide;
|
||||
|
||||
/// Whether or not members exported are hidden by default.
|
||||
bool get _hideByDefault => !show.isEmpty;
|
||||
|
||||
/// Creates a new export.
|
||||
///
|
||||
/// This will normalize [show] and [hide] so that if both are non-empty, only
|
||||
/// [show] will be set.
|
||||
factory Export.fromMirror(LibraryDependencyMirror mirror) {
|
||||
var show = <String>[];
|
||||
var hide = <String>[];
|
||||
for (var combinator in mirror.combinators) {
|
||||
if (combinator.isShow) {
|
||||
show.addAll(combinator.identifiers);
|
||||
}
|
||||
if (combinator.isHide) {
|
||||
hide.addAll(combinator.identifiers);
|
||||
}
|
||||
}
|
||||
return new Export(
|
||||
mirror.sourceLibrary, mirror.targetLibrary, show: show, hide: hide);
|
||||
}
|
||||
|
||||
Export(this.exporter, this.exported,
|
||||
{Iterable<String> show, Iterable<String> hide}) {
|
||||
_show = new Set<String>.from(show == null ? [] : show);
|
||||
_hide = new Set<String>.from(hide == null ? [] : hide);
|
||||
|
||||
if (!_show.isEmpty) {
|
||||
_show.removeAll(_hide);
|
||||
_hide = new Set<String>();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new [Export] that represents [this] composed with [nested], as
|
||||
/// though [this] was used to export a library that in turn exported [nested].
|
||||
Export compose(Export nested) {
|
||||
var show = new Set<String>();
|
||||
var hide = new Set<String>();
|
||||
|
||||
if (this._hideByDefault) {
|
||||
show.addAll(this.show);
|
||||
if (nested._hideByDefault) {
|
||||
show.retainAll(nested.show);
|
||||
} else {
|
||||
show.removeAll(nested.hide);
|
||||
}
|
||||
} else if (nested._hideByDefault) {
|
||||
show.addAll(nested.show);
|
||||
show.removeAll(this.hide);
|
||||
} else {
|
||||
hide.addAll(this.hide);
|
||||
hide.addAll(nested.hide);
|
||||
}
|
||||
|
||||
return new Export(this.exporter, nested.exported, show: show, hide: hide);
|
||||
}
|
||||
|
||||
/// Returns a new [Export] that merges [this] with [nested], as though both
|
||||
/// exports were included in the same library.
|
||||
///
|
||||
/// [this] and [other] must have the same values for [exporter] and [path].
|
||||
Export merge(Export other) {
|
||||
if (this.exported != other.exported) {
|
||||
throw new ArgumentError("Can't merge two Exports with different paths: "
|
||||
"export '$exported' from '$exporter' and export '${other.exported}' "
|
||||
"from '${other.exporter}'.");
|
||||
} if (this.exporter != other.exporter) {
|
||||
throw new ArgumentError("Can't merge two Exports with different "
|
||||
"exporters: export '$exported' from '$exporter' and export "
|
||||
"'${other.exported}' from '${other.exporter}'.");
|
||||
}
|
||||
|
||||
var show = new Set<String>();
|
||||
var hide = new Set<String>();
|
||||
|
||||
if (this._hideByDefault) {
|
||||
if (other._hideByDefault) {
|
||||
show.addAll(this.show);
|
||||
show.addAll(other.show);
|
||||
} else {
|
||||
hide.addAll(other.hide);
|
||||
hide.removeAll(this.show);
|
||||
}
|
||||
} else {
|
||||
hide.addAll(this.hide);
|
||||
if (other._hideByDefault) {
|
||||
hide.removeAll(other.show);
|
||||
} else {
|
||||
hide.retainAll(other.hide);
|
||||
}
|
||||
}
|
||||
|
||||
return new Export(exporter, exported, show: show, hide: hide);
|
||||
}
|
||||
|
||||
/// Returns whether or not a member named [name] is visible through this
|
||||
/// import, as goverend by [show] and [hide].
|
||||
bool isMemberVisible(String name) =>
|
||||
_hideByDefault ? show.contains(name) : !hide.contains(name);
|
||||
|
||||
bool operator==(other) => other is Export && other.exporter == exporter &&
|
||||
other.exported == exported && show.containsAll(other.show) &&
|
||||
other.show.containsAll(show) && hide.containsAll(other.hide) &&
|
||||
other.hide.containsAll(hide);
|
||||
|
||||
int get hashCode {
|
||||
var hashCode = exporter.hashCode ^ exported.hashCode;
|
||||
combineHashCode(name) => hashCode ^= name.hashCode;
|
||||
show.forEach(combineHashCode);
|
||||
hide.forEach(combineHashCode);
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
String toString() {
|
||||
var combinator = '';
|
||||
if (!show.isEmpty) {
|
||||
combinator = ' show ${show.join(', ')}';
|
||||
} else if (!hide.isEmpty) {
|
||||
combinator = ' hide ${hide.join(', ')}';
|
||||
}
|
||||
return "export '${displayName(exported)}'"
|
||||
"$combinator (from ${displayName(exporter)})";
|
||||
}
|
||||
}
|
|
@ -1,257 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* Simple library to serialize acyclic Dart types to JSON.
|
||||
* This library is not intended for broad consumption and should be replaced
|
||||
* with a more generic Dart serialization library when one is available.
|
||||
*/
|
||||
library json_serializer;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:mirrors';
|
||||
|
||||
String serialize(Object o) {
|
||||
var printer = new JsonPrinter();
|
||||
_serialize(null, o, printer);
|
||||
return printer.toString();
|
||||
}
|
||||
|
||||
/// Serialize the object with pretty printing.
|
||||
String prettySerialize(Object o) {
|
||||
var printer = new JsonPrinter(prettyPrint: true);
|
||||
_serialize(null, o, printer);
|
||||
return printer.toString();
|
||||
}
|
||||
|
||||
|
||||
void _serialize(String name, Object o, JsonPrinter printer) {
|
||||
if (o == null) return;
|
||||
|
||||
if (o is List) {
|
||||
_serializeList(name, o, printer);
|
||||
} else if (o is Map) {
|
||||
_serializeMap(name, o, printer);
|
||||
} else if (o is String) {
|
||||
printer.addString(name, o);
|
||||
} else if (o is bool) {
|
||||
printer.addBool(name, o);
|
||||
} else {
|
||||
_serializeObject(name, o, printer);
|
||||
}
|
||||
}
|
||||
|
||||
void _serializeObject(String name, Object o, JsonPrinter printer) {
|
||||
printer.startObject(name);
|
||||
|
||||
var mirror = reflect(o);
|
||||
var classMirror = mirror.type;
|
||||
var members = <String>[];
|
||||
determineAllMembers(classMirror, members);
|
||||
|
||||
// TODO(jacobr): this code works only because futures for mirrors return
|
||||
// immediately.
|
||||
for(String memberName in members) {
|
||||
var result = mirror.getField(new Symbol(memberName));
|
||||
_serialize(memberName, result.reflectee, printer);
|
||||
}
|
||||
printer.endObject();
|
||||
}
|
||||
|
||||
void determineAllMembers(ClassMirror classMirror,
|
||||
List<String> members) {
|
||||
for (var mirror in classMirror.declarations.values) {
|
||||
if (mirror is VariableMirror ||
|
||||
(mirror is MethodMirror && mirror.isGetter)) {
|
||||
if (!members.contains(MirrorSystem.getName(mirror.simpleName))) {
|
||||
members.add(MirrorSystem.getName(mirror.simpleName));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (classMirror.superclass != null &&
|
||||
|
||||
// TODO(ahe): What is this test for? Consider removing it,
|
||||
// dart2js will issue an error if there is a cycle in superclass
|
||||
// hierarchy.
|
||||
classMirror.superclass.qualifiedName != classMirror.qualifiedName &&
|
||||
|
||||
MirrorSystem.getName(classMirror.superclass.qualifiedName) !=
|
||||
'dart.core.Object') {
|
||||
determineAllMembers(classMirror.superclass, members);
|
||||
}
|
||||
}
|
||||
|
||||
void _serializeList(String name, List l, JsonPrinter printer) {
|
||||
printer.startList(name);
|
||||
for(var o in l) {
|
||||
_serialize(null, o, printer);
|
||||
}
|
||||
printer.endList();
|
||||
}
|
||||
|
||||
void _serializeMap(String name, Map m, JsonPrinter printer) {
|
||||
printer.startObject(name);
|
||||
m.forEach((key, value) =>
|
||||
_serialize(key, value, printer));
|
||||
printer.endObject();
|
||||
}
|
||||
|
||||
class JsonPrinter {
|
||||
static const int BACKSPACE = 8;
|
||||
static const int TAB = 9;
|
||||
static const int NEW_LINE = 10;
|
||||
static const int FORM_FEED = 12;
|
||||
static const int CARRIAGE_RETURN = 13;
|
||||
static const int QUOTE = 34;
|
||||
static const int BACKSLASH = 92;
|
||||
static const int CHAR_B = 98;
|
||||
static const int CHAR_F = 102;
|
||||
static const int CHAR_N = 110;
|
||||
static const int CHAR_R = 114;
|
||||
static const int CHAR_T = 116;
|
||||
static const int CHAR_U = 117;
|
||||
|
||||
StringBuffer _sb;
|
||||
int _indent = 0;
|
||||
bool _inSet = false;
|
||||
|
||||
bool prettyPrint;
|
||||
JsonPrinter({this.prettyPrint: false}) {
|
||||
_sb = new StringBuffer();
|
||||
}
|
||||
|
||||
void startObject(String name) {
|
||||
_start(name);
|
||||
_sb.write('{');
|
||||
|
||||
_indent += 1;
|
||||
_inSet = false;
|
||||
}
|
||||
|
||||
void endObject() {
|
||||
_indent -= 1;
|
||||
if (_inSet) {
|
||||
_newline();
|
||||
}
|
||||
_sb.write('}');
|
||||
_inSet = true;
|
||||
}
|
||||
|
||||
void startList(String name) {
|
||||
_start(name);
|
||||
_inSet = false;
|
||||
|
||||
_sb.write('[');
|
||||
_indent += 1;
|
||||
}
|
||||
|
||||
void endList() {
|
||||
_indent -= 1;
|
||||
if (_inSet) {
|
||||
_newline();
|
||||
}
|
||||
_sb.write(']');
|
||||
_inSet = true;
|
||||
}
|
||||
|
||||
void addString(String name, String value) {
|
||||
_start(name);
|
||||
_sb.write('"');
|
||||
_escape(_sb, value);
|
||||
_sb.write('"');
|
||||
_inSet = true;
|
||||
}
|
||||
|
||||
void addBool(String name, bool value) {
|
||||
_start(name);
|
||||
_sb.write(value.toString());
|
||||
_inSet = true;
|
||||
}
|
||||
|
||||
void addNum(String name, num value) {
|
||||
_start(name);
|
||||
_sb.write(value.toString());
|
||||
_inSet = true;
|
||||
}
|
||||
|
||||
void _start(String name) {
|
||||
if (_inSet) {
|
||||
_sb.write(',');
|
||||
}
|
||||
// Do not print a newline at the beginning of the file.
|
||||
if (!_sb.isEmpty) {
|
||||
_newline();
|
||||
}
|
||||
if (name != null) {
|
||||
_sb.write('"$name": ');
|
||||
}
|
||||
}
|
||||
|
||||
void _newline([int indent = 0]) {
|
||||
_sb.write('\n');
|
||||
_indent += indent;
|
||||
|
||||
for (var i = 0; i < _indent; ++i) {
|
||||
_sb.write(' ');
|
||||
}
|
||||
}
|
||||
|
||||
String toString() {
|
||||
if (prettyPrint) {
|
||||
return _sb.toString();
|
||||
} else {
|
||||
// Convenient hack to remove the pretty printing this serializer adds by
|
||||
// default.
|
||||
return JSON.encode(JSON.decode(_sb.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
static int _hexDigit(int x) => x < 10 ? 48 + x : 87 + x;
|
||||
|
||||
static void _escape(StringBuffer sb, String s) {
|
||||
final int length = s.length;
|
||||
bool needsEscape = false;
|
||||
final codeUnits = new List<int>();
|
||||
for (int i = 0; i < length; i++) {
|
||||
int codeUnit = s.codeUnitAt(i);
|
||||
if (codeUnit < 32) {
|
||||
needsEscape = true;
|
||||
codeUnits.add(JsonPrinter.BACKSLASH);
|
||||
switch (codeUnit) {
|
||||
case JsonPrinter.BACKSPACE:
|
||||
codeUnits.add(JsonPrinter.CHAR_B);
|
||||
break;
|
||||
case JsonPrinter.TAB:
|
||||
codeUnits.add(JsonPrinter.CHAR_T);
|
||||
break;
|
||||
case JsonPrinter.NEW_LINE:
|
||||
codeUnits.add(JsonPrinter.CHAR_N);
|
||||
break;
|
||||
case JsonPrinter.FORM_FEED:
|
||||
codeUnits.add(JsonPrinter.CHAR_F);
|
||||
break;
|
||||
case JsonPrinter.CARRIAGE_RETURN:
|
||||
codeUnits.add(JsonPrinter.CHAR_R);
|
||||
break;
|
||||
default:
|
||||
codeUnits.add(JsonPrinter.CHAR_U);
|
||||
codeUnits.add(_hexDigit((codeUnit >> 12) & 0xf));
|
||||
codeUnits.add(_hexDigit((codeUnit >> 8) & 0xf));
|
||||
codeUnits.add(_hexDigit((codeUnit >> 4) & 0xf));
|
||||
codeUnits.add(_hexDigit(codeUnit & 0xf));
|
||||
break;
|
||||
}
|
||||
} else if (codeUnit == JsonPrinter.QUOTE ||
|
||||
codeUnit == JsonPrinter.BACKSLASH) {
|
||||
needsEscape = true;
|
||||
codeUnits.add(JsonPrinter.BACKSLASH);
|
||||
codeUnits.add(codeUnit);
|
||||
} else {
|
||||
codeUnits.add(codeUnit);
|
||||
}
|
||||
}
|
||||
sb.write(needsEscape ? new String.fromCharCodes(codeUnits) : s);
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
part of markdown;
|
||||
|
||||
/// Base class for any AST item. Roughly corresponds to Node in the DOM. Will
|
||||
/// be either an Element or Text.
|
||||
abstract class Node {
|
||||
void accept(NodeVisitor visitor);
|
||||
}
|
||||
|
||||
/// A named tag that can contain other nodes.
|
||||
class Element implements Node {
|
||||
final String tag;
|
||||
final List<Node> children;
|
||||
final Map<String, String> attributes;
|
||||
|
||||
Element(this.tag, this.children)
|
||||
: attributes = <String, String>{};
|
||||
|
||||
Element.empty(this.tag)
|
||||
: children = null,
|
||||
attributes = <String, String>{};
|
||||
|
||||
Element.withTag(this.tag)
|
||||
: children = [],
|
||||
attributes = <String, String>{};
|
||||
|
||||
Element.text(this.tag, String text)
|
||||
: children = [new Text(text)],
|
||||
attributes = <String, String>{};
|
||||
|
||||
bool get isEmpty => children == null;
|
||||
|
||||
void accept(NodeVisitor visitor) {
|
||||
if (visitor.visitElementBefore(this)) {
|
||||
for (final child in children) child.accept(visitor);
|
||||
visitor.visitElementAfter(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A plain text element.
|
||||
class Text implements Node {
|
||||
final String text;
|
||||
Text(this.text);
|
||||
|
||||
void accept(NodeVisitor visitor) => visitor.visitText(this);
|
||||
}
|
||||
|
||||
/// Visitor pattern for the AST. Renderers or other AST transformers should
|
||||
/// implement this.
|
||||
abstract class NodeVisitor {
|
||||
/// Called when a Text node has been reached.
|
||||
void visitText(Text text);
|
||||
|
||||
/// Called when an Element has been reached, before its children have been
|
||||
/// visited. Return `false` to skip its children.
|
||||
bool visitElementBefore(Element element);
|
||||
|
||||
/// Called when an Element has been reached, after its children have been
|
||||
/// visited. Will not be called if [visitElementBefore] returns `false`.
|
||||
void visitElementAfter(Element element);
|
||||
}
|
|
@ -1,463 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
part of markdown;
|
||||
|
||||
/// The line contains only whitespace or is empty.
|
||||
final _RE_EMPTY = new RegExp(r'^([ \t]*)$');
|
||||
|
||||
/// A series of `=` or `-` (on the next line) define setext-style headers.
|
||||
final _RE_SETEXT = new RegExp(r'^((=+)|(-+))$');
|
||||
|
||||
/// Leading (and trailing) `#` define atx-style headers.
|
||||
final _RE_HEADER = new RegExp(r'^(#{1,6})(.*?)#*$');
|
||||
|
||||
/// The line starts with `>` with one optional space after.
|
||||
final _RE_BLOCKQUOTE = new RegExp(r'^[ ]{0,3}>[ ]?(.*)$');
|
||||
|
||||
/// A line indented four spaces. Used for code blocks and lists.
|
||||
final _RE_INDENT = new RegExp(r'^(?: |\t)(.*)$');
|
||||
|
||||
/// Three or more hyphens, asterisks or underscores by themselves. Note that
|
||||
/// a line like `----` is valid as both HR and SETEXT. In case of a tie,
|
||||
/// SETEXT should win.
|
||||
final _RE_HR = new RegExp(r'^[ ]{0,3}((-+[ ]{0,2}){3,}|'
|
||||
r'(_+[ ]{0,2}){3,}|'
|
||||
r'(\*+[ ]{0,2}){3,})$');
|
||||
|
||||
/// Really hacky way to detect block-level embedded HTML. Just looks for
|
||||
/// "<somename".
|
||||
final _RE_HTML = new RegExp(r'^<[ ]*\w+[ >]');
|
||||
|
||||
/// A line starting with one of these markers: `-`, `*`, `+`. May have up to
|
||||
/// three leading spaces before the marker and any number of spaces or tabs
|
||||
/// after.
|
||||
final _RE_UL = new RegExp(r'^[ ]{0,3}[*+-][ \t]+(.*)$');
|
||||
|
||||
/// A line starting with a number like `123.`. May have up to three leading
|
||||
/// spaces before the marker and any number of spaces or tabs after.
|
||||
final _RE_OL = new RegExp(r'^[ ]{0,3}\d+\.[ \t]+(.*)$');
|
||||
|
||||
/// Maintains the internal state needed to parse a series of lines into blocks
|
||||
/// of markdown suitable for further inline parsing.
|
||||
class BlockParser {
|
||||
final List<String> lines;
|
||||
|
||||
/// The markdown document this parser is parsing.
|
||||
final Document document;
|
||||
|
||||
/// Index of the current line.
|
||||
int pos;
|
||||
|
||||
BlockParser(this.lines, this.document)
|
||||
: pos = 0;
|
||||
|
||||
/// Gets the current line.
|
||||
String get current => lines[pos];
|
||||
|
||||
/// Gets the line after the current one or `null` if there is none.
|
||||
String get next {
|
||||
// Don't read past the end.
|
||||
if (pos >= lines.length - 1) return null;
|
||||
return lines[pos + 1];
|
||||
}
|
||||
|
||||
void advance() {
|
||||
pos++;
|
||||
}
|
||||
|
||||
bool get isDone => pos >= lines.length;
|
||||
|
||||
/// Gets whether or not the current line matches the given pattern.
|
||||
bool matches(RegExp regex) {
|
||||
if (isDone) return false;
|
||||
return regex.firstMatch(current) != null;
|
||||
}
|
||||
|
||||
/// Gets whether or not the current line matches the given pattern.
|
||||
bool matchesNext(RegExp regex) {
|
||||
if (next == null) return false;
|
||||
return regex.firstMatch(next) != null;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class BlockSyntax {
|
||||
/// Gets the collection of built-in block parsers. To turn a series of lines
|
||||
/// into blocks, each of these will be tried in turn. Order matters here.
|
||||
static List<BlockSyntax> get syntaxes {
|
||||
// Lazy initialize.
|
||||
if (_syntaxes == null) {
|
||||
_syntaxes = [
|
||||
new EmptyBlockSyntax(),
|
||||
new BlockHtmlSyntax(),
|
||||
new SetextHeaderSyntax(),
|
||||
new HeaderSyntax(),
|
||||
new CodeBlockSyntax(),
|
||||
new BlockquoteSyntax(),
|
||||
new HorizontalRuleSyntax(),
|
||||
new UnorderedListSyntax(),
|
||||
new OrderedListSyntax(),
|
||||
new ParagraphSyntax()
|
||||
];
|
||||
}
|
||||
|
||||
return _syntaxes;
|
||||
}
|
||||
|
||||
static List<BlockSyntax> _syntaxes;
|
||||
|
||||
/// Gets the regex used to identify the beginning of this block, if any.
|
||||
RegExp get pattern => null;
|
||||
|
||||
bool get canEndBlock => true;
|
||||
|
||||
bool canParse(BlockParser parser) {
|
||||
return pattern.firstMatch(parser.current) != null;
|
||||
}
|
||||
|
||||
Node parse(BlockParser parser);
|
||||
|
||||
List<String> parseChildLines(BlockParser parser) {
|
||||
// Grab all of the lines that form the blockquote, stripping off the ">".
|
||||
final childLines = <String>[];
|
||||
|
||||
while (!parser.isDone) {
|
||||
final match = pattern.firstMatch(parser.current);
|
||||
if (match == null) break;
|
||||
childLines.add(match[1]);
|
||||
parser.advance();
|
||||
}
|
||||
|
||||
return childLines;
|
||||
}
|
||||
|
||||
/// Gets whether or not [parser]'s current line should end the previous block.
|
||||
static bool isAtBlockEnd(BlockParser parser) {
|
||||
if (parser.isDone) return true;
|
||||
return syntaxes.any((s) => s.canParse(parser) && s.canEndBlock);
|
||||
}
|
||||
}
|
||||
|
||||
class EmptyBlockSyntax extends BlockSyntax {
|
||||
RegExp get pattern => _RE_EMPTY;
|
||||
|
||||
Node parse(BlockParser parser) {
|
||||
parser.advance();
|
||||
|
||||
// Don't actually emit anything.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses setext-style headers.
|
||||
class SetextHeaderSyntax extends BlockSyntax {
|
||||
bool canParse(BlockParser parser) {
|
||||
// Note: matches *next* line, not the current one. We're looking for the
|
||||
// underlining after this line.
|
||||
return parser.matchesNext(_RE_SETEXT);
|
||||
}
|
||||
|
||||
Node parse(BlockParser parser) {
|
||||
final match = _RE_SETEXT.firstMatch(parser.next);
|
||||
|
||||
final tag = (match[1][0] == '=') ? 'h1' : 'h2';
|
||||
final contents = parser.document.parseInline(parser.current);
|
||||
parser.advance();
|
||||
parser.advance();
|
||||
|
||||
return new Element(tag, contents);
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses atx-style headers: `## Header ##`.
|
||||
class HeaderSyntax extends BlockSyntax {
|
||||
RegExp get pattern => _RE_HEADER;
|
||||
|
||||
Node parse(BlockParser parser) {
|
||||
final match = pattern.firstMatch(parser.current);
|
||||
parser.advance();
|
||||
final level = match[1].length;
|
||||
final contents = parser.document.parseInline(match[2].trim());
|
||||
return new Element('h$level', contents);
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses email-style blockquotes: `> quote`.
|
||||
class BlockquoteSyntax extends BlockSyntax {
|
||||
RegExp get pattern => _RE_BLOCKQUOTE;
|
||||
|
||||
Node parse(BlockParser parser) {
|
||||
final childLines = parseChildLines(parser);
|
||||
|
||||
// Recursively parse the contents of the blockquote.
|
||||
final children = parser.document.parseLines(childLines);
|
||||
|
||||
return new Element('blockquote', children);
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses preformatted code blocks that are indented four spaces.
|
||||
class CodeBlockSyntax extends BlockSyntax {
|
||||
RegExp get pattern => _RE_INDENT;
|
||||
|
||||
List<String> parseChildLines(BlockParser parser) {
|
||||
final childLines = <String>[];
|
||||
|
||||
while (!parser.isDone) {
|
||||
var match = pattern.firstMatch(parser.current);
|
||||
if (match != null) {
|
||||
childLines.add(match[1]);
|
||||
parser.advance();
|
||||
} else {
|
||||
// If there's a codeblock, then a newline, then a codeblock, keep the
|
||||
// code blocks together.
|
||||
var nextMatch = parser.next != null ?
|
||||
pattern.firstMatch(parser.next) : null;
|
||||
if (parser.current.trim() == '' && nextMatch != null) {
|
||||
childLines.add('');
|
||||
childLines.add(nextMatch[1]);
|
||||
parser.advance();
|
||||
parser.advance();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return childLines;
|
||||
}
|
||||
|
||||
Node parse(BlockParser parser) {
|
||||
final childLines = parseChildLines(parser);
|
||||
|
||||
// The Markdown tests expect a trailing newline.
|
||||
childLines.add('');
|
||||
|
||||
// Escape the code.
|
||||
final escaped = escapeHtml(childLines.join('\n'));
|
||||
|
||||
return new Element('pre', [new Element.text('code', escaped)]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses horizontal rules like `---`, `_ _ _`, `* * *`, etc.
|
||||
class HorizontalRuleSyntax extends BlockSyntax {
|
||||
RegExp get pattern => _RE_HR;
|
||||
|
||||
Node parse(BlockParser parser) {
|
||||
final match = pattern.firstMatch(parser.current);
|
||||
parser.advance();
|
||||
return new Element.empty('hr');
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses inline HTML at the block level. This differs from other markdown
|
||||
/// implementations in several ways:
|
||||
///
|
||||
/// 1. This one is way way WAY simpler.
|
||||
/// 2. All HTML tags at the block level will be treated as blocks. If you
|
||||
/// start a paragraph with `<em>`, it will not wrap it in a `<p>` for you.
|
||||
/// As soon as it sees something like HTML, it stops mucking with it until
|
||||
/// it hits the next block.
|
||||
/// 3. Absolutely no HTML parsing or validation is done. We're a markdown
|
||||
/// parser not an HTML parser!
|
||||
class BlockHtmlSyntax extends BlockSyntax {
|
||||
RegExp get pattern => _RE_HTML;
|
||||
|
||||
bool get canEndBlock => false;
|
||||
|
||||
Node parse(BlockParser parser) {
|
||||
final childLines = [];
|
||||
|
||||
// Eat until we hit a blank line.
|
||||
while (!parser.isDone && !parser.matches(_RE_EMPTY)) {
|
||||
childLines.add(parser.current);
|
||||
parser.advance();
|
||||
}
|
||||
|
||||
return new Text(childLines.join('\n'));
|
||||
}
|
||||
}
|
||||
|
||||
class ListItem {
|
||||
bool forceBlock = false;
|
||||
final List<String> lines;
|
||||
|
||||
ListItem(this.lines);
|
||||
}
|
||||
|
||||
/// Base class for both ordered and unordered lists.
|
||||
abstract class ListSyntax extends BlockSyntax {
|
||||
bool get canEndBlock => false;
|
||||
|
||||
String get listTag;
|
||||
|
||||
Node parse(BlockParser parser) {
|
||||
final items = <ListItem>[];
|
||||
var childLines = <String>[];
|
||||
|
||||
endItem() {
|
||||
if (childLines.length > 0) {
|
||||
items.add(new ListItem(childLines));
|
||||
childLines = <String>[];
|
||||
}
|
||||
}
|
||||
|
||||
var match;
|
||||
tryMatch(RegExp pattern) {
|
||||
match = pattern.firstMatch(parser.current);
|
||||
return match != null;
|
||||
}
|
||||
|
||||
bool afterEmpty = false;
|
||||
while (!parser.isDone) {
|
||||
if (tryMatch(_RE_EMPTY)) {
|
||||
// Add a blank line to the current list item.
|
||||
childLines.add('');
|
||||
} else if (tryMatch(_RE_UL) || tryMatch(_RE_OL)) {
|
||||
// End the current list item and start a new one.
|
||||
endItem();
|
||||
childLines.add(match[1]);
|
||||
} else if (tryMatch(_RE_INDENT)) {
|
||||
// Strip off indent and add to current item.
|
||||
childLines.add(match[1]);
|
||||
} else if (BlockSyntax.isAtBlockEnd(parser)) {
|
||||
// Done with the list.
|
||||
break;
|
||||
} else {
|
||||
// Anything else is paragraph text or other stuff that can be in a list
|
||||
// item. However, if the previous item is a blank line, this means we're
|
||||
// done with the list and are starting a new top-level paragraph.
|
||||
if ((childLines.length > 0) && (childLines.last == '')) break;
|
||||
childLines.add(parser.current);
|
||||
}
|
||||
parser.advance();
|
||||
}
|
||||
|
||||
endItem();
|
||||
|
||||
// Markdown, because it hates us, specifies two kinds of list items. If you
|
||||
// have a list like:
|
||||
//
|
||||
// * one
|
||||
// * two
|
||||
//
|
||||
// Then it will insert the conents of the lines directly in the <li>, like:
|
||||
// <ul>
|
||||
// <li>one</li>
|
||||
// <li>two</li>
|
||||
// <ul>
|
||||
//
|
||||
// If, however, there are blank lines between the items, each is wrapped in
|
||||
// paragraphs:
|
||||
//
|
||||
// * one
|
||||
//
|
||||
// * two
|
||||
//
|
||||
// <ul>
|
||||
// <li><p>one</p></li>
|
||||
// <li><p>two</p></li>
|
||||
// <ul>
|
||||
//
|
||||
// In other words, sometimes we parse the contents of a list item like a
|
||||
// block, and sometimes line an inline. The rules our parser implements are:
|
||||
//
|
||||
// - If it has more than one line, it's a block.
|
||||
// - If the line matches any block parser (BLOCKQUOTE, HEADER, HR, INDENT,
|
||||
// UL, OL) it's a block. (This is for cases like "* > quote".)
|
||||
// - If there was a blank line between this item and the previous one, it's
|
||||
// a block.
|
||||
// - If there was a blank line between this item and the next one, it's a
|
||||
// block.
|
||||
// - Otherwise, parse it as an inline.
|
||||
|
||||
// Remove any trailing empty lines and note which items are separated by
|
||||
// empty lines. Do this before seeing which items are single-line so that
|
||||
// trailing empty lines on the last item don't force it into being a block.
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
for (int j = items[i].lines.length - 1; j > 0; j--) {
|
||||
if (_RE_EMPTY.firstMatch(items[i].lines[j]) != null) {
|
||||
// Found an empty line. Item and one after it are blocks.
|
||||
if (i < items.length - 1) {
|
||||
items[i].forceBlock = true;
|
||||
items[i + 1].forceBlock = true;
|
||||
}
|
||||
items[i].lines.removeLast();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the list items to Nodes.
|
||||
final itemNodes = <Node>[];
|
||||
for (final item in items) {
|
||||
bool blockItem = item.forceBlock || (item.lines.length > 1);
|
||||
|
||||
// See if it matches some block parser.
|
||||
final blocksInList = [
|
||||
_RE_BLOCKQUOTE,
|
||||
_RE_HEADER,
|
||||
_RE_HR,
|
||||
_RE_INDENT,
|
||||
_RE_UL,
|
||||
_RE_OL
|
||||
];
|
||||
|
||||
if (!blockItem) {
|
||||
for (final pattern in blocksInList) {
|
||||
if (pattern.firstMatch(item.lines[0]) != null) {
|
||||
blockItem = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the item as a block or inline.
|
||||
if (blockItem) {
|
||||
// Block list item.
|
||||
final children = parser.document.parseLines(item.lines);
|
||||
itemNodes.add(new Element('li', children));
|
||||
} else {
|
||||
// Raw list item.
|
||||
final contents = parser.document.parseInline(item.lines[0]);
|
||||
itemNodes.add(new Element('li', contents));
|
||||
}
|
||||
}
|
||||
|
||||
return new Element(listTag, itemNodes);
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses unordered lists.
|
||||
class UnorderedListSyntax extends ListSyntax {
|
||||
RegExp get pattern => _RE_UL;
|
||||
String get listTag => 'ul';
|
||||
}
|
||||
|
||||
/// Parses ordered lists.
|
||||
class OrderedListSyntax extends ListSyntax {
|
||||
RegExp get pattern => _RE_OL;
|
||||
String get listTag => 'ol';
|
||||
}
|
||||
|
||||
/// Parses paragraphs of regular text.
|
||||
class ParagraphSyntax extends BlockSyntax {
|
||||
bool get canEndBlock => false;
|
||||
|
||||
bool canParse(BlockParser parser) => true;
|
||||
|
||||
Node parse(BlockParser parser) {
|
||||
final childLines = [];
|
||||
|
||||
// Eat until we hit something that ends a paragraph.
|
||||
while (!BlockSyntax.isAtBlockEnd(parser)) {
|
||||
childLines.add(parser.current);
|
||||
parser.advance();
|
||||
}
|
||||
|
||||
final contents = parser.document.parseInline(childLines.join('\n'));
|
||||
return new Element('p', contents);
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
part of markdown;
|
||||
|
||||
String renderToHtml(List<Node> nodes) => new HtmlRenderer().render(nodes);
|
||||
|
||||
/// Translates a parsed AST to HTML.
|
||||
class HtmlRenderer implements NodeVisitor {
|
||||
static final _BLOCK_TAGS = new RegExp(
|
||||
'blockquote|h1|h2|h3|h4|h5|h6|hr|p|pre');
|
||||
|
||||
StringBuffer buffer;
|
||||
|
||||
HtmlRenderer();
|
||||
|
||||
String render(List<Node> nodes) {
|
||||
buffer = new StringBuffer();
|
||||
|
||||
for (final node in nodes) node.accept(this);
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
void visitText(Text text) {
|
||||
buffer.write(text.text);
|
||||
}
|
||||
|
||||
bool visitElementBefore(Element element) {
|
||||
// Hackish. Separate block-level elements with newlines.
|
||||
if (!buffer.isEmpty &&
|
||||
_BLOCK_TAGS.firstMatch(element.tag) != null) {
|
||||
buffer.write('\n');
|
||||
}
|
||||
|
||||
buffer.write('<${element.tag}');
|
||||
|
||||
// Sort the keys so that we generate stable output.
|
||||
// TODO(rnystrom): This assumes keys returns a fresh mutable
|
||||
// collection.
|
||||
final attributeNames = element.attributes.keys.toList();
|
||||
attributeNames.sort((a, b) => a.compareTo(b));
|
||||
for (final name in attributeNames) {
|
||||
buffer.write(' $name="${element.attributes[name]}"');
|
||||
}
|
||||
|
||||
if (element.isEmpty) {
|
||||
// Empty element like <hr/>.
|
||||
buffer.write(' />');
|
||||
return false;
|
||||
} else {
|
||||
buffer.write('>');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void visitElementAfter(Element element) {
|
||||
buffer.write('</${element.tag}>');
|
||||
}
|
||||
}
|
|
@ -1,419 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
part of markdown;
|
||||
|
||||
/// Maintains the internal state needed to parse inline span elements in
|
||||
/// markdown.
|
||||
class InlineParser {
|
||||
static List<InlineSyntax> defaultSyntaxes = <InlineSyntax>[
|
||||
// This first regexp matches plain text to accelerate parsing. It must
|
||||
// be written so that it does not match any prefix of any following
|
||||
// syntax. Most markdown is plain text, so it is faster to match one
|
||||
// regexp per 'word' rather than fail to match all the following regexps
|
||||
// at each non-syntax character position. It is much more important
|
||||
// that the regexp is fast than complete (for example, adding grouping
|
||||
// is likely to slow the regexp down enough to negate its benefit).
|
||||
// Since it is purely for optimization, it can be removed for debugging.
|
||||
|
||||
// TODO(amouravski): this regex will glom up any custom syntaxes unless
|
||||
// they're at the beginning.
|
||||
new TextSyntax(r'\s*[A-Za-z0-9]+'),
|
||||
|
||||
// The real syntaxes.
|
||||
|
||||
new AutolinkSyntax(),
|
||||
new LinkSyntax(),
|
||||
// "*" surrounded by spaces is left alone.
|
||||
new TextSyntax(r' \* '),
|
||||
// "_" surrounded by spaces is left alone.
|
||||
new TextSyntax(r' _ '),
|
||||
// Leave already-encoded HTML entities alone. Ensures we don't turn
|
||||
// "&" into "&amp;"
|
||||
new TextSyntax(r'&[#a-zA-Z0-9]*;'),
|
||||
// Encode "&".
|
||||
new TextSyntax(r'&', sub: '&'),
|
||||
// Encode "<". (Why not encode ">" too? Gruber is toying with us.)
|
||||
new TextSyntax(r'<', sub: '<'),
|
||||
// Parse "**strong**" tags.
|
||||
new TagSyntax(r'\*\*', tag: 'strong'),
|
||||
// Parse "__strong__" tags.
|
||||
new TagSyntax(r'__', tag: 'strong'),
|
||||
// Parse "*emphasis*" tags.
|
||||
new TagSyntax(r'\*', tag: 'em'),
|
||||
// Parse "_emphasis_" tags.
|
||||
// TODO(rnystrom): Underscores in the middle of a word should not be
|
||||
// parsed as emphasis like_in_this.
|
||||
new TagSyntax(r'_', tag: 'em'),
|
||||
// Parse inline code within double backticks: "``code``".
|
||||
new CodeSyntax(r'``\s?((?:.|\n)*?)\s?``'),
|
||||
// Parse inline code within backticks: "`code`".
|
||||
new CodeSyntax(r'`([^`]*)`')
|
||||
// We will add the LinkSyntax once we know about the specific link resolver.
|
||||
];
|
||||
|
||||
/// The string of markdown being parsed.
|
||||
final String source;
|
||||
|
||||
/// The markdown document this parser is parsing.
|
||||
final Document document;
|
||||
|
||||
List<InlineSyntax> syntaxes;
|
||||
|
||||
/// The current read position.
|
||||
int pos = 0;
|
||||
|
||||
/// Starting position of the last unconsumed text.
|
||||
int start = 0;
|
||||
|
||||
final List<TagState> _stack;
|
||||
|
||||
InlineParser(this.source, this.document)
|
||||
: _stack = <TagState>[] {
|
||||
/// User specified syntaxes will be the first syntaxes to be evaluated.
|
||||
if (document.inlineSyntaxes != null) {
|
||||
syntaxes = [];
|
||||
syntaxes.addAll(document.inlineSyntaxes);
|
||||
syntaxes.addAll(defaultSyntaxes);
|
||||
} else {
|
||||
syntaxes = defaultSyntaxes;
|
||||
}
|
||||
// Custom link resolver goes after the generic text syntax.
|
||||
syntaxes.insert(1, new LinkSyntax(linkResolver: document.linkResolver));
|
||||
}
|
||||
|
||||
List<Node> parse() {
|
||||
// Make a fake top tag to hold the results.
|
||||
_stack.add(new TagState(0, 0, null));
|
||||
|
||||
while (!isDone) {
|
||||
bool matched = false;
|
||||
|
||||
// See if any of the current tags on the stack match. We don't allow tags
|
||||
// of the same kind to nest, so this takes priority over other possible // matches.
|
||||
for (int i = _stack.length - 1; i > 0; i--) {
|
||||
if (_stack[i].tryMatch(this)) {
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matched) continue;
|
||||
|
||||
// See if the current text matches any defined markdown syntax.
|
||||
for (final syntax in syntaxes) {
|
||||
if (syntax.tryMatch(this)) {
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matched) continue;
|
||||
|
||||
// If we got here, it's just text.
|
||||
advanceBy(1);
|
||||
}
|
||||
|
||||
// Unwind any unmatched tags and get the results.
|
||||
return _stack[0].close(this, null);
|
||||
}
|
||||
|
||||
writeText() {
|
||||
writeTextRange(start, pos);
|
||||
start = pos;
|
||||
}
|
||||
|
||||
writeTextRange(int start, int end) {
|
||||
if (end > start) {
|
||||
final text = source.substring(start, end);
|
||||
final nodes = _stack.last.children;
|
||||
|
||||
// If the previous node is text too, just append.
|
||||
if ((nodes.length > 0) && (nodes.last is Text)) {
|
||||
final newNode = new Text('${nodes.last.text}$text');
|
||||
nodes[nodes.length - 1] = newNode;
|
||||
} else {
|
||||
nodes.add(new Text(text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addNode(Node node) {
|
||||
_stack.last.children.add(node);
|
||||
}
|
||||
|
||||
// TODO(rnystrom): Only need this because RegExp doesn't let you start
|
||||
// searching from a given offset.
|
||||
String get currentSource => source.substring(pos, source.length);
|
||||
|
||||
bool get isDone => pos == source.length;
|
||||
|
||||
void advanceBy(int length) {
|
||||
pos += length;
|
||||
}
|
||||
|
||||
void consume(int length) {
|
||||
pos += length;
|
||||
start = pos;
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents one kind of markdown tag that can be parsed.
|
||||
abstract class InlineSyntax {
|
||||
final RegExp pattern;
|
||||
|
||||
InlineSyntax(String pattern)
|
||||
: pattern = new RegExp(pattern, multiLine: true);
|
||||
|
||||
bool tryMatch(InlineParser parser) {
|
||||
final startMatch = pattern.firstMatch(parser.currentSource);
|
||||
if ((startMatch != null) && (startMatch.start == 0)) {
|
||||
// Write any existing plain text up to this point.
|
||||
parser.writeText();
|
||||
|
||||
if (onMatch(parser, startMatch)) {
|
||||
parser.consume(startMatch[0].length);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool onMatch(InlineParser parser, Match match);
|
||||
}
|
||||
|
||||
/// Matches stuff that should just be passed through as straight text.
|
||||
class TextSyntax extends InlineSyntax {
|
||||
String substitute;
|
||||
TextSyntax(String pattern, {String sub})
|
||||
: super(pattern),
|
||||
substitute = sub;
|
||||
|
||||
bool onMatch(InlineParser parser, Match match) {
|
||||
if (substitute == null) {
|
||||
// Just use the original matched text.
|
||||
parser.advanceBy(match[0].length);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Insert the substitution.
|
||||
parser.addNode(new Text(substitute));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Matches autolinks like `<http://foo.com>`.
|
||||
class AutolinkSyntax extends InlineSyntax {
|
||||
AutolinkSyntax()
|
||||
: super(r'<((http|https|ftp)://[^>]*)>');
|
||||
// TODO(rnystrom): Make case insensitive.
|
||||
|
||||
bool onMatch(InlineParser parser, Match match) {
|
||||
final url = match[1];
|
||||
|
||||
final anchor = new Element.text('a', escapeHtml(url));
|
||||
anchor.attributes['href'] = url;
|
||||
parser.addNode(anchor);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Matches syntax that has a pair of tags and becomes an element, like `*` for
|
||||
/// `<em>`. Allows nested tags.
|
||||
class TagSyntax extends InlineSyntax {
|
||||
final RegExp endPattern;
|
||||
final String tag;
|
||||
|
||||
TagSyntax(String pattern, {String tag, String end})
|
||||
: super(pattern),
|
||||
endPattern = new RegExp((end != null) ? end : pattern, multiLine: true),
|
||||
tag = tag;
|
||||
// TODO(rnystrom): Doing this.field doesn't seem to work with named args.
|
||||
|
||||
bool onMatch(InlineParser parser, Match match) {
|
||||
parser._stack.add(new TagState(parser.pos,
|
||||
parser.pos + match[0].length, this));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onMatchEnd(InlineParser parser, Match match, TagState state) {
|
||||
parser.addNode(new Element(tag, state.children));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Matches inline links like `[blah] [id]` and `[blah] (url)`.
|
||||
class LinkSyntax extends TagSyntax {
|
||||
Resolver linkResolver;
|
||||
|
||||
/// The regex for the end of a link needs to handle both reference style and
|
||||
/// inline styles as well as optional titles for inline links. To make that
|
||||
/// a bit more palatable, this breaks it into pieces.
|
||||
static get linkPattern {
|
||||
final refLink = r'\s?\[([^\]]*)\]'; // "[id]" reflink id.
|
||||
final title = r'(?:[ ]*"([^"]+)"|)'; // Optional title in quotes.
|
||||
final inlineLink = '\\s?\\(([^ )]+)$title\\)'; // "(url "title")" link.
|
||||
return '\](?:($refLink|$inlineLink)|)';
|
||||
|
||||
// The groups matched by this are:
|
||||
// 1: Will be non-empty if it's either a ref or inline link. Will be empty
|
||||
// if it's just a bare pair of square brackets with nothing after them.
|
||||
// 2: Contains the id inside [] for a reference-style link.
|
||||
// 3: Contains the URL for an inline link.
|
||||
// 4: Contains the title, if present, for an inline link.
|
||||
}
|
||||
|
||||
LinkSyntax({this.linkResolver})
|
||||
: super(r'\[', end: linkPattern);
|
||||
|
||||
bool onMatchEnd(InlineParser parser, Match match, TagState state) {
|
||||
var url;
|
||||
var title;
|
||||
|
||||
// If we didn't match refLink or inlineLink, then it means there was
|
||||
// nothing after the first square bracket, so it isn't a normal markdown
|
||||
// link at all. Instead, we allow users of the library to specify a special
|
||||
// resolver function ([linkResolver]) that may choose to handle
|
||||
// this. Otherwise, it's just treated as plain text.
|
||||
if ((match[1] == null) || (match[1] == '')) {
|
||||
if (linkResolver == null) return false;
|
||||
|
||||
// Only allow implicit links if the content is just text.
|
||||
// TODO(rnystrom): Do we want to relax this?
|
||||
if (state.children.length != 1) return false;
|
||||
if (state.children[0] is! Text) return false;
|
||||
|
||||
Text link = state.children[0];
|
||||
|
||||
// See if we have a resolver that will generate a link for us.
|
||||
final node = linkResolver(link.text);
|
||||
if (node == null) return false;
|
||||
|
||||
parser.addNode(node);
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((match[3] != null) && (match[3] != '')) {
|
||||
// Inline link like [foo](url).
|
||||
url = match[3];
|
||||
title = match[4];
|
||||
|
||||
// For whatever reason, markdown allows angle-bracketed URLs here.
|
||||
if (url.startsWith('<') && url.endsWith('>')) {
|
||||
url = url.substring(1, url.length - 1);
|
||||
}
|
||||
} else {
|
||||
// Reference link like [foo] [bar].
|
||||
var id = match[2];
|
||||
if (id == '') {
|
||||
// The id is empty ("[]") so infer it from the contents.
|
||||
id = parser.source.substring(state.startPos + 1, parser.pos);
|
||||
}
|
||||
|
||||
// References are case-insensitive.
|
||||
id = id.toLowerCase();
|
||||
|
||||
// Look up the link.
|
||||
final link = parser.document.refLinks[id];
|
||||
// If it's an unknown link just emit plaintext.
|
||||
if (link == null) return false;
|
||||
|
||||
url = link.url;
|
||||
title = link.title;
|
||||
}
|
||||
|
||||
final anchor = new Element('a', state.children);
|
||||
anchor.attributes['href'] = escapeHtml(url);
|
||||
if ((title != null) && (title != '')) {
|
||||
anchor.attributes['title'] = escapeHtml(title);
|
||||
}
|
||||
|
||||
parser.addNode(anchor);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Matches backtick-enclosed inline code blocks.
|
||||
class CodeSyntax extends InlineSyntax {
|
||||
CodeSyntax(String pattern)
|
||||
: super(pattern);
|
||||
|
||||
bool onMatch(InlineParser parser, Match match) {
|
||||
parser.addNode(new Element.text('code', escapeHtml(match[1])));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps track of a currently open tag while it is being parsed. The parser
|
||||
/// maintains a stack of these so it can handle nested tags.
|
||||
class TagState {
|
||||
/// The point in the original source where this tag started.
|
||||
int startPos;
|
||||
|
||||
/// The point in the original source where open tag ended.
|
||||
int endPos;
|
||||
|
||||
/// The syntax that created this node.
|
||||
final TagSyntax syntax;
|
||||
|
||||
/// The children of this node. Will be `null` for text nodes.
|
||||
final List<Node> children;
|
||||
|
||||
TagState(this.startPos, this.endPos, this.syntax)
|
||||
: children = <Node>[];
|
||||
|
||||
/// Attempts to close this tag by matching the current text against its end
|
||||
/// pattern.
|
||||
bool tryMatch(InlineParser parser) {
|
||||
Match endMatch = syntax.endPattern.firstMatch(parser.currentSource);
|
||||
if ((endMatch != null) && (endMatch.start == 0)) {
|
||||
// Close the tag.
|
||||
close(parser, endMatch);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Pops this tag off the stack, completes it, and adds it to the output.
|
||||
/// Will discard any unmatched tags that happen to be above it on the stack.
|
||||
/// If this is the last node in the stack, returns its children.
|
||||
List<Node> close(InlineParser parser, Match endMatch) {
|
||||
// If there are unclosed tags on top of this one when it's closed, that
|
||||
// means they are mismatched. Mismatched tags are treated as plain text in
|
||||
// markdown. So for each tag above this one, we write its start tag as text
|
||||
// and then adds its children to this one's children.
|
||||
int index = parser._stack.indexOf(this);
|
||||
|
||||
// Remove the unmatched children.
|
||||
final unmatchedTags = parser._stack.sublist(index + 1);
|
||||
parser._stack.removeRange(index + 1, parser._stack.length);
|
||||
|
||||
// Flatten them out onto this tag.
|
||||
for (final unmatched in unmatchedTags) {
|
||||
// Write the start tag as text.
|
||||
parser.writeTextRange(unmatched.startPos, unmatched.endPos);
|
||||
|
||||
// Bequeath its children unto this tag.
|
||||
children.addAll(unmatched.children);
|
||||
}
|
||||
|
||||
// Pop this off the stack.
|
||||
parser.writeText();
|
||||
parser._stack.removeLast();
|
||||
|
||||
// If the stack is empty now, this is the special "results" node.
|
||||
if (parser._stack.length == 0) return children;
|
||||
|
||||
// We are still parsing, so add this to its parent's children.
|
||||
if (syntax.onMatchEnd(parser, endMatch, this)) {
|
||||
parser.consume(endMatch[0].length);
|
||||
} else {
|
||||
// Didn't close correctly so revert to text.
|
||||
parser.start = startPos;
|
||||
parser.advanceBy(endMatch[0].length);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,541 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* This library serializes the Dart2Js AST into a compact and easy to use
|
||||
* [Element] tree useful for code exploration tools such as DartDoc.
|
||||
*/
|
||||
library universe_serializer;
|
||||
|
||||
import 'dartdoc.dart';
|
||||
|
||||
// TODO(rnystrom): Use "package:" URL (#4968).
|
||||
import 'package:path/path.dart' as path;
|
||||
import '../../compiler/implementation/mirrors/source_mirrors.dart';
|
||||
import '../../compiler/implementation/mirrors/mirrors_util.dart';
|
||||
|
||||
String _stripUri(String uri) {
|
||||
String prefix = "/dart/";
|
||||
int start = uri.indexOf(prefix);
|
||||
if (start != -1) {
|
||||
return uri.substring(start + prefix.length);
|
||||
} else {
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
|
||||
String _escapeId(String id) {
|
||||
return id.replaceAll(new RegExp('[/]'), '#slash');
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for all elements in the AST.
|
||||
*/
|
||||
class Element {
|
||||
/** Human readable type name for the node. */
|
||||
final String kind;
|
||||
/** Human readable name for the element. */
|
||||
final String name;
|
||||
/** Id for the node that is unique within its parent's children. */
|
||||
final String id;
|
||||
/** Raw text of the comment associated with the Element if any. */
|
||||
final String comment;
|
||||
/** Raw html comment for the Element from MDN. */
|
||||
String mdnCommentHtml;
|
||||
/**
|
||||
* The URL to the page on MDN that content was pulled from for the current
|
||||
* type being documented. Will be `null` if the type doesn't use any MDN
|
||||
* content.
|
||||
*/
|
||||
String mdnUrl;
|
||||
/** Children of the node. */
|
||||
List<Element> children;
|
||||
/** Whether the element is private. */
|
||||
final bool isPrivate;
|
||||
|
||||
/**
|
||||
* Uri containing the definition of the element.
|
||||
*/
|
||||
String uri;
|
||||
/**
|
||||
* Line in the original source file that starts the definition of the element.
|
||||
*/
|
||||
String line;
|
||||
|
||||
// TODO(jacobr): refactor the code so that lookupMdnComment does not need to
|
||||
// be passed to every Element constructor.
|
||||
Element(DeclarationMirror mirror, this.kind, this.name,
|
||||
String id, this.comment,
|
||||
MdnComment lookupMdnComment(Mirror))
|
||||
: line = mirror.location.line.toString(),
|
||||
id = _escapeId(id),
|
||||
isPrivate = _optionalBool(mirror.isPrivate),
|
||||
uri = _stripUri(mirror.location.sourceUri.toString()) {
|
||||
if (lookupMdnComment != null) {
|
||||
var mdnComment = lookupMdnComment(mirror);
|
||||
if (mdnComment != null) {
|
||||
mdnCommentHtml = mdnComment.mdnComment;
|
||||
mdnUrl = mdnComment.mdnUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addChild(Element child) {
|
||||
if (children == null) {
|
||||
children = <Element>[];
|
||||
}
|
||||
children.add(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all URIs that exactly match the parent node's URI.
|
||||
* This reduces output file size by about 20%.
|
||||
*/
|
||||
void stripDuplicateUris(String parentUri, parentLine) {
|
||||
if (children != null) {
|
||||
for (var child in children) {
|
||||
child.stripDuplicateUris(uri, line);
|
||||
}
|
||||
}
|
||||
if (parentUri == uri) {
|
||||
uri = null;
|
||||
}
|
||||
if (line == parentLine) {
|
||||
line = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts false to null. Useful as the serialization scheme we use
|
||||
* omits null values.
|
||||
*/
|
||||
bool _optionalBool(bool value) => value == true ? true : null;
|
||||
|
||||
Reference _optionalReference(DeclarationMirror mirror) {
|
||||
return (mirror != null && nameOf(mirror) != "Dynamic_" &&
|
||||
nameOf(mirror) != "dynamic") ?
|
||||
new Reference(mirror) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to track what members of a library should be included.
|
||||
*/
|
||||
class LibrarySubset {
|
||||
final LibraryMirror library;
|
||||
Set<String> includedChildren;
|
||||
|
||||
LibrarySubset(this.library) : includedChildren = new Set<String>();
|
||||
}
|
||||
|
||||
/**
|
||||
* [Element] describing a Dart library.
|
||||
*/
|
||||
class LibraryElement extends Element {
|
||||
/**
|
||||
* Partial versions of LibraryElements containing classes that are extended
|
||||
* or implemented by classes in this library.
|
||||
*/
|
||||
List<LibraryElement> dependencies;
|
||||
|
||||
/**
|
||||
* Construct a LibraryElement from a [mirror].
|
||||
*
|
||||
* If [includedChildren] is specified, only elements matching names in
|
||||
* [includedChildren] are included and no dependencies are included.
|
||||
* [lookupMdnComment] is an optional function that returns the MDN
|
||||
* documentation for elements. [dependencies] is an optional map
|
||||
* tracking all classes dependend on by this [ClassElement].
|
||||
*/
|
||||
LibraryElement(LibraryMirror mirror,
|
||||
{MdnComment lookupMdnComment(Mirror), Set<String> includedChildren})
|
||||
: super(mirror, 'library', _libraryName(mirror), nameOf(mirror),
|
||||
computeComment(mirror), lookupMdnComment) {
|
||||
var requiredDependencies;
|
||||
// We don't need to track our required dependencies when generating a
|
||||
// filtered version of this library which will be used as a dependency for
|
||||
// another library.
|
||||
if (includedChildren == null)
|
||||
requiredDependencies = new Map<String, LibrarySubset>();
|
||||
methodsOf(mirror.declarations).forEach((childMirror) {
|
||||
var childName = nameOf(childMirror);
|
||||
if (includedChildren == null || includedChildren.contains(childName))
|
||||
addChild(new MethodElement(childName, childMirror, lookupMdnComment));
|
||||
});
|
||||
|
||||
gettersOf(mirror.declarations).forEach((childMirror) {
|
||||
var childName = nameOf(childMirror);
|
||||
if (includedChildren == null || includedChildren.contains(childName))
|
||||
addChild(new GetterElement(childName, childMirror, lookupMdnComment));
|
||||
});
|
||||
|
||||
variablesOf(mirror.declarations).forEach((childMirror) {
|
||||
var childName = nameOf(childMirror);
|
||||
if (includedChildren == null || includedChildren.contains(childName))
|
||||
addChild(new VariableElement(childName, childMirror, lookupMdnComment));
|
||||
});
|
||||
|
||||
typesOf(mirror.declarations).forEach((classMirror) {
|
||||
var className = nameOf(classMirror);
|
||||
if (includedChildren == null || includedChildren.contains(className)) {
|
||||
if (classMirror is TypedefMirror) {
|
||||
addChild(new TypedefElement(className, classMirror));
|
||||
} else {
|
||||
addChild(new ClassElement(classMirror,
|
||||
dependencies: requiredDependencies,
|
||||
lookupMdnComment: lookupMdnComment));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (requiredDependencies != null && !requiredDependencies.isEmpty) {
|
||||
dependencies = requiredDependencies.values.map((librarySubset) =>
|
||||
new LibraryElement(
|
||||
librarySubset.library,
|
||||
lookupMdnComment: lookupMdnComment,
|
||||
includedChildren: librarySubset.includedChildren)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
static String _libraryName(LibraryMirror mirror) {
|
||||
if (mirror.uri.scheme == 'file') {
|
||||
// TODO(jacobr): this is a hack. Remove once these libraries are removed
|
||||
// from the sdk.
|
||||
var uri = mirror.uri;
|
||||
var uriPath = uri.path;
|
||||
|
||||
var parts = path.split(uriPath);
|
||||
|
||||
// Find either pkg/ or packages/
|
||||
var pkgDir = parts.lastIndexOf('pkg');
|
||||
var packageDir = parts.lastIndexOf('packages');
|
||||
|
||||
if (pkgDir >= 0) {
|
||||
packageDir = pkgDir;
|
||||
}
|
||||
|
||||
var libDir = parts.lastIndexOf('lib');
|
||||
var rest = parts.sublist(libDir + 1);
|
||||
|
||||
// If there's no lib, we can't find the package.
|
||||
if (libDir < 0 || libDir < packageDir) {
|
||||
// TODO(jacobr): this is a lousy fallback.
|
||||
print("Unable to determine package for $uriPath.");
|
||||
return mirror.uri.toString();
|
||||
} else if (packageDir >= 0 && rest.length >= 1) {
|
||||
// For URI: foo/bar/packages/widget/lib/sprocket.dart will return:
|
||||
// 'package:widget/sprocket.dart'
|
||||
return 'package:${parts[packageDir + 1]}/${rest.join('/')}';
|
||||
}
|
||||
} else {
|
||||
return mirror.uri.toString();
|
||||
}
|
||||
}
|
||||
|
||||
void stripDuplicateUris(String parentUri, parentLine) {
|
||||
super.stripDuplicateUris(parentUri, parentLine);
|
||||
|
||||
if (dependencies != null) {
|
||||
for (var child in dependencies) {
|
||||
child.stripDuplicateUris(null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the class implements or extends [Error] or [Exception].
|
||||
*/
|
||||
bool _isThrowable(ClassMirror mirror) {
|
||||
if (getLibrary(mirror).uri.toString() == 'dart:core' &&
|
||||
nameOf(mirror) == 'Error' || nameOf(mirror) == 'Exception')
|
||||
return true;
|
||||
if (mirror.superclass != null && _isThrowable(mirror.superclass))
|
||||
return true;
|
||||
return mirror.superinterfaces.any(_isThrowable);
|
||||
}
|
||||
|
||||
/**
|
||||
* [Element] describing a Dart class.
|
||||
*/
|
||||
class ClassElement extends Element {
|
||||
/** Base class.*/
|
||||
final Reference superclass;
|
||||
/** Whether the class is abstract. */
|
||||
final bool isAbstract;
|
||||
/** Interfaces the class implements. */
|
||||
List<Reference> interfaces;
|
||||
/** Whether the class implements or extends [Error] or [Exception]. */
|
||||
bool isThrowable;
|
||||
|
||||
/**
|
||||
* Constructs a [ClassElement] from a [ClassMirror].
|
||||
*
|
||||
* [dependencies] is an optional map updated as a side effect of running
|
||||
* this constructor that tracks what classes from other libraries are
|
||||
* dependencies of classes in this library. A class is considered a
|
||||
* dependency if it implements or extends another class.
|
||||
* [lookupMdnComment] is an optional function that returns the MDN
|
||||
* documentation for elements. [dependencies] is an optional map
|
||||
* tracking all classes dependend on by this [ClassElement].
|
||||
*/
|
||||
ClassElement(ClassSourceMirror mirror,
|
||||
{Map<String, LibrarySubset> dependencies,
|
||||
MdnComment lookupMdnComment(Mirror)})
|
||||
: super(mirror, 'class', nameOf(mirror),
|
||||
nameOf(mirror), computeComment(mirror),
|
||||
lookupMdnComment),
|
||||
superclass = _optionalReference(mirror.superclass),
|
||||
isAbstract = _optionalBool(mirror.isAbstract),
|
||||
isThrowable = _optionalBool(_isThrowable(mirror)){
|
||||
|
||||
addCrossLibraryDependencies(clazz) {
|
||||
if (clazz == null) return;
|
||||
|
||||
var clazzLibrary = getLibrary(clazz);
|
||||
if (getLibrary(mirror) != clazzLibrary) {
|
||||
String name = nameOf(clazz);
|
||||
var libraryStub = dependencies.putIfAbsent(name,
|
||||
() => new LibrarySubset(clazzLibrary));
|
||||
libraryStub.includedChildren.add(name);
|
||||
}
|
||||
|
||||
for (var interface in clazz.superinterfaces) {
|
||||
addCrossLibraryDependencies(interface);
|
||||
}
|
||||
addCrossLibraryDependencies(clazz.superclass);
|
||||
}
|
||||
|
||||
if (dependencies != null) {
|
||||
addCrossLibraryDependencies(mirror);
|
||||
}
|
||||
|
||||
for (var interface in mirror.superinterfaces) {
|
||||
if (this.interfaces == null) {
|
||||
this.interfaces = <Reference>[];
|
||||
}
|
||||
this.interfaces.add(_optionalReference(interface));
|
||||
}
|
||||
|
||||
methodsOf(mirror.declarations).forEach((childMirror) {
|
||||
String childName = nameOf(childMirror);
|
||||
addChild(new MethodElement(childName, childMirror, lookupMdnComment));
|
||||
});
|
||||
|
||||
gettersOf(mirror.declarations).forEach((childMirror) {
|
||||
String childName = nameOf(childMirror);
|
||||
addChild(new GetterElement(childName, childMirror, lookupMdnComment));
|
||||
});
|
||||
|
||||
variablesOf(mirror.declarations).forEach((childMirror) {
|
||||
String childName = nameOf(childMirror);
|
||||
addChild(new VariableElement(childName, childMirror,
|
||||
lookupMdnComment));
|
||||
});
|
||||
|
||||
constructorsOf(mirror.declarations).forEach((methodMirror) {
|
||||
String constructorName = nameOf(methodMirror);
|
||||
addChild(new MethodElement(constructorName, methodMirror,
|
||||
lookupMdnComment, 'constructor'));
|
||||
});
|
||||
|
||||
for (var typeVariable in mirror.originalDeclaration.typeVariables) {
|
||||
addChild(new TypeParameterElement(typeVariable));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [Element] describing a getter.
|
||||
*/
|
||||
class GetterElement extends Element {
|
||||
/** Type of the getter. */
|
||||
final Reference ref;
|
||||
final bool isStatic;
|
||||
|
||||
GetterElement(String name, MethodMirror mirror,
|
||||
MdnComment lookupMdnComment(Mirror))
|
||||
: super(mirror, 'property', nameOf(mirror),
|
||||
name, computeComment(mirror), lookupMdnComment),
|
||||
ref = _optionalReference(mirror.returnType),
|
||||
isStatic = _optionalBool(mirror.isStatic);
|
||||
}
|
||||
|
||||
/**
|
||||
* [Element] describing a method which may be a regular method, a setter, or an
|
||||
* operator.
|
||||
*/
|
||||
class MethodElement extends Element {
|
||||
final Reference returnType;
|
||||
final bool isSetter;
|
||||
final bool isOperator;
|
||||
final bool isStatic;
|
||||
|
||||
MethodElement(String name, MethodMirror mirror,
|
||||
MdnComment lookupMdnComment(Mirror), [String kind = 'method'])
|
||||
: super(mirror, kind, name, '$name${mirror.parameters.length}()',
|
||||
computeComment(mirror), lookupMdnComment),
|
||||
returnType = _optionalReference(mirror.returnType),
|
||||
isSetter = _optionalBool(mirror.isSetter),
|
||||
isOperator = _optionalBool(mirror.isOperator),
|
||||
isStatic = _optionalBool(mirror.isStatic) {
|
||||
|
||||
for (var param in mirror.parameters) {
|
||||
addChild(new ParameterElement(param));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Element describing a parameter.
|
||||
*/
|
||||
class ParameterElement extends Element {
|
||||
/** Type of the parameter. */
|
||||
final Reference ref;
|
||||
|
||||
/**
|
||||
* Returns the default value for this parameter.
|
||||
*/
|
||||
final String defaultValue;
|
||||
|
||||
/**
|
||||
* Is this parameter optional?
|
||||
*/
|
||||
final bool isOptional;
|
||||
|
||||
/**
|
||||
* Is this parameter named?
|
||||
*/
|
||||
final bool isNamed;
|
||||
|
||||
/**
|
||||
* Returns the initialized field, if this parameter is an initializing formal.
|
||||
*/
|
||||
final Reference initializedField;
|
||||
|
||||
ParameterElement(ParameterSourceMirror mirror)
|
||||
: super(mirror, 'param', nameOf(mirror),
|
||||
nameOf(mirror), null, null),
|
||||
ref = _optionalReference(mirror.type),
|
||||
isOptional = _optionalBool(mirror.isOptional),
|
||||
defaultValue = '${mirror.defaultValue}',
|
||||
isNamed = _optionalBool(mirror.isNamed),
|
||||
initializedField = _optionalReference(mirror.initializedField) {
|
||||
|
||||
if (mirror.type is FunctionTypeMirror) {
|
||||
addChild(new FunctionTypeElement(mirror.type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FunctionTypeElement extends Element {
|
||||
final Reference returnType;
|
||||
|
||||
FunctionTypeElement(FunctionTypeMirror mirror)
|
||||
: super(mirror, 'functiontype', nameOf(mirror),
|
||||
nameOf(mirror), null, null),
|
||||
returnType = _optionalReference(mirror.returnType) {
|
||||
for (var param in mirror.parameters) {
|
||||
addChild(new ParameterElement(param));
|
||||
}
|
||||
// TODO(jacobr): can a FunctionTypeElement really have type variables?
|
||||
for (var typeVariable in mirror.originalDeclaration.typeVariables) {
|
||||
addChild(new TypeParameterElement(typeVariable));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Element describing a generic type parameter.
|
||||
*/
|
||||
class TypeParameterElement extends Element {
|
||||
/**
|
||||
* Upper bound for the parameter.
|
||||
*
|
||||
* In the following code sample, [:Bar:] is an upper bound:
|
||||
* [: class Bar<T extends Foo> { } :]
|
||||
*/
|
||||
final Reference upperBound;
|
||||
|
||||
TypeParameterElement(TypeVariableMirror mirror)
|
||||
: super(mirror, 'typeparam', nameOf(mirror),
|
||||
nameOf(mirror), null, null),
|
||||
upperBound = mirror.upperBound != null && !isObject(mirror.upperBound) ?
|
||||
new Reference(mirror.upperBound) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Element describing a variable.
|
||||
*/
|
||||
class VariableElement extends Element {
|
||||
/** Type of the variable. */
|
||||
final Reference ref;
|
||||
/** Whether the variable is static. */
|
||||
final bool isStatic;
|
||||
/** Whether the variable is final. */
|
||||
final bool isFinal;
|
||||
|
||||
VariableElement(String name, VariableMirror mirror,
|
||||
MdnComment lookupMdnComment(Mirror))
|
||||
: super(mirror, 'variable', nameOf(mirror), name,
|
||||
computeComment(mirror), lookupMdnComment),
|
||||
ref = _optionalReference(mirror.type),
|
||||
isStatic = _optionalBool(mirror.isStatic),
|
||||
isFinal = _optionalBool(mirror.isFinal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Element describing a typedef.
|
||||
*/
|
||||
|
||||
class TypedefElement extends Element {
|
||||
/** Return type of the typedef. */
|
||||
final Reference returnType;
|
||||
|
||||
TypedefElement(String name, TypedefMirror mirror)
|
||||
: super(mirror, 'typedef', nameOf(mirror), name,
|
||||
computeComment(mirror), null),
|
||||
returnType = _optionalReference(mirror.referent.returnType) {
|
||||
for (var param in mirror.referent.parameters) {
|
||||
addChild(new ParameterElement(param));
|
||||
}
|
||||
for (var typeVariable in mirror.originalDeclaration.typeVariables) {
|
||||
addChild(new TypeParameterElement(typeVariable));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reference to an Element with type argument if the reference is parameterized.
|
||||
*/
|
||||
class Reference {
|
||||
final String name;
|
||||
final String refId;
|
||||
List<Reference> arguments;
|
||||
|
||||
Reference(DeclarationMirror mirror)
|
||||
: name = displayName(mirror),
|
||||
refId = getId(mirror) {
|
||||
if (mirror is ClassMirror) {
|
||||
if (mirror is !TypedefMirror && !mirror.typeArguments.isEmpty) {
|
||||
arguments = <Reference>[];
|
||||
for (var typeArg in mirror.typeArguments) {
|
||||
arguments.add(_optionalReference(typeArg));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(jacobr): compute the referenceId correctly for the general case so
|
||||
// that this method can work with all element types not just LibraryElements.
|
||||
Reference.fromElement(LibraryElement e) : name = e.name, refId = e.id;
|
||||
|
||||
static String getId(DeclarationMirror mirror) {
|
||||
String id = _escapeId(nameOf(mirror));
|
||||
if (mirror.owner != null) {
|
||||
id = '${getId(mirror.owner)}/$id';
|
||||
}
|
||||
return id;
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
name: dartdoc
|
||||
description: >
|
||||
Libraries for generating documentation from Dart source code.
|
||||
|
||||
dependencies:
|
||||
args: ">=0.4.2 <1.0.0"
|
||||
path: ">=0.4.2 <1.0.0"
|
||||
|
||||
dev_dependencies:
|
||||
unittest: ">=0.4.2 <1.0.0"
|
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 952 B |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB |
|
@ -1,487 +0,0 @@
|
|||
/* Reset */
|
||||
body, h1, h2, h3, h4, li, ol, p, pre, section, ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Georgia, serif;
|
||||
background: #e8e8e8;
|
||||
color: #333;
|
||||
background-image: url('body-bg.png');
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font: 400 28px/44px 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font: 600 14px/22px 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande',
|
||||
sans-serif;
|
||||
color: #999;
|
||||
margin: 22px 0 0 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font: 600 16px/22px 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
margin: 0 0 22px 0;
|
||||
}
|
||||
|
||||
h3 + p {
|
||||
/* Text immediately following a subheader should be snug with it. */
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
pre, code {
|
||||
font: 14px/22px Menlo, Monaco, Consolas, Courier, monospace;
|
||||
color: hsl(220, 20%, 30%);
|
||||
background: hsl(220, 20%, 95%);
|
||||
margin: 22px 0;
|
||||
padding: 0 4px;
|
||||
border-radius: 4px;
|
||||
overflow-x:auto;
|
||||
overflow-y:hidden;
|
||||
}
|
||||
|
||||
pre > code {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #15c;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #15c;
|
||||
}
|
||||
|
||||
li {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
/* First paragraph in an li is snug, but others are spaced out. */
|
||||
li p:first-child {
|
||||
margin: 0 0 0 0;
|
||||
}
|
||||
|
||||
li > p {
|
||||
margin: 22px 0 0 0;
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
height: 1px;
|
||||
background: #ccc;
|
||||
margin: 22px 0 21px 0;
|
||||
}
|
||||
|
||||
hr + h2 {
|
||||
margin-top: 21px; /* To compensate for the thickness of the hr. */
|
||||
}
|
||||
|
||||
.page {
|
||||
max-width: 1000px;
|
||||
background: #fff;
|
||||
margin: 0 auto 22px auto;
|
||||
border: solid 1px #ccc;
|
||||
border-top: none;
|
||||
box-shadow: 0 0 50px #888;
|
||||
|
||||
background-image: url('content-bg.png');
|
||||
background-repeat: repeat-y;
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: #333;
|
||||
background-image: url('header-bg.png');
|
||||
height: 44px;
|
||||
color: hsl(0, 0%, 50%);
|
||||
font: 400 15px/44px 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.header .logo {
|
||||
background-image: url('dart-logo-small.png');
|
||||
width: 66px;
|
||||
height: 44px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.header a {
|
||||
color: hsl(0, 0%, 80%);
|
||||
}
|
||||
|
||||
.header a:hover {
|
||||
color: hsl(0, 0%, 100%);
|
||||
}
|
||||
|
||||
.header #search-box {
|
||||
display: inline;
|
||||
float: right;
|
||||
margin-right: 11px;
|
||||
}
|
||||
|
||||
.search-input, .drop-down {
|
||||
font: 400 13px/22px 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande',
|
||||
sans-serif;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.drop-down {
|
||||
visibility: hidden;
|
||||
z-index: 1000;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 36px;
|
||||
border: 1px #CCC solid;
|
||||
background-color: white;
|
||||
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.drop-down-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.drop-down-link-tr {
|
||||
padding: 4px 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.drop-down-link-td {
|
||||
border-bottom: 1px solid #EEE;
|
||||
}
|
||||
|
||||
.drop-down-link-tr:hover {
|
||||
background: #EEE;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.drop-down-link-select {
|
||||
background: #15C;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.drop-down-link-select:hover {
|
||||
background: #2AF;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.drop-down-link-kind, .drop-down-link-library {
|
||||
font: 400 10px/10px 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.drop-down-link-library {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.drop-down-link-highlight {
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
.nav {
|
||||
float: left;
|
||||
width: 263px; /* 12 x 22px - 1 for border */
|
||||
padding: 0 22px;
|
||||
overflow: hidden;
|
||||
background: #f4f4f4;
|
||||
border-right: solid 1px #ccc;
|
||||
}
|
||||
|
||||
.nav h2 {
|
||||
font: 400 16px/22px 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande',
|
||||
sans-serif;
|
||||
margin: 0 -21px;
|
||||
padding: 11px 22px;
|
||||
|
||||
/* Using http://www.colorzilla.com/gradient-editor/ */
|
||||
background: -moz-linear-gradient(top, hsla(0,0%,0%,0.05) 0%, hsla(0,0%,0%,0) 100%);
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,hsla(0,0%,0%,0.05)), color-stop(100%,hsla(0,0%,0%,0)));
|
||||
background: -webkit-linear-gradient(top, hsla(0,0%,0%,0.05) 0%,hsla(0,0%,0%,0) 100%);
|
||||
background: -o-linear-gradient(top, hsla(0,0%,0%,0.05) 0%,hsla(0,0%,0%,0) 100%);
|
||||
background: -ms-linear-gradient(top, hsla(0,0%,0%,0.05) 0%,hsla(0,0%,0%,0) 100%);
|
||||
background: linear-gradient(top, hsla(0,0%,0%,0.05) 0%,hsla(0,0%,0%,0) 100%);
|
||||
}
|
||||
|
||||
ul.icon {
|
||||
margin: 0 0 22px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.icon li {
|
||||
font: 600 13px/22px 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande',
|
||||
sans-serif;
|
||||
list-style-type: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Indent nested lists. */
|
||||
ul.icon ul {
|
||||
margin-left: 22px;
|
||||
}
|
||||
|
||||
.icon-class,
|
||||
.icon-exception,
|
||||
.icon-interface {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin: 5px 10px 0 2px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.icon-class { background: url('class.png'); }
|
||||
.icon-exception { background: url('exception.png'); }
|
||||
.icon-interface { background: url('interface.png'); }
|
||||
|
||||
.icon-library {
|
||||
background: url('library.png');
|
||||
width: 16px;
|
||||
height: 14px;
|
||||
display: inline-block;
|
||||
margin: 4px 8px 0 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.type-box {
|
||||
display: inline-block;
|
||||
font: 600 13px/22px 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande',
|
||||
sans-serif;
|
||||
background: #f4f4f4;
|
||||
padding: 0 6px 0 3px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.type-box .icon-class,
|
||||
.type-box .icon-exception,
|
||||
.type-box .icon-interface {
|
||||
/* Make a little snugger with the text. */
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-left: 308px; /* 14 x 22 */
|
||||
padding: 22px 22px;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.footer {
|
||||
max-width: 956px; /* 1000 - 22 - 22 */
|
||||
text-align: center;
|
||||
margin: 22px auto;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
font: 400 13px/22px 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.inherited {
|
||||
}
|
||||
|
||||
.inherited-from {
|
||||
font: 400 13px/22px 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande',
|
||||
sans-serif;
|
||||
text-align: right;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.inherited-from, .docs-inherited-from {
|
||||
font: 400 13px/22px 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande',
|
||||
sans-serif;
|
||||
text-align: right;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.docs-inherited-from {
|
||||
margin-top: -22px;
|
||||
}
|
||||
|
||||
.method .doc,
|
||||
.field .doc {
|
||||
padding-left: 44px;
|
||||
/* Ensure there is some space between members with no documentation. */
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.param {
|
||||
font: 600 14px 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.crossref {
|
||||
font: 600 15px 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.doc h1 {
|
||||
font: 700 17px/22px 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande',
|
||||
sans-serif;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.doc h2 {
|
||||
font: 600 16px/22px 'Open Sans', 'Lucida Sans Unicode',
|
||||
'Lucida Grande', sans-serif;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* Style external links in docs differently. */
|
||||
.doc a[href^="http:"]:after,
|
||||
.doc a[href^="https:"]:after {
|
||||
content: url('external-link.png');
|
||||
}
|
||||
|
||||
/* Highlight members on hover so you can see which docs are for which member. */
|
||||
.method:hover,
|
||||
.field:hover {
|
||||
margin: 0 -22px;
|
||||
border: solid 4px hsl(40, 100%, 85%);
|
||||
padding: 0 18px;
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Only show the "code" link for the highlighted member. */
|
||||
.show-code, .show-inherited {
|
||||
float: right;
|
||||
font: 600 11px 'Open Sans', 'Lucida Sans Unicode', 'Lucida Grande',
|
||||
sans-serif;
|
||||
padding: 0 0 0 6px; /* In case it gets too close to the member. */
|
||||
border: none;
|
||||
background: transparent;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.method:hover .show-code,
|
||||
.field:hover .show-code {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Only show permalinks when hovered over. */
|
||||
.anchor-link {
|
||||
display: none;
|
||||
}
|
||||
|
||||
h2:hover > .anchor-link,
|
||||
h4:hover > .anchor-link {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Only show code when expanded. */
|
||||
pre.source {
|
||||
display: none;
|
||||
}
|
||||
|
||||
pre.source.expanded {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Links that don't cause a page refresh. */
|
||||
.anchor-link, .anchor-link:visited,
|
||||
.show-code, .show-code:visited,
|
||||
.show-inherited, .show-inherited:visited {
|
||||
color: hsl(40, 100%, 40%);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.anchor-link, .anchor-link:visited,
|
||||
.show-code, .show-code:visited {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.anchor-link:hover,
|
||||
.show-code:hover,
|
||||
.show-inherited:hover {
|
||||
color: hsl(40, 100%, 60%);
|
||||
}
|
||||
|
||||
/* Syntax highlighting. */
|
||||
/* Note: these correspond to the constants in classify.dart. */
|
||||
.doc pre .e, pre.source .e { color: hsl( 0, 100%, 70%); } /* Error */
|
||||
.doc pre .c, pre.source .c { color: hsl(220, 20%, 65%); } /* Comment */
|
||||
.doc pre .i, pre.source .i { color: hsl(220, 20%, 20%); } /* Identifier */
|
||||
.doc pre .k, pre.source .k { color: hsl(220, 100%, 50%); } /* Keyword */
|
||||
.doc pre .p, pre.source .p { color: hsl(220, 20%, 50%); } /* Punctuation */
|
||||
.doc pre .o, pre.source .o { color: hsl(220, 40%, 50%); } /* Operator */
|
||||
.doc pre .s, pre.source .s { color: hsl( 40, 90%, 40%); } /* String */
|
||||
.doc pre .n, pre.source .n { color: hsl( 30, 70%, 50%); } /* Number */
|
||||
.doc pre .t, pre.source .t { color: hsl(180, 40%, 40%); } /* Type Name */
|
||||
.doc pre .r, pre.source .r { color: hsl(200, 100%, 50%); } /* Special Identifier */
|
||||
.doc pre .a, pre.source .a { color: hsl(220, 100%, 45%); } /* Arrow */
|
||||
|
||||
/* Interpolated expressions within strings. */
|
||||
.doc pre .si, pre.source .si {
|
||||
background: hsl(40, 100%, 90%);
|
||||
}
|
||||
|
||||
.doc pre .s.si, pre.source .s.si { background: hsl(40, 80%, 95%); }
|
||||
.doc pre .i.si, pre.source .i.si { color: hsl(40, 30%, 30%); }
|
||||
.doc pre .p.si, pre.source .p.si { color: hsl(40, 60%, 40%); }
|
||||
|
||||
/* Enable these to debug the grid: */
|
||||
|
||||
/*
|
||||
body {
|
||||
background: -webkit-linear-gradient(top, #eee 0, #fff 10%, #fff 90%, #eee 100%);
|
||||
background-size: 22px 22px;
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
.page {
|
||||
background: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
border-left: solid 4px green;
|
||||
}
|
||||
|
||||
h2 {
|
||||
border-left: solid 4px blue;
|
||||
}
|
||||
|
||||
h3 {
|
||||
border-left: solid 4px red;
|
||||
}
|
||||
|
||||
h4 {
|
||||
border-left: solid 4px orange;
|
||||
}
|
||||
|
||||
p {
|
||||
border-left: solid 4px purple;
|
||||
}
|
||||
|
||||
section {
|
||||
border-left: solid 4px gray;
|
||||
}
|
||||
*/
|
|
@ -1,53 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library dartdoc_search_test;
|
||||
|
||||
// TODO(rnystrom): Use "package:" URL (#4968).
|
||||
import '../../../../../pkg/expect/lib/expect.dart';
|
||||
import '../lib/src/dartdoc/nav.dart';
|
||||
import '../lib/src/client/search.dart';
|
||||
|
||||
const String URL = 'dummy-url';
|
||||
|
||||
testTopLevelVsMembers() {
|
||||
var search = new SearchText('timer');
|
||||
var match = obtainMatch(search, 'timer');
|
||||
// Matching a top-level field 'timer';
|
||||
var topLevelResult = new Result(match, FIELD, URL);
|
||||
// Matching a member field 'timer' in 'Foo';
|
||||
var memberResult = new Result(match, FIELD, URL, type: 'Foo');
|
||||
Expect.equals(-1, resultComparator(topLevelResult, memberResult),
|
||||
"Top level fields should be preferred to member fields");
|
||||
}
|
||||
|
||||
testTopLevelFullVsPrefix() {
|
||||
var search = new SearchText('cancel');
|
||||
var fullMatch = obtainMatch(search, 'cancel');
|
||||
var prefixMatch = obtainMatch(search, 'cancelable');
|
||||
// Matching a top-level method 'cancel';
|
||||
var fullResult = new Result(fullMatch, METHOD, URL);
|
||||
// Matching a top-level method 'cancelable';
|
||||
var prefixResult = new Result(prefixMatch, METHOD, URL);
|
||||
Expect.equals(-1, resultComparator(fullResult, prefixResult),
|
||||
"Full matches should be preferred to prefix matches");
|
||||
}
|
||||
|
||||
testMemberFullVsPrefix() {
|
||||
var search = new SearchText('cancel');
|
||||
var fullMatch = obtainMatch(search, 'cancel');
|
||||
var prefixMatch = obtainMatch(search, 'cancelable');
|
||||
// Matching a member method 'cancel' in 'Isolate';
|
||||
var fullResult = new Result(fullMatch, METHOD, URL, type: 'Isolate');
|
||||
// Matching a member field 'cancelable' in 'Event';
|
||||
var prefixResult = new Result(prefixMatch, FIELD, URL, type: 'Event');
|
||||
Expect.equals(-1, resultComparator(fullResult, prefixResult),
|
||||
"Full matches should be preferred to prefix matches");
|
||||
}
|
||||
|
||||
void main() {
|
||||
testTopLevelVsMembers();
|
||||
testTopLevelFullVsPrefix();
|
||||
testMemberFullVsPrefix();
|
||||
}
|
|
@ -1,284 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/// Unit tests for doc.
|
||||
library dartdocTests;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:unittest/unittest.dart';
|
||||
|
||||
// TODO(rnystrom): Use "package:" URL (#4968).
|
||||
import '../lib/dartdoc.dart' as dd;
|
||||
import '../lib/markdown.dart';
|
||||
import 'markdown_test.dart';
|
||||
|
||||
main() {
|
||||
// Some tests take more than the default 20 second unittest timeout.
|
||||
unittestConfiguration.timeout = null;
|
||||
group('isAbsolute', () {
|
||||
final doc = new dd.Dartdoc();
|
||||
|
||||
test('returns false if there is no scheme', () {
|
||||
expect(doc.isAbsolute('index.html'), isFalse);
|
||||
expect(doc.isAbsolute('foo/index.html'), isFalse);
|
||||
expect(doc.isAbsolute('foo/bar/index.html'), isFalse);
|
||||
});
|
||||
|
||||
test('returns true if there is a scheme', () {
|
||||
expect(doc.isAbsolute('http://google.com'), isTrue);
|
||||
expect(doc.isAbsolute('hTtPs://google.com'), isTrue);
|
||||
expect(doc.isAbsolute('mailto:fake@email.com'), isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('relativePath', () {
|
||||
final doc = new dd.Dartdoc();
|
||||
|
||||
test('absolute path is unchanged', () {
|
||||
doc.startFile('dir/sub/file.html');
|
||||
expect(doc.relativePath('http://foo.com'), equals('http://foo.com'));
|
||||
});
|
||||
|
||||
test('from root to root', () {
|
||||
doc.startFile('root.html');
|
||||
expect(doc.relativePath('other.html'), equals('other.html'));
|
||||
});
|
||||
|
||||
test('from root to directory', () {
|
||||
doc.startFile('root.html');
|
||||
expect(doc.relativePath('dir/file.html'), equals('dir/file.html'));
|
||||
});
|
||||
|
||||
test('from root to nested', () {
|
||||
doc.startFile('root.html');
|
||||
expect(doc.relativePath('dir/sub/file.html'), equals(
|
||||
'dir/sub/file.html'));
|
||||
});
|
||||
|
||||
test('from directory to root', () {
|
||||
doc.startFile('dir/file.html');
|
||||
expect(doc.relativePath('root.html'), equals('../root.html'));
|
||||
});
|
||||
|
||||
test('from nested to root', () {
|
||||
doc.startFile('dir/sub/file.html');
|
||||
expect(doc.relativePath('root.html'), equals('../../root.html'));
|
||||
});
|
||||
|
||||
test('from dir to dir with different path', () {
|
||||
doc.startFile('dir/file.html');
|
||||
expect(doc.relativePath('other/file.html'), equals('../other/file.html'));
|
||||
});
|
||||
|
||||
test('from nested to nested with different path', () {
|
||||
doc.startFile('dir/sub/file.html');
|
||||
expect(doc.relativePath('other/sub/file.html'), equals(
|
||||
'../../other/sub/file.html'));
|
||||
});
|
||||
|
||||
test('from nested to directory with different path', () {
|
||||
doc.startFile('dir/sub/file.html');
|
||||
expect(doc.relativePath('other/file.html'), equals(
|
||||
'../../other/file.html'));
|
||||
});
|
||||
});
|
||||
|
||||
group('dartdoc markdown', () {
|
||||
group('[::] blocks', () {
|
||||
|
||||
validateDartdocMarkdown('simple case', '''
|
||||
before [:source:] after
|
||||
''', '''
|
||||
<p>before <code>source</code> after</p>
|
||||
''');
|
||||
|
||||
validateDartdocMarkdown('unmatched [:', '''
|
||||
before [: after
|
||||
''', '''
|
||||
<p>before [: after</p>
|
||||
''');
|
||||
validateDartdocMarkdown('multiple spans in one text', '''
|
||||
a [:one:] b [:two:] c
|
||||
''', '''
|
||||
<p>a <code>one</code> b <code>two</code> c</p>
|
||||
''');
|
||||
|
||||
validateDartdocMarkdown('multi-line', '''
|
||||
before [:first
|
||||
second:] after
|
||||
''', '''
|
||||
<p>before <code>first
|
||||
second</code> after</p>
|
||||
''');
|
||||
|
||||
validateDartdocMarkdown('contain backticks', '''
|
||||
before [:can `contain` backticks:] after
|
||||
''', '''
|
||||
<p>before <code>can `contain` backticks</code> after</p>
|
||||
''');
|
||||
|
||||
validateDartdocMarkdown('contain double backticks', '''
|
||||
before [:can ``contain`` backticks:] after
|
||||
''', '''
|
||||
<p>before <code>can ``contain`` backticks</code> after</p>
|
||||
''');
|
||||
|
||||
validateDartdocMarkdown('contain backticks with spaces', '''
|
||||
before [: `tick` :] after
|
||||
''', '''
|
||||
<p>before <code>`tick`</code> after</p>
|
||||
''');
|
||||
|
||||
validateDartdocMarkdown('multiline with spaces', '''
|
||||
before [:in `tick`
|
||||
another:] after
|
||||
''', '''
|
||||
<p>before <code>in `tick`
|
||||
another</code> after</p>
|
||||
''');
|
||||
|
||||
validateDartdocMarkdown('ignore markup inside code', '''
|
||||
before [:*b* _c_:] after
|
||||
''', '''
|
||||
<p>before <code>*b* _c_</code> after</p>
|
||||
''');
|
||||
|
||||
validateDartdocMarkdown('escape HTML characters', '''
|
||||
[:<&>:]
|
||||
''', '''
|
||||
<p><code><&></code></p>
|
||||
''');
|
||||
|
||||
validateDartdocMarkdown('escape HTML tags', '''
|
||||
'*' [:<em>:]
|
||||
''', '''
|
||||
<p>'*' <code><em></code></p>
|
||||
''');
|
||||
});
|
||||
});
|
||||
|
||||
group('integration tests', () {
|
||||
test('no entrypoints', () {
|
||||
_testRunDartDoc([], (result) {
|
||||
expect(result.exitCode, 1);
|
||||
});
|
||||
});
|
||||
|
||||
test('entrypoint in lib', () {
|
||||
_testRunDartDoc(['test_files/lib/no_package_test_file.dart'], (result) {
|
||||
expect(result.exitCode, 0);
|
||||
_expectDocumented(result.stdout, libCount: 1, typeCount: 1, memberCount: 0);
|
||||
});
|
||||
});
|
||||
|
||||
test('entrypoint somewhere with packages locally', () {
|
||||
_testRunDartDoc(['test_files/package_test_file.dart'], (result) {
|
||||
expect(result.exitCode, 0);
|
||||
_expectDocumented(result.stdout, libCount: 1, typeCount: 1, memberCount: 0);
|
||||
});
|
||||
});
|
||||
|
||||
test('file does not exist', () {
|
||||
_testRunDartDoc(['test_files/this_file_does_not_exist.dart'], (result) {
|
||||
expect(result.exitCode, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _testRunDartDoc(List<String> libraryPaths, void eval(ProcessResult)) {
|
||||
expect(_runDartdoc(libraryPaths).then(eval), completes);
|
||||
}
|
||||
|
||||
/// The path to the root directory of the dartdoc entrypoint.
|
||||
String get _dartdocDir {
|
||||
var dir = path.absolute(Platform.script.toFilePath());
|
||||
while (path.basename(dir) != 'dartdoc') {
|
||||
if (!path.absolute(dir).contains('dartdoc') || dir == path.dirname(dir)) {
|
||||
fail('Unable to find root dartdoc directory.');
|
||||
}
|
||||
dir = path.dirname(dir);
|
||||
}
|
||||
return path.absolute(dir);
|
||||
}
|
||||
|
||||
/// The path to use for the package root for subprocesses.
|
||||
String get _packageRoot {
|
||||
var sdkVersionPath = path.join(_dartdocDir, '..', '..', '..', 'version');
|
||||
if (new File(sdkVersionPath).existsSync()) {
|
||||
// It looks like dartdoc is being run from the SDK, so we should set the
|
||||
// package root to the SDK's packages directory.
|
||||
return path.absolute(path.join(_dartdocDir, '..', '..', '..', 'packages'));
|
||||
}
|
||||
|
||||
// It looks like Dartdoc is being run from the Dart repo, so the package root
|
||||
// is in the build output directory. We can find that directory relative to
|
||||
// the Dart executable, but that could be in one of two places: in
|
||||
// "$BUILD/dart" or "$BUILD/dart-sdk/bin/dart".
|
||||
var executableDir = path.dirname(Platform.executable);
|
||||
if (new Directory(path.join(executableDir, 'dart-sdk')).existsSync()) {
|
||||
// The executable is in "$BUILD/dart".
|
||||
return path.absolute(path.join(executableDir, 'packages'));
|
||||
} else {
|
||||
// The executable is in "$BUILD/dart-sdk/bin/dart".
|
||||
return path.absolute(path.join(executableDir, '..', '..', 'packages'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs dartdoc with the libraryPaths provided, and completes to dartdoc's
|
||||
/// ProcessResult.
|
||||
Future<ProcessResult> _runDartdoc(List<String> libraryPaths) {
|
||||
var dartBin = Platform.executable;
|
||||
|
||||
var dartdoc = path.join(_dartdocDir, 'bin/dartdoc.dart');
|
||||
|
||||
final runArgs = ['--package-root=$_packageRoot/', dartdoc];
|
||||
|
||||
// Turn relative libraryPaths to absolute ones.
|
||||
runArgs.addAll(libraryPaths
|
||||
.map((e) => path.join(path.absolute(dd.scriptDir), e)));
|
||||
|
||||
return Process.run(dartBin, runArgs);
|
||||
}
|
||||
|
||||
final _dartdocCompletionRegExp =
|
||||
new RegExp(r'Documentation complete -- documented (\d+) libraries, (\d+) types, and (\d+) members\.');
|
||||
|
||||
void _expectDocumented(String output, { int libCount, int typeCount,
|
||||
int memberCount}) {
|
||||
|
||||
final completionMatches = _dartdocCompletionRegExp.allMatches(output)
|
||||
.toList();
|
||||
|
||||
expect(completionMatches, hasLength(1),
|
||||
reason: 'dartdoc output should contain one summary');
|
||||
|
||||
final completionMatch = completionMatches.single;
|
||||
|
||||
if(libCount != null) {
|
||||
expect(int.parse(completionMatch[1]), libCount,
|
||||
reason: 'expected library count');
|
||||
}
|
||||
|
||||
if(typeCount != null) {
|
||||
expect(int.parse(completionMatch[2]), typeCount,
|
||||
reason: 'expected type count');
|
||||
}
|
||||
|
||||
if(memberCount != null) {
|
||||
expect(int.parse(completionMatch[3]), memberCount,
|
||||
reason: 'expected member count');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
validateDartdocMarkdown(String description, String markdown,
|
||||
String html) {
|
||||
var dartdoc = new dd.Dartdoc();
|
||||
validate(description, markdown, html, linkResolver: dartdoc.dartdocResolver,
|
||||
inlineSyntaxes: dartdoc.dartdocSyntaxes);
|
||||
}
|
|
@ -1,467 +0,0 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. 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:unittest/unittest.dart';
|
||||
|
||||
import 'dart:async' show Future;
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import '../lib/src/export_map.dart';
|
||||
import '../../../../../tests/compiler/dart2js/memory_source_file_helper.dart'
|
||||
show MemorySourceFileProvider;
|
||||
import '../../compiler/implementation/mirrors/source_mirrors.dart'
|
||||
show MirrorSystem, LibraryMirror;
|
||||
import '../../compiler/implementation/mirrors/analyze.dart'
|
||||
as source_mirrors;
|
||||
|
||||
Future<MirrorSystem> mirrorSystemFor(Map<String, String> memorySourceFiles) {
|
||||
var provider = new MemorySourceFileProvider(memorySourceFiles);
|
||||
handler(Uri uri, int begin, int end, String message, kind) {}
|
||||
|
||||
var script = Uri.base.resolveUri(Platform.script);
|
||||
var libraryRoot = script.resolve('../../../../');
|
||||
// Read packages from 'memory:packages/'.
|
||||
var packageRoot = Uri.parse('memory:packages/');
|
||||
|
||||
var libraries = <Uri>[];
|
||||
memorySourceFiles.forEach((String path, _) {
|
||||
if (path.startsWith('packages/')) {
|
||||
// Analyze files from 'packages/' as packages.
|
||||
libraries.add(new Uri(scheme: 'package', path: path.substring(9)));
|
||||
} else {
|
||||
libraries.add(new Uri(scheme: 'memory', path: path));
|
||||
}
|
||||
});
|
||||
libraries.add(new Uri(scheme: 'dart', path: 'async'));
|
||||
return source_mirrors.analyze(
|
||||
libraries, libraryRoot, packageRoot, provider, handler);
|
||||
}
|
||||
|
||||
Future<ExportMap> testExports(Map<String, String> sourceFiles) {
|
||||
return mirrorSystemFor(sourceFiles).then((mirrors) {
|
||||
libMirror = (uri) => mirrors.libraries[Uri.parse(uri)];
|
||||
return new ExportMap(mirrors);
|
||||
});
|
||||
}
|
||||
|
||||
Function libMirror;
|
||||
|
||||
main() {
|
||||
group('ExportMap', () {
|
||||
test('with an empty library', () {
|
||||
return testExports({'lib.dart': ''}).then((map) {
|
||||
var lib = libMirror('memory:lib.dart');
|
||||
var nonexistent = libMirror('memory:nonexistent.dart');
|
||||
|
||||
var expectedExports = {};
|
||||
expectedExports[lib] = [];
|
||||
expect(map.exports, equals(expectedExports));
|
||||
expect(map.transitiveExports(lib), isEmpty);
|
||||
expect(map.transitiveExports(nonexistent), isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
test('with one library with one export', () {
|
||||
return testExports({
|
||||
'a.dart': 'export "b.dart";',
|
||||
'b.dart': ''
|
||||
}).then((map) {
|
||||
var a = libMirror('memory:a.dart');
|
||||
var b = libMirror('memory:b.dart');
|
||||
|
||||
expect(map.exports[a], unorderedEquals([
|
||||
new Export(a, b)
|
||||
]));
|
||||
|
||||
expect(map.transitiveExports(a), unorderedEquals([
|
||||
new Export(a, b)
|
||||
]));
|
||||
|
||||
expect(map.exports[b], isEmpty);
|
||||
|
||||
expect(map.transitiveExports(b), isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
test('with one library with multiple exports', () {
|
||||
return testExports({
|
||||
'a.dart': 'export "b.dart";\nexport "c.dart";',
|
||||
'b.dart': '', 'c.dart': ''
|
||||
}).then((map) {
|
||||
var a = libMirror('memory:a.dart');
|
||||
var b = libMirror('memory:b.dart');
|
||||
var c = libMirror('memory:c.dart');
|
||||
|
||||
expect(map.exports[a], unorderedEquals([
|
||||
new Export(a, b),
|
||||
new Export(a, c)
|
||||
]));
|
||||
|
||||
expect(map.transitiveExports(a), unorderedEquals([
|
||||
new Export(a, b),
|
||||
new Export(a, c)
|
||||
]));
|
||||
|
||||
expect(map.exports[b], isEmpty);
|
||||
expect(map.transitiveExports(b), isEmpty);
|
||||
expect(map.exports[c], isEmpty);
|
||||
expect(map.transitiveExports(c), isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
test('with two libraries each with one export', () {
|
||||
return testExports({
|
||||
'a.dart': 'export "a_export.dart";',
|
||||
'b.dart': 'export "b_export.dart";',
|
||||
'a_export.dart': '',
|
||||
'b_export.dart': ''
|
||||
}).then((map) {
|
||||
var a = libMirror('memory:a.dart');
|
||||
var b = libMirror('memory:b.dart');
|
||||
var a_export = libMirror('memory:a_export.dart');
|
||||
var b_export = libMirror('memory:b_export.dart');
|
||||
|
||||
expect(map.exports[a], unorderedEquals([
|
||||
new Export(a, a_export),
|
||||
]));
|
||||
expect(map.transitiveExports(a), unorderedEquals([
|
||||
new Export(a, a_export),
|
||||
]));
|
||||
|
||||
expect(map.transitiveExports(b), unorderedEquals([
|
||||
new Export(b, b_export),
|
||||
]));
|
||||
expect(map.exports[b], unorderedEquals([
|
||||
new Export(b, b_export)
|
||||
]));
|
||||
|
||||
expect(map.exports[a_export], isEmpty);
|
||||
expect(map.transitiveExports(a_export), isEmpty);
|
||||
expect(map.exports[b_export], isEmpty);
|
||||
expect(map.transitiveExports(b_export), isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
test('with a transitive export', () {
|
||||
return testExports({
|
||||
'a.dart': 'export "b.dart";',
|
||||
'b.dart': 'export "c.dart";',
|
||||
'c.dart': ''
|
||||
}).then((map) {
|
||||
var a = libMirror('memory:a.dart');
|
||||
var b = libMirror('memory:b.dart');
|
||||
var c = libMirror('memory:c.dart');
|
||||
|
||||
expect(map.exports[a], unorderedEquals([
|
||||
new Export(a, b),
|
||||
]));
|
||||
expect(map.transitiveExports(a), unorderedEquals([
|
||||
new Export(a, b),
|
||||
new Export(a, c),
|
||||
]));
|
||||
|
||||
expect(map.exports[b], unorderedEquals([
|
||||
new Export(b, c),
|
||||
]));
|
||||
expect(map.transitiveExports(b), unorderedEquals([
|
||||
new Export(b, c),
|
||||
]));
|
||||
|
||||
expect(map.exports[c], isEmpty);
|
||||
expect(map.transitiveExports(c), isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
test('with an export through an import', () {
|
||||
return testExports({
|
||||
'a.dart': 'import "b.dart";',
|
||||
'b.dart': 'export "c.dart";',
|
||||
'c.dart': ''
|
||||
}).then((map) {
|
||||
var a = libMirror('memory:a.dart');
|
||||
var b = libMirror('memory:b.dart');
|
||||
var c = libMirror('memory:c.dart');
|
||||
|
||||
expect(map.exports[b], unorderedEquals([
|
||||
new Export(b, c),
|
||||
]));
|
||||
expect(map.transitiveExports(b), unorderedEquals([
|
||||
new Export(b, c),
|
||||
]));
|
||||
|
||||
expect(map.exports[a], isEmpty);
|
||||
expect(map.exports[c], isEmpty);
|
||||
expect(map.transitiveExports(a), isEmpty);
|
||||
expect(map.transitiveExports(c), isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
test('with an export with a show combinator', () {
|
||||
return testExports({
|
||||
'a.dart': 'export "b.dart" show x, y;',
|
||||
'b.dart': ''
|
||||
}).then((map) {
|
||||
var a = libMirror('memory:a.dart');
|
||||
var b = libMirror('memory:b.dart');
|
||||
|
||||
expect(map.exports[a], unorderedEquals([
|
||||
new Export(a, b, show: ['x', 'y'])
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
test('with an export with a hide combinator', () {
|
||||
return testExports({
|
||||
'a.dart': 'export "b.dart" hide x, y;',
|
||||
'b.dart': ''
|
||||
}).then((map) {
|
||||
var a = libMirror('memory:a.dart');
|
||||
var b = libMirror('memory:b.dart');
|
||||
|
||||
expect(map.exports[a], unorderedEquals([
|
||||
new Export(a, b, hide: ['x', 'y'])
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
test('with an export with a show and a hide combinator', () {
|
||||
return testExports({
|
||||
'a.dart': 'export "b.dart" show x, y hide y, z;',
|
||||
'b.dart': ''
|
||||
}).then((map) {
|
||||
var a = libMirror('memory:a.dart');
|
||||
var b = libMirror('memory:b.dart');
|
||||
|
||||
expect(map.exports[a], unorderedEquals([
|
||||
new Export(a, b, show: ['x'])
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
test('composes transitive exports', () {
|
||||
return testExports({
|
||||
'a.dart': 'export "b.dart" hide x;',
|
||||
'b.dart': 'export "c.dart" hide y;',
|
||||
'c.dart': ''
|
||||
}).then((map) {
|
||||
var a = libMirror('memory:a.dart');
|
||||
var b = libMirror('memory:b.dart');
|
||||
var c = libMirror('memory:c.dart');
|
||||
|
||||
expect(map.transitiveExports(a), unorderedEquals([
|
||||
new Export(a, b, hide: ['x']),
|
||||
new Export(a, c, hide: ['x', 'y'])
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
test('merges adjacent exports', () {
|
||||
return testExports({
|
||||
'a.dart': '''
|
||||
export "b.dart" show x, y;
|
||||
export "b.dart" hide y, z;
|
||||
''',
|
||||
'b.dart': ''
|
||||
}).then((map) {
|
||||
var a = libMirror('memory:a.dart');
|
||||
var b = libMirror('memory:b.dart');
|
||||
|
||||
expect(map.exports[a], unorderedEquals([
|
||||
new Export(a, b, hide: ['z']),
|
||||
]));
|
||||
expect(map.transitiveExports(a), unorderedEquals([
|
||||
new Export(a, b, hide: ['z']),
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
test('merges adjacent exports transitively', () {
|
||||
return testExports({
|
||||
'a.dart': 'export "b.dart";\nexport "c.dart";',
|
||||
'b.dart': 'export "d.dart" show x, y;',
|
||||
'c.dart': 'export "d.dart" hide y, z;',
|
||||
'd.dart': ''
|
||||
}).then((map) {
|
||||
var a = libMirror('memory:a.dart');
|
||||
var b = libMirror('memory:b.dart');
|
||||
var c = libMirror('memory:c.dart');
|
||||
var d = libMirror('memory:d.dart');
|
||||
|
||||
expect(map.exports[a], unorderedEquals([
|
||||
new Export(a, b),
|
||||
new Export(a, c),
|
||||
]));
|
||||
expect(map.transitiveExports(a), unorderedEquals([
|
||||
new Export(a, b),
|
||||
new Export(a, c),
|
||||
new Export(a, d, hide: ['z']),
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
test('resolves package: exports', () {
|
||||
return testExports({
|
||||
'a.dart': 'export "package:b/b.dart";',
|
||||
'packages/b/b.dart': ''
|
||||
}).then((map) {
|
||||
var a = libMirror('memory:a.dart');
|
||||
var b = libMirror('package:b/b.dart');
|
||||
expect(map.exports[a], unorderedEquals([
|
||||
new Export(a, b)
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
test('ignores dart: exports', () {
|
||||
return testExports({'a.dart': 'export "dart:async";'}).then((map) {
|
||||
var a = libMirror('memory:a.dart');
|
||||
|
||||
expect(map.exports[a], isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
test('.parse() resolves package: imports', () {
|
||||
return testExports({
|
||||
'packages/a/a.dart': 'export "package:b/b.dart";',
|
||||
'packages/b/b.dart': ''
|
||||
}).then((map) {
|
||||
var a = libMirror('package:a/a.dart');
|
||||
var b = libMirror('package:b/b.dart');
|
||||
|
||||
expect(map.exports[a], unorderedEquals([
|
||||
new Export(a, b)
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
test('.parse() ignores dart: imports', () {
|
||||
return testExports({}).then((map) {
|
||||
expect(map.exports, isEmpty);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('Export', () {
|
||||
test('normalizes hide and show', () {
|
||||
expect(new Export(null, null, show: ['x', 'y'], hide: ['y', 'z']),
|
||||
equals(new Export(null, null, show: ['x'])));
|
||||
});
|
||||
|
||||
test("doesn't care about the order of show or hide", () {
|
||||
expect(new Export(null, null, show: ['x', 'y']),
|
||||
equals(new Export(null, null, show: ['y', 'x'])));
|
||||
expect(new Export(null, null, hide: ['x', 'y']),
|
||||
equals(new Export(null, null, hide: ['y', 'x'])));
|
||||
});
|
||||
|
||||
test('with no combinators considers anything visible', () {
|
||||
var export = new Export(null, null);
|
||||
expect(export.isMemberVisible('x'), isTrue);
|
||||
expect(export.isMemberVisible('y'), isTrue);
|
||||
expect(export.isMemberVisible('z'), isTrue);
|
||||
});
|
||||
|
||||
test('with hide combinators considers anything not hidden visible', () {
|
||||
var export = new Export(null, null, hide: ['x', 'y']);
|
||||
expect(export.isMemberVisible('x'), isFalse);
|
||||
expect(export.isMemberVisible('y'), isFalse);
|
||||
expect(export.isMemberVisible('z'), isTrue);
|
||||
});
|
||||
|
||||
test('with show combinators considers anything not shown invisible', () {
|
||||
var export = new Export(null, null, show: ['x', 'y']);
|
||||
expect(export.isMemberVisible('x'), isTrue);
|
||||
expect(export.isMemberVisible('y'), isTrue);
|
||||
expect(export.isMemberVisible('z'), isFalse);
|
||||
});
|
||||
|
||||
test('composing uses the parent exporter and child path', () {
|
||||
return testExports({
|
||||
'exporter1.dart': '',
|
||||
'exporter2.dart': '',
|
||||
'path1.dart': '',
|
||||
'path2.dart': ''
|
||||
}).then((map) {
|
||||
var exporter1 = libMirror('memory:exporter1.dart');
|
||||
var exporter2 = libMirror('memory:exporter2.dart');
|
||||
var path1 = libMirror('memory:path1.dart');
|
||||
var path2 = libMirror('memory:path2.dart');
|
||||
expect(new Export(exporter1, path1)
|
||||
.compose(new Export(exporter2, path2)),
|
||||
equals(new Export(exporter1, path2)));
|
||||
});
|
||||
});
|
||||
|
||||
test('composing show . show takes the intersection', () {
|
||||
expect(new Export(null, null, show: ['x', 'y'])
|
||||
.compose(new Export(null, null, show: ['y', 'z'])),
|
||||
equals(new Export(null, null, show: ['y'])));
|
||||
});
|
||||
|
||||
test('composing show . hide takes the difference', () {
|
||||
expect(new Export(null, null, show: ['x', 'y'])
|
||||
.compose(new Export(null, null, hide: ['y', 'z'])),
|
||||
equals(new Export(null, null, show: ['x'])));
|
||||
});
|
||||
|
||||
test('composing hide . show takes the reverse difference', () {
|
||||
expect(new Export(null, null, hide: ['x', 'y'])
|
||||
.compose(new Export(null, null, show: ['y', 'z'])),
|
||||
equals(new Export(null, null, show: ['z'])));
|
||||
});
|
||||
|
||||
test('composing hide . hide takes the union', () {
|
||||
expect(new Export(null, null, hide: ['x', 'y'])
|
||||
.compose(new Export(null, null, hide: ['y', 'z'])),
|
||||
equals(new Export(null, null, hide: ['x', 'y', 'z'])));
|
||||
});
|
||||
|
||||
test('merging requires identical exporters and paths', () {
|
||||
return testExports({
|
||||
'exporter1.dart': '',
|
||||
'exporter2.dart': '',
|
||||
'path1.dart': '',
|
||||
'path2.dart': ''
|
||||
}).then((map) {
|
||||
var exporter1 = libMirror('memory:exporter1.dart');
|
||||
var exporter2 = libMirror('memory:exporter2.dart');
|
||||
var path1 = libMirror('memory:path1.dart');
|
||||
var path2 = libMirror('memory:path2.dart');
|
||||
expect(() => new Export(exporter1, null)
|
||||
.merge(new Export(exporter2, null)),
|
||||
throwsA(isArgumentError));
|
||||
expect(() => new Export(null, path1)
|
||||
.merge(new Export(null, path2)),
|
||||
throwsA(isArgumentError));
|
||||
expect(new Export(null, null)
|
||||
.merge(new Export(null, null)),
|
||||
equals(new Export(null, null)));
|
||||
});
|
||||
});
|
||||
|
||||
test('merging show + show takes the union', () {
|
||||
expect(new Export(null, null, show: ['x', 'y'])
|
||||
.merge(new Export(null, null, show: ['y', 'z'])),
|
||||
equals(new Export(null, null, show: ['x', 'y', 'z'])));
|
||||
});
|
||||
|
||||
test('merging show + hide takes the difference', () {
|
||||
expect(new Export(null, null, show: ['x', 'y'])
|
||||
.merge(new Export(null, null, hide: ['y', 'z'])),
|
||||
equals(new Export(null, null, hide: ['z'])));
|
||||
});
|
||||
|
||||
test('merging hide + show takes the difference', () {
|
||||
expect(new Export(null, null, hide: ['x', 'y'])
|
||||
.merge(new Export(null, null, show: ['y', 'z'])),
|
||||
equals(new Export(null, null, hide: ['x'])));
|
||||
});
|
||||
|
||||
test('merging hide + hide takes the intersection', () {
|
||||
expect(new Export(null, null, hide: ['x', 'y'])
|
||||
.merge(new Export(null, null, hide: ['y', 'z'])),
|
||||
equals(new Export(null, null, hide: ['y'])));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,912 +0,0 @@
|
|||
// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/// Unit tests for markdown.
|
||||
library markdownTests;
|
||||
|
||||
import 'package:unittest/unittest.dart';
|
||||
|
||||
// TODO(rnystrom): Use "package:" URL (#4968).
|
||||
import '../lib/markdown.dart';
|
||||
|
||||
/// Most of these tests are based on observing how showdown behaves:
|
||||
/// http://softwaremaniacs.org/playground/showdown-highlight/
|
||||
void main() {
|
||||
group('Paragraphs', () {
|
||||
validate('consecutive lines form a single paragraph', '''
|
||||
This is the first line.
|
||||
This is the second line.
|
||||
''', '''
|
||||
<p>This is the first line.
|
||||
This is the second line.</p>
|
||||
''');
|
||||
|
||||
// TODO(rnystrom): The rules here for what happens to lines following a
|
||||
// paragraph appear to be completely arbitrary in markdown. If it makes the
|
||||
// code significantly cleaner, we should consider ourselves free to change
|
||||
// these tests.
|
||||
|
||||
validate('are terminated by a header', '''
|
||||
para
|
||||
# header
|
||||
''', '''
|
||||
<p>para</p>
|
||||
<h1>header</h1>
|
||||
''');
|
||||
|
||||
validate('are terminated by a setext header', '''
|
||||
para
|
||||
header
|
||||
==
|
||||
''', '''
|
||||
<p>para</p>
|
||||
<h1>header</h1>
|
||||
''');
|
||||
|
||||
validate('are terminated by a hr', '''
|
||||
para
|
||||
___
|
||||
''', '''
|
||||
<p>para</p>
|
||||
<hr />
|
||||
''');
|
||||
|
||||
validate('consume an unordered list', '''
|
||||
para
|
||||
* list
|
||||
''', '''
|
||||
<p>para
|
||||
* list</p>
|
||||
''');
|
||||
|
||||
validate('consume an ordered list', '''
|
||||
para
|
||||
1. list
|
||||
''', '''
|
||||
<p>para
|
||||
1. list</p>
|
||||
''');
|
||||
|
||||
// Windows line endings have a \r\n format
|
||||
// instead of the unix \n format.
|
||||
validate('take account of windows line endings', '''
|
||||
line1\r\n\r\n line2\r\n
|
||||
''', '''
|
||||
<p>line1</p>
|
||||
<p>line2</p>
|
||||
''');
|
||||
});
|
||||
|
||||
group('Setext headers', () {
|
||||
validate('h1', '''
|
||||
text
|
||||
===
|
||||
''', '''
|
||||
<h1>text</h1>
|
||||
''');
|
||||
|
||||
validate('h2', '''
|
||||
text
|
||||
---
|
||||
''', '''
|
||||
<h2>text</h2>
|
||||
''');
|
||||
|
||||
validate('h1 on first line becomes text', '''
|
||||
===
|
||||
''', '''
|
||||
<p>===</p>
|
||||
''');
|
||||
|
||||
validate('h2 on first line becomes text', '''
|
||||
-
|
||||
''', '''
|
||||
<p>-</p>
|
||||
''');
|
||||
|
||||
validate('h1 turns preceding list into text', '''
|
||||
- list
|
||||
===
|
||||
''', '''
|
||||
<h1>- list</h1>
|
||||
''');
|
||||
|
||||
validate('h2 turns preceding list into text', '''
|
||||
- list
|
||||
===
|
||||
''', '''
|
||||
<h1>- list</h1>
|
||||
''');
|
||||
|
||||
validate('h1 turns preceding blockquote into text', '''
|
||||
> quote
|
||||
===
|
||||
''', '''
|
||||
<h1>> quote</h1>
|
||||
''');
|
||||
|
||||
validate('h2 turns preceding blockquote into text', '''
|
||||
> quote
|
||||
===
|
||||
''', '''
|
||||
<h1>> quote</h1>
|
||||
''');
|
||||
});
|
||||
|
||||
group('Headers', () {
|
||||
validate('h1', '''
|
||||
# header
|
||||
''', '''
|
||||
<h1>header</h1>
|
||||
''');
|
||||
|
||||
validate('h2', '''
|
||||
## header
|
||||
''', '''
|
||||
<h2>header</h2>
|
||||
''');
|
||||
|
||||
validate('h3', '''
|
||||
### header
|
||||
''', '''
|
||||
<h3>header</h3>
|
||||
''');
|
||||
|
||||
validate('h4', '''
|
||||
#### header
|
||||
''', '''
|
||||
<h4>header</h4>
|
||||
''');
|
||||
|
||||
validate('h5', '''
|
||||
##### header
|
||||
''', '''
|
||||
<h5>header</h5>
|
||||
''');
|
||||
|
||||
validate('h6', '''
|
||||
###### header
|
||||
''', '''
|
||||
<h6>header</h6>
|
||||
''');
|
||||
|
||||
validate('trailing "#" are removed', '''
|
||||
# header ######
|
||||
''', '''
|
||||
<h1>header</h1>
|
||||
''');
|
||||
|
||||
});
|
||||
|
||||
group('Unordered lists', () {
|
||||
validate('asterisk, plus and hyphen', '''
|
||||
* star
|
||||
- dash
|
||||
+ plus
|
||||
''', '''
|
||||
<ul>
|
||||
<li>star</li>
|
||||
<li>dash</li>
|
||||
<li>plus</li>
|
||||
</ul>
|
||||
''');
|
||||
|
||||
validate('allow numbered lines after first', '''
|
||||
* a
|
||||
1. b
|
||||
''', '''
|
||||
<ul>
|
||||
<li>a</li>
|
||||
<li>b</li>
|
||||
</ul>
|
||||
''');
|
||||
|
||||
validate('allow a tab after the marker', '''
|
||||
*\ta
|
||||
+\tb
|
||||
-\tc
|
||||
1.\td
|
||||
''', '''
|
||||
<ul>
|
||||
<li>a</li>
|
||||
<li>b</li>
|
||||
<li>c</li>
|
||||
<li>d</li>
|
||||
</ul>
|
||||
''');
|
||||
|
||||
validate('wrap items in paragraphs if blank lines separate', '''
|
||||
* one
|
||||
|
||||
* two
|
||||
''', '''
|
||||
<ul>
|
||||
<li><p>one</p></li>
|
||||
<li><p>two</p></li>
|
||||
</ul>
|
||||
''');
|
||||
|
||||
validate('force paragraph on item before and after blank lines', '''
|
||||
* one
|
||||
* two
|
||||
|
||||
* three
|
||||
''', '''
|
||||
<ul>
|
||||
<li>one</li>
|
||||
<li>
|
||||
<p>two</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>three</p>
|
||||
</li>
|
||||
</ul>
|
||||
''');
|
||||
|
||||
validate('do not force paragraph if item is already block', '''
|
||||
* > quote
|
||||
|
||||
* # header
|
||||
''', '''
|
||||
<ul>
|
||||
<li><blockquote><p>quote</p></blockquote></li>
|
||||
<li><h1>header</h1></li>
|
||||
</ul>
|
||||
''');
|
||||
|
||||
validate('can contain multiple paragraphs', '''
|
||||
* one
|
||||
|
||||
two
|
||||
|
||||
* three
|
||||
''', '''
|
||||
<ul>
|
||||
<li>
|
||||
<p>one</p>
|
||||
<p>two</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>three</p>
|
||||
</li>
|
||||
</ul>
|
||||
''');
|
||||
|
||||
validate('can span newlines', '''
|
||||
* one
|
||||
two
|
||||
* three
|
||||
''', '''
|
||||
<ul>
|
||||
<li>
|
||||
<p>one
|
||||
two</p>
|
||||
</li>
|
||||
<li>
|
||||
three
|
||||
</li>
|
||||
</ul>
|
||||
''');
|
||||
|
||||
// TODO(rnystrom): This is how most other markdown parsers handle
|
||||
// this but that seems like a nasty special case. For now, let's not
|
||||
// worry about it.
|
||||
/*
|
||||
validate('can nest using indentation', '''
|
||||
* parent
|
||||
* child
|
||||
''', '''
|
||||
<ul>
|
||||
<li>parent
|
||||
<ul><li>child</li></ul></li>
|
||||
</ul>
|
||||
''');
|
||||
*/
|
||||
});
|
||||
|
||||
group('Ordered lists', () {
|
||||
validate('start with numbers', '''
|
||||
1. one
|
||||
45. two
|
||||
12345. three
|
||||
''', '''
|
||||
<ol>
|
||||
<li>one</li>
|
||||
<li>two</li>
|
||||
<li>three</li>
|
||||
</ol>
|
||||
''');
|
||||
|
||||
validate('allow unordered lines after first', '''
|
||||
1. a
|
||||
* b
|
||||
''', '''
|
||||
<ol>
|
||||
<li>a</li>
|
||||
<li>b</li>
|
||||
</ol>
|
||||
''');
|
||||
});
|
||||
|
||||
group('Blockquotes', () {
|
||||
validate('single line', '''
|
||||
> blah
|
||||
''', '''
|
||||
<blockquote>
|
||||
<p>blah</p>
|
||||
</blockquote>
|
||||
''');
|
||||
|
||||
validate('with two paragraphs', '''
|
||||
> first
|
||||
>
|
||||
> second
|
||||
''', '''
|
||||
<blockquote>
|
||||
<p>first</p>
|
||||
<p>second</p>
|
||||
</blockquote>
|
||||
''');
|
||||
|
||||
validate('nested', '''
|
||||
> one
|
||||
>> two
|
||||
> > > three
|
||||
''', '''
|
||||
<blockquote>
|
||||
<p>one</p>
|
||||
<blockquote>
|
||||
<p>two</p>
|
||||
<blockquote>
|
||||
<p>three</p>
|
||||
</blockquote>
|
||||
</blockquote>
|
||||
</blockquote>
|
||||
''');
|
||||
});
|
||||
|
||||
group('Code blocks', () {
|
||||
validate('single line', '''
|
||||
code
|
||||
''', '''
|
||||
<pre><code>code</code></pre>
|
||||
''');
|
||||
|
||||
validate('include leading whitespace after indentation', '''
|
||||
zero
|
||||
one
|
||||
two
|
||||
three
|
||||
''', '''
|
||||
<pre><code>zero
|
||||
one
|
||||
two
|
||||
three</code></pre>
|
||||
''');
|
||||
|
||||
validate('code blocks separated by newlines form one block', '''
|
||||
zero
|
||||
one
|
||||
|
||||
two
|
||||
|
||||
three
|
||||
''', '''
|
||||
<pre><code>zero
|
||||
one
|
||||
|
||||
two
|
||||
|
||||
three</code></pre>
|
||||
''');
|
||||
|
||||
validate('code blocks separated by two newlines form multiple blocks', '''
|
||||
zero
|
||||
one
|
||||
|
||||
|
||||
two
|
||||
|
||||
|
||||
three
|
||||
''', '''
|
||||
<pre><code>zero
|
||||
one</code></pre>
|
||||
<pre><code>two</code></pre>
|
||||
<pre><code>three</code></pre>
|
||||
''');
|
||||
|
||||
validate('escape HTML characters', '''
|
||||
<&>
|
||||
''', '''
|
||||
<pre><code><&></code></pre>
|
||||
''');
|
||||
});
|
||||
|
||||
group('Horizontal rules', () {
|
||||
validate('from dashes', '''
|
||||
---
|
||||
''', '''
|
||||
<hr />
|
||||
''');
|
||||
|
||||
validate('from asterisks', '''
|
||||
***
|
||||
''', '''
|
||||
<hr />
|
||||
''');
|
||||
|
||||
validate('from underscores', '''
|
||||
___
|
||||
''', '''
|
||||
<hr />
|
||||
''');
|
||||
|
||||
validate('can include up to two spaces', '''
|
||||
_ _ _
|
||||
''', '''
|
||||
<hr />
|
||||
''');
|
||||
});
|
||||
|
||||
group('Block-level HTML', () {
|
||||
validate('single line', '''
|
||||
<table></table>
|
||||
''', '''
|
||||
<table></table>
|
||||
''');
|
||||
|
||||
validate('multi-line', '''
|
||||
<table>
|
||||
blah
|
||||
</table>
|
||||
''', '''
|
||||
<table>
|
||||
blah
|
||||
</table>
|
||||
''');
|
||||
|
||||
validate('blank line ends block', '''
|
||||
<table>
|
||||
blah
|
||||
</table>
|
||||
|
||||
para
|
||||
''', '''
|
||||
<table>
|
||||
blah
|
||||
</table>
|
||||
<p>para</p>
|
||||
''');
|
||||
|
||||
validate('HTML can be bogus', '''
|
||||
<bogus>
|
||||
blah
|
||||
</weird>
|
||||
|
||||
para
|
||||
''', '''
|
||||
<bogus>
|
||||
blah
|
||||
</weird>
|
||||
<p>para</p>
|
||||
''');
|
||||
});
|
||||
|
||||
group('Strong', () {
|
||||
validate('using asterisks', '''
|
||||
before **strong** after
|
||||
''', '''
|
||||
<p>before <strong>strong</strong> after</p>
|
||||
''');
|
||||
|
||||
validate('using underscores', '''
|
||||
before __strong__ after
|
||||
''', '''
|
||||
<p>before <strong>strong</strong> after</p>
|
||||
''');
|
||||
|
||||
validate('unmatched asterisks', '''
|
||||
before ** after
|
||||
''', '''
|
||||
<p>before ** after</p>
|
||||
''');
|
||||
|
||||
validate('unmatched underscores', '''
|
||||
before __ after
|
||||
''', '''
|
||||
<p>before __ after</p>
|
||||
''');
|
||||
|
||||
validate('multiple spans in one text', '''
|
||||
a **one** b __two__ c
|
||||
''', '''
|
||||
<p>a <strong>one</strong> b <strong>two</strong> c</p>
|
||||
''');
|
||||
|
||||
validate('multi-line', '''
|
||||
before **first
|
||||
second** after
|
||||
''', '''
|
||||
<p>before <strong>first
|
||||
second</strong> after</p>
|
||||
''');
|
||||
});
|
||||
|
||||
group('Emphasis and strong', () {
|
||||
validate('single asterisks', '''
|
||||
before *em* after
|
||||
''', '''
|
||||
<p>before <em>em</em> after</p>
|
||||
''');
|
||||
|
||||
validate('single underscores', '''
|
||||
before _em_ after
|
||||
''', '''
|
||||
<p>before <em>em</em> after</p>
|
||||
''');
|
||||
|
||||
validate('double asterisks', '''
|
||||
before **strong** after
|
||||
''', '''
|
||||
<p>before <strong>strong</strong> after</p>
|
||||
''');
|
||||
|
||||
validate('double underscores', '''
|
||||
before __strong__ after
|
||||
''', '''
|
||||
<p>before <strong>strong</strong> after</p>
|
||||
''');
|
||||
|
||||
validate('unmatched asterisk', '''
|
||||
before *after
|
||||
''', '''
|
||||
<p>before *after</p>
|
||||
''');
|
||||
|
||||
validate('unmatched underscore', '''
|
||||
before _after
|
||||
''', '''
|
||||
<p>before _after</p>
|
||||
''');
|
||||
|
||||
validate('multiple spans in one text', '''
|
||||
a *one* b _two_ c
|
||||
''', '''
|
||||
<p>a <em>one</em> b <em>two</em> c</p>
|
||||
''');
|
||||
|
||||
validate('multi-line', '''
|
||||
before *first
|
||||
second* after
|
||||
''', '''
|
||||
<p>before <em>first
|
||||
second</em> after</p>
|
||||
''');
|
||||
|
||||
validate('not processed when surrounded by spaces', '''
|
||||
a * b * c _ d _ e
|
||||
''', '''
|
||||
<p>a * b * c _ d _ e</p>
|
||||
''');
|
||||
|
||||
validate('strong then emphasis', '''
|
||||
**strong***em*
|
||||
''', '''
|
||||
<p><strong>strong</strong><em>em</em></p>
|
||||
''');
|
||||
|
||||
validate('emphasis then strong', '''
|
||||
*em***strong**
|
||||
''', '''
|
||||
<p><em>em</em><strong>strong</strong></p>
|
||||
''');
|
||||
|
||||
validate('emphasis inside strong', '''
|
||||
**strong *em***
|
||||
''', '''
|
||||
<p><strong>strong <em>em</em></strong></p>
|
||||
''');
|
||||
|
||||
validate('mismatched in nested', '''
|
||||
*a _b* c_
|
||||
''', '''
|
||||
<p><em>a _b</em> c_</p>
|
||||
''');
|
||||
|
||||
validate('cannot nest tags of same type', '''
|
||||
*a _b *c* d_ e*
|
||||
''', '''
|
||||
<p><em>a _b </em>c<em> d_ e</em></p>
|
||||
''');
|
||||
});
|
||||
|
||||
group('Inline code', () {
|
||||
validate('simple case', '''
|
||||
before `source` after
|
||||
''', '''
|
||||
<p>before <code>source</code> after</p>
|
||||
''');
|
||||
|
||||
validate('unmatched backtick', '''
|
||||
before ` after
|
||||
''', '''
|
||||
<p>before ` after</p>
|
||||
''');
|
||||
validate('multiple spans in one text', '''
|
||||
a `one` b `two` c
|
||||
''', '''
|
||||
<p>a <code>one</code> b <code>two</code> c</p>
|
||||
''');
|
||||
|
||||
validate('multi-line', '''
|
||||
before `first
|
||||
second` after
|
||||
''', '''
|
||||
<p>before <code>first
|
||||
second</code> after</p>
|
||||
''');
|
||||
|
||||
validate('simple double backticks', '''
|
||||
before ``source`` after
|
||||
''', '''
|
||||
<p>before <code>source</code> after</p>
|
||||
''');
|
||||
|
||||
validate('double backticks', '''
|
||||
before ``can `contain` backticks`` after
|
||||
''', '''
|
||||
<p>before <code>can `contain` backticks</code> after</p>
|
||||
''');
|
||||
|
||||
validate('double backticks with spaces', '''
|
||||
before `` `tick` `` after
|
||||
''', '''
|
||||
<p>before <code>`tick`</code> after</p>
|
||||
''');
|
||||
|
||||
validate('multiline double backticks with spaces', '''
|
||||
before ``in `tick`
|
||||
another`` after
|
||||
''', '''
|
||||
<p>before <code>in `tick`
|
||||
another</code> after</p>
|
||||
''');
|
||||
|
||||
validate('ignore markup inside code', '''
|
||||
before `*b* _c_` after
|
||||
''', '''
|
||||
<p>before <code>*b* _c_</code> after</p>
|
||||
''');
|
||||
|
||||
validate('escape HTML characters', '''
|
||||
`<&>`
|
||||
''', '''
|
||||
<p><code><&></code></p>
|
||||
''');
|
||||
|
||||
validate('escape HTML tags', '''
|
||||
'*' `<em>`
|
||||
''', '''
|
||||
<p>'*' <code><em></code></p>
|
||||
''');
|
||||
});
|
||||
|
||||
group('HTML encoding', () {
|
||||
validate('less than and ampersand are escaped', '''
|
||||
< &
|
||||
''', '''
|
||||
<p>< &</p>
|
||||
''');
|
||||
validate('greater than is not escaped', '''
|
||||
not you >
|
||||
''', '''
|
||||
<p>not you ></p>
|
||||
''');
|
||||
validate('existing entities are untouched', '''
|
||||
&
|
||||
''', '''
|
||||
<p>&</p>
|
||||
''');
|
||||
});
|
||||
|
||||
group('Autolinks', () {
|
||||
validate('basic link', '''
|
||||
before <http://foo.com/> after
|
||||
''', '''
|
||||
<p>before <a href="http://foo.com/">http://foo.com/</a> after</p>
|
||||
''');
|
||||
validate('handles ampersand in url', '''
|
||||
<http://foo.com/?a=1&b=2>
|
||||
''', '''
|
||||
<p><a href="http://foo.com/?a=1&b=2">http://foo.com/?a=1&b=2</a></p>
|
||||
''');
|
||||
});
|
||||
|
||||
group('Reference links', () {
|
||||
validate('double quotes for title', '''
|
||||
links [are] [a] awesome
|
||||
|
||||
[a]: http://foo.com "woo"
|
||||
''', '''
|
||||
<p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
|
||||
''');
|
||||
validate('single quoted title', """
|
||||
links [are] [a] awesome
|
||||
|
||||
[a]: http://foo.com 'woo'
|
||||
""", '''
|
||||
<p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
|
||||
''');
|
||||
validate('parentheses for title', '''
|
||||
links [are] [a] awesome
|
||||
|
||||
[a]: http://foo.com (woo)
|
||||
''', '''
|
||||
<p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
|
||||
''');
|
||||
validate('no title', '''
|
||||
links [are] [a] awesome
|
||||
|
||||
[a]: http://foo.com
|
||||
''', '''
|
||||
<p>links <a href="http://foo.com">are</a> awesome</p>
|
||||
''');
|
||||
validate('unknown link becomes plaintext', '''
|
||||
[not] [known]
|
||||
''', '''
|
||||
<p>[not] [known]</p>
|
||||
''');
|
||||
validate('can style link contents', '''
|
||||
links [*are*] [a] awesome
|
||||
|
||||
[a]: http://foo.com
|
||||
''', '''
|
||||
<p>links <a href="http://foo.com"><em>are</em></a> awesome</p>
|
||||
''');
|
||||
validate('inline styles after a bad link are processed', '''
|
||||
[bad] `code`
|
||||
''', '''
|
||||
<p>[bad] <code>code</code></p>
|
||||
''');
|
||||
validate('empty reference uses text from link', '''
|
||||
links [are][] awesome
|
||||
|
||||
[are]: http://foo.com
|
||||
''', '''
|
||||
<p>links <a href="http://foo.com">are</a> awesome</p>
|
||||
''');
|
||||
validate('references are case-insensitive', '''
|
||||
links [ARE][] awesome
|
||||
|
||||
[are]: http://foo.com
|
||||
''', '''
|
||||
<p>links <a href="http://foo.com">ARE</a> awesome</p>
|
||||
''');
|
||||
});
|
||||
|
||||
group('Inline links', () {
|
||||
validate('double quotes for title', '''
|
||||
links [are](http://foo.com "woo") awesome
|
||||
''', '''
|
||||
<p>links <a href="http://foo.com" title="woo">are</a> awesome</p>
|
||||
''');
|
||||
validate('no title', '''
|
||||
links [are] (http://foo.com) awesome
|
||||
''', '''
|
||||
<p>links <a href="http://foo.com">are</a> awesome</p>
|
||||
''');
|
||||
validate('can style link contents', '''
|
||||
links [*are*](http://foo.com) awesome
|
||||
''', '''
|
||||
<p>links <a href="http://foo.com"><em>are</em></a> awesome</p>
|
||||
''');
|
||||
});
|
||||
|
||||
group('Resolver', () {
|
||||
var nyanResolver = (text) => new Text('~=[,,_${text}_,,]:3');
|
||||
validate('simple resolver', '''
|
||||
resolve [this] thing
|
||||
''', '''
|
||||
<p>resolve ~=[,,_this_,,]:3 thing</p>
|
||||
''', linkResolver: nyanResolver);
|
||||
});
|
||||
|
||||
group('Custom inline syntax', () {
|
||||
List<InlineSyntax> nyanSyntax =
|
||||
[new TextSyntax('nyan', sub: '~=[,,_,,]:3')];
|
||||
validate('simple inline syntax', '''
|
||||
nyan
|
||||
''', '''
|
||||
<p>~=[,,_,,]:3</p>
|
||||
''', inlineSyntaxes: nyanSyntax);
|
||||
|
||||
// TODO(amouravski): need more tests here for custom syntaxes, as some
|
||||
// things are not quite working properly. The regexps are sometime a little
|
||||
// too greedy, I think.
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes eight spaces of leading indentation from a multiline string.
|
||||
*
|
||||
* Note that this is very sensitive to how the literals are styled. They should
|
||||
* be:
|
||||
* '''
|
||||
* Text starts on own line. Lines up with subsequent lines.
|
||||
* Lines are indented exactly 8 characters from the left margin.'''
|
||||
*
|
||||
* This does nothing if text is only a single line.
|
||||
*/
|
||||
// TODO(nweiz): Make this auto-detect the indentation level from the first
|
||||
// non-whitespace line.
|
||||
String cleanUpLiteral(String text) {
|
||||
var lines = text.split('\n');
|
||||
if (lines.length <= 1) return text;
|
||||
|
||||
for (var j = 0; j < lines.length; j++) {
|
||||
if (lines[j].length > 8) {
|
||||
lines[j] = lines[j].substring(8, lines[j].length);
|
||||
} else {
|
||||
lines[j] = '';
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
validate(String description, String markdown, String html,
|
||||
{bool verbose: false, inlineSyntaxes, linkResolver}) {
|
||||
test(description, () {
|
||||
markdown = cleanUpLiteral(markdown);
|
||||
html = cleanUpLiteral(html);
|
||||
|
||||
var result = markdownToHtml(markdown, inlineSyntaxes: inlineSyntaxes,
|
||||
linkResolver: linkResolver);
|
||||
var passed = compareOutput(html, result);
|
||||
|
||||
if (!passed) {
|
||||
// Remove trailing newline.
|
||||
html = html.substring(0, html.length - 1);
|
||||
|
||||
print('FAIL: $description');
|
||||
print(' expect: ${html.replaceAll("\n", "\n ")}');
|
||||
print(' actual: ${result.replaceAll("\n", "\n ")}');
|
||||
print('');
|
||||
}
|
||||
|
||||
expect(passed, isTrue, verbose: verbose);
|
||||
});
|
||||
}
|
||||
|
||||
/// Does a loose comparison of the two strings of HTML. Ignores differences in
|
||||
/// newlines and indentation.
|
||||
compareOutput(String a, String b) {
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
|
||||
skipIgnored(String s, int i) {
|
||||
// Ignore newlines.
|
||||
while ((i < s.length) && (s[i] == '\n')) {
|
||||
i++;
|
||||
// Ignore indentation.
|
||||
while ((i < s.length) && (s[i] == ' ')) i++;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
i = skipIgnored(a, i);
|
||||
j = skipIgnored(b, j);
|
||||
|
||||
// If one string runs out of non-ignored strings, the other must too.
|
||||
if (i == a.length) return j == b.length;
|
||||
if (j == b.length) return i == a.length;
|
||||
|
||||
if (a[i] != b[j]) return false;
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
library no_package_test;
|
||||
|
||||
class NoPackageTestFile {
|
||||
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
library package_test;
|
||||
|
||||
class PackageTestFile {
|
||||
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. 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:unittest/unittest.dart';
|
||||
|
||||
import '../lib/src/dartdoc/utils.dart';
|
||||
|
||||
void main() {
|
||||
group('countOccurrences', () {
|
||||
test('empty text returns 0', () {
|
||||
expect(countOccurrences('', 'needle'), equals(0));
|
||||
});
|
||||
|
||||
test('one occurrence', () {
|
||||
expect(countOccurrences('bananarama', 'nara'), equals(1));
|
||||
});
|
||||
|
||||
test('multiple occurrences', () {
|
||||
expect(countOccurrences('bananarama', 'a'), equals(5));
|
||||
});
|
||||
|
||||
test('overlapping matches do not count', () {
|
||||
expect(countOccurrences('bananarama', 'ana'), equals(1));
|
||||
});
|
||||
});
|
||||
|
||||
group('repeat', () {
|
||||
test('zero times returns an empty string', () {
|
||||
expect(repeat('ba', 0), isEmpty);
|
||||
});
|
||||
|
||||
test('one time returns the string', () {
|
||||
expect(repeat('ba', 1), equals('ba'));
|
||||
});
|
||||
|
||||
test('multiple times', () {
|
||||
expect(repeat('ba', 3), equals('bababa'));
|
||||
});
|
||||
|
||||
test('multiple times with a separator', () {
|
||||
expect(repeat('ba', 3, separator: ' '), equals('ba ba ba'));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -15,13 +15,6 @@ lib/indexed_db/dart2js/indexed_db_dart2js: CompileTimeError # Issue 16524
|
|||
lib/indexed_db/dartium/indexed_db_dartium: CompileTimeError # Issue 16524
|
||||
lib/_internal/compiler/samples/compile_loop/compile_loop: CompileTimeError # Issue 16524
|
||||
lib/_internal/compiler/samples/darttags/darttags: CompileTimeError # Issue 16524
|
||||
lib/_internal/dartdoc/bin/dartdoc: CompileTimeError # Issue 16523
|
||||
lib/_internal/dartdoc/lib/dartdoc: CompileTimeError # Issue 16523
|
||||
lib/_internal/dartdoc/lib/src/client/client-live-nav: CompileTimeError # Issue 16523
|
||||
lib/_internal/dartdoc/lib/src/client/client-shared: CompileTimeError # Issue 16523
|
||||
lib/_internal/dartdoc/lib/src/client/dropdown: CompileTimeError # Issue 16523
|
||||
lib/_internal/dartdoc/lib/src/client/search: CompileTimeError # Issue 16523
|
||||
lib/_internal/dartdoc/lib/universe_serializer: CompileTimeError # Issue 16523
|
||||
lib/js/dart2js/js_dart2js: CompileTimeError # Issue 16524
|
||||
lib/js/dartium/js_dartium: CompileTimeError # Issue 16524
|
||||
lib/svg/dart2js/svg_dart2js: CompileTimeError # Issue 16524
|
||||
|
@ -37,7 +30,6 @@ lib/web_sql/dartium/web_sql_dartium: CompileTimeError # Issue 16524
|
|||
lib/_internal/compiler/samples/jsonify/jsonify: CompileTimeError # issue 16466
|
||||
lib/_internal/compiler/implementation/mirrors/analyze: CompileTimeError # issue 16466
|
||||
lib/_internal/compiler/implementation/mirrors/dart2js_mirrors: CompileTimeError # issue 16466
|
||||
lib/_internal/dartdoc/lib/src/dart2js_mirrors: CompileTimeError # issue 16466
|
||||
|
||||
# Pass necessary, since CompileTimeError is valid for everything in that
|
||||
# directory (not only for src/command.dart)
|
||||
|
|
|
@ -105,7 +105,7 @@ def CopyShellScript(src_file, dest_dir):
|
|||
|
||||
|
||||
def CopyDartScripts(home, sdk_root):
|
||||
for executable in ['dart2js', 'dartanalyzer', 'dartdoc', 'docgen', 'pub']:
|
||||
for executable in ['dart2js', 'dartanalyzer', 'docgen', 'pub']:
|
||||
CopyShellScript(os.path.join(home, 'sdk', 'bin', executable),
|
||||
os.path.join(sdk_root, 'bin'))
|
||||
|
||||
|
@ -194,7 +194,6 @@ def Main(argv):
|
|||
|
||||
for library in [join('_chrome', 'dart2js'), join('_chrome', 'dartium'),
|
||||
join('_internal', 'compiler'),
|
||||
join('_internal', 'dartdoc'),
|
||||
join('_internal', 'lib'),
|
||||
'async', 'collection', 'convert', 'core',
|
||||
'crypto', 'internal', 'io', 'isolate',
|
||||
|
@ -236,7 +235,7 @@ def Main(argv):
|
|||
join(RESOURCE, '7zip'),
|
||||
ignore=ignore_patterns('.svn'))
|
||||
|
||||
# Copy dart2js/dartdoc/pub.
|
||||
# Copy dart2js/pub.
|
||||
CopyDartScripts(HOME, SDK_tmp)
|
||||
CopySnapshots(SNAPSHOT, SDK_tmp)
|
||||
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
'create_snapshot.dart',
|
||||
'--output_dir=<(SHARED_INTERMEDIATE_DIR)',
|
||||
'--dart2js_main=sdk/lib/_internal/compiler/implementation/dart2js.dart',
|
||||
'--dartdoc_main=sdk/lib/_internal/dartdoc/bin/dartdoc.dart',
|
||||
'--docgen_main=pkg/docgen/bin/docgen.dart',
|
||||
'--package_root=<(PRODUCT_DIR)/packages/',
|
||||
],
|
||||
|
|
|
@ -18,13 +18,11 @@ Future<String> getVersion(var rootPath) {
|
|||
|
||||
Future<String> getSnapshotGenerationFile(var args, var rootPath) {
|
||||
var dart2js = rootPath.resolve(args["dart2js_main"]);
|
||||
var dartdoc = rootPath.resolve(args["dartdoc_main"]);
|
||||
var docgen = rootPath.resolve(args["docgen_main"]);
|
||||
return getVersion(rootPath).then((version) {
|
||||
var snapshotGenerationText =
|
||||
"""
|
||||
import '${dart2js.toFilePath(windows: false)}' as dart2jsMain;
|
||||
import '${dartdoc.toFilePath(windows: false)}' as dartdocMain;
|
||||
import '${docgen.toFilePath(windows: false)}' as docgenMain;
|
||||
import 'dart:io';
|
||||
|
||||
|
@ -34,8 +32,6 @@ void main(List<String> arguments) {
|
|||
if (tool == "dart2js") {
|
||||
dart2jsMain.BUILD_ID = "$version";
|
||||
dart2jsMain.main(arguments.skip(1).toList());
|
||||
} else if (tool == "dartdoc") {
|
||||
dartdocMain.main(arguments.skip(1).toList());
|
||||
} else if (tool == "docgen") {
|
||||
docgenMain.main(arguments.skip(1).toList());
|
||||
}
|
||||
|
@ -88,12 +84,11 @@ Future createSnapshot(var dart_file, var packageRoot) {
|
|||
* Takes the following arguments:
|
||||
* --output_dir=val The full path to the output_dir.
|
||||
* --dart2js_main=val The path to the dart2js main script relative to root.
|
||||
* --dartdoc_main=val The path to the dartdoc main script relative to root.
|
||||
* --docgen_main=val The path to the docgen main script relative to root.
|
||||
* --package-root=val The package-root used to find packages for the snapshot.
|
||||
*/
|
||||
void main(List<String> arguments) {
|
||||
var validArguments = ["--output_dir", "--dart2js_main", "--dartdoc_main",
|
||||
var validArguments = ["--output_dir", "--dart2js_main",
|
||||
"--docgen_main", "--package_root"];
|
||||
var args = {};
|
||||
for (var argument in arguments) {
|
||||
|
@ -105,7 +100,6 @@ void main(List<String> arguments) {
|
|||
args[argumentSplit[0].substring(2)] = argumentSplit[1];
|
||||
}
|
||||
if (!args.containsKey("dart2js_main")) throw "Please specify dart2js_main";
|
||||
if (!args.containsKey("dartdoc_main")) throw "Please specify dartdoc_main";
|
||||
if (!args.containsKey("docgen_main")) throw "Please specify docgen_main";
|
||||
if (!args.containsKey("output_dir")) throw "Please specify output_dir";
|
||||
if (!args.containsKey("package_root")) throw "Please specify package_root";
|
||||
|
|