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/scanner/scannerlib.dart', | 27 #import('../compiler/implementation/scanner/scannerlib.dart', |
28 prefix: 'dart2js'); | 28 prefix: 'dart2js'); |
| 29 #import('../compiler/implementation/library_map.dart'); |
29 | 30 |
30 #source('comment_map.dart'); | 31 #source('comment_map.dart'); |
31 #source('utils.dart'); | 32 #source('utils.dart'); |
32 | 33 |
33 // TODO(johnniwinther): Note that [IN_SDK] gets initialized to true when this | 34 // 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 // 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 // that dartdoc still works when run from the built SDK directory. |
36 final bool IN_SDK = false; | 37 final bool IN_SDK = false; |
37 | 38 |
38 /** | 39 /** |
(...skipping 10 matching lines...) Expand all Loading... |
49 * | 50 * |
50 * This dramatically reduces the generated size of the HTML since a large | 51 * This dramatically reduces the generated size of the HTML since a large |
51 * fraction of each static page is just redundant navigation links. | 52 * fraction of each static page is just redundant navigation links. |
52 * | 53 * |
53 * In this mode, the browser will do a XHR for nav.json which means that to | 54 * In this mode, the browser will do a XHR for nav.json which means that to |
54 * preview docs locally, you will need to enable requesting file:// links in | 55 * preview docs locally, you will need to enable requesting file:// links in |
55 * your browser or run a little local server like `python -m SimpleHTTPServer`. | 56 * your browser or run a little local server like `python -m SimpleHTTPServer`. |
56 */ | 57 */ |
57 final MODE_LIVE_NAV = 1; | 58 final MODE_LIVE_NAV = 1; |
58 | 59 |
| 60 final API_LOCATION = 'http://api.dartlang.org/'; |
| 61 |
59 /** | 62 /** |
60 * Run this from the `lib/dartdoc` directory. | 63 * Run this from the `lib/dartdoc` directory. |
61 */ | 64 */ |
62 void main() { | 65 void main() { |
63 final args = new Options().arguments; | 66 final args = new Options().arguments; |
64 | 67 |
65 // Parse the dartdoc options. | 68 final dartdoc = new Dartdoc(); |
66 bool includeSource; | |
67 int mode; | |
68 Path outputDir; | |
69 bool generateAppCache; | |
70 bool omitGenerationTime; | |
71 bool verbose; | |
72 | 69 |
73 if (args.isEmpty()) { | 70 if (args.isEmpty()) { |
74 print('No arguments provided.'); | 71 print('No arguments provided.'); |
75 printUsage(); | 72 printUsage(); |
76 return; | 73 return; |
77 } | 74 } |
78 | 75 |
79 for (int i = 0; i < args.length - 1; i++) { | 76 final entrypoints = <Path>[]; |
| 77 |
| 78 var i = 0; |
| 79 while (i < args.length) { |
80 final arg = args[i]; | 80 final arg = args[i]; |
| 81 if (!arg.startsWith('--')) { |
| 82 // The remaining arguments must be entry points. |
| 83 break; |
| 84 } |
81 | 85 |
82 switch (arg) { | 86 switch (arg) { |
83 case '--no-code': | 87 case '--no-code': |
84 includeSource = false; | 88 dartdoc.includeSource = false; |
85 break; | 89 break; |
86 | 90 |
87 case '--mode=static': | 91 case '--mode=static': |
88 mode = MODE_STATIC; | 92 dartdoc.mode = MODE_STATIC; |
89 break; | 93 break; |
90 | 94 |
91 case '--mode=live-nav': | 95 case '--mode=live-nav': |
92 mode = MODE_LIVE_NAV; | 96 dartdoc.mode = MODE_LIVE_NAV; |
93 break; | 97 break; |
94 | 98 |
95 case '--generate-app-cache': | 99 case '--generate-app-cache': |
96 case '--generate-app-cache=true': | 100 case '--generate-app-cache=true': |
97 generateAppCache = true; | 101 dartdoc.generateAppCache = true; |
98 break; | 102 break; |
99 | 103 |
100 case '--omit-generation-time': | 104 case '--omit-generation-time': |
101 omitGenerationTime = true; | 105 dartdoc.omitGenerationTime = true; |
102 break; | 106 break; |
103 case '--verbose': | 107 case '--verbose': |
104 verbose = true; | 108 dartdoc.verbose = true; |
| 109 break; |
| 110 case '--include-api': |
| 111 dartdoc.includeApi = true; |
| 112 break; |
| 113 case '--link-api': |
| 114 dartdoc.linkToApi = true; |
105 break; | 115 break; |
106 | 116 |
107 default: | 117 default: |
108 if (arg.startsWith('--out=')) { | 118 if (arg.startsWith('--out=')) { |
109 outputDir = new Path.fromNative(arg.substring('--out='.length)); | 119 dartdoc.outputDir = |
| 120 new Path.fromNative(arg.substring('--out='.length)); |
| 121 } else if (arg.startsWith('--include-lib=')) { |
| 122 dartdoc.includedLibraries = |
| 123 arg.substring('--include-lib='.length).split(','); |
| 124 } else if (arg.startsWith('--exclude-lib=')) { |
| 125 dartdoc.excludedLibraries = |
| 126 arg.substring('--exclude-lib='.length).split(','); |
110 } else { | 127 } else { |
111 print('Unknown option: $arg'); | 128 print('Unknown option: $arg'); |
112 printUsage(); | 129 printUsage(); |
113 return; | 130 return; |
114 } | 131 } |
115 break; | 132 break; |
116 } | 133 } |
| 134 i++; |
| 135 } |
| 136 while (i < args.length) { |
| 137 final arg = args[i]; |
| 138 entrypoints.add(new Path.fromNative(arg)); |
| 139 i++; |
117 } | 140 } |
118 | 141 |
119 final entrypoint = new Path.fromNative(args[args.length - 1]); | 142 if (entrypoints.isEmpty()) { |
120 | 143 print('No entrypoints provided.'); |
121 final dartdoc = new Dartdoc(); | 144 printUsage(); |
122 | 145 return; |
123 if (includeSource != null) dartdoc.includeSource = includeSource; | |
124 if (mode != null) dartdoc.mode = mode; | |
125 if (outputDir != null) dartdoc.outputDir = outputDir; | |
126 if (generateAppCache != null) dartdoc.generateAppCache = generateAppCache; | |
127 if (omitGenerationTime != null) { | |
128 dartdoc.omitGenerationTime = omitGenerationTime; | |
129 } | 146 } |
130 if (verbose != null) dartdoc.verbose = verbose; | |
131 | 147 |
132 cleanOutputDirectory(dartdoc.outputDir); | 148 cleanOutputDirectory(dartdoc.outputDir); |
133 | 149 |
134 dartdoc.documentEntryPoint(entrypoint, libPath); | 150 dartdoc.documentLibraries(entrypoints, libPath); |
135 | 151 |
136 // Compile the client-side code to JS. | 152 // Compile the client-side code to JS. |
137 final clientScript = (dartdoc.mode == MODE_STATIC) ? 'static' : 'live-nav'; | 153 final clientScript = (dartdoc.mode == MODE_STATIC) ? 'static' : 'live-nav'; |
138 Future compiled = compileScript( | 154 Future compiled = compileScript( |
139 scriptDir.append('client-$clientScript.dart'), | 155 scriptDir.append('client-$clientScript.dart'), |
140 dartdoc.outputDir.append('client-$clientScript.js')); | 156 dartdoc.outputDir.append('client-$clientScript.js')); |
141 | 157 |
142 Future filesCopied = copyDirectory(scriptDir.append('static'), | 158 Future filesCopied = copyDirectory(scriptDir.append('static'), |
143 dartdoc.outputDir); | 159 dartdoc.outputDir); |
144 | 160 |
145 Futures.wait([compiled, filesCopied]).then((_) { | 161 Futures.wait([compiled, filesCopied]).then((_) { |
146 print('Documented ${dartdoc._totalLibraries} libraries, ' | 162 print('Documented ${dartdoc._totalLibraries} libraries, ' |
147 '${dartdoc._totalTypes} types, and ' | 163 '${dartdoc._totalTypes} types, and ' |
148 '${dartdoc._totalMembers} members.'); | 164 '${dartdoc._totalMembers} members.'); |
149 }); | 165 }); |
150 } | 166 } |
151 | 167 |
152 void printUsage() { | 168 void printUsage() { |
153 print(''' | 169 print(''' |
154 Usage dartdoc [options] <entrypoint> | 170 Usage dartdoc [options] <entrypoint(s)> |
155 [options] include | 171 [options] include |
156 --no-code Do not include source code in the documentation. | 172 --no-code Do not include source code in the documentation. |
157 | 173 |
158 --mode=static Generates completely static HTML containing | 174 --mode=static Generates completely static HTML containing |
159 everything you need to browse the docs. The only | 175 everything you need to browse the docs. The only |
160 client side behavior is trivial stuff like syntax | 176 client side behavior is trivial stuff like syntax |
161 highlighting code. | 177 highlighting code. |
162 | 178 |
163 --mode=live-nav (default) Generated docs do not include baked HTML | 179 --mode=live-nav (default) Generated docs do not include baked HTML |
164 navigation. Instead, a single `nav.json` file is | 180 navigation. Instead, a single `nav.json` file is |
165 created and the appropriate navigation is generated | 181 created and the appropriate navigation is generated |
166 client-side by parsing that and building HTML. | 182 client-side by parsing that and building HTML. |
167 This dramatically reduces the generated size of | 183 This dramatically reduces the generated size of |
168 the HTML since a large fraction of each static page | 184 the HTML since a large fraction of each static page |
169 is just redundant navigation links. | 185 is just redundant navigation links. |
170 In this mode, the browser will do a XHR for | 186 In this mode, the browser will do a XHR for |
171 nav.json which means that to preview docs locally, | 187 nav.json which means that to preview docs locally, |
172 you will need to enable requesting file:// links in | 188 you will need to enable requesting file:// links in |
173 your browser or run a little local server like | 189 your browser or run a little local server like |
174 `python -m SimpleHTTPServer`. | 190 `python -m SimpleHTTPServer`. |
175 | 191 |
176 --generate-app-cache Generates the App Cache manifest file, enabling | 192 --generate-app-cache Generates the App Cache manifest file, enabling |
177 offline doc viewing. | 193 offline doc viewing. |
178 | 194 |
179 --out=<dir> Generates files into directory <dir>. If omitted | 195 --out=<dir> Generates files into directory <dir>. If omitted |
180 the files are generated into ./docs/ | 196 the files are generated into ./docs/ |
181 | 197 |
| 198 --link-api Link to the online language API in the generated |
| 199 documentation. The option overrides inclusion |
| 200 through --include-api or --include-lib. |
| 201 |
| 202 --include-api Include the used API libraries in the generated |
| 203 documentation. If the --link-api option is used, |
| 204 this option is ignored. |
| 205 |
| 206 --include-lib=<libs> Use this option to explicitly specify which |
| 207 libraries to include in the documentation. If |
| 208 omitted, all used libraries are included by |
| 209 default. <libs> is comma-separated list of library |
| 210 names. |
| 211 |
| 212 --exclude-lib=<libs> Use this option to explicitly specify which |
| 213 libraries to exclude from the documentation. If |
| 214 omitted, no libraries are excluded. <libs> is |
| 215 comma-separated list of library names. |
| 216 |
182 --verbose Print verbose information during generation. | 217 --verbose Print verbose information during generation. |
183 '''); | 218 '''); |
184 } | 219 } |
185 | 220 |
186 /** | 221 /** |
187 * Gets the full path to the directory containing the entrypoint of the current | 222 * Gets the full path to the directory containing the entrypoint of the current |
188 * script. In other words, if you invoked dartdoc, directly, it will be the | 223 * script. In other words, if you invoked dartdoc, directly, it will be the |
189 * path to the directory containing `dartdoc.dart`. If you're running a script | 224 * path to the directory containing `dartdoc.dart`. If you're running a script |
190 * that imports dartdoc, it will be the path to that script. | 225 * that imports dartdoc, it will be the path to that script. |
191 */ | 226 */ |
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
305 | 340 |
306 /** Set this to add content before the footer */ | 341 /** Set this to add content before the footer */ |
307 String preFooterText = ''; | 342 String preFooterText = ''; |
308 | 343 |
309 /** Set this to omit generation timestamp from output */ | 344 /** Set this to omit generation timestamp from output */ |
310 bool omitGenerationTime = false; | 345 bool omitGenerationTime = false; |
311 | 346 |
312 /** Set by Dartdoc user to print extra information during generation. */ | 347 /** Set by Dartdoc user to print extra information during generation. */ |
313 bool verbose = false; | 348 bool verbose = false; |
314 | 349 |
315 /** Set this to select the libraries to document */ | 350 /** Set this to include API libraries in the documentation. */ |
316 List<String> libraries = null; | 351 bool includeApi = false; |
| 352 |
| 353 /** Set this to generate links to the online API. */ |
| 354 bool linkToApi = false; |
| 355 |
| 356 /** Set this to select the libraries to include in the documentation. */ |
| 357 List<String> includedLibraries = const <String>[]; |
| 358 |
| 359 /** Set this to select the libraries to exclude from the documentation. */ |
| 360 List<String> excludedLibraries = const <String>[]; |
317 | 361 |
318 /** | 362 /** |
319 * This list contains the libraries sorted in by the library name. | 363 * This list contains the libraries sorted in by the library name. |
320 */ | 364 */ |
321 List<LibraryMirror> _sortedLibraries; | 365 List<LibraryMirror> _sortedLibraries; |
322 | 366 |
323 CommentMap _comments; | 367 CommentMap _comments; |
324 | 368 |
325 /** The library that we're currently generating docs for. */ | 369 /** The library that we're currently generating docs for. */ |
326 LibraryMirror _currentLibrary; | 370 LibraryMirror _currentLibrary; |
(...skipping 19 matching lines...) Expand all Loading... |
346 // Patch in support for [:...:]-style code to the markdown parser. | 390 // Patch in support for [:...:]-style code to the markdown parser. |
347 // TODO(rnystrom): Markdown already has syntax for this. Phase this out? | 391 // TODO(rnystrom): Markdown already has syntax for this. Phase this out? |
348 md.InlineParser.syntaxes.insertRange(0, 1, | 392 md.InlineParser.syntaxes.insertRange(0, 1, |
349 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]')); | 393 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]')); |
350 | 394 |
351 md.setImplicitLinkResolver((name) => resolveNameReference(name, | 395 md.setImplicitLinkResolver((name) => resolveNameReference(name, |
352 currentLibrary: _currentLibrary, currentType: _currentType, | 396 currentLibrary: _currentLibrary, currentType: _currentType, |
353 currentMember: _currentMember)); | 397 currentMember: _currentMember)); |
354 } | 398 } |
355 | 399 |
356 bool includeLibrary(LibraryMirror library) { | 400 /** |
357 if (libraries != null) { | 401 * Returns `true` if [library] is included in the generated documentation. |
358 return libraries.indexOf(library.simpleName()) != -1; | 402 */ |
| 403 bool shouldIncludeLibrary(LibraryMirror library) { |
| 404 if (shouldLinkToPublicApi(library)) { |
| 405 return false; |
359 } | 406 } |
360 return true; | 407 var includeByDefault = true; |
| 408 String libraryName = library.simpleName(); |
| 409 if (!includedLibraries.isEmpty()) { |
| 410 includeByDefault = false; |
| 411 if (includedLibraries.indexOf(libraryName) != -1) { |
| 412 return true; |
| 413 } |
| 414 } |
| 415 if (excludedLibraries.indexOf(libraryName) != -1) { |
| 416 return false; |
| 417 } |
| 418 if (libraryName.startsWith('dart:')) { |
| 419 String suffix = libraryName.substring('dart:'.length); |
| 420 LibraryInfo info = DART2JS_LIBRARY_MAP[suffix]; |
| 421 if (info != null) { |
| 422 return !info.isInternal && includeApi; |
| 423 } |
| 424 } |
| 425 return includeByDefault; |
| 426 } |
| 427 |
| 428 /** |
| 429 * Returns `true` if links to the public API should be generated for |
| 430 * [library]. |
| 431 */ |
| 432 bool shouldLinkToPublicApi(LibraryMirror library) { |
| 433 if (linkToApi) { |
| 434 String libraryName = library.simpleName(); |
| 435 if (libraryName.startsWith('dart:')) { |
| 436 String suffix = libraryName.substring('dart:'.length); |
| 437 LibraryInfo info = DART2JS_LIBRARY_MAP[suffix]; |
| 438 if (info != null) { |
| 439 return !info.isInternal; |
| 440 } |
| 441 } |
| 442 } |
| 443 return false; |
361 } | 444 } |
362 | 445 |
363 String get footerContent(){ | 446 String get footerContent(){ |
364 var footerItems = []; | 447 var footerItems = []; |
365 if(!omitGenerationTime) { | 448 if(!omitGenerationTime) { |
366 footerItems.add("This page was generated at ${new Date.now()}"); | 449 footerItems.add("This page was generated at ${new Date.now()}"); |
367 } | 450 } |
368 if(footerText != null) { | 451 if(footerText != null) { |
369 footerItems.add(footerText); | 452 footerItems.add(footerText); |
370 } | 453 } |
(...skipping 13 matching lines...) Expand all Loading... |
384 } | 467 } |
385 | 468 |
386 void documentLibraries(List<Path> libraryList, Path libPath) { | 469 void documentLibraries(List<Path> libraryList, Path libPath) { |
387 final compilation = new Compilation.library(libraryList, libPath); | 470 final compilation = new Compilation.library(libraryList, libPath); |
388 _document(compilation); | 471 _document(compilation); |
389 } | 472 } |
390 | 473 |
391 void _document(Compilation compilation) { | 474 void _document(Compilation compilation) { |
392 // Sort the libraries by name (not key). | 475 // Sort the libraries by name (not key). |
393 _sortedLibraries = new List<LibraryMirror>.from( | 476 _sortedLibraries = new List<LibraryMirror>.from( |
394 compilation.mirrors().libraries().getValues().filter(includeLibrary)); | 477 compilation.mirrors().libraries().getValues().filter( |
| 478 shouldIncludeLibrary)); |
395 _sortedLibraries.sort((x, y) { | 479 _sortedLibraries.sort((x, y) { |
396 return x.simpleName().toUpperCase().compareTo( | 480 return x.simpleName().toUpperCase().compareTo( |
397 y.simpleName().toUpperCase()); | 481 y.simpleName().toUpperCase()); |
398 }); | 482 }); |
399 | 483 |
400 // Generate the docs. | 484 // Generate the docs. |
401 if (mode == MODE_LIVE_NAV) docNavigationJson(); | 485 if (mode == MODE_LIVE_NAV) docNavigationJson(); |
402 | 486 |
403 docIndex(); | 487 docIndex(); |
404 for (final library in _sortedLibraries) { | 488 for (final library in _sortedLibraries) { |
(...skipping 869 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1274 if (type.isTypeVariable) { | 1358 if (type.isTypeVariable) { |
1275 // If we're using a type parameter within the body of a generic class then | 1359 // If we're using a type parameter within the body of a generic class then |
1276 // just link back up to the class. | 1360 // just link back up to the class. |
1277 write(a(typeUrl(enclosingType), type.simpleName())); | 1361 write(a(typeUrl(enclosingType), type.simpleName())); |
1278 return; | 1362 return; |
1279 } | 1363 } |
1280 | 1364 |
1281 assert(type is InterfaceMirror); | 1365 assert(type is InterfaceMirror); |
1282 | 1366 |
1283 // Link to the type. | 1367 // Link to the type. |
1284 if (includeLibrary(type.library())) { | 1368 if (shouldLinkToPublicApi(type.library())) { |
| 1369 write('<a href="$API_LOCATION${typeUrl(type)}">${type.simpleName()}</a>'); |
| 1370 } else if (shouldIncludeLibrary(type.library())) { |
1285 write(a(typeUrl(type), type.simpleName())); | 1371 write(a(typeUrl(type), type.simpleName())); |
1286 } else { | 1372 } else { |
1287 write(type.simpleName()); | 1373 write(type.simpleName()); |
1288 } | 1374 } |
1289 | 1375 |
1290 if (type.isDeclaration) { | 1376 if (type.isDeclaration) { |
1291 // Avoid calling [:typeArguments():] on a declaration. | 1377 // Avoid calling [:typeArguments():] on a declaration. |
1292 return; | 1378 return; |
1293 } | 1379 } |
1294 | 1380 |
(...skipping 203 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1498 } | 1584 } |
1499 | 1585 |
1500 /** | 1586 /** |
1501 * Returns [:true:] if [type] should be regarded as an exception. | 1587 * Returns [:true:] if [type] should be regarded as an exception. |
1502 */ | 1588 */ |
1503 bool isException(TypeMirror type) { | 1589 bool isException(TypeMirror type) { |
1504 return type.simpleName().endsWith('Exception'); | 1590 return type.simpleName().endsWith('Exception'); |
1505 } | 1591 } |
1506 } | 1592 } |
1507 | 1593 |
OLD | NEW |