| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, 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 /** | |
| 6 * This generates the reference documentation for the core libraries that come | |
| 7 * with dart. It is built on top of dartdoc, which is a general-purpose library | |
| 8 * for generating docs from any Dart code. This library extends that to include | |
| 9 * additional information and styling specific to our standard library. | |
| 10 * | |
| 11 * Usage: | |
| 12 * | |
| 13 * $ dart apidoc.dart [--out=<output directory>] | |
| 14 */ | |
| 15 library apidoc; | |
| 16 | |
| 17 import 'dart:async'; | |
| 18 import 'dart:convert'; | |
| 19 import 'dart:io'; | |
| 20 | |
| 21 import 'html_diff.dart'; | |
| 22 | |
| 23 import 'package:compiler/src/mirrors/source_mirrors.dart'; | |
| 24 import 'package:compiler/src/mirrors/mirrors_util.dart'; | |
| 25 import 'package:compiler/src/filenames.dart'; | |
| 26 import 'package:dartdoc/dartdoc.dart'; | |
| 27 import 'package:sdk_library_metadata/libraries.dart'; | |
| 28 import 'package:path/path.dart' as path; | |
| 29 | |
| 30 HtmlDiff _diff; | |
| 31 | |
| 32 void main(List<String> args) { | |
| 33 int mode = MODE_STATIC; | |
| 34 String outputDir = 'docs'; | |
| 35 bool generateAppCache = false; | |
| 36 | |
| 37 List<String> excludedLibraries = <String>[]; | |
| 38 | |
| 39 // For libraries that have names matching the package name, | |
| 40 // such as library unittest in package unittest, we just give | |
| 41 // the package name with a --include-lib argument, such as: | |
| 42 // --include-lib=unittest. These arguments are collected in | |
| 43 // includedLibraries. | |
| 44 List<String> includedLibraries = <String>[]; | |
| 45 | |
| 46 // For libraries that lie within packages but have a different name, | |
| 47 // such as the matcher library in package unittest, we can use | |
| 48 // --extra-lib with a full relative path under pkg, such as | |
| 49 // --extra-lib=unittest/lib/matcher.dart. These arguments are | |
| 50 // collected in extraLibraries. | |
| 51 List<String> extraLibraries = <String>[]; | |
| 52 | |
| 53 String packageRoot; | |
| 54 String version; | |
| 55 | |
| 56 // Parse the command-line arguments. | |
| 57 for (int i = 0; i < args.length; i++) { | |
| 58 final arg = args[i]; | |
| 59 | |
| 60 switch (arg) { | |
| 61 case '--mode=static': | |
| 62 mode = MODE_STATIC; | |
| 63 break; | |
| 64 | |
| 65 case '--mode=live-nav': | |
| 66 mode = MODE_LIVE_NAV; | |
| 67 break; | |
| 68 | |
| 69 case '--generate-app-cache=true': | |
| 70 generateAppCache = true; | |
| 71 break; | |
| 72 | |
| 73 default: | |
| 74 if (arg.startsWith('--exclude-lib=')) { | |
| 75 excludedLibraries.add(arg.substring('--exclude-lib='.length)); | |
| 76 } else if (arg.startsWith('--include-lib=')) { | |
| 77 includedLibraries.add(arg.substring('--include-lib='.length)); | |
| 78 } else if (arg.startsWith('--extra-lib=')) { | |
| 79 extraLibraries.add(arg.substring('--extra-lib='.length)); | |
| 80 } else if (arg.startsWith('--out=')) { | |
| 81 outputDir = arg.substring('--out='.length); | |
| 82 } else if (arg.startsWith('--package-root=')) { | |
| 83 packageRoot = arg.substring('--package-root='.length); | |
| 84 } else if (arg.startsWith('--version=')) { | |
| 85 version = arg.substring('--version='.length); | |
| 86 } else { | |
| 87 print('Unknown option: $arg'); | |
| 88 return; | |
| 89 } | |
| 90 break; | |
| 91 } | |
| 92 } | |
| 93 | |
| 94 final libPath = path.join(scriptDir, '..', '..', 'sdk/'); | |
| 95 | |
| 96 cleanOutputDirectory(outputDir); | |
| 97 | |
| 98 print('Copying static files...'); | |
| 99 // The basic dartdoc-provided static content. | |
| 100 final copiedStatic = copyDirectory( | |
| 101 path.join(scriptDir, | |
| 102 '..', '..', 'sdk', 'lib', '_internal', 'dartdoc', 'static'), | |
| 103 outputDir); | |
| 104 | |
| 105 // The apidoc-specific static content. | |
| 106 final copiedApiDocStatic = copyDirectory( | |
| 107 path.join(scriptDir, 'static'), | |
| 108 outputDir); | |
| 109 | |
| 110 print('Parsing MDN data...'); | |
| 111 final mdnFile = new File(path.join(scriptDir, 'mdn', 'database.json')); | |
| 112 final mdn = JSON.decode(mdnFile.readAsStringSync()); | |
| 113 | |
| 114 print('Cross-referencing dart:html...'); | |
| 115 // TODO(amouravski): move HtmlDiff inside of the future chain below to re-use | |
| 116 // the MirrorSystem already analyzed. | |
| 117 _diff = new HtmlDiff(printWarnings:false); | |
| 118 Future htmlDiff = _diff.run(currentDirectory.resolveUri(path.toUri(libPath))); | |
| 119 | |
| 120 // TODO(johnniwinther): Libraries for the compilation seem to be more like | |
| 121 // URIs. Perhaps Path should have a toURI() method. | |
| 122 // Add all of the core libraries. | |
| 123 final apidocLibraries = <Uri>[]; | |
| 124 LIBRARIES.forEach((String name, LibraryInfo info) { | |
| 125 if (info.documented) { | |
| 126 apidocLibraries.add(Uri.parse('dart:$name')); | |
| 127 } | |
| 128 }); | |
| 129 | |
| 130 // TODO(amouravski): This code is really wonky. | |
| 131 var lister = new Directory(path.join(scriptDir, '..', '..', 'pkg')).list(); | |
| 132 lister.listen((entity) { | |
| 133 if (entity is Directory) { | |
| 134 var libName = path.basename(entity.path); | |
| 135 var libPath = path.join(entity.path, 'lib', '${libName}.dart'); | |
| 136 | |
| 137 // Ignore some libraries. | |
| 138 if (excludedLibraries.contains(libName)) { | |
| 139 return; | |
| 140 } | |
| 141 | |
| 142 // Ignore hidden directories (like .svn) as well as pkg.xcodeproj. | |
| 143 if (libName.startsWith('.') || libName.endsWith('.xcodeproj')) { | |
| 144 return; | |
| 145 } | |
| 146 | |
| 147 if (new File(libPath).existsSync()) { | |
| 148 apidocLibraries.add(path.toUri(libPath)); | |
| 149 includedLibraries.add(libName); | |
| 150 } else { | |
| 151 print('Warning: could not find package at ${entity.path}'); | |
| 152 } | |
| 153 } | |
| 154 }, onDone: () { | |
| 155 // Add any --extra libraries that had full pkg paths. | |
| 156 // TODO(gram): if the handling of --include-lib libraries in the | |
| 157 // listen() block above is cleaned up, then this will need to be | |
| 158 // too, as it is a special case of the above. | |
| 159 for (var lib in extraLibraries) { | |
| 160 var libPath = '../../$lib'; | |
| 161 if (new File(libPath).existsSync()) { | |
| 162 apidocLibraries.add(path.toUri(libPath)); | |
| 163 var libName = path.basename(libPath).replaceAll('.dart', ''); | |
| 164 includedLibraries.add(libName); | |
| 165 } | |
| 166 } | |
| 167 | |
| 168 final apidoc = new Apidoc(mdn, outputDir, mode, generateAppCache, | |
| 169 excludedLibraries, version); | |
| 170 apidoc.dartdocPath = | |
| 171 path.join(scriptDir, '..', '..', 'sdk', 'lib', '_internal', 'dartdoc'); | |
| 172 // Select the libraries to include in the produced documentation: | |
| 173 apidoc.includeApi = true; | |
| 174 apidoc.includedLibraries = includedLibraries; | |
| 175 | |
| 176 // TODO(amouravski): make apidoc use roughly the same flow as bin/dartdoc. | |
| 177 Future.wait([copiedStatic, copiedApiDocStatic, htmlDiff]) | |
| 178 .then((_) => apidoc.documentLibraries(apidocLibraries, libPath, | |
| 179 packageRoot)) | |
| 180 .then((_) => compileScript(mode, outputDir, libPath, apidoc.tmpPath)) | |
| 181 .then((_) => print(apidoc.status)) | |
| 182 .catchError((e, trace) { | |
| 183 print('Error: generation failed: ${e}'); | |
| 184 if (trace != null) print("StackTrace: $trace"); | |
| 185 apidoc.cleanup(); | |
| 186 exit(1); | |
| 187 }) | |
| 188 .whenComplete(() => apidoc.cleanup()); | |
| 189 }); | |
| 190 } | |
| 191 | |
| 192 class Apidoc extends Dartdoc { | |
| 193 /** Big ball of JSON containing the scraped MDN documentation. */ | |
| 194 final Map mdn; | |
| 195 | |
| 196 | |
| 197 // A set of type names (TypeMirror.simpleName values) to ignore while | |
| 198 // looking up information from MDN data. TODO(eub, jacobr): fix up the MDN | |
| 199 // import scripts so they run correctly and generate data that doesn't have | |
| 200 // any entries that need to be ignored. | |
| 201 static Set<String> _mdnTypeNamesToSkip = null; | |
| 202 | |
| 203 /** | |
| 204 * The URL to the page on MDN that content was pulled from for the current | |
| 205 * type being documented. Will be `null` if the type doesn't use any MDN | |
| 206 * content. | |
| 207 */ | |
| 208 String mdnUrl = null; | |
| 209 | |
| 210 Apidoc(this.mdn, String outputDir, int mode, bool generateAppCache, | |
| 211 [List<String> excludedLibraries, String version]) { | |
| 212 if (excludedLibraries != null) this.excludedLibraries = excludedLibraries; | |
| 213 this.version = version; | |
| 214 this.outputDir = outputDir; | |
| 215 this.mode = mode; | |
| 216 this.generateAppCache = generateAppCache; | |
| 217 | |
| 218 // Skip bad entries in the checked-in mdn/database.json: | |
| 219 // * UnknownElement has a top-level Gecko DOM page in German. | |
| 220 if (_mdnTypeNamesToSkip == null) | |
| 221 _mdnTypeNamesToSkip = new Set.from(['UnknownElement']); | |
| 222 | |
| 223 mainTitle = 'Dart API Reference'; | |
| 224 mainUrl = 'http://dartlang.org'; | |
| 225 | |
| 226 final note = 'http://code.google.com/policies.html#restrictions'; | |
| 227 final cca = 'http://creativecommons.org/licenses/by/3.0/'; | |
| 228 final bsd = 'http://code.google.com/google_bsd_license.html'; | |
| 229 final tos = 'http://www.dartlang.org/tos.html'; | |
| 230 final privacy = 'http://www.google.com/intl/en/privacy/privacy-policy.html'; | |
| 231 | |
| 232 footerText = | |
| 233 ''' | |
| 234 <p>Except as otherwise <a href="$note">noted</a>, the content of this | |
| 235 page is licensed under the <a href="$cca">Creative Commons Attribution | |
| 236 3.0 License</a>, and code samples are licensed under the | |
| 237 <a href="$bsd">BSD License</a>.</p> | |
| 238 <p><a href="$tos">Terms of Service</a> | | |
| 239 <a href="$privacy">Privacy Policy</a></p> | |
| 240 '''; | |
| 241 | |
| 242 searchEngineId = '011220921317074318178:i4mscbaxtru'; | |
| 243 searchResultsUrl = 'http://www.dartlang.org/search.html'; | |
| 244 } | |
| 245 | |
| 246 void writeHeadContents(String title) { | |
| 247 super.writeHeadContents(title); | |
| 248 | |
| 249 // Include the apidoc-specific CSS. | |
| 250 // TODO(rnystrom): Use our CSS pre-processor to combine these. | |
| 251 writeln( | |
| 252 ''' | |
| 253 <link rel="stylesheet" type="text/css" | |
| 254 href="${relativePath('apidoc-styles.css')}" /> | |
| 255 '''); | |
| 256 | |
| 257 // Add the analytics code. | |
| 258 writeln( | |
| 259 ''' | |
| 260 <script type="text/javascript"> | |
| 261 var _gaq = _gaq || []; | |
| 262 _gaq.push(["_setAccount", "UA-26406144-9"]); | |
| 263 _gaq.push(["_trackPageview"]); | |
| 264 | |
| 265 (function() { | |
| 266 var ga = document.createElement("script"); | |
| 267 ga.type = "text/javascript"; ga.async = true; | |
| 268 ga.src = ("https:" == document.location.protocol ? | |
| 269 "https://ssl" : "http://www") + ".google-analytics.com/ga.js"; | |
| 270 var s = document.getElementsByTagName("script")[0]; | |
| 271 s.parentNode.insertBefore(ga, s); | |
| 272 })(); | |
| 273 </script> | |
| 274 '''); | |
| 275 } | |
| 276 | |
| 277 void docIndexLibrary(LibraryMirror library) { | |
| 278 // TODO(rnystrom): Hackish. The IO libraries reference this but we don't | |
| 279 // want it in the docs. | |
| 280 if (displayName(library) == 'dart:nativewrappers') return; | |
| 281 super.docIndexLibrary(library); | |
| 282 } | |
| 283 | |
| 284 void docLibraryNavigationJson(LibraryMirror library, List libraryList) { | |
| 285 // TODO(rnystrom): Hackish. The IO libraries reference this but we don't | |
| 286 // want it in the docs. | |
| 287 if (displayName(library) == 'dart:nativewrappers') return; | |
| 288 super.docLibraryNavigationJson(library, libraryList); | |
| 289 } | |
| 290 | |
| 291 void docLibrary(LibraryMirror library) { | |
| 292 // TODO(rnystrom): Hackish. The IO libraries reference this but we don't | |
| 293 // want it in the docs. | |
| 294 if (displayName(library) == 'dart:nativewrappers') return; | |
| 295 super.docLibrary(library); | |
| 296 } | |
| 297 | |
| 298 DocComment getLibraryComment(LibraryMirror library) { | |
| 299 return super.getLibraryComment(library); | |
| 300 } | |
| 301 | |
| 302 DocComment getTypeComment(TypeMirror type) { | |
| 303 return _mergeDocs( | |
| 304 includeMdnTypeComment(type), super.getTypeComment(type)); | |
| 305 } | |
| 306 | |
| 307 DocComment getMemberComment(DeclarationMirror member) { | |
| 308 return _mergeDocs( | |
| 309 includeMdnMemberComment(member), super.getMemberComment(member)); | |
| 310 } | |
| 311 | |
| 312 DocComment _mergeDocs(MdnComment mdnComment, | |
| 313 DocComment fileComment) { | |
| 314 // Otherwise, prefer comment from the (possibly generated) Dart file. | |
| 315 if (fileComment != null) return fileComment; | |
| 316 | |
| 317 // Finally, fallback on MDN if available. | |
| 318 if (mdnComment != null) { | |
| 319 mdnUrl = mdnComment.mdnUrl; | |
| 320 return mdnComment; | |
| 321 } | |
| 322 | |
| 323 // We got nothing! | |
| 324 return null; | |
| 325 } | |
| 326 | |
| 327 void docType(TypeMirror type) { | |
| 328 // Track whether we've inserted MDN content into this page. | |
| 329 mdnUrl = null; | |
| 330 | |
| 331 super.docType(type); | |
| 332 } | |
| 333 | |
| 334 void writeTypeFooter() { | |
| 335 if (mdnUrl != null) { | |
| 336 final MOZ = 'http://www.mozilla.org/'; | |
| 337 final MDN = 'https://developer.mozilla.org'; | |
| 338 final CCA = 'http://creativecommons.org/licenses/by-sa/2.5/'; | |
| 339 final CONTRIB = 'https://developer.mozilla.org/Project:en/How_to_Help'; | |
| 340 writeln( | |
| 341 ''' | |
| 342 <p class="mdn-attribution"> | |
| 343 <a href="$MDN"> | |
| 344 <img src="${relativePath('mdn-logo-tiny.png')}" class="mdn-logo" /> | |
| 345 </a> | |
| 346 This page includes <a href="$mdnUrl">content</a> from the | |
| 347 <a href="$MOZ">Mozilla Foundation</a> that is graciously | |
| 348 <a href="$MDN/Project:Copyrights">licensed</a> under a | |
| 349 <a href="$CCA">Creative Commons: Attribution-Sharealike license</a>. | |
| 350 Mozilla has no other association with Dart or dartlang.org. We | |
| 351 encourage you to improve the web by | |
| 352 <a href="$CONTRIB">contributing</a> to | |
| 353 <a href="$MDN">The Mozilla Developer Network</a>. | |
| 354 </p> | |
| 355 '''); | |
| 356 } | |
| 357 } | |
| 358 | |
| 359 MdnComment lookupMdnComment(Mirror mirror) { | |
| 360 if (mirror is TypeMirror) { | |
| 361 return includeMdnTypeComment(mirror); | |
| 362 } else if (mirror is MethodMirror || mirror is VariableMirror) { | |
| 363 return includeMdnMemberComment(mirror); | |
| 364 } else { | |
| 365 return null; | |
| 366 } | |
| 367 } | |
| 368 | |
| 369 /** | |
| 370 * Gets the MDN-scraped docs for [type], or `null` if this type isn't | |
| 371 * scraped from MDN. | |
| 372 */ | |
| 373 MdnComment includeMdnTypeComment(TypeMirror type) { | |
| 374 if (_mdnTypeNamesToSkip.contains(type.simpleName)) { | |
| 375 return null; | |
| 376 } | |
| 377 | |
| 378 var typeString = ''; | |
| 379 if (HTML_LIBRARY_URIS.contains(getLibrary(type).uri)) { | |
| 380 // If it's an HTML type, try to map it to a base DOM type so we can find | |
| 381 // the MDN docs. | |
| 382 final domTypes = _diff.htmlTypesToDom[type.qualifiedName]; | |
| 383 | |
| 384 // Couldn't find a DOM type. | |
| 385 if ((domTypes == null) || (domTypes.length != 1)) return null; | |
| 386 | |
| 387 // Use the corresponding DOM type when searching MDN. | |
| 388 // TODO(rnystrom): Shame there isn't a simpler way to get the one item | |
| 389 // out of a singleton Set. | |
| 390 // TODO(floitsch): switch to domTypes.first, once that's implemented. | |
| 391 var iter = domTypes.iterator; | |
| 392 iter.moveNext(); | |
| 393 typeString = iter.current; | |
| 394 } else { | |
| 395 // Not a DOM type. | |
| 396 return null; | |
| 397 } | |
| 398 | |
| 399 final mdnType = mdn[typeString]; | |
| 400 if (mdnType == null) return null; | |
| 401 if (mdnType['skipped'] != null) return null; | |
| 402 if (mdnType['summary'] == null) return null; | |
| 403 if (mdnType['summary'].trim().isEmpty) return null; | |
| 404 | |
| 405 // Remember which MDN page we're using so we can attribute it. | |
| 406 return new MdnComment(mdnType['summary'], mdnType['srcUrl']); | |
| 407 } | |
| 408 | |
| 409 /** | |
| 410 * Gets the MDN-scraped docs for [member], or `null` if this type isn't | |
| 411 * scraped from MDN. | |
| 412 */ | |
| 413 MdnComment includeMdnMemberComment(DeclarationMirror member) { | |
| 414 var library = getLibrary(member); | |
| 415 var memberString = ''; | |
| 416 if (HTML_LIBRARY_URIS.contains(library.uri)) { | |
| 417 // If it's an HTML type, try to map it to a DOM type name so we can find | |
| 418 // the MDN docs. | |
| 419 final domMembers = _diff.htmlToDom[member.qualifiedName]; | |
| 420 | |
| 421 // Couldn't find a DOM type. | |
| 422 if ((domMembers == null) || (domMembers.length != 1)) return null; | |
| 423 | |
| 424 // Use the corresponding DOM member when searching MDN. | |
| 425 // TODO(rnystrom): Shame there isn't a simpler way to get the one item | |
| 426 // out of a singleton Set. | |
| 427 // TODO(floitsch): switch to domTypes.first, once that's implemented. | |
| 428 var iter = domMembers.iterator; | |
| 429 iter.moveNext(); | |
| 430 memberString = iter.current; | |
| 431 } else { | |
| 432 // Not a DOM type. | |
| 433 return null; | |
| 434 } | |
| 435 | |
| 436 // Ignore top-level functions. | |
| 437 if (member.isTopLevel) return null; | |
| 438 | |
| 439 var mdnMember = null; | |
| 440 var mdnType = null; | |
| 441 var pieces = memberString.split('.'); | |
| 442 if (pieces.length == 2) { | |
| 443 mdnType = mdn[pieces[0]]; | |
| 444 if (mdnType == null) return null; | |
| 445 var nameToFind = pieces[1]; | |
| 446 for (final candidateMember in mdnType['members']) { | |
| 447 if (candidateMember['name'] == nameToFind) { | |
| 448 mdnMember = candidateMember; | |
| 449 break; | |
| 450 } | |
| 451 } | |
| 452 } | |
| 453 | |
| 454 if (mdnMember == null) return null; | |
| 455 if (mdnMember['help'] == null) return null; | |
| 456 if (mdnMember['help'].trim().isEmpty) return null; | |
| 457 | |
| 458 // Remember which MDN page we're using so we can attribute it. | |
| 459 return new MdnComment(mdnMember['help'], mdnType['srcUrl']); | |
| 460 } | |
| 461 | |
| 462 /** | |
| 463 * Returns a link to [member], relative to a type page that may be in a | |
| 464 * different library than [member]. | |
| 465 */ | |
| 466 String _linkMember(DeclarationMirror member) { | |
| 467 final typeName = member.owner.simpleName; | |
| 468 var memberName = '$typeName.${member.simpleName}'; | |
| 469 if (member is MethodMirror && member.isConstructor) { | |
| 470 final separator = member.constructorName == '' ? '' : '.'; | |
| 471 memberName = 'new $typeName$separator${member.constructorName}'; | |
| 472 } | |
| 473 | |
| 474 return a(memberUrl(member), memberName); | |
| 475 } | |
| 476 } | |
| 477 | |
| OLD | NEW |