OLD | NEW |
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2011, 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 use it, from this directory, run: | 6 * To use it, from this directory, run: |
7 * | 7 * |
8 * $ ./dartdoc <path to .dart file> | 8 * $ ./dartdoc <path to .dart file> |
9 * | 9 * |
10 * This will create a "docs" directory with the docs for your libraries. To | 10 * This will create a "docs" directory with the docs for your libraries. To |
11 * create these beautiful docs, dartdoc parses your library and every library | 11 * create these beautiful docs, dartdoc parses your library and every library |
12 * it imports (recursively). From each library, it parses all classes and | 12 * it imports (recursively). From each library, it parses all classes and |
13 * members, finds the associated doc comments and builds crosslinked docs from | 13 * members, finds the associated doc comments and builds crosslinked docs from |
14 * them. | 14 * them. |
15 */ | 15 */ |
16 #library('dartdoc'); | 16 #library('dartdoc'); |
17 | 17 |
18 #import('dart:json'); | 18 #import('dart:json'); |
19 #import('../../frog/lang.dart'); | 19 #import('../../frog/lang.dart'); |
20 #import('../../frog/file_system.dart'); | 20 #import('../../frog/file_system.dart'); |
21 #import('../../frog/file_system_node.dart'); | 21 #import('../../frog/file_system_node.dart'); |
22 #import('../../frog/lib/node/node.dart'); | 22 #import('../../frog/lib/node/node.dart'); |
23 #import('classify.dart'); | 23 #import('classify.dart'); |
24 #import('markdown.dart', prefix: 'md'); | 24 #import('markdown.dart', prefix: 'md'); |
25 | 25 |
26 #source('comment_map.dart'); | 26 #source('comment_map.dart'); |
27 #source('files.dart'); | |
28 #source('utils.dart'); | 27 #source('utils.dart'); |
29 | 28 |
| 29 /** Path to generate HTML files into. */ |
| 30 final _outdir = 'docs'; |
| 31 |
30 /** | 32 /** |
31 * Generates completely static HTML containing everything you need to browse | 33 * Generates completely static HTML containing everything you need to browse |
32 * the docs. The only client side behavior is trivial stuff like syntax | 34 * the docs. The only client side behavior is trivial stuff like syntax |
33 * highlighting code. | 35 * highlighting code. |
34 */ | 36 */ |
35 final MODE_STATIC = 0; | 37 final MODE_STATIC = 0; |
36 | 38 |
37 /** | 39 /** |
38 * Generated docs do not include baked HTML navigation. Instead, a single | 40 * Generated docs do not include baked HTML navigation. Instead, a single |
39 * `nav.json` file is created and the appropriate navigation is generated | 41 * `nav.json` file is created and the appropriate navigation is generated |
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
126 | 128 |
127 /** The library that we're currently generating docs for. */ | 129 /** The library that we're currently generating docs for. */ |
128 Library _currentLibrary; | 130 Library _currentLibrary; |
129 | 131 |
130 /** The type that we're currently generating docs for. */ | 132 /** The type that we're currently generating docs for. */ |
131 Type _currentType; | 133 Type _currentType; |
132 | 134 |
133 /** The member that we're currently generating docs for. */ | 135 /** The member that we're currently generating docs for. */ |
134 Member _currentMember; | 136 Member _currentMember; |
135 | 137 |
| 138 /** The path to the file currently being written to, relative to [outdir]. */ |
| 139 String _filePath; |
| 140 |
| 141 /** The file currently being written to. */ |
| 142 StringBuffer _file; |
| 143 |
136 int _totalLibraries = 0; | 144 int _totalLibraries = 0; |
137 int _totalTypes = 0; | 145 int _totalTypes = 0; |
138 int _totalMembers = 0; | 146 int _totalMembers = 0; |
139 | 147 |
140 Dartdoc() | 148 Dartdoc() |
141 : _comments = new CommentMap() { | 149 : _comments = new CommentMap() { |
142 // Patch in support for [:...:]-style code to the markdown parser. | 150 // Patch in support for [:...:]-style code to the markdown parser. |
143 // TODO(rnystrom): Markdown already has syntax for this. Phase this out? | 151 // TODO(rnystrom): Markdown already has syntax for this. Phase this out? |
144 md.InlineParser.syntaxes.insertRange(0, 1, | 152 md.InlineParser.syntaxes.insertRange(0, 1, |
145 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]')); | 153 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]')); |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
192 | 200 |
193 docIndex(); | 201 docIndex(); |
194 for (final library in world.libraries.getValues()) { | 202 for (final library in world.libraries.getValues()) { |
195 docLibrary(library); | 203 docLibrary(library); |
196 } | 204 } |
197 } finally { | 205 } finally { |
198 options.dietParse = oldDietParse; | 206 options.dietParse = oldDietParse; |
199 } | 207 } |
200 } | 208 } |
201 | 209 |
| 210 startFile(String path) { |
| 211 _filePath = path; |
| 212 _file = new StringBuffer(); |
| 213 } |
| 214 |
| 215 endFile() { |
| 216 String outPath = '$_outdir/$_filePath'; |
| 217 world.files.createDirectory(dirname(outPath), recursive: true); |
| 218 |
| 219 world.files.writeString(outPath, _file.toString()); |
| 220 _filePath = null; |
| 221 _file = null; |
| 222 } |
| 223 |
| 224 write(String s) { |
| 225 _file.add(s); |
| 226 } |
| 227 |
| 228 writeln(String s) { |
| 229 write(s); |
| 230 write('\n'); |
| 231 } |
| 232 |
202 /** | 233 /** |
203 * Writes the page header with the given [title] and [breadcrumbs]. The | 234 * Writes the page header with the given [title] and [breadcrumbs]. The |
204 * breadcrumbs are an interleaved list of links and titles. If a link is null, | 235 * breadcrumbs are an interleaved list of links and titles. If a link is null, |
205 * then no link will be generated. For example, given: | 236 * then no link will be generated. For example, given: |
206 * | 237 * |
207 * ['foo', 'foo.html', 'bar', null] | 238 * ['foo', 'foo.html', 'bar', null] |
208 * | 239 * |
209 * It will output: | 240 * It will output: |
210 * | 241 * |
211 * <a href="foo.html">foo</a> › bar | 242 * <a href="foo.html">foo</a> › bar |
(...skipping 271 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
483 ''' | 514 ''' |
484 <h2>${type.isClass ? "Class" : "Interface"} | 515 <h2>${type.isClass ? "Class" : "Interface"} |
485 <strong>${typeName(type, showBounds: true)}</strong></h2> | 516 <strong>${typeName(type, showBounds: true)}</strong></h2> |
486 '''); | 517 '''); |
487 | 518 |
488 docCode(type.span, getTypeComment(type)); | 519 docCode(type.span, getTypeComment(type)); |
489 docInheritance(type); | 520 docInheritance(type); |
490 docConstructors(type); | 521 docConstructors(type); |
491 docMembers(type); | 522 docMembers(type); |
492 | 523 |
| 524 writeTypeFooter(); |
493 writeFooter(); | 525 writeFooter(); |
494 endFile(); | 526 endFile(); |
495 } | 527 } |
496 | 528 |
| 529 /** Override this to write additional content at the end of a type's page. */ |
| 530 void writeTypeFooter() { |
| 531 // Do nothing. |
| 532 } |
| 533 |
497 /** | 534 /** |
498 * Writes an inline type span for the given type. This is a little box with | 535 * Writes an inline type span for the given type. This is a little box with |
499 * an icon and the type's name. It's similar to how types appear in the | 536 * an icon and the type's name. It's similar to how types appear in the |
500 * navigation, but is suitable for inline (as opposed to in a `<ul>`) use. | 537 * navigation, but is suitable for inline (as opposed to in a `<ul>`) use. |
501 */ | 538 */ |
502 typeSpan(Type type) { | 539 typeSpan(Type type) { |
503 var icon = 'interface'; | 540 var icon = 'interface'; |
504 if (type.name.endsWith('Exception')) { | 541 if (type.name.endsWith('Exception')) { |
505 icon = 'exception'; | 542 icon = 'exception'; |
506 } else if (type.isClass) { | 543 } else if (type.isClass) { |
(...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
699 var name = method.name; | 736 var name = method.name; |
700 if (name.startsWith('get:')) { | 737 if (name.startsWith('get:')) { |
701 // Getter. | 738 // Getter. |
702 name = 'get ${name.substring(4)}'; | 739 name = 'get ${name.substring(4)}'; |
703 } else if (name.startsWith('set:')) { | 740 } else if (name.startsWith('set:')) { |
704 // Setter. | 741 // Setter. |
705 name = 'set ${name.substring(4)}'; | 742 name = 'set ${name.substring(4)}'; |
706 } else if (name == ':negate') { | 743 } else if (name == ':negate') { |
707 // Dart uses 'negate' for prefix negate operators, not '!'. | 744 // Dart uses 'negate' for prefix negate operators, not '!'. |
708 name = 'operator negate'; | 745 name = 'operator negate'; |
| 746 } else if (name == ':call') { |
| 747 name = 'operator call'; |
709 } else { | 748 } else { |
710 // See if it's an operator. | 749 // See if it's an operator. |
711 name = TokenKind.rawOperatorFromMethod(name); | 750 name = TokenKind.rawOperatorFromMethod(name); |
712 if (name == null) { | 751 if (name == null) { |
713 name = method.name; | 752 name = method.name; |
714 } else { | 753 } else { |
715 name = 'operator $name'; | 754 name = 'operator $name'; |
716 } | 755 } |
717 } | 756 } |
718 | 757 |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
798 write(')'); | 837 write(')'); |
799 } | 838 } |
800 | 839 |
801 /** | 840 /** |
802 * Documents the code contained within [span] with [comment]. If [showCode] | 841 * Documents the code contained within [span] with [comment]. If [showCode] |
803 * is `true` (and [includeSource] is set), also includes the source code. | 842 * is `true` (and [includeSource] is set), also includes the source code. |
804 */ | 843 */ |
805 docCode(SourceSpan span, String comment, [bool showCode = false]) { | 844 docCode(SourceSpan span, String comment, [bool showCode = false]) { |
806 writeln('<div class="doc">'); | 845 writeln('<div class="doc">'); |
807 if (comment != null) { | 846 if (comment != null) { |
808 writeln(md.markdownToHtml(comment)); | 847 writeln(comment); |
809 } | 848 } |
810 | 849 |
811 if (includeSource && showCode) { | 850 if (includeSource && showCode) { |
812 writeln('<pre class="source">'); | 851 writeln('<pre class="source">'); |
813 writeln(md.escapeHtml(unindentCode(span))); | 852 writeln(md.escapeHtml(unindentCode(span))); |
814 writeln('</pre>'); | 853 writeln('</pre>'); |
815 } | 854 } |
816 | 855 |
817 writeln('</div>'); | 856 writeln('</div>'); |
818 } | 857 } |
819 | 858 |
820 /** Get the doc comment associated with the given type. */ | 859 /** Get the doc comment associated with the given type. */ |
821 String getTypeComment(Type type) => _comments.find(type.span); | 860 String getTypeComment(Type type) { |
| 861 String comment = _comments.find(type.span); |
| 862 if (comment == null) return null; |
| 863 return md.markdownToHtml(comment); |
| 864 } |
822 | 865 |
823 /** Get the doc comment associated with the given method. */ | 866 /** Get the doc comment associated with the given method. */ |
824 String getMethodComment(MethodMember method) => _comments.find(method.span); | 867 String getMethodComment(MethodMember method) { |
| 868 String comment = _comments.find(method.span); |
| 869 if (comment == null) return null; |
| 870 return md.markdownToHtml(comment); |
| 871 } |
825 | 872 |
826 /** Get the doc comment associated with the given field. */ | 873 /** Get the doc comment associated with the given field. */ |
827 String getFieldComment(FieldMember field) => _comments.find(field.span); | 874 String getFieldComment(FieldMember field) { |
| 875 String comment = _comments.find(field.span); |
| 876 if (comment == null) return null; |
| 877 return md.markdownToHtml(comment); |
| 878 } |
| 879 |
| 880 /** |
| 881 * Converts [fullPath] which is understood to be a full path from the root of |
| 882 * the generated docs to one relative to the current file. |
| 883 */ |
| 884 String relativePath(String fullPath) { |
| 885 // Don't make it relative if it's an absolute path. |
| 886 if (isAbsolute(fullPath)) return fullPath; |
| 887 |
| 888 // TODO(rnystrom): Walks all the way up to root each time. Shouldn't do |
| 889 // this if the paths overlap. |
| 890 return repeat('../', countOccurrences(_filePath, '/')) + fullPath; |
| 891 } |
| 892 |
| 893 /** Gets whether or not the given URL is absolute or relative. */ |
| 894 bool isAbsolute(String url) { |
| 895 // TODO(rnystrom): Why don't we have a nice type in the platform for this? |
| 896 // TODO(rnystrom): This is a bit hackish. We consider any URL that lacks |
| 897 // a scheme to be relative. |
| 898 return const RegExp(@'^\w+:').hasMatch(url); |
| 899 } |
| 900 |
| 901 /** Gets the URL to the documentation for [library]. */ |
| 902 String libraryUrl(Library library) { |
| 903 return '${sanitize(library.name)}.html'; |
| 904 } |
| 905 |
| 906 /** Gets the URL for the documentation for [type]. */ |
| 907 String typeUrl(Type type) { |
| 908 // Always get the generic type to strip off any type parameters or |
| 909 // arguments. If the type isn't generic, genericType returns `this`, so it |
| 910 // works for non-generic types too. |
| 911 return '${sanitize(type.library.name)}/${type.genericType.name}.html'; |
| 912 } |
| 913 |
| 914 /** Gets the URL for the documentation for [member]. */ |
| 915 String memberUrl(Member member) { |
| 916 return '${typeUrl(member.declaringType)}#${member.name}'; |
| 917 } |
| 918 |
| 919 /** Gets the anchor id for the document for [member]. */ |
| 920 String memberAnchor(Member member) { |
| 921 return '${member.name}'; |
| 922 } |
828 | 923 |
829 /** | 924 /** |
830 * Creates a hyperlink. Handles turning the [href] into an appropriate | 925 * Creates a hyperlink. Handles turning the [href] into an appropriate |
831 * relative path from the current file. | 926 * relative path from the current file. |
832 */ | 927 */ |
833 String a(String href, String contents, [String css]) { | 928 String a(String href, String contents, [String css]) { |
| 929 // Mark outgoing external links, mainly so we can style them. |
| 930 final rel = isAbsolute(href) ? ' ref="external"' : ''; |
834 final cssClass = css == null ? '' : ' class="$css"'; | 931 final cssClass = css == null ? '' : ' class="$css"'; |
835 return '<a href="${relativePath(href)}"$cssClass>$contents</a>'; | 932 return '<a href="${relativePath(href)}"$cssClass$rel>$contents</a>'; |
836 } | 933 } |
837 | 934 |
838 /** | 935 /** |
839 * Writes a type annotation for the given type and (optional) parameter name. | 936 * Writes a type annotation for the given type and (optional) parameter name. |
840 */ | 937 */ |
841 annotateType(Type enclosingType, Type type, [String paramName = null]) { | 938 annotateType(Type enclosingType, Type type, [String paramName = null]) { |
842 // Don't bother explicitly displaying Dynamic. | 939 // Don't bother explicitly displaying Dynamic. |
843 if (type.isVar) { | 940 if (type.isVar) { |
844 if (paramName !== null) write(paramName); | 941 if (paramName !== null) write(paramName); |
845 return; | 942 return; |
(...skipping 210 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1056 | 1153 |
1057 return new md.Element.text('code', name); | 1154 return new md.Element.text('code', name); |
1058 } | 1155 } |
1059 | 1156 |
1060 // TODO(rnystrom): Move into SourceSpan? | 1157 // TODO(rnystrom): Move into SourceSpan? |
1061 int getSpanColumn(SourceSpan span) { | 1158 int getSpanColumn(SourceSpan span) { |
1062 final line = span.file.getLine(span.start); | 1159 final line = span.file.getLine(span.start); |
1063 return span.file.getColumn(line, span.start); | 1160 return span.file.getColumn(line, span.start); |
1064 } | 1161 } |
1065 } | 1162 } |
OLD | NEW |