Chromium Code Reviews| Index: lib/dartdoc/dartdoc.dart |
| diff --git a/lib/dartdoc/dartdoc.dart b/lib/dartdoc/dartdoc.dart |
| index 90e3b705329c7aaa5ae7c8ac930c373aa3c8dd19..3a46e96fcd9559d16f8d5462ba44514467f2e6ba 100644 |
| --- a/lib/dartdoc/dartdoc.dart |
| +++ b/lib/dartdoc/dartdoc.dart |
| @@ -24,14 +24,17 @@ |
| #import('mirrors/dart2js_mirror.dart', prefix: 'dart2js'); |
| #import('classify.dart'); |
| #import('markdown.dart', prefix: 'md'); |
| -#import('../compiler/implementation/dart2js.dart', prefix: 'dart2js'); |
| #import('../compiler/implementation/scanner/scannerlib.dart', |
| prefix: 'dart2js'); |
| -#import('file_util.dart'); |
| #source('comment_map.dart'); |
| #source('utils.dart'); |
| +// TODO(johnniwinther): Note that the following lines get munged by create-sdk |
| +// to work with the SDK's different file layout. If you change, be sure to test |
|
Bill Hesse
2012/07/19 13:36:19
following line. Or "IN_SDK gets initialized to tr
Johnni Winther
2012/07/19 14:12:25
Done.
|
| +// that dartdoc still works when run from the built SDK directory. |
| +final bool IN_SDK = false; |
| + |
| /** |
| * Generates completely static HTML containing everything you need to browse |
| * the docs. The only client side behavior is trivial stuff like syntax |
| @@ -62,7 +65,7 @@ void main() { |
| // Parse the dartdoc options. |
| bool includeSource; |
| int mode; |
| - String outputDir; |
| + Path outputDir; |
| bool generateAppCache; |
| bool omitGenerationTime; |
| bool verbose; |
| @@ -103,7 +106,7 @@ void main() { |
| default: |
| if (arg.startsWith('--out=')) { |
| - outputDir = arg.substring('--out='.length); |
| + outputDir = new Path.fromNative(arg.substring('--out='.length)); |
| } else { |
| print('Unknown option: $arg'); |
| printUsage(); |
| @@ -113,19 +116,7 @@ void main() { |
| } |
| } |
| - if (args.length == 0) { |
| - print('Provide at least one dart file to process.'); |
| - return; |
| - } |
| - |
| - // TODO(rnystrom): Note that the following lines get munged by create-sdk to |
| - // work with the SDK's different file layout. If you change, be sure to test |
| - // that dartdoc still works when run from the built SDK directory. |
| - final String libPath = joinPaths(scriptDir, '../'); |
| - |
| - // The entrypoint of the library to generate docs for. |
| - // TODO(johnniwinther): Handle absolute/relative paths |
| - final entrypoint = canonicalizePath(args[args.length - 1]); |
| + final entrypoint = new Path.fromNative(args[args.length - 1]); |
| final dartdoc = new Dartdoc(); |
| @@ -144,13 +135,14 @@ void main() { |
| // Compile the client-side code to JS. |
| final clientScript = (dartdoc.mode == MODE_STATIC) ? 'static' : 'live-nav'; |
| - compileScript( |
| - '$scriptDir/client-$clientScript.dart', |
| - '${dartdoc.outputDir}/client-$clientScript.js'); |
| + Future compiled = compileScript( |
| + scriptDir.append('client-$clientScript.dart'), |
| + dartdoc.outputDir.append('client-$clientScript.js')); |
| - final Future filesCopied = copyFiles('$scriptDir/static', dartdoc.outputDir); |
| + Future filesCopied = copyDirectory(scriptDir.append('static'), |
| + dartdoc.outputDir); |
| - Futures.wait([filesCopied]).then((_) { |
| + Futures.wait([compiled, filesCopied]).then((_) { |
| print('Documented ${dartdoc._totalLibraries} libraries, ' |
| '${dartdoc._totalTypes} types, and ' |
| '${dartdoc._totalMembers} members.'); |
| @@ -163,25 +155,25 @@ Usage dartdoc [options] <entrypoint> |
| [options] include |
| --no-code Do not include source code in the documentation. |
| - --mode=static Generates completely static HTML containing |
| - everything you need to browse the docs. The only |
| + --mode=static Generates completely static HTML containing |
| + everything you need to browse the docs. The only |
| client side behavior is trivial stuff like syntax |
| highlighting code. |
| - --mode=live-nav (default) Generated docs do not include baked HTML |
| - navigation. Instead, a single `nav.json` file is |
| + --mode=live-nav (default) Generated docs do not include baked HTML |
| + navigation. Instead, a single `nav.json` file is |
| created and the appropriate navigation is generated |
| client-side by parsing that and building HTML. |
| - This dramatically reduces the generated size of |
| - the HTML since a large fraction of each static page |
| + This dramatically reduces the generated size of |
| + the HTML since a large fraction of each static page |
| is just redundant navigation links. |
| - In this mode, the browser will do a XHR for |
| - nav.json which means that to preview docs locally, |
| + In this mode, the browser will do a XHR for |
| + nav.json which means that to preview docs locally, |
| you will need to enable requesting file:// links in |
| - your browser or run a little local server like |
| + your browser or run a little local server like |
| `python -m SimpleHTTPServer`. |
| - --generate-app-cache Generates the App Cache manifest file, enabling |
| + --generate-app-cache Generates the App Cache manifest file, enabling |
| offline doc viewing. |
| --out=<dir> Generates files into directory <dir>. If omitted |
| @@ -197,21 +189,28 @@ Usage dartdoc [options] <entrypoint> |
| * path to the directory containing `dartdoc.dart`. If you're running a script |
| * that imports dartdoc, it will be the path to that script. |
| */ |
| -String get scriptDir() { |
| - return dirname(new File(new Options().script).fullPathSync()); |
| -} |
| +// TODO(johnniwinther): Convert to final (lazily initialized) variables when |
| +// the feature is supported. |
| +Path get scriptDir() => |
| + new Path.fromNative(new Options().script).directoryPath; |
| + |
| +// TODO(johnniwinther): Trailing slashes matter due to the use of [libPath] as |
| +// a base URI with [Uri.resolve]. |
| +Path get libPath() => IN_SDK |
| + ? scriptDir.append('../dart2js/') |
| + : scriptDir.append('../../'); |
| /** |
| * Deletes and recreates the output directory at [path] if it exists. |
| */ |
| -void cleanOutputDirectory(String path) { |
| - final outputDir = new Directory(path); |
| +void cleanOutputDirectory(Path path) { |
| + final outputDir = new Directory.fromPath(path); |
| if (outputDir.existsSync()) { |
| outputDir.deleteRecursivelySync(); |
| } |
| try { |
| - // TODO(johnniwinther): Hack to avoid 'file already exists' exception thrown |
| + // TODO(3914): Hack to avoid 'file already exists' exception thrown |
| // due to invalid result from dir.existsSync() (probably due to race |
| // conditions). |
| outputDir.createSync(); |
| @@ -227,22 +226,19 @@ void cleanOutputDirectory(String path) { |
| * Note: runs asynchronously, so you won't see any files copied until after the |
| * event loop has had a chance to pump (i.e. after `main()` has returned). |
| */ |
| -Future copyFiles(String from, String to) { |
| +Future copyDirectory(Path from, Path to) { |
| final completer = new Completer(); |
| - final fromDir = new Directory(from); |
| + final fromDir = new Directory.fromPath(from); |
| final lister = fromDir.list(recursive: false); |
| - lister.onFile = (path) { |
| - final name = basename(path); |
| + lister.onFile = (String path) { |
| + final name = new Path.fromNative(path).filename; |
| // TODO(rnystrom): Hackish. Ignore 'hidden' files like .DS_Store. |
| if (name.startsWith('.')) return; |
| - new File(path).readAsBytes().then((bytes) { |
| - final outFile = new File('$to/$name'); |
| - final stream = outFile.openOutputStream(FileMode.WRITE); |
| - stream.write(bytes, copyBuffer: false); |
| - stream.close(); |
| - }); |
| + File fromFile = new File(path); |
| + File toFile = new File.fromPath(to.append(name)); |
| + fromFile.openInputStream().pipe(toFile.openOutputStream()); |
| }; |
| lister.onDone = (done) => completer.complete(true); |
| return completer.future; |
| @@ -252,17 +248,16 @@ Future copyFiles(String from, String to) { |
| * Compiles the given Dart script to a JavaScript file at [jsPath] using the |
| * Dart2js compiler. |
| */ |
| -void compileScript(String dartPath, String jsPath) { |
| - dart2js.compile([ |
| - '--no-colors', |
| - // TODO(johnniwinther): The following lines get munged by create-sdk to |
| - // work with the SDK's different file layout. If you change, be sure to |
| - // test that dartdoc still works when run from the built SDK directory. |
| - '--library-root=${joinPaths(scriptDir, '../../')}', |
| - '--out=$jsPath', |
| - '--throw-on-error', |
| - '--suppress-warnings', |
| - dartPath]); |
| +Future<bool> compileScript(Path dartPath, Path jsPath) { |
| + var completer = new Completer<bool>(); |
| + var compilation = new Compilation(dartPath, libPath); |
| + Future<String> result = compilation.compileToJavaScript(); |
| + result.then((jsCode) { |
| + writeString(new File.fromPath(jsPath), jsCode); |
| + completer.complete(true); |
| + }); |
| + result.handleException((e) => completer.completeException(e)); |
| + return completer.future; |
| } |
| class Dartdoc { |
| @@ -283,7 +278,7 @@ class Dartdoc { |
| bool generateAppCache = false; |
| /** Path to generate HTML files into. */ |
| - String outputDir = 'docs'; |
| + Path outputDir = const Path('docs'); |
| /** |
| * The title used for the overall generated output. Set this to change it. |
| @@ -337,7 +332,7 @@ class Dartdoc { |
| MemberMirror _currentMember; |
| /** The path to the file currently being written to, relative to [outdir]. */ |
| - String _filePath; |
| + Path _filePath; |
| /** The file currently being written to. */ |
| StringBuffer _file; |
| @@ -354,8 +349,8 @@ class Dartdoc { |
| new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]')); |
| md.setImplicitLinkResolver((name) => resolveNameReference(name, |
| - library: _currentLibrary, type: _currentType, |
| - member: _currentMember)); |
| + currentLibrary: _currentLibrary, currentType: _currentType, |
| + currentMember: _currentMember)); |
| } |
| bool includeLibrary(LibraryMirror library) { |
| @@ -383,13 +378,13 @@ class Dartdoc { |
| return content; |
| } |
| - void documentEntryPoint(String entrypoint, String libPath) { |
| + void documentEntryPoint(Path entrypoint, Path libPath) { |
| final compilation = new Compilation(entrypoint, libPath); |
| _document(compilation); |
| } |
| - void documentLibraries(List<String> libraries, String libPath) { |
| - final compilation = new Compilation.library(libraries, libPath); |
| + void documentLibraries(List<Path> libraryList, Path libPath) { |
| + final compilation = new Compilation.library(libraryList, libPath); |
| _document(compilation); |
| } |
| @@ -416,15 +411,15 @@ class Dartdoc { |
| } |
| void startFile(String path) { |
| - _filePath = path; |
| + _filePath = new Path(path); |
| _file = new StringBuffer(); |
| } |
| void endFile() { |
| - final outPath = '$outputDir/$_filePath'; |
| - final dir = new Directory(dirname(outPath)); |
| + final outPath = outputDir.join(_filePath); |
| + final dir = new Directory.fromPath(outPath.directoryPath); |
| if (!dir.existsSync()) { |
| - // TODO(johnniwinther): Hack to avoid 'file already exists' exception |
| + // TODO(3914): Hack to avoid 'file already exists' exception |
| // thrown due to invalid result from dir.existsSync() (probably due to |
| // race conditions). |
| try { |
| @@ -434,7 +429,7 @@ class Dartdoc { |
| } |
| } |
| - writeString(new File(outPath), _file.toString()); |
| + writeString(new File.fromPath(outPath), _file.toString()); |
| _filePath = null; |
| _file = null; |
| } |
| @@ -595,7 +590,7 @@ class Dartdoc { |
| void docLibraryNavigationJson(LibraryMirror library, Map libraryMap) { |
| final types = []; |
| - for (final type in orderByName(library.types().getValues())) { |
| + for (InterfaceMirror type in orderByName(library.types().getValues())) { |
| if (type.isPrivate) continue; |
| final kind = type.isClass ? 'class' : 'interface'; |
| @@ -637,7 +632,7 @@ class Dartdoc { |
| final types = <InterfaceMirror>[]; |
| final exceptions = <InterfaceMirror>[]; |
| - for (final type in orderByName(library.types().getValues())) { |
| + for (InterfaceMirror type in orderByName(library.types().getValues())) { |
| if (type.isPrivate) continue; |
| if (isException(type)) { |
| @@ -702,7 +697,7 @@ class Dartdoc { |
| final interfaces = <InterfaceMirror>[]; |
| final exceptions = <InterfaceMirror>[]; |
| - for (final type in orderByName(library.types().getValues())) { |
| + for (InterfaceMirror type in orderByName(library.types().getValues())) { |
| if (type.isPrivate) continue; |
| if (isException(type)) { |
| @@ -952,7 +947,7 @@ class Dartdoc { |
| final instanceMethods = []; |
| final instanceFields = []; |
| - for (final member in orderByName(host.declaredMembers().getValues())) { |
| + for (MemberMirror member in orderByName(host.declaredMembers().getValues())) { |
| if (member.isPrivate) continue; |
| final methods = member.isStatic ? staticMethods : instanceMethods; |
| @@ -1176,7 +1171,8 @@ class Dartdoc { |
| // TODO(rnystrom): Walks all the way up to root each time. Shouldn't do |
| // this if the paths overlap. |
| - return '${repeat('../', countOccurrences(_filePath, '/'))}$fullPath'; |
| + return '${repeat('../', |
| + countOccurrences(_filePath.toString(), '/'))}$fullPath'; |
| } |
| /** Gets whether or not the given URL is absolute or relative. */ |
| @@ -1194,7 +1190,9 @@ class Dartdoc { |
| /** Gets the URL for the documentation for [type]. */ |
| String typeUrl(ObjectMirror type) { |
| - if (type is LibraryMirror) return '${sanitize(type.simpleName())}.html'; |
| + if (type is LibraryMirror) { |
| + return '${sanitize(type.simpleName())}.html'; |
| + } |
| assert (type is TypeMirror); |
| // Always get the generic type to strip off any type parameters or |
| // arguments. If the type isn't generic, genericType returns `this`, so it |
| @@ -1205,10 +1203,14 @@ class Dartdoc { |
| /** Gets the URL for the documentation for [member]. */ |
| String memberUrl(MemberMirror member) { |
| - final url = typeUrl(member.surroundingDeclaration()); |
| - if (!member.isConstructor) return '$url#${member.simpleName()}'; |
| + String url = typeUrl(member.surroundingDeclaration()); |
| + if (!member.isConstructor) { |
| + return '$url#${member.simpleName()}'; |
| + } |
| assert (member is MethodMirror); |
| - if (member.constructorName == '') return '$url#new:${member.simpleName()}'; |
| + if (member.constructorName == '') { |
| + return '$url#new:${member.simpleName()}'; |
| + } |
| return '$url#new:${member.simpleName()}.${member.constructorName}'; |
| } |
| @@ -1378,9 +1380,9 @@ class Dartdoc { |
| * style it appropriately. |
| */ |
| md.Node resolveNameReference(String name, |
| - [MemberMirror member = null, |
| - ObjectMirror type = null, |
| - LibraryMirror library = null]) { |
| + [MemberMirror currentMember = null, |
| + ObjectMirror currentType = null, |
| + LibraryMirror currentLibrary = null]) { |
| makeLink(String href) { |
| final anchor = new md.Element.text('a', name); |
| anchor.attributes['href'] = relativePath(href); |
| @@ -1389,8 +1391,8 @@ class Dartdoc { |
| } |
| // See if it's a parameter of the current method. |
| - if (member is MethodMirror) { |
| - for (final parameter in member.parameters()) { |
| + if (currentMember is MethodMirror) { |
| + for (final parameter in currentMember.parameters()) { |
| if (parameter.simpleName() == name) { |
| final element = new md.Element.text('span', name); |
| element.attributes['class'] = 'param'; |
| @@ -1400,25 +1402,25 @@ class Dartdoc { |
| } |
| // See if it's another member of the current type. |
| - if (type != null) { |
| - final member = findMirror(type.declaredMembers(), name); |
| - if (member != null) { |
| - return makeLink(memberUrl(member)); |
| + if (currentType != null) { |
| + final foundMember = findMirror(currentType.declaredMembers(), name); |
| + if (foundMember != null) { |
| + return makeLink(memberUrl(foundMember)); |
| } |
| } |
| // See if it's another type or a member of another type in the current |
| // library. |
| - if (library != null) { |
| + if (currentLibrary != null) { |
| // See if it's a constructor |
| final constructorLink = (() { |
| final match = |
| new RegExp(@'new ([\w$]+)(?:\.([\w$]+))?').firstMatch(name); |
| if (match == null) return; |
| - final type = findMirror(library.types(), match[1]); |
| - if (type == null) return; |
| + InterfaceMirror foundtype = findMirror(currentLibrary.types(), match[1]); |
| + if (foundtype == null) return; |
| final constructor = |
| - findMirror(type.constructors(), |
| + findMirror(foundtype.constructors(), |
| match[2] == null ? '' : match[2]); |
| if (constructor == null) return; |
| return makeLink(memberUrl(constructor)); |
| @@ -1429,23 +1431,23 @@ class Dartdoc { |
| final foreignMemberLink = (() { |
| final match = new RegExp(@'([\w$]+)\.([\w$]+)').firstMatch(name); |
| if (match == null) return; |
| - final type = findMirror(library.types(), match[1]); |
| - if (type == null) return; |
| - final member = findMirror(type.declaredMembers(), match[2]); |
| - if (member == null) return; |
| - return makeLink(memberUrl(member)); |
| + InterfaceMirror foundtype = findMirror(currentLibrary.types(), match[1]); |
| + if (foundtype == null) return; |
| + MemberMirror foundMember = findMirror(foundtype.declaredMembers(), match[2]); |
| + if (foundMember == null) return; |
| + return makeLink(memberUrl(foundMember)); |
| })(); |
| if (foreignMemberLink != null) return foreignMemberLink; |
| - final type = findMirror(library.types(), name); |
| - if (type != null) { |
| - return makeLink(typeUrl(type)); |
| + InterfaceMirror foundType = findMirror(currentLibrary.types(), name); |
| + if (foundType != null) { |
| + return makeLink(typeUrl(foundType)); |
| } |
| // See if it's a top-level member in the current library. |
| - final member = findMirror(library.declaredMembers(), name); |
| - if (member != null) { |
| - return makeLink(memberUrl(member)); |
| + MemberMirror foundMember = findMirror(currentLibrary.declaredMembers(), name); |
| + if (foundMember != null) { |
| + return makeLink(memberUrl(foundMember)); |
| } |
| } |
| @@ -1457,24 +1459,37 @@ class Dartdoc { |
| } |
| generateAppCacheManifest() { |
| - print('Generating app cache manifest from output $outputDir'); |
| + if (verbose) { |
| + print('Generating app cache manifest from output $outputDir'); |
| + } |
| startFile('appcache.manifest'); |
| write("CACHE MANIFEST\n\n"); |
| write("# VERSION: ${new Date.now()}\n\n"); |
| write("NETWORK:\n*\n\n"); |
| write("CACHE:\n"); |
| - var toCache = new Directory(outputDir); |
| - var pathPrefix = new File(outputDir).fullPathSync(); |
| - var pathPrefixLength = pathPrefix.length; |
| - toCache.onFile = (filename) { |
| + var toCache = new Directory.fromPath(outputDir); |
| + var toCacheLister = toCache.list(recursive: true); |
| + toCacheLister.onFile = (filename) { |
| if (filename.endsWith('appcache.manifest')) { |
| return; |
| } |
| - var relativePath = filename.substring(pathPrefixLength + 1); |
| - write("$relativePath\n"); |
| + // TODO(johnniwinther): If [outputDir] has trailing slashes, [filename] |
| + // contains double (back)slashes for files in the immediate [toCache] |
| + // directory. These are not handled by [relativeTo] thus |
| + // wrongfully producing the path `/foo.html` for a file `foo.html` in |
| + // [toCache]. |
| + // |
| + // This can be handled in two ways. 1) By ensuring that |
| + // [Directory.fromPath] does not receive a path with a trailing slash, or |
| + // better, by making [Directory.fromPath] handle such trailing slashes. |
| + // 2) By ensuring that [filePath] does not have double slashes before |
| + // calling [relativeTo], or better, by making [relativeTo] handle double |
| + // slashes correctly. |
| + Path filePath = new Path.fromNative(filename).canonicalize(); |
| + Path relativeFilePath = filePath.relativeTo(outputDir); |
| + write("$relativeFilePath\n"); |
| }; |
| - toCache.onDone = (done) => endFile(); |
| - toCache.list(recursive: true); |
| + toCacheLister.onDone = (done) => endFile(); |
| } |
| /** |