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 * |
(...skipping 12 matching lines...) Expand all Loading... |
23 #import('mirrors/mirrors.dart'); | 23 #import('mirrors/mirrors.dart'); |
24 #import('mirrors/mirrors_util.dart'); | 24 #import('mirrors/mirrors_util.dart'); |
25 #import('mirrors/dart2js_mirror.dart', prefix: 'dart2js'); | 25 #import('mirrors/dart2js_mirror.dart', prefix: 'dart2js'); |
26 #import('classify.dart'); | 26 #import('classify.dart'); |
27 #import('markdown.dart', prefix: 'md'); | 27 #import('markdown.dart', prefix: 'md'); |
28 #import('../../lib/compiler/implementation/scanner/scannerlib.dart', | 28 #import('../../lib/compiler/implementation/scanner/scannerlib.dart', |
29 prefix: 'dart2js'); | 29 prefix: 'dart2js'); |
30 #import('../../lib/compiler/implementation/library_map.dart'); | 30 #import('../../lib/compiler/implementation/library_map.dart'); |
31 | 31 |
32 #source('comment_map.dart'); | 32 #source('comment_map.dart'); |
| 33 #source('nav.dart'); |
33 #source('utils.dart'); | 34 #source('utils.dart'); |
34 | 35 |
35 // TODO(johnniwinther): Note that [IN_SDK] gets initialized to true when this | 36 // TODO(johnniwinther): Note that [IN_SDK] gets initialized to true when this |
36 // file is modified by the SDK deployment script. If you change, be sure to test | 37 // file is modified by the SDK deployment script. If you change, be sure to test |
37 // that dartdoc still works when run from the built SDK directory. | 38 // that dartdoc still works when run from the built SDK directory. |
38 final bool IN_SDK = false; | 39 final bool IN_SDK = false; |
39 | 40 |
40 /** | 41 /** |
41 * Generates completely static HTML containing everything you need to browse | 42 * Generates completely static HTML containing everything you need to browse |
42 * the docs. The only client side behavior is trivial stuff like syntax | 43 * the docs. The only client side behavior is trivial stuff like syntax |
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
153 // Compile the client-side code to JS. | 154 // Compile the client-side code to JS. |
154 final clientScript = (dartdoc.mode == MODE_STATIC) ? 'static' : 'live-nav'; | 155 final clientScript = (dartdoc.mode == MODE_STATIC) ? 'static' : 'live-nav'; |
155 Future compiled = compileScript( | 156 Future compiled = compileScript( |
156 scriptDir.append('client-$clientScript.dart'), | 157 scriptDir.append('client-$clientScript.dart'), |
157 dartdoc.outputDir.append('client-$clientScript.js')); | 158 dartdoc.outputDir.append('client-$clientScript.js')); |
158 | 159 |
159 Future filesCopied = copyDirectory(scriptDir.append('static'), | 160 Future filesCopied = copyDirectory(scriptDir.append('static'), |
160 dartdoc.outputDir); | 161 dartdoc.outputDir); |
161 | 162 |
162 Futures.wait([compiled, filesCopied]).then((_) { | 163 Futures.wait([compiled, filesCopied]).then((_) { |
| 164 dartdoc.cleanup(); |
163 print('Documented ${dartdoc._totalLibraries} libraries, ' | 165 print('Documented ${dartdoc._totalLibraries} libraries, ' |
164 '${dartdoc._totalTypes} types, and ' | 166 '${dartdoc._totalTypes} types, and ' |
165 '${dartdoc._totalMembers} members.'); | 167 '${dartdoc._totalMembers} members.'); |
166 }); | 168 }); |
167 } | 169 } |
168 | 170 |
169 void printUsage() { | 171 void printUsage() { |
170 print(''' | 172 print(''' |
171 Usage dartdoc [options] <entrypoint(s)> | 173 Usage dartdoc [options] <entrypoint(s)> |
172 [options] include | 174 [options] include |
(...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
307 * want the client-side behavior to be. The value for this should be one of | 309 * want the client-side behavior to be. The value for this should be one of |
308 * the `MODE_` constants. | 310 * the `MODE_` constants. |
309 */ | 311 */ |
310 int mode = MODE_LIVE_NAV; | 312 int mode = MODE_LIVE_NAV; |
311 | 313 |
312 /** | 314 /** |
313 * Generates the App Cache manifest file, enabling offline doc viewing. | 315 * Generates the App Cache manifest file, enabling offline doc viewing. |
314 */ | 316 */ |
315 bool generateAppCache = false; | 317 bool generateAppCache = false; |
316 | 318 |
| 319 /** Path to the dartdoc directory. */ |
| 320 Path dartdocPath; |
| 321 |
317 /** Path to generate HTML files into. */ | 322 /** Path to generate HTML files into. */ |
318 Path outputDir = const Path('docs'); | 323 Path outputDir = const Path('docs'); |
319 | 324 |
320 /** | 325 /** |
321 * The title used for the overall generated output. Set this to change it. | 326 * The title used for the overall generated output. Set this to change it. |
322 */ | 327 */ |
323 String mainTitle = 'Dart Documentation'; | 328 String mainTitle = 'Dart Documentation'; |
324 | 329 |
325 /** | 330 /** |
326 * The URL that the Dart logo links to. Defaults "index.html", the main | 331 * The URL that the Dart logo links to. Defaults "index.html", the main |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
381 Path _filePath; | 386 Path _filePath; |
382 | 387 |
383 /** The file currently being written to. */ | 388 /** The file currently being written to. */ |
384 StringBuffer _file; | 389 StringBuffer _file; |
385 | 390 |
386 int _totalLibraries = 0; | 391 int _totalLibraries = 0; |
387 int _totalTypes = 0; | 392 int _totalTypes = 0; |
388 int _totalMembers = 0; | 393 int _totalMembers = 0; |
389 | 394 |
390 Dartdoc() | 395 Dartdoc() |
391 : _comments = new CommentMap() { | 396 : _comments = new CommentMap(), |
| 397 dartdocPath = scriptDir { |
| 398 |
392 // Patch in support for [:...:]-style code to the markdown parser. | 399 // Patch in support for [:...:]-style code to the markdown parser. |
393 // TODO(rnystrom): Markdown already has syntax for this. Phase this out? | 400 // TODO(rnystrom): Markdown already has syntax for this. Phase this out? |
394 md.InlineParser.syntaxes.insertRange(0, 1, | 401 md.InlineParser.syntaxes.insertRange(0, 1, |
395 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]')); | 402 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]')); |
396 | 403 |
397 md.setImplicitLinkResolver((name) => resolveNameReference(name, | 404 md.setImplicitLinkResolver((name) => resolveNameReference(name, |
398 currentLibrary: _currentLibrary, currentType: _currentType, | 405 currentLibrary: _currentLibrary, currentType: _currentType, |
399 currentMember: _currentMember)); | 406 currentMember: _currentMember)); |
400 } | 407 } |
401 | 408 |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
477 // Sort the libraries by name (not key). | 484 // Sort the libraries by name (not key). |
478 _sortedLibraries = new List<LibraryMirror>.from( | 485 _sortedLibraries = new List<LibraryMirror>.from( |
479 compilation.mirrors.libraries.getValues().filter( | 486 compilation.mirrors.libraries.getValues().filter( |
480 shouldIncludeLibrary)); | 487 shouldIncludeLibrary)); |
481 _sortedLibraries.sort((x, y) { | 488 _sortedLibraries.sort((x, y) { |
482 return x.simpleName.toUpperCase().compareTo( | 489 return x.simpleName.toUpperCase().compareTo( |
483 y.simpleName.toUpperCase()); | 490 y.simpleName.toUpperCase()); |
484 }); | 491 }); |
485 | 492 |
486 // Generate the docs. | 493 // Generate the docs. |
487 if (mode == MODE_LIVE_NAV) docNavigationJson(); | 494 if (mode == MODE_LIVE_NAV) { |
| 495 docNavigationJson(); |
| 496 } else { |
| 497 docNavigationDart(); |
| 498 } |
488 | 499 |
489 docIndex(); | 500 docIndex(); |
490 for (final library in _sortedLibraries) { | 501 for (final library in _sortedLibraries) { |
491 docLibrary(library); | 502 docLibrary(library); |
492 } | 503 } |
493 | 504 |
494 if (generateAppCache) { | 505 if (generateAppCache) { |
495 generateAppCacheManifest(); | 506 generateAppCacheManifest(); |
496 } | 507 } |
497 } | 508 } |
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
583 } | 594 } |
584 | 595 |
585 if (searchEngineId != null) { | 596 if (searchEngineId != null) { |
586 writeln( | 597 writeln( |
587 ''' | 598 ''' |
588 <form action="$searchResultsUrl" id="search-box"> | 599 <form action="$searchResultsUrl" id="search-box"> |
589 <input type="hidden" name="cx" value="$searchEngineId"> | 600 <input type="hidden" name="cx" value="$searchEngineId"> |
590 <input type="hidden" name="ie" value="UTF-8"> | 601 <input type="hidden" name="ie" value="UTF-8"> |
591 <input type="hidden" name="hl" value="en"> | 602 <input type="hidden" name="hl" value="en"> |
592 <input type="search" name="q" id="q" autocomplete="off" | 603 <input type="search" name="q" id="q" autocomplete="off" |
593 placeholder="Search"> | 604 class="search-input" placeholder="Search API"> |
594 </form> | 605 </form> |
595 '''); | 606 '''); |
| 607 } else { |
| 608 writeln( |
| 609 ''' |
| 610 <div id="search-box"> |
| 611 <input type="search" name="q" id="q" autocomplete="off" |
| 612 class="search-input" placeholder="Search API"> |
| 613 </div> |
| 614 '''); |
596 } | 615 } |
597 | 616 |
598 writeln('</div>'); | 617 writeln( |
| 618 ''' |
| 619 </div> |
| 620 <div class="drop-down" id="drop-down"></div> |
| 621 '''); |
599 | 622 |
600 docNavigation(); | 623 docNavigation(); |
601 writeln('<div class="content">'); | 624 writeln('<div class="content">'); |
602 } | 625 } |
603 | 626 |
604 String get clientScript() { | 627 String get clientScript() { |
605 switch (mode) { | 628 switch (mode) { |
606 case MODE_STATIC: return 'client-static'; | 629 case MODE_STATIC: return 'client-static'; |
607 case MODE_LIVE_NAV: return 'client-live-nav'; | 630 case MODE_LIVE_NAV: return 'client-live-nav'; |
608 default: throw 'Unknown mode $mode.'; | 631 default: throw 'Unknown mode $mode.'; |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
655 void docIndexLibrary(LibraryMirror library) { | 678 void docIndexLibrary(LibraryMirror library) { |
656 writeln('<h4>${a(libraryUrl(library), library.simpleName)}</h4>'); | 679 writeln('<h4>${a(libraryUrl(library), library.simpleName)}</h4>'); |
657 } | 680 } |
658 | 681 |
659 /** | 682 /** |
660 * Walks the libraries and creates a JSON object containing the data needed | 683 * Walks the libraries and creates a JSON object containing the data needed |
661 * to generate navigation for them. | 684 * to generate navigation for them. |
662 */ | 685 */ |
663 void docNavigationJson() { | 686 void docNavigationJson() { |
664 startFile('nav.json'); | 687 startFile('nav.json'); |
665 | 688 writeln(JSON.stringify(createNavigationInfo())); |
666 final libraryMap = {}; | |
667 | |
668 for (final library in _sortedLibraries) { | |
669 docLibraryNavigationJson(library, libraryMap); | |
670 } | |
671 | |
672 writeln(JSON.stringify(libraryMap)); | |
673 endFile(); | 689 endFile(); |
674 } | 690 } |
675 | 691 |
676 void docLibraryNavigationJson(LibraryMirror library, Map libraryMap) { | 692 void docNavigationDart() { |
| 693 final dir = new Directory.fromPath(tmpPath); |
| 694 if (!dir.existsSync()) { |
| 695 // TODO(3914): Hack to avoid 'file already exists' exception |
| 696 // thrown due to invalid result from dir.existsSync() (probably due to |
| 697 // race conditions). |
| 698 try { |
| 699 dir.createSync(); |
| 700 } catch (DirectoryIOException e) { |
| 701 // Ignore. |
| 702 } |
| 703 } |
| 704 String jsonString = JSON.stringify(createNavigationInfo()); |
| 705 String dartString = jsonString.replaceAll(@"$", @"\$"); |
| 706 final filePath = tmpPath.append('nav.dart'); |
| 707 writeString(new File.fromPath(filePath), |
| 708 'get json() => $dartString;'); |
| 709 } |
| 710 |
| 711 Path get tmpPath() => dartdocPath.append('tmp'); |
| 712 |
| 713 void cleanup() { |
| 714 final dir = new Directory.fromPath(tmpPath); |
| 715 if (dir.existsSync()) { |
| 716 dir.deleteRecursivelySync(); |
| 717 } |
| 718 } |
| 719 |
| 720 List createNavigationInfo() { |
| 721 final libraryList = []; |
| 722 for (final library in _sortedLibraries) { |
| 723 docLibraryNavigationJson(library, libraryList); |
| 724 } |
| 725 return libraryList; |
| 726 } |
| 727 |
| 728 void docLibraryNavigationJson(LibraryMirror library, List libraryList) { |
| 729 var libraryInfo = {}; |
| 730 libraryInfo[NAME] = library.simpleName; |
| 731 final List members = docMembersJson(library.declaredMembers); |
| 732 if (!members.isEmpty()) { |
| 733 libraryInfo[MEMBERS] = members; |
| 734 } |
| 735 |
677 final types = []; | 736 final types = []; |
678 | |
679 for (InterfaceMirror type in orderByName(library.types.getValues())) { | 737 for (InterfaceMirror type in orderByName(library.types.getValues())) { |
680 if (type.isPrivate) continue; | 738 if (type.isPrivate) continue; |
681 | 739 |
682 final kind = type.isClass ? 'class' : 'interface'; | 740 var typeInfo = {}; |
683 final url = typeUrl(type); | 741 typeInfo[NAME] = type.simpleName; |
684 types.add({ 'name': typeName(type), 'kind': kind, 'url': url }); | 742 if (type.isClass) { |
| 743 typeInfo[KIND] = CLASS; |
| 744 } else if (type.isInterface) { |
| 745 typeInfo[KIND] = INTERFACE; |
| 746 } else { |
| 747 assert(type.isTypedef); |
| 748 typeInfo[KIND] = TYPEDEF; |
| 749 } |
| 750 final List typeMembers = docMembersJson(type.declaredMembers); |
| 751 if (!typeMembers.isEmpty()) { |
| 752 typeInfo[MEMBERS] = typeMembers; |
| 753 } |
| 754 |
| 755 if (!type.declaration.typeVariables.isEmpty()) { |
| 756 final typeVariables = []; |
| 757 for (final typeVariable in type.declaration.typeVariables) { |
| 758 typeVariables.add(typeVariable.simpleName); |
| 759 } |
| 760 typeInfo[ARGS] = Strings.join(typeVariables, ', '); |
| 761 } |
| 762 types.add(typeInfo); |
| 763 } |
| 764 if (!types.isEmpty()) { |
| 765 libraryInfo[TYPES] = types; |
685 } | 766 } |
686 | 767 |
687 libraryMap[library.simpleName] = types; | 768 libraryList.add(libraryInfo); |
| 769 } |
| 770 |
| 771 List docMembersJson(Map<Object,MemberMirror> memberMap) { |
| 772 final members = []; |
| 773 for (MemberMirror member in orderByName(memberMap.getValues())) { |
| 774 if (member.isPrivate) continue; |
| 775 |
| 776 var memberInfo = {}; |
| 777 if (member.isField) { |
| 778 memberInfo[NAME] = member.simpleName; |
| 779 memberInfo[KIND] = FIELD; |
| 780 } else { |
| 781 MethodMirror method = member; |
| 782 if (method.isConstructor) { |
| 783 if (method.constructorName != '') { |
| 784 memberInfo[NAME] = '${method.simpleName}.${method.constructorName}'; |
| 785 memberInfo[KIND] = CONSTRUCTOR; |
| 786 } else { |
| 787 memberInfo[NAME] = member.simpleName; |
| 788 memberInfo[KIND] = CONSTRUCTOR; |
| 789 } |
| 790 } else if (method.isOperator) { |
| 791 memberInfo[NAME] = '${method.simpleName} ${method.operatorName}'; |
| 792 memberInfo[KIND] = METHOD; |
| 793 } else if (method.isSetter) { |
| 794 memberInfo[NAME] = member.simpleName; |
| 795 memberInfo[KIND] = SETTER; |
| 796 } else if (method.isGetter) { |
| 797 memberInfo[NAME] = member.simpleName; |
| 798 memberInfo[KIND] = GETTER; |
| 799 } else { |
| 800 memberInfo[NAME] = member.simpleName; |
| 801 memberInfo[KIND] = METHOD; |
| 802 } |
| 803 } |
| 804 var anchor = memberAnchor(member); |
| 805 if (anchor != memberInfo[NAME]) { |
| 806 memberInfo[LINK_NAME] = anchor; |
| 807 } |
| 808 members.add(memberInfo); |
| 809 } |
| 810 return members; |
688 } | 811 } |
689 | 812 |
690 void docNavigation() { | 813 void docNavigation() { |
691 writeln( | 814 writeln( |
692 ''' | 815 ''' |
693 <div class="nav"> | 816 <div class="nav"> |
694 '''); | 817 '''); |
695 | 818 |
696 if (mode == MODE_STATIC) { | 819 if (mode == MODE_STATIC) { |
697 for (final library in _sortedLibraries) { | 820 for (final library in _sortedLibraries) { |
(...skipping 588 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1286 // Always get the generic type to strip off any type parameters or | 1409 // Always get the generic type to strip off any type parameters or |
1287 // arguments. If the type isn't generic, genericType returns `this`, so it | 1410 // arguments. If the type isn't generic, genericType returns `this`, so it |
1288 // works for non-generic types too. | 1411 // works for non-generic types too. |
1289 return '${sanitize(type.library.simpleName)}/' | 1412 return '${sanitize(type.library.simpleName)}/' |
1290 '${type.declaration.simpleName}.html'; | 1413 '${type.declaration.simpleName}.html'; |
1291 } | 1414 } |
1292 | 1415 |
1293 /** Gets the URL for the documentation for [member]. */ | 1416 /** Gets the URL for the documentation for [member]. */ |
1294 String memberUrl(MemberMirror member) { | 1417 String memberUrl(MemberMirror member) { |
1295 String url = typeUrl(member.surroundingDeclaration); | 1418 String url = typeUrl(member.surroundingDeclaration); |
1296 if (!member.isConstructor) { | 1419 return '$url#${memberAnchor(member)}'; |
1297 return '$url#${member.simpleName}'; | |
1298 } | |
1299 assert (member is MethodMirror); | |
1300 if (member.constructorName == '') { | |
1301 return '$url#new:${member.simpleName}'; | |
1302 } | |
1303 return '$url#new:${member.simpleName}.${member.constructorName}'; | |
1304 } | 1420 } |
1305 | 1421 |
1306 /** Gets the anchor id for the document for [member]. */ | 1422 /** Gets the anchor id for the document for [member]. */ |
1307 String memberAnchor(MemberMirror member) { | 1423 String memberAnchor(MemberMirror member) { |
1308 return '${member.simpleName}'; | 1424 if (member.isField) { |
| 1425 return member.simpleName; |
| 1426 } |
| 1427 MethodMirror method = member; |
| 1428 if (method.isConstructor) { |
| 1429 if (method.constructorName == '') { |
| 1430 return method.simpleName; |
| 1431 } else { |
| 1432 return '${method.simpleName}.${method.constructorName}'; |
| 1433 } |
| 1434 } else if (method.isOperator) { |
| 1435 return '${method.simpleName} ${method.operatorName}'; |
| 1436 } else if (method.isSetter) { |
| 1437 return '${method.simpleName}='; |
| 1438 } else { |
| 1439 return method.simpleName; |
| 1440 } |
1309 } | 1441 } |
1310 | 1442 |
1311 /** | 1443 /** |
1312 * Creates a hyperlink. Handles turning the [href] into an appropriate | 1444 * Creates a hyperlink. Handles turning the [href] into an appropriate |
1313 * relative path from the current file. | 1445 * relative path from the current file. |
1314 */ | 1446 */ |
1315 String a(String href, String contents, [String css]) { | 1447 String a(String href, String contents, [String css]) { |
1316 // Mark outgoing external links, mainly so we can style them. | 1448 // Mark outgoing external links, mainly so we can style them. |
1317 final rel = isAbsolute(href) ? ' ref="external"' : ''; | 1449 final rel = isAbsolute(href) ? ' ref="external"' : ''; |
1318 final cssClass = css == null ? '' : ' class="$css"'; | 1450 final cssClass = css == null ? '' : ' class="$css"'; |
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1405 } | 1537 } |
1406 | 1538 |
1407 /** Generates a human-friendly string representation for a type. */ | 1539 /** Generates a human-friendly string representation for a type. */ |
1408 typeName(TypeMirror type, [bool showBounds = false]) { | 1540 typeName(TypeMirror type, [bool showBounds = false]) { |
1409 if (type.isVoid) { | 1541 if (type.isVoid) { |
1410 return 'void'; | 1542 return 'void'; |
1411 } | 1543 } |
1412 if (type is TypeVariableMirror) { | 1544 if (type is TypeVariableMirror) { |
1413 return type.simpleName; | 1545 return type.simpleName; |
1414 } | 1546 } |
1415 assert (type is InterfaceMirror); | 1547 assert(type is InterfaceMirror); |
1416 | 1548 |
1417 // See if it's a generic type. | 1549 // See if it's a generic type. |
1418 if (type.isDeclaration) { | 1550 if (type.isDeclaration) { |
1419 final typeParams = []; | 1551 final typeParams = []; |
1420 for (final typeParam in type.declaration.typeVariables) { | 1552 for (final typeParam in type.declaration.typeVariables) { |
1421 if (showBounds && | 1553 if (showBounds && |
1422 (typeParam.bound != null) && | 1554 (typeParam.bound != null) && |
1423 !typeParam.bound.isObject) { | 1555 !typeParam.bound.isObject) { |
1424 final bound = typeName(typeParam.bound, showBounds: true); | 1556 final bound = typeName(typeParam.bound, showBounds: true); |
1425 typeParams.add('${typeParam.simpleName} extends $bound'); | 1557 typeParams.add('${typeParam.simpleName} extends $bound'); |
(...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1589 } | 1721 } |
1590 | 1722 |
1591 /** | 1723 /** |
1592 * Returns [:true:] if [type] should be regarded as an exception. | 1724 * Returns [:true:] if [type] should be regarded as an exception. |
1593 */ | 1725 */ |
1594 bool isException(TypeMirror type) { | 1726 bool isException(TypeMirror type) { |
1595 return type.simpleName.endsWith('Exception'); | 1727 return type.simpleName.endsWith('Exception'); |
1596 } | 1728 } |
1597 } | 1729 } |
1598 | 1730 |
OLD | NEW |