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 |