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

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: 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
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>${type.name}</strong>")} 251 ${a(typeUrl(type), "<strong>${type.name}</strong>")}
309 </h4> 252 </h4>
310 </div> 253 </div>
311 '''); 254 ''');
312 } 255 }
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after
449 if (method.isConstructor) { 392 if (method.isConstructor) {
450 write(method.isConst ? 'const ' : 'new '); 393 write(method.isConst ? 'const ' : 'new ');
451 } 394 }
452 395
453 if (constructorName == null) { 396 if (constructorName == null) {
454 write(annotation(type, method.returnType)); 397 write(annotation(type, method.returnType));
455 } 398 }
456 399
457 // Translate specially-named methods: getters, setters, operators. 400 // Translate specially-named methods: getters, setters, operators.
458 var name = method.name; 401 var name = method.name;
459 if (name.startsWith('get\$')) { 402 if (name.startsWith('get:')) {
460 // Getter. 403 // Getter.
461 name = 'get ${name.substring(4)}'; 404 name = 'get ${name.substring(4)}';
462 } else if (name.startsWith('set\$')) { 405 } else if (name.startsWith('set:')) {
463 // Setter. 406 // Setter.
464 name = 'set ${name.substring(4)}'; 407 name = 'set ${name.substring(4)}';
465 } else { 408 } else {
466 // See if it's an operator. 409 // See if it's an operator.
467 name = TokenKind.rawOperatorFromMethod(name); 410 name = TokenKind.rawOperatorFromMethod(name);
468 if (name == null) { 411 if (name == null) {
469 name = method.name; 412 name = method.name;
470 } else { 413 } else {
471 name = 'operator $name'; 414 name = 'operator $name';
472 } 415 }
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
522 <strong>${field.name}</strong> <a class="anchor-link" 465 <strong>${field.name}</strong> <a class="anchor-link"
523 href="#${memberAnchor(field)}" 466 href="#${memberAnchor(field)}"
524 title="Permalink to ${type.name}.${field.name}">#</a> 467 title="Permalink to ${type.name}.${field.name}">#</a>
525 </h4> 468 </h4>
526 '''); 469 ''');
527 470
528 docCode(field.span, showCode: true); 471 docCode(field.span, showCode: true);
529 writeln('</div>'); 472 writeln('</div>');
530 } 473 }
531 474
475 /**
476 * Creates a hyperlink. Handles turning the [href] into an appropriate relative
477 * path from the current file.
478 */
479 String a(String href, String contents, [String class]) {
480 final css = class == null ? '' : ' class="$class"';
481 return '<a href="${relativePath(href)}"$css>$contents</a>';
482 }
483
532 /** Generates a human-friendly string representation for a type. */ 484 /** Generates a human-friendly string representation for a type. */
533 typeName(Type type) { 485 typeName(Type type) {
534 // See if it's a generic type. 486 // See if it's a generic type.
535 if (type.isGeneric) { 487 if (type.isGeneric) {
536 final typeParams = type.genericType.typeParameters; 488 final typeParams = type.genericType.typeParameters;
537 final params = Strings.join(map(typeParams, (p) => p.name), ', '); 489 final params = Strings.join(map(typeParams, (p) => p.name), ', ');
538 return '${type.name}&lt;$params&gt;'; 490 return '${type.name}&lt;$params&gt;';
539 } 491 }
540 492
541 // See if it's an instantiation of a generic type. 493 // See if it's an instantiation of a generic type.
542 final typeArgs = type.typeArgsInOrder; 494 final typeArgs = type.typeArgsInOrder;
543 if (typeArgs != null) { 495 if (typeArgs != null) {
544 final args = Strings.join(map(typeArgs, typeName), ', '); 496 final args = Strings.join(map(typeArgs, typeName), ', ');
545 return '${type.genericType.name}&lt;$args&gt;'; 497 return '${type.genericType.name}&lt;$args&gt;';
546 } 498 }
547 499
548 // Regular type. 500 // Regular type.
549 return type.name; 501 return type.name;
550 } 502 }
551 503
552 /** Gets the URL to the documentation for [library]. */
553 libraryUrl(Library library) {
554 return '${sanitize(library.name)}.html';
555 }
556
557 /** Gets the URL for the documentation for [type]. */
558 typeUrl(Type type) {
559 // Always get the generic type to strip off any type parameters or arguments.
560 // If the type isn't generic, genericType returns `this`, so it works for
561 // non-generic types too.
562 return '${sanitize(type.library.name)}/${type.genericType.name}.html';
563 }
564
565 /** Gets the URL for the documentation for [member]. */
566 memberUrl(Member member) {
567 return '${typeUrl(member.declaringType)}#${member.name}';
568 }
569
570 /** Gets the anchor id for the document for [member]. */
571 memberAnchor(Member member) => '${member.name}';
572
573 /** Writes a linked cross reference to [type]. */ 504 /** Writes a linked cross reference to [type]. */
574 typeReference(Type type) { 505 typeReference(Type type) {
575 // TODO(rnystrom): Do we need to handle ParameterTypes here like 506 // TODO(rnystrom): Do we need to handle ParameterTypes here like
576 // annotation() does? 507 // annotation() does?
577 return a(typeUrl(type), typeName(type), class: 'crossref'); 508 return a(typeUrl(type), typeName(type), class: 'crossref');
578 } 509 }
579 510
580 /** 511 /**
581 * Creates a linked string for an optional type annotation. Returns an empty 512 * Creates a linked string for an optional type annotation. Returns an empty
582 * string if the type is Dynamic. 513 * string if the type is Dynamic.
(...skipping 177 matching lines...) Expand 10 before | Expand all | Expand 10 after
760 // Syntax highlight. 691 // Syntax highlight.
761 return classifySource(new SourceFile('', code)); 692 return classifySource(new SourceFile('', code));
762 } 693 }
763 694
764 // TODO(rnystrom): Move into SourceSpan? 695 // TODO(rnystrom): Move into SourceSpan?
765 int getSpanColumn(SourceSpan span) { 696 int getSpanColumn(SourceSpan span) {
766 final line = span.file.getLine(span.start); 697 final line = span.file.getLine(span.start);
767 return span.file.getColumn(line, span.start); 698 return span.file.getColumn(line, span.start);
768 } 699 }
769 700
770 /** Removes up to [indentation] leading whitespace characters from [text]. */
771 unindent(String text, int indentation) {
772 var start;
773 for (start = 0; start < Math.min(indentation, text.length); start++) {
774 // Stop if we hit a non-whitespace character.
775 if (text[start] != ' ') break;
776 }
777
778 return text.substring(start);
779 }
780
781 /** 701 /**
782 * Pulls the raw text out of a doc comment (i.e. removes the comment 702 * Pulls the raw text out of a doc comment (i.e. removes the comment
783 * characters). 703 * characters).
784 */ 704 */
785 stripComment(comment) { 705 stripComment(comment) {
786 StringBuffer buf = new StringBuffer(); 706 StringBuffer buf = new StringBuffer();
787 707
788 for (final line in comment.split('\n')) { 708 for (final line in comment.split('\n')) {
789 line = line.trim(); 709 line = line.trim();
790 if (line.startsWith('/**')) line = line.substring(3, line.length); 710 if (line.startsWith('/**')) line = line.substring(3, line.length);
791 if (line.endsWith('*/')) line = line.substring(0, line.length - 2); 711 if (line.endsWith('*/')) line = line.substring(0, line.length - 2);
792 line = line.trim(); 712 line = line.trim();
793 if (line.startsWith('* ')) { 713 if (line.startsWith('* ')) {
794 line = line.substring(2, line.length); 714 line = line.substring(2, line.length);
795 } else if (line.startsWith('*')) { 715 } else if (line.startsWith('*')) {
796 line = line.substring(1, line.length); 716 line = line.substring(1, line.length);
797 } 717 }
798 718
799 buf.add(line); 719 buf.add(line);
800 buf.add('\n'); 720 buf.add('\n');
801 } 721 }
802 722
803 return buf.toString(); 723 return buf.toString();
804 } 724 }
OLDNEW
« no previous file with comments | « utils/dartdoc/dartdoc ('k') | utils/dartdoc/files.dart » ('j') | utils/dartdoc/files.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698