| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * To generate docs for a library, run this script with the path to an | 6 * To generate docs for a library, run this script with the path to an |
| 7 * entrypoint .dart file, like: | 7 * entrypoint .dart file, like: |
| 8 * | 8 * |
| 9 * $ dart dartdoc.dart foo.dart | 9 * $ dart dartdoc.dart foo.dart |
| 10 * | 10 * |
| 11 * This will create a "docs" directory with the docs for your libraries. To | 11 * This will create a "docs" directory with the docs for your libraries. To |
| 12 * create these beautiful docs, dartdoc parses your library and every library | 12 * create these beautiful docs, dartdoc parses your library and every library |
| 13 * it imports (recursively). From each library, it parses all classes and | 13 * it imports (recursively). From each library, it parses all classes and |
| 14 * members, finds the associated doc comments and builds crosslinked docs from | 14 * members, finds the associated doc comments and builds crosslinked docs from |
| 15 * them. | 15 * them. |
| 16 */ | 16 */ |
| 17 #library('dartdoc'); | 17 #library('dartdoc'); |
| 18 | 18 |
| 19 #import('dart:io'); | 19 #import('dart:io'); |
| 20 #import('dart:uri'); | 20 #import('dart:uri'); |
| 21 #import('dart:json'); | 21 #import('dart:json'); |
| 22 #import('mirrors/mirrors.dart'); | 22 #import('mirrors/mirrors.dart'); |
| 23 #import('mirrors/mirrors_util.dart'); | 23 #import('mirrors/mirrors_util.dart'); |
| 24 #import('mirrors/dart2js_mirror.dart', prefix: 'dart2js'); | 24 #import('mirrors/dart2js_mirror.dart', prefix: 'dart2js'); |
| 25 #import('classify.dart'); | 25 #import('classify.dart'); |
| 26 #import('markdown.dart', prefix: 'md'); | 26 #import('markdown.dart', prefix: 'md'); |
| 27 #import('../compiler/implementation/dart2js.dart', prefix: 'dart2js'); | |
| 28 #import('../compiler/implementation/scanner/scannerlib.dart', | 27 #import('../compiler/implementation/scanner/scannerlib.dart', |
| 29 prefix: 'dart2js'); | 28 prefix: 'dart2js'); |
| 30 #import('file_util.dart'); | |
| 31 | 29 |
| 32 #source('comment_map.dart'); | 30 #source('comment_map.dart'); |
| 33 #source('utils.dart'); | 31 #source('utils.dart'); |
| 34 | 32 |
| 33 // TODO(johnniwinther): Note that [IN_SDK] gets initialized to true when this |
| 34 // file is modified by the SDK deployment script. If you change, be sure to test |
| 35 // that dartdoc still works when run from the built SDK directory. |
| 36 final bool IN_SDK = false; |
| 37 |
| 35 /** | 38 /** |
| 36 * Generates completely static HTML containing everything you need to browse | 39 * Generates completely static HTML containing everything you need to browse |
| 37 * the docs. The only client side behavior is trivial stuff like syntax | 40 * the docs. The only client side behavior is trivial stuff like syntax |
| 38 * highlighting code. | 41 * highlighting code. |
| 39 */ | 42 */ |
| 40 final MODE_STATIC = 0; | 43 final MODE_STATIC = 0; |
| 41 | 44 |
| 42 /** | 45 /** |
| 43 * Generated docs do not include baked HTML navigation. Instead, a single | 46 * Generated docs do not include baked HTML navigation. Instead, a single |
| 44 * `nav.json` file is created and the appropriate navigation is generated | 47 * `nav.json` file is created and the appropriate navigation is generated |
| (...skipping 10 matching lines...) Expand all Loading... |
| 55 | 58 |
| 56 /** | 59 /** |
| 57 * Run this from the `lib/dartdoc` directory. | 60 * Run this from the `lib/dartdoc` directory. |
| 58 */ | 61 */ |
| 59 void main() { | 62 void main() { |
| 60 final args = new Options().arguments; | 63 final args = new Options().arguments; |
| 61 | 64 |
| 62 // Parse the dartdoc options. | 65 // Parse the dartdoc options. |
| 63 bool includeSource; | 66 bool includeSource; |
| 64 int mode; | 67 int mode; |
| 65 String outputDir; | 68 Path outputDir; |
| 66 bool generateAppCache; | 69 bool generateAppCache; |
| 67 bool omitGenerationTime; | 70 bool omitGenerationTime; |
| 68 bool verbose; | 71 bool verbose; |
| 69 | 72 |
| 70 if (args.isEmpty()) { | 73 if (args.isEmpty()) { |
| 71 print('No arguments provided.'); | 74 print('No arguments provided.'); |
| 72 printUsage(); | 75 printUsage(); |
| 73 return; | 76 return; |
| 74 } | 77 } |
| 75 | 78 |
| (...skipping 20 matching lines...) Expand all Loading... |
| 96 | 99 |
| 97 case '--omit-generation-time': | 100 case '--omit-generation-time': |
| 98 omitGenerationTime = true; | 101 omitGenerationTime = true; |
| 99 break; | 102 break; |
| 100 case '--verbose': | 103 case '--verbose': |
| 101 verbose = true; | 104 verbose = true; |
| 102 break; | 105 break; |
| 103 | 106 |
| 104 default: | 107 default: |
| 105 if (arg.startsWith('--out=')) { | 108 if (arg.startsWith('--out=')) { |
| 106 outputDir = arg.substring('--out='.length); | 109 outputDir = new Path.fromNative(arg.substring('--out='.length)); |
| 107 } else { | 110 } else { |
| 108 print('Unknown option: $arg'); | 111 print('Unknown option: $arg'); |
| 109 printUsage(); | 112 printUsage(); |
| 110 return; | 113 return; |
| 111 } | 114 } |
| 112 break; | 115 break; |
| 113 } | 116 } |
| 114 } | 117 } |
| 115 | 118 |
| 116 if (args.length == 0) { | 119 final entrypoint = new Path.fromNative(args[args.length - 1]); |
| 117 print('Provide at least one dart file to process.'); | |
| 118 return; | |
| 119 } | |
| 120 | |
| 121 // TODO(rnystrom): Note that the following lines get munged by create-sdk to | |
| 122 // work with the SDK's different file layout. If you change, be sure to test | |
| 123 // that dartdoc still works when run from the built SDK directory. | |
| 124 final String libPath = joinPaths(scriptDir, '../'); | |
| 125 | |
| 126 // The entrypoint of the library to generate docs for. | |
| 127 // TODO(johnniwinther): Handle absolute/relative paths | |
| 128 final entrypoint = canonicalizePath(args[args.length - 1]); | |
| 129 | 120 |
| 130 final dartdoc = new Dartdoc(); | 121 final dartdoc = new Dartdoc(); |
| 131 | 122 |
| 132 if (includeSource != null) dartdoc.includeSource = includeSource; | 123 if (includeSource != null) dartdoc.includeSource = includeSource; |
| 133 if (mode != null) dartdoc.mode = mode; | 124 if (mode != null) dartdoc.mode = mode; |
| 134 if (outputDir != null) dartdoc.outputDir = outputDir; | 125 if (outputDir != null) dartdoc.outputDir = outputDir; |
| 135 if (generateAppCache != null) dartdoc.generateAppCache = generateAppCache; | 126 if (generateAppCache != null) dartdoc.generateAppCache = generateAppCache; |
| 136 if (omitGenerationTime != null) { | 127 if (omitGenerationTime != null) { |
| 137 dartdoc.omitGenerationTime = omitGenerationTime; | 128 dartdoc.omitGenerationTime = omitGenerationTime; |
| 138 } | 129 } |
| 139 if (verbose != null) dartdoc.verbose = verbose; | 130 if (verbose != null) dartdoc.verbose = verbose; |
| 140 | 131 |
| 141 cleanOutputDirectory(dartdoc.outputDir); | 132 cleanOutputDirectory(dartdoc.outputDir); |
| 142 | 133 |
| 143 dartdoc.documentEntryPoint(entrypoint, libPath); | 134 dartdoc.documentEntryPoint(entrypoint, libPath); |
| 144 | 135 |
| 145 // Compile the client-side code to JS. | 136 // Compile the client-side code to JS. |
| 146 final clientScript = (dartdoc.mode == MODE_STATIC) ? 'static' : 'live-nav'; | 137 final clientScript = (dartdoc.mode == MODE_STATIC) ? 'static' : 'live-nav'; |
| 147 compileScript( | 138 Future compiled = compileScript( |
| 148 '$scriptDir/client-$clientScript.dart', | 139 scriptDir.append('client-$clientScript.dart'), |
| 149 '${dartdoc.outputDir}/client-$clientScript.js'); | 140 dartdoc.outputDir.append('client-$clientScript.js')); |
| 150 | 141 |
| 151 final Future filesCopied = copyFiles('$scriptDir/static', dartdoc.outputDir); | 142 Future filesCopied = copyDirectory(scriptDir.append('static'), |
| 143 dartdoc.outputDir); |
| 152 | 144 |
| 153 Futures.wait([filesCopied]).then((_) { | 145 Futures.wait([compiled, filesCopied]).then((_) { |
| 154 print('Documented ${dartdoc._totalLibraries} libraries, ' | 146 print('Documented ${dartdoc._totalLibraries} libraries, ' |
| 155 '${dartdoc._totalTypes} types, and ' | 147 '${dartdoc._totalTypes} types, and ' |
| 156 '${dartdoc._totalMembers} members.'); | 148 '${dartdoc._totalMembers} members.'); |
| 157 }); | 149 }); |
| 158 } | 150 } |
| 159 | 151 |
| 160 void printUsage() { | 152 void printUsage() { |
| 161 print(''' | 153 print(''' |
| 162 Usage dartdoc [options] <entrypoint> | 154 Usage dartdoc [options] <entrypoint> |
| 163 [options] include | 155 [options] include |
| 164 --no-code Do not include source code in the documentation. | 156 --no-code Do not include source code in the documentation. |
| 165 | 157 |
| 166 --mode=static Generates completely static HTML containing | 158 --mode=static Generates completely static HTML containing |
| 167 everything you need to browse the docs. The only | 159 everything you need to browse the docs. The only |
| 168 client side behavior is trivial stuff like syntax | 160 client side behavior is trivial stuff like syntax |
| 169 highlighting code. | 161 highlighting code. |
| 170 | 162 |
| 171 --mode=live-nav (default) Generated docs do not include baked HTML | 163 --mode=live-nav (default) Generated docs do not include baked HTML |
| 172 navigation. Instead, a single `nav.json` file is | 164 navigation. Instead, a single `nav.json` file is |
| 173 created and the appropriate navigation is generated | 165 created and the appropriate navigation is generated |
| 174 client-side by parsing that and building HTML. | 166 client-side by parsing that and building HTML. |
| 175 This dramatically reduces the generated size of | 167 This dramatically reduces the generated size of |
| 176 the HTML since a large fraction of each static page
| 168 the HTML since a large fraction of each static page |
| 177 is just redundant navigation links. | 169 is just redundant navigation links. |
| 178 In this mode, the browser will do a XHR for | 170 In this mode, the browser will do a XHR for |
| 179 nav.json which means that to preview docs locally, | 171 nav.json which means that to preview docs locally, |
| 180 you will need to enable requesting file:// links in | 172 you will need to enable requesting file:// links in |
| 181 your browser or run a little local server like | 173 your browser or run a little local server like |
| 182 `python -m SimpleHTTPServer`. | 174 `python -m SimpleHTTPServer`. |
| 183 | 175 |
| 184 --generate-app-cache Generates the App Cache manifest file, enabling | 176 --generate-app-cache Generates the App Cache manifest file, enabling |
| 185 offline doc viewing. | 177 offline doc viewing. |
| 186 | 178 |
| 187 --out=<dir> Generates files into directory <dir>. If omitted | 179 --out=<dir> Generates files into directory <dir>. If omitted |
| 188 the files are generated into ./docs/ | 180 the files are generated into ./docs/ |
| 189 | 181 |
| 190 --verbose Print verbose information during generation. | 182 --verbose Print verbose information during generation. |
| 191 '''); | 183 '''); |
| 192 } | 184 } |
| 193 | 185 |
| 194 /** | 186 /** |
| 195 * Gets the full path to the directory containing the entrypoint of the current | 187 * Gets the full path to the directory containing the entrypoint of the current |
| 196 * script. In other words, if you invoked dartdoc, directly, it will be the | 188 * script. In other words, if you invoked dartdoc, directly, it will be the |
| 197 * path to the directory containing `dartdoc.dart`. If you're running a script | 189 * path to the directory containing `dartdoc.dart`. If you're running a script |
| 198 * that imports dartdoc, it will be the path to that script. | 190 * that imports dartdoc, it will be the path to that script. |
| 199 */ | 191 */ |
| 200 String get scriptDir() { | 192 // TODO(johnniwinther): Convert to final (lazily initialized) variables when |
| 201 return dirname(new File(new Options().script).fullPathSync()); | 193 // the feature is supported. |
| 202 } | 194 Path get scriptDir() => |
| 195 new Path.fromNative(new Options().script).directoryPath; |
| 196 |
| 197 // TODO(johnniwinther): Trailing slashes matter due to the use of [libPath] as |
| 198 // a base URI with [Uri.resolve]. |
| 199 Path get libPath() => IN_SDK |
| 200 ? scriptDir.append('../dart2js/') |
| 201 : scriptDir.append('../../'); |
| 203 | 202 |
| 204 /** | 203 /** |
| 205 * Deletes and recreates the output directory at [path] if it exists. | 204 * Deletes and recreates the output directory at [path] if it exists. |
| 206 */ | 205 */ |
| 207 void cleanOutputDirectory(String path) { | 206 void cleanOutputDirectory(Path path) { |
| 208 final outputDir = new Directory(path); | 207 final outputDir = new Directory.fromPath(path); |
| 209 if (outputDir.existsSync()) { | 208 if (outputDir.existsSync()) { |
| 210 outputDir.deleteRecursivelySync(); | 209 outputDir.deleteRecursivelySync(); |
| 211 } | 210 } |
| 212 | 211 |
| 213 try { | 212 try { |
| 214 // TODO(johnniwinther): Hack to avoid 'file already exists' exception thrown | 213 // TODO(3914): Hack to avoid 'file already exists' exception thrown |
| 215 // due to invalid result from dir.existsSync() (probably due to race | 214 // due to invalid result from dir.existsSync() (probably due to race |
| 216 // conditions). | 215 // conditions). |
| 217 outputDir.createSync(); | 216 outputDir.createSync(); |
| 218 } catch (DirectoryIOException e) { | 217 } catch (DirectoryIOException e) { |
| 219 // Ignore. | 218 // Ignore. |
| 220 } | 219 } |
| 221 } | 220 } |
| 222 | 221 |
| 223 /** | 222 /** |
| 224 * Copies all of the files in the directory [from] to [to]. Does *not* | 223 * Copies all of the files in the directory [from] to [to]. Does *not* |
| 225 * recursively copy subdirectories. | 224 * recursively copy subdirectories. |
| 226 * | 225 * |
| 227 * Note: runs asynchronously, so you won't see any files copied until after the | 226 * Note: runs asynchronously, so you won't see any files copied until after the |
| 228 * event loop has had a chance to pump (i.e. after `main()` has returned). | 227 * event loop has had a chance to pump (i.e. after `main()` has returned). |
| 229 */ | 228 */ |
| 230 Future copyFiles(String from, String to) { | 229 Future copyDirectory(Path from, Path to) { |
| 231 final completer = new Completer(); | 230 final completer = new Completer(); |
| 232 final fromDir = new Directory(from); | 231 final fromDir = new Directory.fromPath(from); |
| 233 final lister = fromDir.list(recursive: false); | 232 final lister = fromDir.list(recursive: false); |
| 234 | 233 |
| 235 lister.onFile = (path) { | 234 lister.onFile = (String path) { |
| 236 final name = basename(path); | 235 final name = new Path.fromNative(path).filename; |
| 237 // TODO(rnystrom): Hackish. Ignore 'hidden' files like .DS_Store. | 236 // TODO(rnystrom): Hackish. Ignore 'hidden' files like .DS_Store. |
| 238 if (name.startsWith('.')) return; | 237 if (name.startsWith('.')) return; |
| 239 | 238 |
| 240 new File(path).readAsBytes().then((bytes) { | 239 File fromFile = new File(path); |
| 241 final outFile = new File('$to/$name'); | 240 File toFile = new File.fromPath(to.append(name)); |
| 242 final stream = outFile.openOutputStream(FileMode.WRITE); | 241 fromFile.openInputStream().pipe(toFile.openOutputStream()); |
| 243 stream.write(bytes, copyBuffer: false); | |
| 244 stream.close(); | |
| 245 }); | |
| 246 }; | 242 }; |
| 247 lister.onDone = (done) => completer.complete(true); | 243 lister.onDone = (done) => completer.complete(true); |
| 248 return completer.future; | 244 return completer.future; |
| 249 } | 245 } |
| 250 | 246 |
| 251 /** | 247 /** |
| 252 * Compiles the given Dart script to a JavaScript file at [jsPath] using the | 248 * Compiles the given Dart script to a JavaScript file at [jsPath] using the |
| 253 * Dart2js compiler. | 249 * Dart2js compiler. |
| 254 */ | 250 */ |
| 255 void compileScript(String dartPath, String jsPath) { | 251 Future<bool> compileScript(Path dartPath, Path jsPath) { |
| 256 dart2js.compile([ | 252 var completer = new Completer<bool>(); |
| 257 '--no-colors', | 253 var compilation = new Compilation(dartPath, libPath); |
| 258 // TODO(johnniwinther): The following lines get munged by create-sdk to | 254 Future<String> result = compilation.compileToJavaScript(); |
| 259 // work with the SDK's different file layout. If you change, be sure to | 255 result.then((jsCode) { |
| 260 // test that dartdoc still works when run from the built SDK directory. | 256 writeString(new File.fromPath(jsPath), jsCode); |
| 261 '--library-root=${joinPaths(scriptDir, '../../')}', | 257 completer.complete(true); |
| 262 '--out=$jsPath', | 258 }); |
| 263 '--throw-on-error', | 259 result.handleException((e) => completer.completeException(e)); |
| 264 '--suppress-warnings', | 260 return completer.future; |
| 265 dartPath]); | |
| 266 } | 261 } |
| 267 | 262 |
| 268 class Dartdoc { | 263 class Dartdoc { |
| 269 | 264 |
| 270 /** Set to `false` to not include the source code in the generated docs. */ | 265 /** Set to `false` to not include the source code in the generated docs. */ |
| 271 bool includeSource = true; | 266 bool includeSource = true; |
| 272 | 267 |
| 273 /** | 268 /** |
| 274 * Dartdoc can generate docs in a few different ways based on how dynamic you | 269 * Dartdoc can generate docs in a few different ways based on how dynamic you |
| 275 * want the client-side behavior to be. The value for this should be one of | 270 * want the client-side behavior to be. The value for this should be one of |
| 276 * the `MODE_` constants. | 271 * the `MODE_` constants. |
| 277 */ | 272 */ |
| 278 int mode = MODE_LIVE_NAV; | 273 int mode = MODE_LIVE_NAV; |
| 279 | 274 |
| 280 /** | 275 /** |
| 281 * Generates the App Cache manifest file, enabling offline doc viewing. | 276 * Generates the App Cache manifest file, enabling offline doc viewing. |
| 282 */ | 277 */ |
| 283 bool generateAppCache = false; | 278 bool generateAppCache = false; |
| 284 | 279 |
| 285 /** Path to generate HTML files into. */ | 280 /** Path to generate HTML files into. */ |
| 286 String outputDir = 'docs'; | 281 Path outputDir = const Path('docs'); |
| 287 | 282 |
| 288 /** | 283 /** |
| 289 * The title used for the overall generated output. Set this to change it. | 284 * The title used for the overall generated output. Set this to change it. |
| 290 */ | 285 */ |
| 291 String mainTitle = 'Dart Documentation'; | 286 String mainTitle = 'Dart Documentation'; |
| 292 | 287 |
| 293 /** | 288 /** |
| 294 * The URL that the Dart logo links to. Defaults "index.html", the main | 289 * The URL that the Dart logo links to. Defaults "index.html", the main |
| 295 * page for the generated docs, but can be anything. | 290 * page for the generated docs, but can be anything. |
| 296 */ | 291 */ |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 330 /** The library that we're currently generating docs for. */ | 325 /** The library that we're currently generating docs for. */ |
| 331 LibraryMirror _currentLibrary; | 326 LibraryMirror _currentLibrary; |
| 332 | 327 |
| 333 /** The type that we're currently generating docs for. */ | 328 /** The type that we're currently generating docs for. */ |
| 334 InterfaceMirror _currentType; | 329 InterfaceMirror _currentType; |
| 335 | 330 |
| 336 /** The member that we're currently generating docs for. */ | 331 /** The member that we're currently generating docs for. */ |
| 337 MemberMirror _currentMember; | 332 MemberMirror _currentMember; |
| 338 | 333 |
| 339 /** The path to the file currently being written to, relative to [outdir]. */ | 334 /** The path to the file currently being written to, relative to [outdir]. */ |
| 340 String _filePath; | 335 Path _filePath; |
| 341 | 336 |
| 342 /** The file currently being written to. */ | 337 /** The file currently being written to. */ |
| 343 StringBuffer _file; | 338 StringBuffer _file; |
| 344 | 339 |
| 345 int _totalLibraries = 0; | 340 int _totalLibraries = 0; |
| 346 int _totalTypes = 0; | 341 int _totalTypes = 0; |
| 347 int _totalMembers = 0; | 342 int _totalMembers = 0; |
| 348 | 343 |
| 349 Dartdoc() | 344 Dartdoc() |
| 350 : _comments = new CommentMap() { | 345 : _comments = new CommentMap() { |
| 351 // Patch in support for [:...:]-style code to the markdown parser. | 346 // Patch in support for [:...:]-style code to the markdown parser. |
| 352 // TODO(rnystrom): Markdown already has syntax for this. Phase this out? | 347 // TODO(rnystrom): Markdown already has syntax for this. Phase this out? |
| 353 md.InlineParser.syntaxes.insertRange(0, 1, | 348 md.InlineParser.syntaxes.insertRange(0, 1, |
| 354 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]')); | 349 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]')); |
| 355 | 350 |
| 356 md.setImplicitLinkResolver((name) => resolveNameReference(name, | 351 md.setImplicitLinkResolver((name) => resolveNameReference(name, |
| 357 library: _currentLibrary, type: _currentType, | 352 currentLibrary: _currentLibrary, currentType: _currentType, |
| 358 member: _currentMember)); | 353 currentMember: _currentMember)); |
| 359 } | 354 } |
| 360 | 355 |
| 361 bool includeLibrary(LibraryMirror library) { | 356 bool includeLibrary(LibraryMirror library) { |
| 362 if (libraries != null) { | 357 if (libraries != null) { |
| 363 return libraries.indexOf(library.simpleName()) != -1; | 358 return libraries.indexOf(library.simpleName()) != -1; |
| 364 } | 359 } |
| 365 return true; | 360 return true; |
| 366 } | 361 } |
| 367 | 362 |
| 368 String get footerContent(){ | 363 String get footerContent(){ |
| 369 var footerItems = []; | 364 var footerItems = []; |
| 370 if(!omitGenerationTime) { | 365 if(!omitGenerationTime) { |
| 371 footerItems.add("This page was generated at ${new Date.now()}"); | 366 footerItems.add("This page was generated at ${new Date.now()}"); |
| 372 } | 367 } |
| 373 if(footerText != null) { | 368 if(footerText != null) { |
| 374 footerItems.add(footerText); | 369 footerItems.add(footerText); |
| 375 } | 370 } |
| 376 var content = ''; | 371 var content = ''; |
| 377 for (int i = 0; i < footerItems.length; i++) { | 372 for (int i = 0; i < footerItems.length; i++) { |
| 378 if(i > 0){ | 373 if(i > 0){ |
| 379 content = content.concat('\n'); | 374 content = content.concat('\n'); |
| 380 } | 375 } |
| 381 content = content.concat('<div>${footerItems[i]}</div>'); | 376 content = content.concat('<div>${footerItems[i]}</div>'); |
| 382 } | 377 } |
| 383 return content; | 378 return content; |
| 384 } | 379 } |
| 385 | 380 |
| 386 void documentEntryPoint(String entrypoint, String libPath) { | 381 void documentEntryPoint(Path entrypoint, Path libPath) { |
| 387 final compilation = new Compilation(entrypoint, libPath); | 382 final compilation = new Compilation(entrypoint, libPath); |
| 388 _document(compilation); | 383 _document(compilation); |
| 389 } | 384 } |
| 390 | 385 |
| 391 void documentLibraries(List<String> libraries, String libPath) { | 386 void documentLibraries(List<Path> libraryList, Path libPath) { |
| 392 final compilation = new Compilation.library(libraries, libPath); | 387 final compilation = new Compilation.library(libraryList, libPath); |
| 393 _document(compilation); | 388 _document(compilation); |
| 394 } | 389 } |
| 395 | 390 |
| 396 void _document(Compilation compilation) { | 391 void _document(Compilation compilation) { |
| 397 // Sort the libraries by name (not key). | 392 // Sort the libraries by name (not key). |
| 398 _sortedLibraries = new List<LibraryMirror>.from( | 393 _sortedLibraries = new List<LibraryMirror>.from( |
| 399 compilation.mirrors().libraries().getValues().filter(includeLibrary)); | 394 compilation.mirrors().libraries().getValues().filter(includeLibrary)); |
| 400 _sortedLibraries.sort((x, y) { | 395 _sortedLibraries.sort((x, y) { |
| 401 return x.simpleName().toUpperCase().compareTo( | 396 return x.simpleName().toUpperCase().compareTo( |
| 402 y.simpleName().toUpperCase()); | 397 y.simpleName().toUpperCase()); |
| 403 }); | 398 }); |
| 404 | 399 |
| 405 // Generate the docs. | 400 // Generate the docs. |
| 406 if (mode == MODE_LIVE_NAV) docNavigationJson(); | 401 if (mode == MODE_LIVE_NAV) docNavigationJson(); |
| 407 | 402 |
| 408 docIndex(); | 403 docIndex(); |
| 409 for (final library in _sortedLibraries) { | 404 for (final library in _sortedLibraries) { |
| 410 docLibrary(library); | 405 docLibrary(library); |
| 411 } | 406 } |
| 412 | 407 |
| 413 if (generateAppCache) { | 408 if (generateAppCache) { |
| 414 generateAppCacheManifest(); | 409 generateAppCacheManifest(); |
| 415 } | 410 } |
| 416 } | 411 } |
| 417 | 412 |
| 418 void startFile(String path) { | 413 void startFile(String path) { |
| 419 _filePath = path; | 414 _filePath = new Path(path); |
| 420 _file = new StringBuffer(); | 415 _file = new StringBuffer(); |
| 421 } | 416 } |
| 422 | 417 |
| 423 void endFile() { | 418 void endFile() { |
| 424 final outPath = '$outputDir/$_filePath'; | 419 final outPath = outputDir.join(_filePath); |
| 425 final dir = new Directory(dirname(outPath)); | 420 final dir = new Directory.fromPath(outPath.directoryPath); |
| 426 if (!dir.existsSync()) { | 421 if (!dir.existsSync()) { |
| 427 // TODO(johnniwinther): Hack to avoid 'file already exists' exception | 422 // TODO(3914): Hack to avoid 'file already exists' exception |
| 428 // thrown due to invalid result from dir.existsSync() (probably due to | 423 // thrown due to invalid result from dir.existsSync() (probably due to |
| 429 // race conditions). | 424 // race conditions). |
| 430 try { | 425 try { |
| 431 dir.createSync(); | 426 dir.createSync(); |
| 432 } catch (DirectoryIOException e) { | 427 } catch (DirectoryIOException e) { |
| 433 // Ignore. | 428 // Ignore. |
| 434 } | 429 } |
| 435 } | 430 } |
| 436 | 431 |
| 437 writeString(new File(outPath), _file.toString()); | 432 writeString(new File.fromPath(outPath), _file.toString()); |
| 438 _filePath = null; | 433 _filePath = null; |
| 439 _file = null; | 434 _file = null; |
| 440 } | 435 } |
| 441 | 436 |
| 442 void write(String s) { | 437 void write(String s) { |
| 443 _file.add(s); | 438 _file.add(s); |
| 444 } | 439 } |
| 445 | 440 |
| 446 void writeln(String s) { | 441 void writeln(String s) { |
| 447 write(s); | 442 write(s); |
| (...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 588 docLibraryNavigationJson(library, libraryMap); | 583 docLibraryNavigationJson(library, libraryMap); |
| 589 } | 584 } |
| 590 | 585 |
| 591 writeln(JSON.stringify(libraryMap)); | 586 writeln(JSON.stringify(libraryMap)); |
| 592 endFile(); | 587 endFile(); |
| 593 } | 588 } |
| 594 | 589 |
| 595 void docLibraryNavigationJson(LibraryMirror library, Map libraryMap) { | 590 void docLibraryNavigationJson(LibraryMirror library, Map libraryMap) { |
| 596 final types = []; | 591 final types = []; |
| 597 | 592 |
| 598 for (final type in orderByName(library.types().getValues())) { | 593 for (InterfaceMirror type in orderByName(library.types().getValues())) { |
| 599 if (type.isPrivate) continue; | 594 if (type.isPrivate) continue; |
| 600 | 595 |
| 601 final kind = type.isClass ? 'class' : 'interface'; | 596 final kind = type.isClass ? 'class' : 'interface'; |
| 602 final url = typeUrl(type); | 597 final url = typeUrl(type); |
| 603 types.add({ 'name': typeName(type), 'kind': kind, 'url': url }); | 598 types.add({ 'name': typeName(type), 'kind': kind, 'url': url }); |
| 604 } | 599 } |
| 605 | 600 |
| 606 libraryMap[library.simpleName()] = types; | 601 libraryMap[library.simpleName()] = types; |
| 607 } | 602 } |
| 608 | 603 |
| (...skipping 21 matching lines...) Expand all Loading... |
| 630 | 625 |
| 631 writeln('</div>'); | 626 writeln('</div>'); |
| 632 } | 627 } |
| 633 | 628 |
| 634 /** Writes the navigation for the types contained by the given library. */ | 629 /** Writes the navigation for the types contained by the given library. */ |
| 635 void docLibraryNavigation(LibraryMirror library) { | 630 void docLibraryNavigation(LibraryMirror library) { |
| 636 // Show the exception types separately. | 631 // Show the exception types separately. |
| 637 final types = <InterfaceMirror>[]; | 632 final types = <InterfaceMirror>[]; |
| 638 final exceptions = <InterfaceMirror>[]; | 633 final exceptions = <InterfaceMirror>[]; |
| 639 | 634 |
| 640 for (final type in orderByName(library.types().getValues())) { | 635 for (InterfaceMirror type in orderByName(library.types().getValues())) { |
| 641 if (type.isPrivate) continue; | 636 if (type.isPrivate) continue; |
| 642 | 637 |
| 643 if (isException(type)) { | 638 if (isException(type)) { |
| 644 exceptions.add(type); | 639 exceptions.add(type); |
| 645 } else { | 640 } else { |
| 646 types.add(type); | 641 types.add(type); |
| 647 } | 642 } |
| 648 } | 643 } |
| 649 | 644 |
| 650 if ((types.length == 0) && (exceptions.length == 0)) return; | 645 if ((types.length == 0) && (exceptions.length == 0)) return; |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 695 } | 690 } |
| 696 | 691 |
| 697 // Document the top-level members. | 692 // Document the top-level members. |
| 698 docMembers(library); | 693 docMembers(library); |
| 699 | 694 |
| 700 // Document the types. | 695 // Document the types. |
| 701 final classes = <InterfaceMirror>[]; | 696 final classes = <InterfaceMirror>[]; |
| 702 final interfaces = <InterfaceMirror>[]; | 697 final interfaces = <InterfaceMirror>[]; |
| 703 final exceptions = <InterfaceMirror>[]; | 698 final exceptions = <InterfaceMirror>[]; |
| 704 | 699 |
| 705 for (final type in orderByName(library.types().getValues())) { | 700 for (InterfaceMirror type in orderByName(library.types().getValues())) { |
| 706 if (type.isPrivate) continue; | 701 if (type.isPrivate) continue; |
| 707 | 702 |
| 708 if (isException(type)) { | 703 if (isException(type)) { |
| 709 exceptions.add(type); | 704 exceptions.add(type); |
| 710 } else if (type.isClass) { | 705 } else if (type.isClass) { |
| 711 classes.add(type); | 706 classes.add(type); |
| 712 } else { | 707 } else { |
| 713 interfaces.add(type); | 708 interfaces.add(type); |
| 714 } | 709 } |
| 715 } | 710 } |
| (...skipping 229 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 945 } | 940 } |
| 946 } | 941 } |
| 947 | 942 |
| 948 void docMembers(ObjectMirror host) { | 943 void docMembers(ObjectMirror host) { |
| 949 // Collect the different kinds of members. | 944 // Collect the different kinds of members. |
| 950 final staticMethods = []; | 945 final staticMethods = []; |
| 951 final staticFields = []; | 946 final staticFields = []; |
| 952 final instanceMethods = []; | 947 final instanceMethods = []; |
| 953 final instanceFields = []; | 948 final instanceFields = []; |
| 954 | 949 |
| 955 for (final member in orderByName(host.declaredMembers().getValues())) { | 950 for (MemberMirror member in orderByName(host.declaredMembers().getValues()))
{ |
| 956 if (member.isPrivate) continue; | 951 if (member.isPrivate) continue; |
| 957 | 952 |
| 958 final methods = member.isStatic ? staticMethods : instanceMethods; | 953 final methods = member.isStatic ? staticMethods : instanceMethods; |
| 959 final fields = member.isStatic ? staticFields : instanceFields; | 954 final fields = member.isStatic ? staticFields : instanceFields; |
| 960 | 955 |
| 961 if (member.isMethod) { | 956 if (member.isMethod) { |
| 962 methods.add(member); | 957 methods.add(member); |
| 963 } else if (member.isField) { | 958 } else if (member.isField) { |
| 964 fields.add(member); | 959 fields.add(member); |
| 965 } | 960 } |
| (...skipping 203 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1169 /** | 1164 /** |
| 1170 * Converts [fullPath] which is understood to be a full path from the root of | 1165 * Converts [fullPath] which is understood to be a full path from the root of |
| 1171 * the generated docs to one relative to the current file. | 1166 * the generated docs to one relative to the current file. |
| 1172 */ | 1167 */ |
| 1173 String relativePath(String fullPath) { | 1168 String relativePath(String fullPath) { |
| 1174 // Don't make it relative if it's an absolute path. | 1169 // Don't make it relative if it's an absolute path. |
| 1175 if (isAbsolute(fullPath)) return fullPath; | 1170 if (isAbsolute(fullPath)) return fullPath; |
| 1176 | 1171 |
| 1177 // TODO(rnystrom): Walks all the way up to root each time. Shouldn't do | 1172 // TODO(rnystrom): Walks all the way up to root each time. Shouldn't do |
| 1178 // this if the paths overlap. | 1173 // this if the paths overlap. |
| 1179 return '${repeat('../', countOccurrences(_filePath, '/'))}$fullPath'; | 1174 return '${repeat('../', |
| 1175 countOccurrences(_filePath.toString(), '/'))}$fullPath'; |
| 1180 } | 1176 } |
| 1181 | 1177 |
| 1182 /** Gets whether or not the given URL is absolute or relative. */ | 1178 /** Gets whether or not the given URL is absolute or relative. */ |
| 1183 bool isAbsolute(String url) { | 1179 bool isAbsolute(String url) { |
| 1184 // TODO(rnystrom): Why don't we have a nice type in the platform for this? | 1180 // TODO(rnystrom): Why don't we have a nice type in the platform for this? |
| 1185 // TODO(rnystrom): This is a bit hackish. We consider any URL that lacks | 1181 // TODO(rnystrom): This is a bit hackish. We consider any URL that lacks |
| 1186 // a scheme to be relative. | 1182 // a scheme to be relative. |
| 1187 return const RegExp(@'^\w+:').hasMatch(url); | 1183 return const RegExp(@'^\w+:').hasMatch(url); |
| 1188 } | 1184 } |
| 1189 | 1185 |
| 1190 /** Gets the URL to the documentation for [library]. */ | 1186 /** Gets the URL to the documentation for [library]. */ |
| 1191 String libraryUrl(LibraryMirror library) { | 1187 String libraryUrl(LibraryMirror library) { |
| 1192 return '${sanitize(library.simpleName())}.html'; | 1188 return '${sanitize(library.simpleName())}.html'; |
| 1193 } | 1189 } |
| 1194 | 1190 |
| 1195 /** Gets the URL for the documentation for [type]. */ | 1191 /** Gets the URL for the documentation for [type]. */ |
| 1196 String typeUrl(ObjectMirror type) { | 1192 String typeUrl(ObjectMirror type) { |
| 1197 if (type is LibraryMirror) return '${sanitize(type.simpleName())}.html'; | 1193 if (type is LibraryMirror) { |
| 1194 return '${sanitize(type.simpleName())}.html'; |
| 1195 } |
| 1198 assert (type is TypeMirror); | 1196 assert (type is TypeMirror); |
| 1199 // Always get the generic type to strip off any type parameters or | 1197 // Always get the generic type to strip off any type parameters or |
| 1200 // arguments. If the type isn't generic, genericType returns `this`, so it | 1198 // arguments. If the type isn't generic, genericType returns `this`, so it |
| 1201 // works for non-generic types too. | 1199 // works for non-generic types too. |
| 1202 return '${sanitize(type.library().simpleName())}/' | 1200 return '${sanitize(type.library().simpleName())}/' |
| 1203 '${type.declaration.simpleName()}.html'; | 1201 '${type.declaration.simpleName()}.html'; |
| 1204 } | 1202 } |
| 1205 | 1203 |
| 1206 /** Gets the URL for the documentation for [member]. */ | 1204 /** Gets the URL for the documentation for [member]. */ |
| 1207 String memberUrl(MemberMirror member) { | 1205 String memberUrl(MemberMirror member) { |
| 1208 final url = typeUrl(member.surroundingDeclaration()); | 1206 String url = typeUrl(member.surroundingDeclaration()); |
| 1209 if (!member.isConstructor) return '$url#${member.simpleName()}'; | 1207 if (!member.isConstructor) { |
| 1208 return '$url#${member.simpleName()}'; |
| 1209 } |
| 1210 assert (member is MethodMirror); | 1210 assert (member is MethodMirror); |
| 1211 if (member.constructorName == '') return '$url#new:${member.simpleName()}'; | 1211 if (member.constructorName == '') { |
| 1212 return '$url#new:${member.simpleName()}'; |
| 1213 } |
| 1212 return '$url#new:${member.simpleName()}.${member.constructorName}'; | 1214 return '$url#new:${member.simpleName()}.${member.constructorName}'; |
| 1213 } | 1215 } |
| 1214 | 1216 |
| 1215 /** Gets the anchor id for the document for [member]. */ | 1217 /** Gets the anchor id for the document for [member]. */ |
| 1216 String memberAnchor(MemberMirror member) { | 1218 String memberAnchor(MemberMirror member) { |
| 1217 return '${member.simpleName()}'; | 1219 return '${member.simpleName()}'; |
| 1218 } | 1220 } |
| 1219 | 1221 |
| 1220 /** | 1222 /** |
| 1221 * Creates a hyperlink. Handles turning the [href] into an appropriate | 1223 * Creates a hyperlink. Handles turning the [href] into an appropriate |
| (...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1371 // Syntax highlight. | 1373 // Syntax highlight. |
| 1372 return classifySource(code); | 1374 return classifySource(code); |
| 1373 } | 1375 } |
| 1374 | 1376 |
| 1375 /** | 1377 /** |
| 1376 * This will be called whenever a doc comment hits a `[name]` in square | 1378 * This will be called whenever a doc comment hits a `[name]` in square |
| 1377 * brackets. It will try to figure out what the name refers to and link or | 1379 * brackets. It will try to figure out what the name refers to and link or |
| 1378 * style it appropriately. | 1380 * style it appropriately. |
| 1379 */ | 1381 */ |
| 1380 md.Node resolveNameReference(String name, | 1382 md.Node resolveNameReference(String name, |
| 1381 [MemberMirror member = null, | 1383 [MemberMirror currentMember = null, |
| 1382 ObjectMirror type = null, | 1384 ObjectMirror currentType = null, |
| 1383 LibraryMirror library = null]) { | 1385 LibraryMirror currentLibrary = null]) { |
| 1384 makeLink(String href) { | 1386 makeLink(String href) { |
| 1385 final anchor = new md.Element.text('a', name); | 1387 final anchor = new md.Element.text('a', name); |
| 1386 anchor.attributes['href'] = relativePath(href); | 1388 anchor.attributes['href'] = relativePath(href); |
| 1387 anchor.attributes['class'] = 'crossref'; | 1389 anchor.attributes['class'] = 'crossref'; |
| 1388 return anchor; | 1390 return anchor; |
| 1389 } | 1391 } |
| 1390 | 1392 |
| 1391 // See if it's a parameter of the current method. | 1393 // See if it's a parameter of the current method. |
| 1392 if (member is MethodMirror) { | 1394 if (currentMember is MethodMirror) { |
| 1393 for (final parameter in member.parameters()) { | 1395 for (final parameter in currentMember.parameters()) { |
| 1394 if (parameter.simpleName() == name) { | 1396 if (parameter.simpleName() == name) { |
| 1395 final element = new md.Element.text('span', name); | 1397 final element = new md.Element.text('span', name); |
| 1396 element.attributes['class'] = 'param'; | 1398 element.attributes['class'] = 'param'; |
| 1397 return element; | 1399 return element; |
| 1398 } | 1400 } |
| 1399 } | 1401 } |
| 1400 } | 1402 } |
| 1401 | 1403 |
| 1402 // See if it's another member of the current type. | 1404 // See if it's another member of the current type. |
| 1403 if (type != null) { | 1405 if (currentType != null) { |
| 1404 final member = findMirror(type.declaredMembers(), name); | 1406 final foundMember = findMirror(currentType.declaredMembers(), name); |
| 1405 if (member != null) { | 1407 if (foundMember != null) { |
| 1406 return makeLink(memberUrl(member)); | 1408 return makeLink(memberUrl(foundMember)); |
| 1407 } | 1409 } |
| 1408 } | 1410 } |
| 1409 | 1411 |
| 1410 // See if it's another type or a member of another type in the current | 1412 // See if it's another type or a member of another type in the current |
| 1411 // library. | 1413 // library. |
| 1412 if (library != null) { | 1414 if (currentLibrary != null) { |
| 1413 // See if it's a constructor | 1415 // See if it's a constructor |
| 1414 final constructorLink = (() { | 1416 final constructorLink = (() { |
| 1415 final match = | 1417 final match = |
| 1416 new RegExp(@'new ([\w$]+)(?:\.([\w$]+))?').firstMatch(name); | 1418 new RegExp(@'new ([\w$]+)(?:\.([\w$]+))?').firstMatch(name); |
| 1417 if (match == null) return; | 1419 if (match == null) return; |
| 1418 final type = findMirror(library.types(), match[1]); | 1420 InterfaceMirror foundtype = findMirror(currentLibrary.types(), match[1])
; |
| 1419 if (type == null) return; | 1421 if (foundtype == null) return; |
| 1420 final constructor = | 1422 final constructor = |
| 1421 findMirror(type.constructors(), | 1423 findMirror(foundtype.constructors(), |
| 1422 match[2] == null ? '' : match[2]); | 1424 match[2] == null ? '' : match[2]); |
| 1423 if (constructor == null) return; | 1425 if (constructor == null) return; |
| 1424 return makeLink(memberUrl(constructor)); | 1426 return makeLink(memberUrl(constructor)); |
| 1425 })(); | 1427 })(); |
| 1426 if (constructorLink != null) return constructorLink; | 1428 if (constructorLink != null) return constructorLink; |
| 1427 | 1429 |
| 1428 // See if it's a member of another type | 1430 // See if it's a member of another type |
| 1429 final foreignMemberLink = (() { | 1431 final foreignMemberLink = (() { |
| 1430 final match = new RegExp(@'([\w$]+)\.([\w$]+)').firstMatch(name); | 1432 final match = new RegExp(@'([\w$]+)\.([\w$]+)').firstMatch(name); |
| 1431 if (match == null) return; | 1433 if (match == null) return; |
| 1432 final type = findMirror(library.types(), match[1]); | 1434 InterfaceMirror foundtype = findMirror(currentLibrary.types(), match[1])
; |
| 1433 if (type == null) return; | 1435 if (foundtype == null) return; |
| 1434 final member = findMirror(type.declaredMembers(), match[2]); | 1436 MemberMirror foundMember = findMirror(foundtype.declaredMembers(), match
[2]); |
| 1435 if (member == null) return; | 1437 if (foundMember == null) return; |
| 1436 return makeLink(memberUrl(member)); | 1438 return makeLink(memberUrl(foundMember)); |
| 1437 })(); | 1439 })(); |
| 1438 if (foreignMemberLink != null) return foreignMemberLink; | 1440 if (foreignMemberLink != null) return foreignMemberLink; |
| 1439 | 1441 |
| 1440 final type = findMirror(library.types(), name); | 1442 InterfaceMirror foundType = findMirror(currentLibrary.types(), name); |
| 1441 if (type != null) { | 1443 if (foundType != null) { |
| 1442 return makeLink(typeUrl(type)); | 1444 return makeLink(typeUrl(foundType)); |
| 1443 } | 1445 } |
| 1444 | 1446 |
| 1445 // See if it's a top-level member in the current library. | 1447 // See if it's a top-level member in the current library. |
| 1446 final member = findMirror(library.declaredMembers(), name); | 1448 MemberMirror foundMember = findMirror(currentLibrary.declaredMembers(), na
me); |
| 1447 if (member != null) { | 1449 if (foundMember != null) { |
| 1448 return makeLink(memberUrl(member)); | 1450 return makeLink(memberUrl(foundMember)); |
| 1449 } | 1451 } |
| 1450 } | 1452 } |
| 1451 | 1453 |
| 1452 // TODO(rnystrom): Should also consider: | 1454 // TODO(rnystrom): Should also consider: |
| 1453 // * Names imported by libraries this library imports. | 1455 // * Names imported by libraries this library imports. |
| 1454 // * Type parameters of the enclosing type. | 1456 // * Type parameters of the enclosing type. |
| 1455 | 1457 |
| 1456 return new md.Element.text('code', name); | 1458 return new md.Element.text('code', name); |
| 1457 } | 1459 } |
| 1458 | 1460 |
| 1459 generateAppCacheManifest() { | 1461 generateAppCacheManifest() { |
| 1460 print('Generating app cache manifest from output $outputDir'); | 1462 if (verbose) { |
| 1463 print('Generating app cache manifest from output $outputDir'); |
| 1464 } |
| 1461 startFile('appcache.manifest'); | 1465 startFile('appcache.manifest'); |
| 1462 write("CACHE MANIFEST\n\n"); | 1466 write("CACHE MANIFEST\n\n"); |
| 1463 write("# VERSION: ${new Date.now()}\n\n"); | 1467 write("# VERSION: ${new Date.now()}\n\n"); |
| 1464 write("NETWORK:\n*\n\n"); | 1468 write("NETWORK:\n*\n\n"); |
| 1465 write("CACHE:\n"); | 1469 write("CACHE:\n"); |
| 1466 var toCache = new Directory(outputDir); | 1470 var toCache = new Directory.fromPath(outputDir); |
| 1467 var pathPrefix = new File(outputDir).fullPathSync(); | 1471 var toCacheLister = toCache.list(recursive: true); |
| 1468 var pathPrefixLength = pathPrefix.length; | 1472 toCacheLister.onFile = (filename) { |
| 1469 toCache.onFile = (filename) { | |
| 1470 if (filename.endsWith('appcache.manifest')) { | 1473 if (filename.endsWith('appcache.manifest')) { |
| 1471 return; | 1474 return; |
| 1472 } | 1475 } |
| 1473 var relativePath = filename.substring(pathPrefixLength + 1); | 1476 // TODO(johnniwinther): If [outputDir] has trailing slashes, [filename] |
| 1474 write("$relativePath\n"); | 1477 // contains double (back)slashes for files in the immediate [toCache] |
| 1478 // directory. These are not handled by [relativeTo] thus |
| 1479 // wrongfully producing the path `/foo.html` for a file `foo.html` in |
| 1480 // [toCache]. |
| 1481 // |
| 1482 // This can be handled in two ways. 1) By ensuring that |
| 1483 // [Directory.fromPath] does not receive a path with a trailing slash, or |
| 1484 // better, by making [Directory.fromPath] handle such trailing slashes. |
| 1485 // 2) By ensuring that [filePath] does not have double slashes before |
| 1486 // calling [relativeTo], or better, by making [relativeTo] handle double |
| 1487 // slashes correctly. |
| 1488 Path filePath = new Path.fromNative(filename).canonicalize(); |
| 1489 Path relativeFilePath = filePath.relativeTo(outputDir); |
| 1490 write("$relativeFilePath\n"); |
| 1475 }; | 1491 }; |
| 1476 toCache.onDone = (done) => endFile(); | 1492 toCacheLister.onDone = (done) => endFile(); |
| 1477 toCache.list(recursive: true); | |
| 1478 } | 1493 } |
| 1479 | 1494 |
| 1480 /** | 1495 /** |
| 1481 * Returns [:true:] if [type] should be regarded as an exception. | 1496 * Returns [:true:] if [type] should be regarded as an exception. |
| 1482 */ | 1497 */ |
| 1483 bool isException(TypeMirror type) { | 1498 bool isException(TypeMirror type) { |
| 1484 return type.simpleName().endsWith('Exception'); | 1499 return type.simpleName().endsWith('Exception'); |
| 1485 } | 1500 } |
| 1486 } | 1501 } |
| 1487 | 1502 |
| OLD | NEW |