| 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 |