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

Side by Side Diff: frog/samples/doc.dart

Issue 8515029: Move doc generator out of frog samples. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 9 years, 1 month 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
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 // TODO(rnystrom): This is moving from a sample to being a real project. Right
6 // now, to try this out:
7 // 1. Compile interact.dart to JS:
8 // $ ./frogsh --out=docs/interact.js --compile-only docs/interact.dart
9 // 2. Run the doc generator:
10 // $ ./frogsh samples/doc.dart
11 // 3. Look at the results in frog/docs/
12
13 /** An awesome documentation generator. */
14 #library('doc');
15
16 #import('../lang.dart');
17 #import('../file_system_node.dart');
18 #import('classify.dart');
19
20 /** Path to starting library or application. */
21 // TODO(rnystrom): Make this a command-line arg.
22 final libPath = 'samples/doc.dart';
23
24 /** Path to corePath library. */
25 final corePath = 'lib';
26
27 /** Path to generate html files into. */
28 final outdir = './docs';
29
30 /** Special comment position used to store the library-level doc comment. */
31 final _libraryDoc = -1;
32
33 /** The file currently being written to. */
34 StringBuffer _file;
35
36 /**
37 * The cached lookup-table to associate doc comments with spans. The outer map
38 * is from filenames to doc comments in that file. The inner map maps from the
39 * token positions to doc comments. Each position is the starting offset of the
40 * next non-comment token *following* the doc comment. For example, the position
41 * for this comment would be the position of the "Map" token below.
42 */
43 Map<String, Map<int, String>> _comments;
44
45 // TODO(jimhug): This generates really ugly output with lots of holes.
46
47 /**
48 * Run this from the frog/samples directory. Before running, you need
49 * to create a docs dir with 'mkdir docs' - since Dart currently doesn't
50 * support creating new directories.
51 */
52 void main() {
53 // TODO(rnystrom): Get options and homedir like frog.dart does.
54 final files = new NodeFileSystem();
55 parseOptions('.', [] /* args */, files);
56
57 initializeWorld(files);
58
59 world.withTiming('parsed', () {
60 world.processScript(libPath);
61 });
62
63 world.withTiming('resolved', () {
64 world.resolveAll();
65 });
66
67 world.withTiming('generated docs', () {
68 _comments = <String, Map<int, String>>{};
69
70 for (var library in world.libraries.getValues()) {
71 docLibrary(library);
72 }
73
74 docIndex(world.libraries.getValues());
75 });
76 }
77
78 startFile() {
79 _file = new StringBuffer();
80 }
81
82 write(String s) {
83 _file.add(s);
84 }
85
86 writeln(String s) {
87 write(s);
88 write('\n');
89 }
90
91 endFile(String outfile) {
92 world.files.writeString(outfile, _file.toString());
93 _file = null;
94 }
95
96 /** Turns a library name into something that's safe to use as a file name. */
97 sanitize(String name) => name.replaceAll(':', '_').replaceAll('/', '_');
98
99 docIndex(List<Library> libraries) {
100 startFile();
101 // TODO(rnystrom): Need to figure out what this should look like.
102 writeln(
103 '''
104 <html><head>
105 <title>Index</title>
106 <link rel="stylesheet" type="text/css" href="styles.css" />
107 </head>
108 <body>
109 <div class="content">
110 <ul>
111 ''');
112
113 var sorted = new List<Library>.from(libraries);
114 sorted.sort((a, b) => a.name.compareTo(b.name));
115
116 for (var library in sorted) {
117 writeln(
118 '''
119 <li><a href="${sanitize(library.name)}.html">
120 Library ${library.name}</a>
121 </li>
122 ''');
123 }
124
125 writeln(
126 '''
127 </ul>
128 </div>
129 </body></html>
130 ''');
131
132 endFile('$outdir/index.html');
133 }
134
135 docLibrary(Library library) {
136 startFile();
137 writeln(
138 '''
139 <html>
140 <head>
141 <title>${library.name}</title>
142 <link rel="stylesheet" type="text/css" href="styles.css" />
143 <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600,700,8 00" rel="stylesheet" type="text/css">
144 <script src="interact.js"></script>
145 </head>
146 <body>
147 <div class="content">
148 <h1>Library <strong>${library.name}</strong></h1>
149 ''');
150
151 bool needsSeparator = false;
152
153 // Look for a comment for the entire library.
154 final comment = findCommentInFile(library.baseSource, _libraryDoc);
155 if (comment != null) {
156 writeln('<div class="doc"><p>$comment</p></div>');
157 needsSeparator = true;
158 }
159
160 for (var type in library.types.getValues()) {
161 if (needsSeparator) writeln('<hr/>');
162 if (docType(type)) needsSeparator = false;
163 }
164
165 writeln(
166 '''
167 </div>
168 </body></html>
169 ''');
170
171 endFile('$outdir/${sanitize(library.name)}.html');
172 }
173
174 /**
175 * Documents [Type]. Handles top-level members if given an unnamed Type.
176 * Returns [:true:] if it wrote anything.
177 */
178 bool docType(Type type) {
179 bool wroteSomething = false;
180
181 if (type.name != null) {
182 write(
183 '''
184 <h2 id="${type.name}">
185 ${type.isClass ? "Class" : "Interface"} <strong>${type.name}</strong>
186 <a class="anchor-link" href="#${type.name}"
187 title="Permalink to ${type.name}">#</a>
188 </h2>
189 ''');
190
191 docInheritance(type);
192 docCode(type.span);
193 docConstructors(type);
194
195 wroteSomething = true;
196 }
197
198 // Collect the different kinds of members.
199 var methods = [];
200 var fields = [];
201
202 for (var member in orderValuesByKeys(type.members)) {
203 if (member.isMethod &&
204 (member.definition != null) &&
205 !member.name.startsWith('_')) {
206 methods.add(member);
207 } else if (member.isProperty) {
208 if (member.canGet) methods.add(member.getter);
209 if (member.canSet) methods.add(member.setter);
210 } else if (member.isField && !member.name.startsWith('_')) {
211 fields.add(member);
212 }
213 }
214
215 if (methods.length > 0) {
216 writeln('<h3>Methods</h3>');
217 for (var method in methods) docMethod(type.name, method);
218 }
219
220 if (fields.length > 0) {
221 writeln('<h3>Fields</h3>');
222 for (var field in fields) docField(type.name, field);
223 }
224
225 return wroteSomething || methods.length > 0 || fields.length > 0;
226 }
227
228 /** Document the superclass and superinterfaces of [Type]. */
229 docInheritance(Type type) {
230 // Show the superclass and superinterface(s).
231 if ((type.parent != null) && (type.parent.isObject) ||
232 (type.interfaces != null && type.interfaces.length > 0)) {
233 writeln('<p>');
234
235 if (type.parent != null) {
236 write('Extends ${typeRef(type.parent)}. ');
237 }
238
239 if (type.interfaces != null) {
240 var interfaces = [];
241 switch (type.interfaces.length) {
242 case 0:
243 // Do nothing.
244 break;
245
246 case 1:
247 write('Implements ${typeRef(type.interfaces[0])}.');
248 break;
249
250 case 2:
251 write('''Implements ${typeRef(type.interfaces[0])} and
252 ${typeRef(type.interfaces[1])}.''');
253 break;
254
255 default:
256 write('Implements ');
257 for (var i = 0; i < type.interfaces.length; i++) {
258 write('${typeRef(type.interfaces[i])}');
259 if (i < type.interfaces.length - 1) {
260 write(', ');
261 } else {
262 write(' and ');
263 }
264 }
265 write('.');
266 break;
267 }
268 }
269 }
270 }
271
272 /** Document the constructors for [Type], if any. */
273 docConstructors(Type type) {
274 if (type.constructors.length > 0) {
275 writeln('<h3>Constructors</h3>');
276 for (var name in type.constructors.getKeys()) {
277 var constructor = type.constructors[name];
278 docMethod(type.name, constructor, namedConstructor: name);
279 }
280 }
281 }
282
283 /**
284 * Documents the [method] in a type named [typeName]. Handles all kinds of
285 * methods including getters, setters, and constructors.
286 */
287 docMethod(String typeName, MethodMember method,
288 [String namedConstructor = null]) {
289 writeln(
290 '''
291 <div class="method"><h4 id="$typeName.${method.name}">
292 <span class="show-code">Code</span>
293 ''');
294
295 // A null typeName means it's a top-level definition which is implicitly
296 // static so doesn't need to annotate it.
297 if (method.isStatic && (typeName != null)) {
298 write('static ');
299 }
300
301 if (method.isConstructor) {
302 write(method.isConst ? 'const ' : 'new ');
303 }
304
305 if (namedConstructor == null) {
306 write(optionalTypeRef(method.returnType));
307 }
308
309 // Translate specially-named methods: getters, setters, operators.
310 var name = method.name;
311 if (name.startsWith('get\$')) {
312 // Getter.
313 name = 'get ${name.substring(4)}';
314 } else if (name.startsWith('set\$')) {
315 // Setter.
316 name = 'set ${name.substring(4)}';
317 } else {
318 // See if it's an operator.
319 name = TokenKind.rawOperatorFromMethod(name);
320 if (name == null) {
321 name = method.name;
322 } else {
323 name = 'operator $name';
324 }
325 }
326
327 write('<strong>$name</strong>');
328
329 // Named constructors.
330 if (namedConstructor != null && namedConstructor != '') {
331 write('.');
332 write(namedConstructor);
333 }
334
335 write('(');
336 var paramList = [];
337 if (method.parameters == null) print(method.name);
338 for (var p in method.parameters) {
339 paramList.add('${optionalTypeRef(p.type)}${p.name}');
340 }
341 write(Strings.join(paramList, ", "));
342 write(')');
343
344 write(''' <a class="anchor-link" href="#$typeName.${method.name}"
345 title="Permalink to $typeName.$name">#</a>''');
346 writeln('</h4>');
347
348 docCode(method.span, showCode: true);
349
350 writeln('</div>');
351 }
352
353 /** Documents the field [field] in a type named [typeName]. */
354 docField(String typeName, FieldMember field) {
355 writeln(
356 '''
357 <div class="field"><h4 id="$typeName.${field.name}">
358 <span class="show-code">Code</span>
359 ''');
360
361 // A null typeName means it's a top-level definition which is implicitly
362 // static so doesn't need to annotate it.
363 if (field.isStatic && (typeName != null)) {
364 write('static ');
365 }
366
367 if (field.isFinal) {
368 write('final ');
369 } else if (field.type.name == 'Dynamic') {
370 write('var ');
371 }
372
373 write(optionalTypeRef(field.type));
374 write(
375 '''
376 <strong>${field.name}</strong> <a class="anchor-link"
377 href="#$typeName.${field.name}"
378 title="Permalink to $typeName.${field.name}">#</a>
379 </h4>
380 ''');
381
382 docCode(field.span, showCode: true);
383 writeln('</div>');
384 }
385
386 /**
387 * Writes a type annotation for [type]. Will hyperlink it to that type's
388 * documentation if possible.
389 */
390 typeRef(Type type) {
391 if (type.library != null) {
392 var library = sanitize(type.library.name);
393 return '<a href="${library}.html#${type.name}">${type.name}</a>';
394 } else {
395 return type.name;
396 }
397 }
398
399 /**
400 * Creates a linked string for an optional type annotation. Returns an empty
401 * string if the type is Dynamic.
402 */
403 optionalTypeRef(Type type) {
404 if (type.name == 'Dynamic') {
405 return '';
406 } else {
407 return typeRef(type) + ' ';
408 }
409 }
410
411 /**
412 * Documents the code contained within [span]. Will include the previous
413 * Dartdoc associated with that span if found, and will include the syntax
414 * highlighted code itself if desired.
415 */
416 docCode(SourceSpan span, [bool showCode = false]) {
417 if (span == null) return;
418
419 writeln('<div class="doc">');
420 var comment = findComment(span);
421 if (comment != null) {
422 writeln('<p>$comment</p>');
423 }
424
425 if (showCode) {
426 writeln('<pre class="source">');
427 write(formatCode(span));
428 writeln('</pre>');
429 }
430
431 writeln('</div>');
432 }
433
434 /** Finds the doc comment preceding the given source span, if there is one. */
435 findComment(SourceSpan span) => findCommentInFile(span.file, span.start);
436
437 /** Finds the doc comment preceding the given source span, if there is one. */
438 findCommentInFile(SourceFile file, int position) {
439 // Get the doc comments for this file.
440 var fileComments = _comments.putIfAbsent(file.filename,
441 () => parseDocComments(file));
442
443 return fileComments[position];
444 }
445
446 parseDocComments(SourceFile file) {
447 var comments = <int, String>{};
448
449 var tokenizer = new Tokenizer(file, false);
450 var lastComment = null;
451
452 while (true) {
453 var token = tokenizer.next();
454 if (token.kind == TokenKind.END_OF_FILE) break;
455
456 if (token.kind == TokenKind.COMMENT) {
457 var text = token.text;
458 if (text.startsWith('/**')) {
459 // Remember that we've encountered a doc comment.
460 lastComment = stripComment(token.text);
461 }
462 } else if (token.kind == TokenKind.WHITESPACE) {
463 // Ignore whitespace tokens.
464 } else if (token.kind == TokenKind.HASH) {
465 // Look for #library() to find the library comment.
466 var next = tokenizer.next();
467 if ((lastComment != null) && (next.kind == TokenKind.LIBRARY)) {
468 comments[_libraryDoc] = lastComment;
469 lastComment = null;
470 }
471 } else {
472 if (lastComment != null) {
473 // We haven't attached the last doc comment to something yet, so stick
474 // it to this token.
475 comments[token.start] = lastComment;
476 lastComment = null;
477 }
478 }
479 }
480
481 return comments;
482 }
483
484 /**
485 * Takes a string of Dart code and turns it into sanitized HTML.
486 */
487 formatCode(SourceSpan span) {
488 // Remove leading indentation to line up with first line.
489 var column = getSpanColumn(span);
490 var lines = span.text.split('\n');
491 // TODO(rnystrom): Dirty hack.
492 for (int i = 1; i < lines.length; i++) {
493 lines[i] = unindent(lines[i], column);
494 }
495
496 var code = Strings.join(lines, '\n');
497
498 // Syntax highlight.
499 return classifySource(new SourceFile('', code));
500 }
501
502 // TODO(rnystrom): Move into SourceSpan?
503 int getSpanColumn(SourceSpan span) {
504 var line = span.file.getLine(span.start);
505 return span.file.getColumn(line, span.start);
506 }
507
508 /** Removes up to [indentation] leading whitespace characters from [text]. */
509 unindent(String text, int indentation) {
510 var start;
511 for (start = 0; start < Math.min(indentation, text.length); start++) {
512 // Stop if we hit a non-whitespace character.
513 if (text[start] != ' ') break;
514 }
515
516 return text.substring(start);
517 }
518
519 /**
520 * Pulls the raw text out of a doc comment (i.e. removes the comment
521 * characters.
522 */
523 // TODO(rnystrom): Should handle [name] and [:code:] in comments. Should also
524 // break empty lines into multiple paragraphs. Other formatting?
525 // See dart/compiler/java/com/google/dart/compiler/backend/doc for ideas.
526 // (/DartDocumentationVisitor.java#180)
527 stripComment(comment) {
528 StringBuffer buf = new StringBuffer();
529
530 for (var line in comment.split('\n')) {
531 line = line.trim();
532 if (line.startsWith('/**')) line = line.substring(3, line.length);
533 if (line.endsWith('*/')) line = line.substring(0, line.length-2);
534 line = line.trim();
535 while (line.startsWith('*')) line = line.substring(1, line.length);
536 line = line.trim();
537 buf.add(line);
538 buf.add(' ');
539 }
540
541 return buf.toString();
542 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698