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