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 |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
47 | 47 |
48 /** | 48 /** |
49 * The cached lookup-table to associate doc comments with spans. The outer map | 49 * The cached lookup-table to associate doc comments with spans. The outer map |
50 * is from filenames to doc comments in that file. The inner map maps from the | 50 * is from filenames to doc comments in that file. The inner map maps from the |
51 * token positions to doc comments. Each position is the starting offset of the | 51 * token positions to doc comments. Each position is the starting offset of the |
52 * next non-comment token *following* the doc comment. For example, the position | 52 * next non-comment token *following* the doc comment. For example, the position |
53 * for this comment would be the position of the "Map" token below. | 53 * for this comment would be the position of the "Map" token below. |
54 */ | 54 */ |
55 Map<String, Map<int, String>> _comments; | 55 Map<String, Map<int, String>> _comments; |
56 | 56 |
| 57 /** A callback that returns additional Markdown documentation for a type. */ |
| 58 typedef String TypeDocumenter(Type type); |
| 59 |
| 60 /** A list of callbacks registered for documenting types. */ |
| 61 List<TypeDocumenter> _typeDocumenters; |
| 62 |
| 63 /** A callback that returns additional Markdown documentation for a method. */ |
| 64 typedef String MethodDocumenter(MethodMember method); |
| 65 |
| 66 /** A list of callbacks registered for documenting methods. */ |
| 67 List<MethodDocumenter> _methodDocumenters; |
| 68 |
| 69 /** A callback that returns additional Markdown documentation for a field. */ |
| 70 typedef String FieldDocumenter(FieldMember field); |
| 71 |
| 72 /** A list of callbacks registered for documenting fields. */ |
| 73 List<FieldDocumenter> _fieldDocumenters; |
| 74 |
57 int _totalLibraries = 0; | 75 int _totalLibraries = 0; |
58 int _totalTypes = 0; | 76 int _totalTypes = 0; |
59 int _totalMembers = 0; | 77 int _totalMembers = 0; |
60 | 78 |
61 /** | 79 /** |
62 * Run this from the `utils/dartdoc` directory. | 80 * Run this from the `utils/dartdoc` directory. |
63 */ | 81 */ |
64 void main() { | 82 void main() { |
65 // The entrypoint of the library to generate docs for. | 83 // The entrypoint of the library to generate docs for. |
66 final entrypoint = process.argv[2]; | 84 final entrypoint = process.argv[2]; |
67 | 85 |
68 // Parse the dartdoc options. | 86 // Parse the dartdoc options. |
69 for (int i = 3; i < process.argv.length; i++) { | 87 for (int i = 3; i < process.argv.length; i++) { |
70 final arg = process.argv[i]; | 88 final arg = process.argv[i]; |
71 switch (arg) { | 89 switch (arg) { |
72 case '--no-code': | 90 case '--no-code': |
73 includeSource = false; | 91 includeSource = false; |
74 break; | 92 break; |
75 | 93 |
76 default: | 94 default: |
77 print('Unknown option: $arg'); | 95 print('Unknown option: $arg'); |
78 } | 96 } |
79 } | 97 } |
80 | 98 |
81 files = new NodeFileSystem(); | 99 files = new NodeFileSystem(); |
82 parseOptions('../../frog', [] /* args */, files); | 100 parseOptions('../../frog', [] /* args */, files); |
83 options.dietParse = true; | 101 initializeWorld(files); |
| 102 |
| 103 final elapsed = time(() { |
| 104 initializeDartDoc(); |
| 105 document(entrypoint); |
| 106 }); |
| 107 |
| 108 printStats(); |
| 109 } |
| 110 |
| 111 void initializeDartDoc() { |
| 112 _comments = <Map<int, String>>{}; |
| 113 _typeDocumenters = <TypeDocumenter>[]; |
| 114 _methodDocumenters = <MethodDocumenter>[]; |
| 115 _fieldDocumenters = <FieldDocumenter>[]; |
84 | 116 |
85 // Patch in support for [:...:]-style code to the markdown parser. | 117 // Patch in support for [:...:]-style code to the markdown parser. |
86 // TODO(rnystrom): Markdown already has syntax for this. Phase this out? | 118 // TODO(rnystrom): Markdown already has syntax for this. Phase this out? |
87 md.InlineParser.syntaxes.insertRange(0, 1, | 119 md.InlineParser.syntaxes.insertRange(0, 1, |
88 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]')); | 120 new md.CodeSyntax(@'\[\:((?:.|\n)*?)\:\]')); |
89 | 121 |
90 md.setImplicitLinkResolver(resolveNameReference); | 122 md.setImplicitLinkResolver(resolveNameReference); |
| 123 } |
91 | 124 |
92 final elapsed = time(() { | 125 document(String entrypoint) { |
93 initializeDartDoc(); | 126 try { |
94 | 127 var oldDietParse = options.dietParse; |
95 initializeWorld(files); | 128 options.dietParse = true; |
96 | 129 |
97 // Handle the built-in entrypoints. | 130 // Handle the built-in entrypoints. |
98 switch (entrypoint) { | 131 switch (entrypoint) { |
99 case 'corelib': | 132 case 'corelib': |
100 world.getOrAddLibrary('dart:core'); | 133 world.getOrAddLibrary('dart:core'); |
101 world.getOrAddLibrary('dart:coreimpl'); | 134 world.getOrAddLibrary('dart:coreimpl'); |
102 world.process(); | 135 world.process(); |
103 break; | 136 break; |
104 | 137 |
105 case 'dom': | 138 case 'dom': |
(...skipping 16 matching lines...) Expand all Loading... |
122 world.processDartScript(entrypoint); | 155 world.processDartScript(entrypoint); |
123 } | 156 } |
124 | 157 |
125 world.resolveAll(); | 158 world.resolveAll(); |
126 | 159 |
127 // Generate the docs. | 160 // Generate the docs. |
128 docIndex(); | 161 docIndex(); |
129 for (final library in world.libraries.getValues()) { | 162 for (final library in world.libraries.getValues()) { |
130 docLibrary(library); | 163 docLibrary(library); |
131 } | 164 } |
132 }); | 165 } finally { |
133 | 166 options.dietParse = oldDietParse; |
134 print('Documented $_totalLibraries libraries, $_totalTypes types, and ' + | 167 } |
135 '$_totalMembers members in ${elapsed}msec.'); | |
136 } | 168 } |
137 | 169 |
138 void initializeDartDoc() { | 170 printStats() { |
139 _comments = <String, Map<int, String>>{}; | 171 print('Documented $_totalLibraries libraries, $_totalTypes types, and ' + |
| 172 '$_totalMembers members in ${elapsed}msec.'); |
140 } | 173 } |
141 | 174 |
142 writeHeader(String title) { | 175 writeHeader(String title) { |
143 writeln( | 176 writeln( |
144 ''' | 177 ''' |
145 <!DOCTYPE html> | 178 <!DOCTYPE html> |
146 <html> | 179 <html> |
147 <head> | 180 <head> |
148 <meta charset="utf-8"> | 181 <meta charset="utf-8"> |
149 <title>$title</title> | 182 <title>$title</title> |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
232 write('<div class="$icon"></div><strong>${typeName(type)}</strong>'); | 265 write('<div class="$icon"></div><strong>${typeName(type)}</strong>'); |
233 } else { | 266 } else { |
234 write(a(typeUrl(type), '<div class="$icon"></div>${typeName(type)}')); | 267 write(a(typeUrl(type), '<div class="$icon"></div>${typeName(type)}')); |
235 } | 268 } |
236 | 269 |
237 writeln('</li>'); | 270 writeln('</li>'); |
238 } | 271 } |
239 writeln('</ul>'); | 272 writeln('</ul>'); |
240 } | 273 } |
241 | 274 |
| 275 String _runDocumenters(var item, List<Function> documenters) => |
| 276 Strings.join(map(documenters, (doc) => doc(item)), '\n\n'); |
| 277 |
242 docLibrary(Library library) { | 278 docLibrary(Library library) { |
243 _totalLibraries++; | 279 _totalLibraries++; |
244 _currentLibrary = library; | 280 _currentLibrary = library; |
245 _currentType = null; | 281 _currentType = null; |
246 | 282 |
247 startFile(libraryUrl(library)); | 283 startFile(libraryUrl(library)); |
248 writeHeader(library.name); | 284 writeHeader(library.name); |
249 writeln('<h1>Library <strong>${library.name}</strong></h1>'); | 285 writeln('<h1>Library <strong>${library.name}</strong></h1>'); |
250 | 286 |
251 // Look for a comment for the entire library. | 287 // Look for a comment for the entire library. |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
315 writeHeader('Library ${type.library.name} / $typeTitle'); | 351 writeHeader('Library ${type.library.name} / $typeTitle'); |
316 writeln( | 352 writeln( |
317 ''' | 353 ''' |
318 <h1>${a(libraryUrl(type.library), | 354 <h1>${a(libraryUrl(type.library), |
319 "Library <strong>${type.library.name}</strong>")}</h1> | 355 "Library <strong>${type.library.name}</strong>")}</h1> |
320 <h2>${type.isClass ? "Class" : "Interface"} | 356 <h2>${type.isClass ? "Class" : "Interface"} |
321 <strong>${typeName(type, showBounds: true)}</strong></h2> | 357 <strong>${typeName(type, showBounds: true)}</strong></h2> |
322 '''); | 358 '''); |
323 | 359 |
324 docInheritance(type); | 360 docInheritance(type); |
325 docCode(type.span); | 361 docCode(type.span, _runDocumenters(type, _typeDocumenters)); |
326 docConstructors(type); | 362 docConstructors(type); |
327 docMembers(type); | 363 docMembers(type); |
328 | 364 |
329 writeFooter(); | 365 writeFooter(); |
330 endFile(); | 366 endFile(); |
331 } | 367 } |
332 | 368 |
333 void docMembers(Type type) { | 369 void docMembers(Type type) { |
334 // Collect the different kinds of members. | 370 // Collect the different kinds of members. |
335 final methods = []; | 371 final methods = []; |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
373 | 409 |
374 if (isSubclass || | 410 if (isSubclass || |
375 (type.interfaces != null && type.interfaces.length > 0) || | 411 (type.interfaces != null && type.interfaces.length > 0) || |
376 (factory != null)) { | 412 (factory != null)) { |
377 writeln('<p>'); | 413 writeln('<p>'); |
378 | 414 |
379 if (isSubclass) { | 415 if (isSubclass) { |
380 write('Extends ${typeReference(type.parent)}. '); | 416 write('Extends ${typeReference(type.parent)}. '); |
381 } | 417 } |
382 | 418 |
383 if (type.interfaces != null) { | 419 if (type.interfaces != null && type.interfaces.length > 0) { |
384 switch (type.interfaces.length) { | 420 var interfaceStr = joinWithCommas(map(type.interfaces, typeReference)); |
385 case 0: | 421 write('Implements ${interfaceStr}. '); |
386 // Do nothing. | |
387 break; | |
388 | |
389 case 1: | |
390 write('Implements ${typeReference(type.interfaces[0])}. '); | |
391 break; | |
392 | |
393 case 2: | |
394 write('''Implements ${typeReference(type.interfaces[0])} and | |
395 ${typeReference(type.interfaces[1])}. '''); | |
396 break; | |
397 | |
398 default: | |
399 write('Implements '); | |
400 for (final i = 0; i < type.interfaces.length; i++) { | |
401 write('${typeReference(type.interfaces[i])}'); | |
402 if (i < type.interfaces.length - 2) { | |
403 write(', '); | |
404 } else if (i < type.interfaces.length - 1) { | |
405 write(', and '); | |
406 } | |
407 } | |
408 write('. '); | |
409 break; | |
410 } | |
411 } | 422 } |
412 | 423 |
413 if (factory != null) { | 424 if (factory != null) { |
414 write('Has factory class ${typeReference(factory)}.'); | 425 write('Has factory class ${typeReference(factory)}.'); |
415 } | 426 } |
416 } | 427 } |
417 } | 428 } |
418 | 429 |
419 /** Document the constructors for [Type], if any. */ | 430 /** Document the constructors for [Type], if any. */ |
420 docConstructors(Type type) { | 431 docConstructors(Type type) { |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
482 write('.'); | 493 write('.'); |
483 write(constructorName); | 494 write(constructorName); |
484 } | 495 } |
485 | 496 |
486 docParamList(type, method); | 497 docParamList(type, method); |
487 | 498 |
488 write(''' <a class="anchor-link" href="#${memberAnchor(method)}" | 499 write(''' <a class="anchor-link" href="#${memberAnchor(method)}" |
489 title="Permalink to ${typeName(type)}.$name">#</a>'''); | 500 title="Permalink to ${typeName(type)}.$name">#</a>'''); |
490 writeln('</h4>'); | 501 writeln('</h4>'); |
491 | 502 |
492 docCode(method.span, showCode: true); | 503 docCode(method.span, _runDocumenters(method, _methodDocumenters), |
| 504 showCode: true); |
493 | 505 |
494 writeln('</div>'); | 506 writeln('</div>'); |
495 } | 507 } |
496 | 508 |
497 docParamList(Type enclosingType, MethodMember member) { | 509 docParamList(Type enclosingType, MethodMember member) { |
498 write('('); | 510 write('('); |
499 bool first = true; | 511 bool first = true; |
500 bool inOptionals = false; | 512 bool inOptionals = false; |
501 for (final parameter in member.parameters) { | 513 for (final parameter in member.parameters) { |
502 if (!first) write(', '); | 514 if (!first) write(', '); |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
550 | 562 |
551 annotateType(type, field.type); | 563 annotateType(type, field.type); |
552 write( | 564 write( |
553 ''' | 565 ''' |
554 <strong>${field.name}</strong> <a class="anchor-link" | 566 <strong>${field.name}</strong> <a class="anchor-link" |
555 href="#${memberAnchor(field)}" | 567 href="#${memberAnchor(field)}" |
556 title="Permalink to ${typeName(type)}.${field.name}">#</a> | 568 title="Permalink to ${typeName(type)}.${field.name}">#</a> |
557 </h4> | 569 </h4> |
558 '''); | 570 '''); |
559 | 571 |
560 docCode(field.span, showCode: true); | 572 docCode(field.span, _runDocumenters(field, _fieldDocumenters), |
| 573 showCode: true); |
561 writeln('</div>'); | 574 writeln('</div>'); |
562 } | 575 } |
563 | 576 |
564 /** | 577 /** |
565 * Creates a hyperlink. Handles turning the [href] into an appropriate relative | 578 * Creates a hyperlink. Handles turning the [href] into an appropriate relative |
566 * path from the current file. | 579 * path from the current file. |
567 */ | 580 */ |
568 String a(String href, String contents, [String class]) { | 581 String a(String href, String contents, [String class]) { |
569 final css = class == null ? '' : ' class="$class"'; | 582 final css = class == null ? '' : ' class="$class"'; |
570 return '<a href="${relativePath(href)}"$css>$contents</a>'; | 583 return '<a href="${relativePath(href)}"$css>$contents</a>'; |
(...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
738 // * Type parameters of the enclosing type. | 751 // * Type parameters of the enclosing type. |
739 | 752 |
740 return new md.Element.text('code', name); | 753 return new md.Element.text('code', name); |
741 } | 754 } |
742 | 755 |
743 /** | 756 /** |
744 * Documents the code contained within [span]. Will include the previous | 757 * Documents the code contained within [span]. Will include the previous |
745 * Dartdoc associated with that span if found, and will include the syntax | 758 * Dartdoc associated with that span if found, and will include the syntax |
746 * highlighted code itself if desired. | 759 * highlighted code itself if desired. |
747 */ | 760 */ |
748 docCode(SourceSpan span, [bool showCode = false]) { | 761 docCode(SourceSpan span, String extraMarkdown, [bool showCode = false]) { |
749 if (span == null) return; | 762 if (span == null) return; |
750 | 763 |
751 writeln('<div class="doc">'); | 764 writeln('<div class="doc">'); |
752 final comment = findComment(span); | 765 final comment = findComment(span); |
753 if (comment != null) { | 766 if (comment != null) { |
754 writeln(md.markdownToHtml(comment)); | 767 writeln(md.markdownToHtml('${comment}\n\n${extraMarkdown}')); |
| 768 } else { |
| 769 writeln(md.markdownToHtml(extraMarkdown)); |
755 } | 770 } |
756 | 771 |
757 if (includeSource && showCode) { | 772 if (includeSource && showCode) { |
758 writeln('<pre class="source">'); | 773 writeln('<pre class="source">'); |
759 write(formatCode(span)); | 774 write(formatCode(span)); |
760 writeln('</pre>'); | 775 writeln('</pre>'); |
761 } | 776 } |
762 | 777 |
763 writeln('</div>'); | 778 writeln('</div>'); |
764 } | 779 } |
765 | 780 |
766 /** Finds the doc comment preceding the given source span, if there is one. */ | 781 /** Finds the doc comment preceding the given source span, if there is one. */ |
767 findComment(SourceSpan span) => findCommentInFile(span.file, span.start); | 782 findComment(SourceSpan span) => findCommentInFile(span.file, span.start); |
768 | 783 |
769 /** Finds the doc comment preceding the given source span, if there is one. */ | 784 /** Finds the doc comment preceding the given source span, if there is one. */ |
770 findCommentInFile(SourceFile file, int position) { | 785 findCommentInFile(SourceFile file, int position) { |
771 // Get the doc comments for this file. | 786 // Get the doc comments for this file. |
772 final fileComments = _comments.putIfAbsent(file.filename, | 787 final fileComments = _comments.putIfAbsent(file.filename, |
773 () => parseDocComments(file)); | 788 () => parseDocComments(file)); |
774 | 789 |
775 return fileComments[position]; | 790 return fileComments[position]; |
776 } | 791 } |
777 | 792 |
778 parseDocComments(SourceFile file) { | 793 parseDocComments(SourceFile file) { |
779 final comments = <int, String>{}; | 794 final comments = new Map<int, String>(); |
780 | 795 |
781 final tokenizer = new Tokenizer(file, false); | 796 final tokenizer = new Tokenizer(file, false); |
782 var lastComment = null; | 797 var lastComment = null; |
783 | 798 |
784 while (true) { | 799 while (true) { |
785 final token = tokenizer.next(); | 800 final token = tokenizer.next(); |
786 if (token.kind == TokenKind.END_OF_FILE) break; | 801 if (token.kind == TokenKind.END_OF_FILE) break; |
787 | 802 |
788 if (token.kind == TokenKind.COMMENT) { | 803 if (token.kind == TokenKind.COMMENT) { |
789 final text = token.text; | 804 final text = token.text; |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
862 line = line.substring(2, line.length); | 877 line = line.substring(2, line.length); |
863 } else if (line.startsWith('*')) { | 878 } else if (line.startsWith('*')) { |
864 line = line.substring(1, line.length); | 879 line = line.substring(1, line.length); |
865 } | 880 } |
866 | 881 |
867 buf.add(line); | 882 buf.add(line); |
868 buf.add('\n'); | 883 buf.add('\n'); |
869 } | 884 } |
870 | 885 |
871 return buf.toString(); | 886 return buf.toString(); |
872 } | 887 } |
| 888 |
| 889 /** Register a callback to add additional documentation to a type. */ |
| 890 addTypeDocumenter(TypeDocumenter fn) => _typeDocumenters.add(fn); |
| 891 |
| 892 /** Register a callback to add additional documentation to a method. */ |
| 893 addMethodDocumenter(MethodDocumenter fn) => _methodDocumenters.add(fn); |
| 894 |
| 895 /** Register a callback to add additional documentation to a field. */ |
| 896 addFieldDocumenter(FieldDocumenter fn) => _fieldDocumenters.add(fn); |
OLD | NEW |