| 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 |
| 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('dart:json'); |
| 18 #import('../../frog/lang.dart'); | 19 #import('../../frog/lang.dart'); |
| 19 #import('../../frog/file_system.dart'); | 20 #import('../../frog/file_system.dart'); |
| 20 #import('../../frog/file_system_node.dart'); | 21 #import('../../frog/file_system_node.dart'); |
| 21 #import('../../frog/lib/node/node.dart'); | 22 #import('../../frog/lib/node/node.dart'); |
| 23 #import('classify.dart'); |
| 22 #import('markdown.dart', prefix: 'md'); | 24 #import('markdown.dart', prefix: 'md'); |
| 23 | 25 |
| 24 #source('classify.dart'); | |
| 25 #source('comment_map.dart'); | 26 #source('comment_map.dart'); |
| 26 #source('files.dart'); | 27 #source('files.dart'); |
| 27 #source('utils.dart'); | 28 #source('utils.dart'); |
| 28 | 29 |
| 29 /** | 30 /** |
| 31 * Generates completely static HTML containing everything you need to browse |
| 32 * the docs. The only client side behavior is trivial stuff like syntax |
| 33 * highlighting code. |
| 34 */ |
| 35 final MODE_STATIC = 0; |
| 36 |
| 37 /** |
| 38 * Generated docs do not include baked HTML navigation. Instead, a single |
| 39 * `nav.json` file is created and the appropriate navigation is generated |
| 40 * client-side by parsing that and building HTML. |
| 41 * |
| 42 * This dramatically reduces the generated size of the HTML since a large |
| 43 * fraction of each static page is just redundant navigation links. |
| 44 * |
| 45 * In this mode, the browser will do a XHR for nav.json which means that to |
| 46 * preview docs locally, you will need to enable requesting file:// links in |
| 47 * your browser or run a little local server like `python -m SimpleHTTPServer`. |
| 48 */ |
| 49 final MODE_LIVE_NAV = 1; |
| 50 |
| 51 /** |
| 30 * Run this from the `utils/dartdoc` directory. | 52 * Run this from the `utils/dartdoc` directory. |
| 31 */ | 53 */ |
| 32 void main() { | 54 void main() { |
| 33 // The entrypoint of the library to generate docs for. | 55 // The entrypoint of the library to generate docs for. |
| 34 final entrypoint = process.argv[process.argv.length - 1]; | 56 final entrypoint = process.argv[process.argv.length - 1]; |
| 35 | 57 |
| 36 // Parse the dartdoc options. | 58 // Parse the dartdoc options. |
| 37 bool includeSource = true; | 59 bool includeSource = true; |
| 60 var mode = MODE_LIVE_NAV; |
| 38 | 61 |
| 39 for (int i = 2; i < process.argv.length - 1; i++) { | 62 for (int i = 2; i < process.argv.length - 1; i++) { |
| 40 final arg = process.argv[i]; | 63 final arg = process.argv[i]; |
| 41 switch (arg) { | 64 switch (arg) { |
| 42 case '--no-code': | 65 case '--no-code': |
| 43 includeSource = false; | 66 includeSource = false; |
| 44 break; | 67 break; |
| 45 | 68 |
| 69 case '--mode=static': |
| 70 mode = MODE_STATIC; |
| 71 break; |
| 72 |
| 73 case '--mode=live-nav': |
| 74 mode = MODE_LIVE_NAV; |
| 75 break; |
| 76 |
| 46 default: | 77 default: |
| 47 print('Unknown option: $arg'); | 78 print('Unknown option: $arg'); |
| 48 } | 79 } |
| 49 } | 80 } |
| 50 | 81 |
| 51 final files = new NodeFileSystem(); | 82 final files = new NodeFileSystem(); |
| 52 parseOptions('../../frog', [] /* args */, files); | 83 parseOptions('../../frog', [] /* args */, files); |
| 53 initializeWorld(files); | 84 initializeWorld(files); |
| 54 | 85 |
| 55 var dartdoc; | 86 var dartdoc; |
| 56 final elapsed = time(() { | 87 final elapsed = time(() { |
| 57 dartdoc = new Dartdoc(); | 88 dartdoc = new Dartdoc(); |
| 58 dartdoc.includeSource = includeSource; | 89 dartdoc.includeSource = includeSource; |
| 90 dartdoc.mode = mode; |
| 91 |
| 59 dartdoc.document(entrypoint); | 92 dartdoc.document(entrypoint); |
| 60 }); | 93 }); |
| 61 | 94 |
| 62 print('Documented ${dartdoc._totalLibraries} libraries, ' + | 95 print('Documented ${dartdoc._totalLibraries} libraries, ' + |
| 63 '${dartdoc._totalTypes} types, and ' + | 96 '${dartdoc._totalTypes} types, and ' + |
| 64 '${dartdoc._totalMembers} members in ${elapsed}msec.'); | 97 '${dartdoc._totalMembers} members in ${elapsed}msec.'); |
| 65 } | 98 } |
| 66 | 99 |
| 67 class Dartdoc { | 100 class Dartdoc { |
| 68 /** Set to `false` to not include the source code in the generated docs. */ | 101 /** Set to `false` to not include the source code in the generated docs. */ |
| 69 bool includeSource = true; | 102 bool includeSource = true; |
| 70 | 103 |
| 71 /** | 104 /** |
| 105 * Dartdoc can generate docs in a few different ways based on how dynamic you |
| 106 * want the client-side behavior to be. The value for this should be one of |
| 107 * the `MODE_` constants. |
| 108 */ |
| 109 int mode = MODE_LIVE_NAV; |
| 110 |
| 111 /** |
| 72 * The title used for the overall generated output. Set this to change it. | 112 * The title used for the overall generated output. Set this to change it. |
| 73 */ | 113 */ |
| 74 String mainTitle = 'Dart Documentation'; | 114 String mainTitle = 'Dart Documentation'; |
| 75 | 115 |
| 76 /** | 116 /** |
| 77 * The URL that the Dart logo links to. Defaults "index.html", the main | 117 * The URL that the Dart logo links to. Defaults "index.html", the main |
| 78 * page for the generated docs, but can be anything. | 118 * page for the generated docs, but can be anything. |
| 79 */ | 119 */ |
| 80 String mainUrl = 'index.html'; | 120 String mainUrl = 'index.html'; |
| 81 | 121 |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 141 break; | 181 break; |
| 142 | 182 |
| 143 default: | 183 default: |
| 144 // Normal entrypoint script. | 184 // Normal entrypoint script. |
| 145 world.processDartScript(entrypoint); | 185 world.processDartScript(entrypoint); |
| 146 } | 186 } |
| 147 | 187 |
| 148 world.resolveAll(); | 188 world.resolveAll(); |
| 149 | 189 |
| 150 // Generate the docs. | 190 // Generate the docs. |
| 191 if (mode == MODE_LIVE_NAV) docNavigationJson(); |
| 192 |
| 151 docIndex(); | 193 docIndex(); |
| 152 for (final library in world.libraries.getValues()) { | 194 for (final library in world.libraries.getValues()) { |
| 153 docLibrary(library); | 195 docLibrary(library); |
| 154 } | 196 } |
| 155 } finally { | 197 } finally { |
| 156 options.dietParse = oldDietParse; | 198 options.dietParse = oldDietParse; |
| 157 } | 199 } |
| 158 } | 200 } |
| 159 | 201 |
| 160 /** | 202 /** |
| 161 * Writes the page header with the given [title] and [breadcrumbs]. The | 203 * Writes the page header with the given [title] and [breadcrumbs]. The |
| 162 * breadcrumbs are an interleaved list of links and titles. If a link is null, | 204 * breadcrumbs are an interleaved list of links and titles. If a link is null, |
| 163 * then no link will be generated. For example, given: | 205 * then no link will be generated. For example, given: |
| 164 * | 206 * |
| 165 * ['foo', 'foo.html', 'bar', null] | 207 * ['foo', 'foo.html', 'bar', null] |
| 166 * | 208 * |
| 167 * It will output: | 209 * It will output: |
| 168 * | 210 * |
| 169 * <a href="foo.html">foo</a> › bar | 211 * <a href="foo.html">foo</a> › bar |
| 170 */ | 212 */ |
| 171 writeHeader(String title, List<String> breadcrumbs) { | 213 writeHeader(String title, List<String> breadcrumbs) { |
| 172 write( | 214 write( |
| 173 ''' | 215 ''' |
| 174 <!DOCTYPE html> | 216 <!DOCTYPE html> |
| 175 <html> | 217 <html> |
| 176 <head> | 218 <head> |
| 177 '''); | 219 '''); |
| 178 writeHeadContents(title); | 220 writeHeadContents(title); |
| 221 |
| 222 // Add data attributes describing what the page documents. |
| 223 var data = ''; |
| 224 if (_currentLibrary != null) { |
| 225 data += ' data-library="${md.escapeHtml(_currentLibrary.name)}"'; |
| 226 } |
| 227 |
| 228 if (_currentType != null) { |
| 229 data += ' data-type="${md.escapeHtml(typeName(_currentType))}"'; |
| 230 } |
| 231 |
| 179 write( | 232 write( |
| 180 ''' | 233 ''' |
| 181 </head> | 234 </head> |
| 182 <body> | 235 <body$data> |
| 183 <div class="page"> | 236 <div class="page"> |
| 184 <div class="header"> | 237 <div class="header"> |
| 185 ${a(mainUrl, '<div class="logo"></div>')} | 238 ${a(mainUrl, '<div class="logo"></div>')} |
| 186 ${a('index.html', mainTitle)} | 239 ${a('index.html', mainTitle)} |
| 187 '''); | 240 '''); |
| 188 | 241 |
| 189 // Write the breadcrumb trail. | 242 // Write the breadcrumb trail. |
| 190 for (int i = 0; i < breadcrumbs.length; i += 2) { | 243 for (int i = 0; i < breadcrumbs.length; i += 2) { |
| 191 if (breadcrumbs[i + 1] == null) { | 244 if (breadcrumbs[i + 1] == null) { |
| 192 write(' › ${breadcrumbs[i]}'); | 245 write(' › ${breadcrumbs[i]}'); |
| 193 } else { | 246 } else { |
| 194 write(' › ${a(breadcrumbs[i + 1], breadcrumbs[i])}'); | 247 write(' › ${a(breadcrumbs[i + 1], breadcrumbs[i])}'); |
| 195 } | 248 } |
| 196 } | 249 } |
| 197 writeln('</div>'); | 250 writeln('</div>'); |
| 198 | 251 |
| 199 docNavigation(); | 252 docNavigation(); |
| 200 writeln('<div class="content">'); | 253 writeln('<div class="content">'); |
| 201 } | 254 } |
| 202 | 255 |
| 256 String get clientScript() { |
| 257 switch (mode) { |
| 258 case MODE_STATIC: return 'client-static'; |
| 259 case MODE_LIVE_NAV: return 'client-live-nav'; |
| 260 default: throw 'Unknown mode $mode.'; |
| 261 } |
| 262 } |
| 263 |
| 203 writeHeadContents(String title) { | 264 writeHeadContents(String title) { |
| 204 writeln( | 265 writeln( |
| 205 ''' | 266 ''' |
| 206 <meta charset="utf-8"> | 267 <meta charset="utf-8"> |
| 207 <title>$title</title> | 268 <title>$title</title> |
| 208 <link rel="stylesheet" type="text/css" | 269 <link rel="stylesheet" type="text/css" |
| 209 href="${relativePath('styles.css')}" /> | 270 href="${relativePath('styles.css')}" /> |
| 210 <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600,700
,800" rel="stylesheet" type="text/css"> | 271 <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,600,700
,800" rel="stylesheet" type="text/css"> |
| 211 <link rel="shortcut icon" href="${relativePath('favicon.ico')}" /> | 272 <link rel="shortcut icon" href="${relativePath('favicon.ico')}" /> |
| 212 <script src="${relativePath('interact.js')}"></script> | 273 <script src="${relativePath('$clientScript.js')}"></script> |
| 213 '''); | 274 '''); |
| 214 } | 275 } |
| 215 | 276 |
| 216 writeFooter() { | 277 writeFooter() { |
| 217 writeln( | 278 writeln( |
| 218 ''' | 279 ''' |
| 219 </div> | 280 </div> |
| 220 <div class="clear"></div> | 281 <div class="clear"></div> |
| 221 </div> | 282 </div> |
| 222 <div class="footer">$footerText</div> | 283 <div class="footer">$footerText</div> |
| (...skipping 13 matching lines...) Expand all Loading... |
| 236 writeln( | 297 writeln( |
| 237 ''' | 298 ''' |
| 238 <h4>${a(libraryUrl(library), library.name)}</h4> | 299 <h4>${a(libraryUrl(library), library.name)}</h4> |
| 239 '''); | 300 '''); |
| 240 } | 301 } |
| 241 | 302 |
| 242 writeFooter(); | 303 writeFooter(); |
| 243 endFile(); | 304 endFile(); |
| 244 } | 305 } |
| 245 | 306 |
| 307 /** |
| 308 * Walks the libraries and creates a JSON object containing the data needed |
| 309 * to generate navigation for them. |
| 310 */ |
| 311 docNavigationJson() { |
| 312 startFile('nav.json'); |
| 313 |
| 314 final libraries = {}; |
| 315 |
| 316 for (final library in orderByName(world.libraries)) { |
| 317 final types = []; |
| 318 |
| 319 for (final type in orderByName(library.types)) { |
| 320 if (type.isTop) continue; |
| 321 if (type.name.startsWith('_')) continue; |
| 322 |
| 323 final kind = type.isClass ? 'class' : 'interface'; |
| 324 final url = typeUrl(type); |
| 325 types.add({ 'name': typeName(type), 'kind': kind, 'url': url }); |
| 326 } |
| 327 |
| 328 libraries[library.name] = types; |
| 329 } |
| 330 |
| 331 writeln(JSON.stringify(libraries)); |
| 332 endFile(); |
| 333 } |
| 334 |
| 246 docNavigation() { | 335 docNavigation() { |
| 247 writeln( | 336 writeln( |
| 248 ''' | 337 ''' |
| 249 <div class="nav"> | 338 <div class="nav"> |
| 250 '''); | 339 '''); |
| 251 | 340 |
| 252 for (final library in orderByName(world.libraries)) { | 341 if (mode == MODE_STATIC) { |
| 253 write('<h2><div class="icon-library"></div>'); | 342 for (final library in orderByName(world.libraries)) { |
| 343 write('<h2><div class="icon-library"></div>'); |
| 254 | 344 |
| 255 if ((_currentLibrary == library) && (_currentType == null)) { | 345 if ((_currentLibrary == library) && (_currentType == null)) { |
| 256 write('<strong>${library.name}</strong>'); | 346 write('<strong>${library.name}</strong>'); |
| 257 } else { | 347 } else { |
| 258 write('${a(libraryUrl(library), library.name)}'); | 348 write('${a(libraryUrl(library), library.name)}'); |
| 349 } |
| 350 write('</h2>'); |
| 351 |
| 352 // Only expand classes in navigation for current library. |
| 353 if (_currentLibrary == library) docLibraryNavigation(library); |
| 259 } | 354 } |
| 260 write('</h2>'); | |
| 261 | |
| 262 // Only expand classes in navigation for current library. | |
| 263 if (_currentLibrary == library) docLibraryNavigation(library); | |
| 264 } | 355 } |
| 265 | 356 |
| 266 writeln('</div>'); | 357 writeln('</div>'); |
| 267 } | 358 } |
| 268 | 359 |
| 269 /** Writes the navigation for the types contained by the given library. */ | 360 /** Writes the navigation for the types contained by the given library. */ |
| 270 docLibraryNavigation(Library library) { | 361 docLibraryNavigation(Library library) { |
| 271 // Show the exception types separately. | 362 // Show the exception types separately. |
| 272 final types = <Type>[]; | 363 final types = <Type>[]; |
| 273 final exceptions = <Type>[]; | 364 final exceptions = <Type>[]; |
| (...skipping 599 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 873 | 964 |
| 874 return new md.Element.text('code', name); | 965 return new md.Element.text('code', name); |
| 875 } | 966 } |
| 876 | 967 |
| 877 // TODO(rnystrom): Move into SourceSpan? | 968 // TODO(rnystrom): Move into SourceSpan? |
| 878 int getSpanColumn(SourceSpan span) { | 969 int getSpanColumn(SourceSpan span) { |
| 879 final line = span.file.getLine(span.start); | 970 final line = span.file.getLine(span.start); |
| 880 return span.file.getColumn(line, span.start); | 971 return span.file.getColumn(line, span.start); |
| 881 } | 972 } |
| 882 } | 973 } |
| OLD | NEW |