Chromium Code Reviews| 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:json'); | 20 #import('dart:json'); |
| 21 #import('../../frog/lang.dart'); | 21 #import('../../frog/lang.dart'); |
| 22 #import('../../frog/file_system.dart'); | 22 #import('../../frog/file_system.dart'); |
| 23 #import('../../frog/file_system_vm.dart'); | 23 #import('../../frog/file_system_vm.dart'); |
| 24 #import('classify.dart'); | 24 #import('classify.dart'); |
| 25 #import('markdown.dart', prefix: 'md'); | 25 #import('markdown.dart', prefix: 'md'); |
| 26 #import('../../frog/minfrogc.dart', prefix: 'frog'); | |
| 26 | 27 |
| 27 #source('comment_map.dart'); | 28 #source('comment_map.dart'); |
| 28 #source('utils.dart'); | 29 #source('utils.dart'); |
| 29 | 30 |
| 30 /** | 31 /** |
| 31 * Generates completely static HTML containing everything you need to browse | 32 * Generates completely static HTML containing everything you need to browse |
| 32 * the docs. The only client side behavior is trivial stuff like syntax | 33 * the docs. The only client side behavior is trivial stuff like syntax |
| 33 * highlighting code. | 34 * highlighting code. |
| 34 */ | 35 */ |
| 35 final MODE_STATIC = 0; | 36 final MODE_STATIC = 0; |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 52 * Run this from the `lib/dartdoc` directory. | 53 * Run this from the `lib/dartdoc` directory. |
| 53 */ | 54 */ |
| 54 void main() { | 55 void main() { |
| 55 final args = new Options().arguments; | 56 final args = new Options().arguments; |
| 56 | 57 |
| 57 // Parse the dartdoc options. | 58 // Parse the dartdoc options. |
| 58 bool includeSource; | 59 bool includeSource; |
| 59 int mode; | 60 int mode; |
| 60 String outputDir; | 61 String outputDir; |
| 61 bool generateAppCache; | 62 bool generateAppCache; |
| 63 | |
| 64 if (args.isEmpty()) { | |
| 65 print('No arguments provided.'); | |
| 66 printUsage(); | |
| 67 return; | |
| 68 } | |
| 62 | 69 |
| 63 for (int i = 0; i < args.length - 1; i++) { | 70 for (int i = 0; i < args.length - 1; i++) { |
| 64 final arg = args[i]; | 71 final arg = args[i]; |
| 65 | 72 |
| 66 switch (arg) { | 73 switch (arg) { |
| 67 case '--no-code': | 74 case '--no-code': |
| 68 includeSource = false; | 75 includeSource = false; |
| 69 break; | 76 break; |
| 70 | 77 |
| 71 case '--mode=static': | 78 case '--mode=static': |
| 72 mode = MODE_STATIC; | 79 mode = MODE_STATIC; |
| 73 break; | 80 break; |
| 74 | 81 |
| 75 case '--mode=live-nav': | 82 case '--mode=live-nav': |
| 76 mode = MODE_LIVE_NAV; | 83 mode = MODE_LIVE_NAV; |
| 77 break; | 84 break; |
| 78 | 85 |
| 79 case '--generate-app-cache': | 86 case '--generate-app-cache': |
| 80 case '--generate-app-cache=true': | 87 case '--generate-app-cache=true': |
| 81 generateAppCache = true; | 88 generateAppCache = true; |
| 82 break; | 89 break; |
| 83 | 90 |
| 84 default: | 91 default: |
| 85 if (arg.startsWith('--out=')) { | 92 if (arg.startsWith('--out=')) { |
| 86 outputDir = arg.substring('--out='.length); | 93 outputDir = arg.substring('--out='.length); |
| 87 } else { | 94 } else { |
| 88 print('Unknown option: $arg'); | 95 print('Unknown option: $arg'); |
| 96 printUsage(); | |
| 89 return; | 97 return; |
| 90 } | 98 } |
| 91 break; | 99 break; |
| 92 } | 100 } |
| 93 } | 101 } |
| 94 | 102 |
| 95 // The entrypoint of the library to generate docs for. | 103 // The entrypoint of the library to generate docs for. |
| 96 final entrypoint = args[args.length - 1]; | 104 final entrypoint = args[args.length - 1]; |
| 97 | 105 |
| 98 final files = new VMFileSystem(); | 106 final files = new VMFileSystem(); |
| 99 | 107 |
| 100 // TODO(rnystrom): Note that the following lines get munged by create-sdk to | 108 // TODO(rnystrom): Note that the following lines get munged by create-sdk to |
| 101 // work with the SDK's different file layout. If you change, be sure to test | 109 // work with the SDK's different file layout. If you change, be sure to test |
| 102 // that dartdoc still works when run from the built SDK directory. | 110 // that dartdoc still works when run from the built SDK directory. |
| 103 final frogPath = joinPaths(scriptDir, '../../frog/'); | 111 final frogPath = joinPaths(scriptDir, '../../frog/'); |
| 104 final libDir = joinPaths(frogPath, 'lib'); | 112 final libDir = joinPaths(frogPath, 'lib'); |
| 105 final compilerPath = joinPaths(frogPath, 'minfrog'); | |
| 106 | 113 |
| 107 parseOptions(frogPath, ['', '', '--libdir=$libDir'], files); | 114 parseOptions(frogPath, ['', '', '--libdir=$libDir'], files); |
| 108 initializeWorld(files); | 115 initializeWorld(files); |
| 109 | 116 |
| 110 final dartdoc = new Dartdoc(); | 117 final dartdoc = new Dartdoc(); |
| 111 | 118 |
| 112 if (includeSource != null) dartdoc.includeSource = includeSource; | 119 if (includeSource != null) dartdoc.includeSource = includeSource; |
| 113 if (mode != null) dartdoc.mode = mode; | 120 if (mode != null) dartdoc.mode = mode; |
| 114 if (outputDir != null) dartdoc.outputDir = outputDir; | 121 if (outputDir != null) dartdoc.outputDir = outputDir; |
| 115 if (generateAppCache != null) dartdoc.generateAppCache = generateAppCache; | 122 if (generateAppCache != null) dartdoc.generateAppCache = generateAppCache; |
| 116 | 123 |
| 117 cleanOutputDirectory(dartdoc.outputDir); | 124 cleanOutputDirectory(dartdoc.outputDir); |
| 118 | 125 |
| 119 // Compile the client-side code to JS. | 126 // Compile the client-side code to JS. |
| 120 final clientScript = (dartdoc.mode == MODE_STATIC) ? 'static' : 'live-nav'; | 127 final clientScript = (dartdoc.mode == MODE_STATIC) ? 'static' : 'live-nav'; |
| 121 final Future scriptCompiled = compileScript(compilerPath, libDir, | 128 final Future scriptCompiled = compileScript(libDir, |
|
Bob Nystrom
2012/05/31 17:53:42
Local variables don't need type annotations.
| |
| 122 '$scriptDir/client-$clientScript.dart', | 129 '$scriptDir/client-$clientScript.dart', |
| 123 '${dartdoc.outputDir}/client-$clientScript.js'); | 130 '${dartdoc.outputDir}/client-$clientScript.js'); |
| 124 | 131 |
| 125 final Future filesCopied = copyFiles('$scriptDir/static', dartdoc.outputDir); | 132 final Future filesCopied = copyFiles('$scriptDir/static', dartdoc.outputDir); |
| 126 | 133 |
| 127 Futures.wait([scriptCompiled, filesCopied]).then((_) { | 134 Futures.wait([scriptCompiled, filesCopied]).then((_) { |
| 128 dartdoc.document(entrypoint); | 135 dartdoc.document(entrypoint); |
| 136 | |
| 137 print('Documented ${dartdoc._totalLibraries} libraries, ' | |
| 138 '${dartdoc._totalTypes} types, and ' | |
| 139 '${dartdoc._totalMembers} members.'); | |
| 129 }); | 140 }); |
| 130 | 141 |
| 131 print('Documented ${dartdoc._totalLibraries} libraries, ' + | 142 } |
| 132 '${dartdoc._totalTypes} types, and ' + | 143 |
| 133 '${dartdoc._totalMembers} members.'); | 144 void printUsage() { |
| 145 print(''' | |
| 146 Usage dartdoc [options] <entrypoint> | |
| 147 [options] include | |
|
Bob Nystrom
2012/05/31 17:53:42
Nice!
| |
| 148 --no-code Do not include source code in the documentation. | |
| 149 | |
| 150 --mode=static Generates completely static HTML containing | |
| 151 everything you need to browse the docs. The only | |
| 152 client side behavior is trivial stuff like syntax | |
| 153 highlighting code. | |
| 154 | |
| 155 --mode=live-nav (default) Generated docs do not include baked HTML | |
| 156 navigation. Instead, a single `nav.json` file is | |
| 157 created and the appropriate navigation is generated | |
| 158 client-side by parsing that and building HTML. | |
| 159 This dramatically reduces the generated size of | |
| 160 the HTML since a large fraction of each static page | |
| 161 is just redundant navigation links. | |
| 162 In this mode, the browser will do a XHR for | |
| 163 nav.json which means that to preview docs locally, | |
| 164 you will need to enable requesting file:// links in | |
| 165 your browser or run a little local server like | |
| 166 `python -m SimpleHTTPServer`. | |
| 167 | |
| 168 --generate-app-cache Generates the App Cache manifest file, enabling | |
| 169 offline doc viewing. | |
| 170 --generate-app-cache=true --''-- | |
| 171 | |
| 172 --out=<dir> Generates files into directory <dir>. If omitted | |
| 173 the files are generated into ./docs/ | |
| 174 '''); | |
| 134 } | 175 } |
| 135 | 176 |
| 136 /** | 177 /** |
| 137 * Gets the full path to the directory containing the entrypoint of the current | 178 * Gets the full path to the directory containing the entrypoint of the current |
| 138 * script. In other words, if you invoked dartdoc, directly, it will be the | 179 * script. In other words, if you invoked dartdoc, directly, it will be the |
| 139 * path to the directory containing `dartdoc.dart`. If you're running a script | 180 * path to the directory containing `dartdoc.dart`. If you're running a script |
| 140 * that imports dartdoc, it will be the path to that script. | 181 * that imports dartdoc, it will be the path to that script. |
| 141 */ | 182 */ |
| 142 String get scriptDir() { | 183 String get scriptDir() { |
| 143 return dirname(new File(new Options().script).fullPathSync()); | 184 return dirname(new File(new Options().script).fullPathSync()); |
| 144 } | 185 } |
| 145 | 186 |
| 146 /** | 187 /** |
| 147 * Deletes and recreates the output directory at [path] if it exists. | 188 * Deletes and recreates the output directory at [path] if it exists. |
| 148 */ | 189 */ |
| 149 void cleanOutputDirectory(String path) { | 190 void cleanOutputDirectory(String path) { |
| 150 final outputDir = new Directory(path); | 191 final outputDir = new Directory(path); |
| 151 if (outputDir.existsSync()) { | 192 if (outputDir.existsSync()) { |
| 152 outputDir.deleteRecursivelySync(); | 193 outputDir.deleteRecursivelySync(); |
| 153 } | 194 } |
| 154 | 195 |
| 155 outputDir.createSync(); | 196 try { |
| 197 // TODO(johnniwinther): hack to avoid 'file already exists' exception thrown | |
| 198 // due to invalid result from dir.existsSync() (probably due to race | |
| 199 // conditions). | |
| 200 outputDir.createSync(); | |
| 201 } catch (DirectoryIOException e) { | |
| 202 // Ignore. | |
| 203 } | |
| 156 } | 204 } |
| 157 | 205 |
| 158 /** | 206 /** |
| 159 * Copies all of the files in the directory [from] to [to]. Does *not* | 207 * Copies all of the files in the directory [from] to [to]. Does *not* |
| 160 * recursively copy subdirectories. | 208 * recursively copy subdirectories. |
| 161 * | 209 * |
| 162 * Note: runs asynchronously, so you won't see any files copied until after the | 210 * Note: runs asynchronously, so you won't see any files copied until after the |
| 163 * event loop has had a chance to pump (i.e. after `main()` has returned). | 211 * event loop has had a chance to pump (i.e. after `main()` has returned). |
| 164 */ | 212 */ |
| 165 Future copyFiles(String from, String to) { | 213 Future copyFiles(String from, String to) { |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 178 stream.write(bytes, copyBuffer: false); | 226 stream.write(bytes, copyBuffer: false); |
| 179 stream.close(); | 227 stream.close(); |
| 180 }); | 228 }); |
| 181 }; | 229 }; |
| 182 lister.onDone = (done) => completer.complete(true); | 230 lister.onDone = (done) => completer.complete(true); |
| 183 return completer.future; | 231 return completer.future; |
| 184 } | 232 } |
| 185 | 233 |
| 186 /** | 234 /** |
| 187 * Compiles the given Dart script to a JavaScript file at [jsPath] using the | 235 * Compiles the given Dart script to a JavaScript file at [jsPath] using the |
| 188 * Dart-to-JS compiler located at [compilerPath]. | 236 * Frog compiler. |
| 189 */ | 237 */ |
| 190 Future compileScript(String compilerPath, String libDir, | 238 Future compileScript(String libDir, String dartPath, String jsPath) { |
|
Bob Nystrom
2012/05/31 17:53:42
Since this is no longer asynchronous, can you get
| |
| 191 String dartPath, String jsPath) { | |
| 192 final completer = new Completer(); | 239 final completer = new Completer(); |
| 193 onExit(ProcessResult result) { | 240 print('Compiling $dartPath to $jsPath'); |
| 194 if (result.exitCode != 0) { | 241 |
| 195 final message = 'Non-zero exit code from $compilerPath'; | 242 var result = true; |
| 243 try { | |
| 244 result = frog.internalMain([ | |
| 245 '--libdir=$libDir', '--out=$jsPath', | |
| 246 '--compile-only', '--enable-type-checks', '--warnings-as-errors', | |
| 247 dartPath]); | |
| 248 if (!result) { | |
| 249 final message = 'Non-zero exit code from the compiler'; | |
| 196 print('$message.'); | 250 print('$message.'); |
| 197 print(result.stdout); | 251 completer.completeException(message); |
| 198 print(result.stderr); | 252 } else { |
| 199 throw message; | 253 completer.complete(result); |
| 200 } | 254 } |
| 201 completer.complete(true); | 255 } catch (Object error) { |
| 256 final message = 'Error trying to execute the compiler. Error: $error'; | |
| 257 print('$message.'); | |
| 258 completer.completeException(message); | |
| 202 } | 259 } |
| 203 | 260 |
| 204 onError(error) { | |
| 205 final message = 'Error trying to execute $compilerPath. Error: $error'; | |
| 206 print('$message.'); | |
| 207 throw message; | |
| 208 } | |
| 209 | |
| 210 print('Compiling $dartPath to $jsPath'); | |
| 211 var processFuture = Process.run(compilerPath, [ | |
| 212 '--libdir=$libDir', '--out=$jsPath', | |
| 213 '--compile-only', '--enable-type-checks', '--warnings-as-errors', | |
| 214 dartPath]); | |
| 215 | |
| 216 processFuture.handleException(onError); | |
| 217 processFuture.then(onExit); | |
| 218 | |
| 219 return completer.future; | 261 return completer.future; |
| 220 } | 262 } |
| 221 | 263 |
| 222 class Dartdoc { | 264 class Dartdoc { |
| 223 | 265 |
| 224 /** Set to `false` to not include the source code in the generated docs. */ | 266 /** Set to `false` to not include the source code in the generated docs. */ |
| 225 bool includeSource = true; | 267 bool includeSource = true; |
| 226 | 268 |
| 227 /** | 269 /** |
| 228 * Dartdoc can generate docs in a few different ways based on how dynamic you | 270 * Dartdoc can generate docs in a few different ways based on how dynamic you |
| (...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 344 | 386 |
| 345 void startFile(String path) { | 387 void startFile(String path) { |
| 346 _filePath = path; | 388 _filePath = path; |
| 347 _file = new StringBuffer(); | 389 _file = new StringBuffer(); |
| 348 } | 390 } |
| 349 | 391 |
| 350 void endFile() { | 392 void endFile() { |
| 351 final outPath = '$outputDir/$_filePath'; | 393 final outPath = '$outputDir/$_filePath'; |
| 352 final dir = new Directory(dirname(outPath)); | 394 final dir = new Directory(dirname(outPath)); |
| 353 if (!dir.existsSync()) { | 395 if (!dir.existsSync()) { |
| 354 dir.createSync(); | 396 //TODO(johnniwinther) hack to avoid 'file already exists' exception thrown |
| 397 // due to invalid result from dir.existsSync() (probably due to race | |
| 398 // conditions) | |
|
Bob Nystrom
2012/05/31 17:53:42
This worries me. I've never seen race conditions w
| |
| 399 try { | |
| 400 dir.createSync(); | |
| 401 } catch (DirectoryIOException e) { | |
| 402 // ignore | |
| 403 } | |
| 355 } | 404 } |
| 356 | 405 |
| 357 world.files.writeString(outPath, _file.toString()); | 406 world.files.writeString(outPath, _file.toString()); |
| 358 _filePath = null; | 407 _filePath = null; |
| 359 _file = null; | 408 _file = null; |
| 360 } | 409 } |
| 361 | 410 |
| 362 void write(String s) { | 411 void write(String s) { |
| 363 _file.add(s); | 412 _file.add(s); |
| 364 } | 413 } |
| (...skipping 983 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1348 toCache.onFile = (filename) { | 1397 toCache.onFile = (filename) { |
| 1349 if (filename.endsWith('appcache.manifest')) { | 1398 if (filename.endsWith('appcache.manifest')) { |
| 1350 return; | 1399 return; |
| 1351 } | 1400 } |
| 1352 var relativePath = filename.substring(pathPrefixLength + 1); | 1401 var relativePath = filename.substring(pathPrefixLength + 1); |
| 1353 write("$relativePath\n"); | 1402 write("$relativePath\n"); |
| 1354 }; | 1403 }; |
| 1355 toCache.onDone = (done) => endFile(); | 1404 toCache.onDone = (done) => endFile(); |
| 1356 toCache.list(recursive: true); | 1405 toCache.list(recursive: true); |
| 1357 } | 1406 } |
| 1358 } | 1407 } |
| OLD | NEW |