Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3095)

Side by Side Diff: utils/dartdoc/dartdoc.dart

Issue 8772067: Navigation in generated docs. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Respond to review. Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « utils/dartdoc/dartdoc ('k') | utils/dartdoc/files.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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('../../frog/lang.dart'); 18 #import('../../frog/lang.dart');
19 #import('../../frog/file_system.dart'); 19 #import('../../frog/file_system.dart');
20 #import('../../frog/file_system_node.dart'); 20 #import('../../frog/file_system_node.dart');
21 #import('../markdown/lib.dart', prefix: 'md'); 21 #import('../markdown/lib.dart', prefix: 'md');
22 22
23 #source('classify.dart'); 23 #source('classify.dart');
24 #source('files.dart');
25 #source('utils.dart');
24 26
25 /** Path to corePath library. */ 27 /** Path to corePath library. */
26 final corePath = 'lib'; 28 final corePath = 'lib';
27 29
28 /** Path to generate html files into. */ 30 /** Path to generate html files into. */
29 final outdir = 'docs'; 31 final outdir = 'docs';
30 32
31 /** Set to `true` to include the source code in the generated docs. */ 33 /** Set to `true` to include the source code in the generated docs. */
32 bool includeSource = true; 34 bool includeSource = false;
33 35
34 /** Special comment position used to store the library-level doc comment. */ 36 /** Special comment position used to store the library-level doc comment. */
35 final _libraryDoc = -1; 37 final _libraryDoc = -1;
36 38
37 /** The path to the file currently being written to, relative to [outdir]. */
38 String _filePath;
39
40 /** The file currently being written to. */
41 StringBuffer _file;
42
43 /** The library that we're currently generating docs for. */ 39 /** The library that we're currently generating docs for. */
44 Library _currentLibrary; 40 Library _currentLibrary;
45 41
46 /** The type that we're currently generating docs for. */ 42 /** The type that we're currently generating docs for. */
47 Type _currentType; 43 Type _currentType;
48 44
49 /** The member that we're currently generating docs for. */ 45 /** The member that we're currently generating docs for. */
50 Member _currentMember; 46 Member _currentMember;
51 47
52 /** 48 /**
53 * 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
54 * 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
55 * 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
56 * 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
57 * 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.
58 */ 54 */
59 Map<String, Map<int, String>> _comments; 55 Map<String, Map<int, String>> _comments;
60 56
61 int _totalLibraries = 0; 57 int _totalLibraries = 0;
62 int _totalTypes = 0; 58 int _totalTypes = 0;
63 int _totalMembers = 0; 59 int _totalMembers = 0;
64 60
65 FileSystem files;
66
67 /** 61 /**
68 * Run this from the `utils/dartdoc` directory. 62 * Run this from the `utils/dartdoc` directory.
69 */ 63 */
70 void main() { 64 void main() {
71 // The entrypoint of the library to generate docs for. 65 // The entrypoint of the library to generate docs for.
72 final libPath = process.argv[2]; 66 final libPath = process.argv[2];
73 67
74 // Parse the dartdoc options. 68 // Parse the dartdoc options.
75 for (int i = 3; i < process.argv.length; i++) { 69 for (int i = 3; i < process.argv.length; i++) {
76 final arg = process.argv[i]; 70 final arg = process.argv[i];
(...skipping 18 matching lines...) Expand all
95 md.setImplicitLinkResolver(resolveNameReference); 89 md.setImplicitLinkResolver(resolveNameReference);
96 90
97 final elapsed = time(() { 91 final elapsed = time(() {
98 initializeDartDoc(); 92 initializeDartDoc();
99 93
100 initializeWorld(files); 94 initializeWorld(files);
101 95
102 world.processDartScript(libPath); 96 world.processDartScript(libPath);
103 world.resolveAll(); 97 world.resolveAll();
104 98
105 // Clean the output directory.
106 if (files.fileExists(outdir)) {
107 files.removeDirectory(outdir, recursive: true);
108 }
109 files.createDirectory(outdir, recursive: true);
110
111 // Copy over the static files.
112 for (final file in ['interact.js', 'styles.css']) {
113 copyStatic(file);
114 }
115
116 // Generate the docs. 99 // Generate the docs.
117 for (final library in world.libraries.getValues()) { 100 for (final library in world.libraries.getValues()) {
118 docLibrary(library); 101 docLibrary(library);
119 } 102 }
120 103
121 docIndex(world.libraries.getValues()); 104 docIndex(world.libraries.getValues());
122 }); 105 });
123 106
124 print('Documented $_totalLibraries libraries, $_totalTypes types, and ' + 107 print('Documented $_totalLibraries libraries, $_totalTypes types, and ' +
125 '$_totalMembers members in ${elapsed}msec.'); 108 '$_totalMembers members in ${elapsed}msec.');
126 } 109 }
127 110
128 void initializeDartDoc() { 111 void initializeDartDoc() {
129 _comments = <String, Map<int, String>>{}; 112 _comments = <String, Map<int, String>>{};
130 } 113 }
131 114
132 /** Copies the static file at 'static/file' to the output directory. */
133 copyStatic(String file) {
134 var contents = files.readAll(joinPaths('static', file));
135 files.writeString(joinPaths(outdir, file), contents);
136 }
137
138 num time(callback()) {
139 // Unlike world.withTiming, returns the elapsed time.
140 final watch = new Stopwatch();
141 watch.start();
142 callback();
143 watch.stop();
144 return watch.elapsedInMs();
145 }
146
147 startFile(String path) {
148 _filePath = path;
149 _file = new StringBuffer();
150 }
151
152 write(String s) {
153 _file.add(s);
154 }
155
156 writeln(String s) {
157 write(s);
158 write('\n');
159 }
160
161 endFile() {
162 String outPath = '$outdir/$_filePath';
163 files.createDirectory(dirname(outPath), recursive: true);
164
165 world.files.writeString(outPath, _file.toString());
166 _filePath = null;
167 _file = null;
168 }
169
170 /** Turns a library name into something that's safe to use as a file name. */
171 sanitize(String name) => name.replaceAll(':', '_').replaceAll('/', '_');
172
173 docIndex(List<Library> libraries) { 115 docIndex(List<Library> libraries) {
174 startFile('index.html'); 116 startFile('index.html');
175 // TODO(rnystrom): Need to figure out what this should look like. 117 // TODO(rnystrom): Need to figure out what this should look like.
176 writeln( 118 writeln(
177 ''' 119 '''
178 <html><head> 120 <html><head>
179 <title>Index</title> 121 <title>Index</title>
180 <link rel="stylesheet" type="text/css" href="styles.css" /> 122 <link rel="stylesheet" type="text/css" href="styles.css" />
181 </head> 123 </head>
182 <body> 124 <body>
(...skipping 14 matching lines...) Expand all
197 writeln( 139 writeln(
198 ''' 140 '''
199 </ul> 141 </ul>
200 </div> 142 </div>
201 </body></html> 143 </body></html>
202 '''); 144 ''');
203 145
204 endFile(); 146 endFile();
205 } 147 }
206 148
207 /** Returns the number of times [search] occurs in [text]. */
208 int countOccurrences(String text, String search) {
209 int start = 0;
210 int count = 0;
211
212 while (true) {
213 start = text.indexOf(search, start);
214 if (start == -1) break;
215 count++;
216 // Offsetting by needle length means overlapping needles are not counted.
217 start += search.length;
218 }
219
220 return count;
221 }
222
223 /** Repeats [text] [count] times, separated by [separator] if given. */
224 String repeat(String text, int count, [String separator]) {
225 // TODO(rnystrom): Should be in corelib.
226 final buffer = new StringBuffer();
227 for (int i = 0; i < count; i++) {
228 buffer.add(text);
229 if ((i < count - 1) && (separator !== null)) buffer.add(separator);
230 }
231
232 return buffer.toString();
233 }
234
235 /**
236 * Converts [absolute] which is understood to be a full path from the root of
237 * the generated docs to one relative to the current file.
238 */
239 String relativePath(String absolute) {
240 // TODO(rnystrom): Walks all the way up to root each time. Shouldn't do this
241 // if the paths overlap.
242 return repeat('../', countOccurrences(_filePath, '/')) + absolute;
243 }
244
245 /**
246 * Creates a hyperlink. Handles turning the [href] into an appropriate relative
247 * path from the current file.
248 */
249 String a(String href, String contents, [String class]) {
250 final css = class == null ? '' : ' class="$class"';
251 return '<a href="${relativePath(href)}"$css>$contents</a>';
252 }
253
254 writeHeader(String title) { 149 writeHeader(String title) {
255 writeln( 150 writeln(
256 ''' 151 '''
257 <!DOCTYPE html> 152 <!DOCTYPE html>
258 <html> 153 <html>
259 <head> 154 <head>
260 <meta charset="utf-8"> 155 <meta charset="utf-8">
261 <title>$title</title> 156 <title>$title</title>
262 <link rel="stylesheet" type="text/css" 157 <link rel="stylesheet" type="text/css"
263 href="${relativePath('styles.css')}" /> 158 href="${relativePath('styles.css')}" />
264 <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600,700,8 00" rel="stylesheet" type="text/css"> 159 <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600,700,8 00" rel="stylesheet" type="text/css">
265 <script src="${relativePath('interact.js')}"></script> 160 <script src="${relativePath('interact.js')}"></script>
266 </head> 161 </head>
267 <body> 162 <body>
268 <div class="content"> 163 <div class="page">
269 '''); 164 ''');
165 docNavigation();
166 writeln('<div class="content">');
270 } 167 }
271 168
272 writeFooter() { 169 writeFooter() {
273 writeln( 170 writeln(
274 ''' 171 '''
275 </div> 172 </div>
173 <div class="footer"</div>
276 </body></html> 174 </body></html>
277 '''); 175 ''');
278 } 176 }
279 177
178 docNavigation() {
179 writeln(
180 '''
181 <div class="nav">
182 <h1>Libraries</h1>
183 ''');
184
185 for (final library in orderValuesByKeys(world.libraries)) {
186 write('<h2><div class="icon-library"></div> ');
187
188 if ((_currentLibrary == library) && (_currentType == null)) {
189 write('<strong>${library.name}</strong>');
190 } else {
191 write('${a(libraryUrl(library), library.name)}');
192 }
193 write('</h2>');
194
195 final types = orderValuesByKeys(library.types);
196 if (types.length > 0) {
197 writeln('<ul>');
198 for (final type in types) {
199 if (type.isTop) continue;
200 if (type.name.startsWith('_')) continue;
201
202 var icon = type.isClass ? 'icon-class' : 'icon-interface';
203 write('<li><div class="$icon"></div> ');
204
205 if (_currentType == type) {
206 write('<strong>${type.name}</strong>');
207 } else {
208 write('${a(typeUrl(type), type.name)}');
209 }
210
211 writeln('</li>');
212 }
213
214 writeln('</ul>');
215 }
216 }
217
218 writeln('</div>');
219 }
220
280 docLibrary(Library library) { 221 docLibrary(Library library) {
281 _totalLibraries++; 222 _totalLibraries++;
282 _currentLibrary = library; 223 _currentLibrary = library;
224 _currentType = null;
283 225
284 startFile(libraryUrl(library)); 226 startFile(libraryUrl(library));
285 writeHeader(library.name); 227 writeHeader(library.name);
286 writeln('<h1>Library <strong>${library.name}</strong></h1>'); 228 writeln('<h1>Library <strong>${library.name}</strong></h1>');
287 229
288 // Look for a comment for the entire library. 230 // Look for a comment for the entire library.
289 final comment = findCommentInFile(library.baseSource, _libraryDoc); 231 final comment = findCommentInFile(library.baseSource, _libraryDoc);
290 if (comment != null) { 232 if (comment != null) {
291 final html = md.markdownToHtml(comment); 233 final html = md.markdownToHtml(comment);
292 writeln('<div class="doc">$html</div>'); 234 writeln('<div class="doc">$html</div>');
293 } 235 }
294 236
295 // Document the top-level members. 237 // Document the top-level members.
296 docMembers(library.topType); 238 docMembers(library.topType);
297 239
298 // TODO(rnystrom): Link to types. 240 // TODO(rnystrom): Link to types.
299 writeln('<h3>Types</h3>'); 241 writeln('<h3>Types</h3>');
300 242
301 for (final type in orderValuesByKeys(library.types)) { 243 for (final type in orderValuesByKeys(library.types)) {
302 if (type.isTop) continue; 244 if (type.isTop) continue;
245 if (type.name.startsWith('_')) continue;
303 writeln( 246 writeln(
304 ''' 247 '''
305 <div class="type"> 248 <div class="type">
306 <h4> 249 <h4>
307 ${type.isClass ? "class" : "interface"} 250 ${type.isClass ? "class" : "interface"}
308 ${a(typeUrl(type), "<strong>${typeName(type)}</strong>")} 251 ${a(typeUrl(type), "<strong>${typeName(type)}</strong>")}
309 </h4> 252 </h4>
310 </div> 253 </div>
311 '''); 254 ''');
312 } 255 }
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after
462 if (method.isConstructor) { 405 if (method.isConstructor) {
463 write(method.isConst ? 'const ' : 'new '); 406 write(method.isConst ? 'const ' : 'new ');
464 } 407 }
465 408
466 if (constructorName == null) { 409 if (constructorName == null) {
467 write(annotation(type, method.returnType)); 410 write(annotation(type, method.returnType));
468 } 411 }
469 412
470 // Translate specially-named methods: getters, setters, operators. 413 // Translate specially-named methods: getters, setters, operators.
471 var name = method.name; 414 var name = method.name;
472 if (name.startsWith('get\$')) { 415 if (name.startsWith('get:')) {
473 // Getter. 416 // Getter.
474 name = 'get ${name.substring(4)}'; 417 name = 'get ${name.substring(4)}';
475 } else if (name.startsWith('set\$')) { 418 } else if (name.startsWith('set:')) {
476 // Setter. 419 // Setter.
477 name = 'set ${name.substring(4)}'; 420 name = 'set ${name.substring(4)}';
478 } else { 421 } else {
479 // See if it's an operator. 422 // See if it's an operator.
480 name = TokenKind.rawOperatorFromMethod(name); 423 name = TokenKind.rawOperatorFromMethod(name);
481 if (name == null) { 424 if (name == null) {
482 name = method.name; 425 name = method.name;
483 } else { 426 } else {
484 name = 'operator $name'; 427 name = 'operator $name';
485 } 428 }
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
535 <strong>${field.name}</strong> <a class="anchor-link" 478 <strong>${field.name}</strong> <a class="anchor-link"
536 href="#${memberAnchor(field)}" 479 href="#${memberAnchor(field)}"
537 title="Permalink to ${typeName(type)}.${field.name}">#</a> 480 title="Permalink to ${typeName(type)}.${field.name}">#</a>
538 </h4> 481 </h4>
539 '''); 482 ''');
540 483
541 docCode(field.span, showCode: true); 484 docCode(field.span, showCode: true);
542 writeln('</div>'); 485 writeln('</div>');
543 } 486 }
544 487
488 /**
489 * Creates a hyperlink. Handles turning the [href] into an appropriate relative
490 * path from the current file.
491 */
492 String a(String href, String contents, [String class]) {
493 final css = class == null ? '' : ' class="$class"';
494 return '<a href="${relativePath(href)}"$css>$contents</a>';
495 }
496
545 /** Generates a human-friendly string representation for a type. */ 497 /** Generates a human-friendly string representation for a type. */
546 typeName(Type type) { 498 typeName(Type type) {
547 // See if it's a generic type. 499 // See if it's a generic type.
548 if (type.isGeneric) { 500 if (type.isGeneric) {
549 final typeParams = type.genericType.typeParameters; 501 final typeParams = type.genericType.typeParameters;
550 final params = Strings.join(map(typeParams, (p) => p.name), ', '); 502 final params = Strings.join(map(typeParams, (p) => p.name), ', ');
551 return '${type.name}&lt;$params&gt;'; 503 return '${type.name}&lt;$params&gt;';
552 } 504 }
553 505
554 // See if it's an instantiation of a generic type. 506 // See if it's an instantiation of a generic type.
555 final typeArgs = type.typeArgsInOrder; 507 final typeArgs = type.typeArgsInOrder;
556 if (typeArgs != null) { 508 if (typeArgs != null) {
557 final args = Strings.join(map(typeArgs, typeName), ', '); 509 final args = Strings.join(map(typeArgs, typeName), ', ');
558 return '${type.genericType.name}&lt;$args&gt;'; 510 return '${type.genericType.name}&lt;$args&gt;';
559 } 511 }
560 512
561 // Regular type. 513 // Regular type.
562 return type.name; 514 return type.name;
563 } 515 }
564 516
565 /** Gets the URL to the documentation for [library]. */
566 libraryUrl(Library library) {
567 return '${sanitize(library.name)}.html';
568 }
569
570 /** Gets the URL for the documentation for [type]. */
571 typeUrl(Type type) {
572 // Always get the generic type to strip off any type parameters or arguments.
573 // If the type isn't generic, genericType returns `this`, so it works for
574 // non-generic types too.
575 return '${sanitize(type.library.name)}/${type.genericType.name}.html';
576 }
577
578 /** Gets the URL for the documentation for [member]. */
579 memberUrl(Member member) {
580 return '${typeUrl(member.declaringType)}#${member.name}';
581 }
582
583 /** Gets the anchor id for the document for [member]. */
584 memberAnchor(Member member) => '${member.name}';
585
586 /** Writes a linked cross reference to [type]. */ 517 /** Writes a linked cross reference to [type]. */
587 typeReference(Type type) { 518 typeReference(Type type) {
588 // TODO(rnystrom): Do we need to handle ParameterTypes here like 519 // TODO(rnystrom): Do we need to handle ParameterTypes here like
589 // annotation() does? 520 // annotation() does?
590 return a(typeUrl(type), typeName(type), class: 'crossref'); 521 return a(typeUrl(type), typeName(type), class: 'crossref');
591 } 522 }
592 523
593 /** 524 /**
594 * Creates a linked string for an optional type annotation. Returns an empty 525 * Creates a linked string for an optional type annotation. Returns an empty
595 * string if the type is Dynamic. 526 * string if the type is Dynamic.
(...skipping 177 matching lines...) Expand 10 before | Expand all | Expand 10 after
773 // Syntax highlight. 704 // Syntax highlight.
774 return classifySource(new SourceFile('', code)); 705 return classifySource(new SourceFile('', code));
775 } 706 }
776 707
777 // TODO(rnystrom): Move into SourceSpan? 708 // TODO(rnystrom): Move into SourceSpan?
778 int getSpanColumn(SourceSpan span) { 709 int getSpanColumn(SourceSpan span) {
779 final line = span.file.getLine(span.start); 710 final line = span.file.getLine(span.start);
780 return span.file.getColumn(line, span.start); 711 return span.file.getColumn(line, span.start);
781 } 712 }
782 713
783 /** Removes up to [indentation] leading whitespace characters from [text]. */
784 unindent(String text, int indentation) {
785 var start;
786 for (start = 0; start < Math.min(indentation, text.length); start++) {
787 // Stop if we hit a non-whitespace character.
788 if (text[start] != ' ') break;
789 }
790
791 return text.substring(start);
792 }
793
794 /** 714 /**
795 * Pulls the raw text out of a doc comment (i.e. removes the comment 715 * Pulls the raw text out of a doc comment (i.e. removes the comment
796 * characters). 716 * characters).
797 */ 717 */
798 stripComment(comment) { 718 stripComment(comment) {
799 StringBuffer buf = new StringBuffer(); 719 StringBuffer buf = new StringBuffer();
800 720
801 for (final line in comment.split('\n')) { 721 for (final line in comment.split('\n')) {
802 line = line.trim(); 722 line = line.trim();
803 if (line.startsWith('/**')) line = line.substring(3, line.length); 723 if (line.startsWith('/**')) line = line.substring(3, line.length);
804 if (line.endsWith('*/')) line = line.substring(0, line.length - 2); 724 if (line.endsWith('*/')) line = line.substring(0, line.length - 2);
805 line = line.trim(); 725 line = line.trim();
806 if (line.startsWith('* ')) { 726 if (line.startsWith('* ')) {
807 line = line.substring(2, line.length); 727 line = line.substring(2, line.length);
808 } else if (line.startsWith('*')) { 728 } else if (line.startsWith('*')) {
809 line = line.substring(1, line.length); 729 line = line.substring(1, line.length);
810 } 730 }
811 731
812 buf.add(line); 732 buf.add(line);
813 buf.add('\n'); 733 buf.add('\n');
814 } 734 }
815 735
816 return buf.toString(); 736 return buf.toString();
817 } 737 }
OLDNEW
« no previous file with comments | « utils/dartdoc/dartdoc ('k') | utils/dartdoc/files.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698