| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /** |
| * This generates the reference documentation for the core libraries that come |
| * with dart. It is built on top of dartdoc, which is a general-purpose library |
| * for generating docs from any Dart code. This library extends that to include |
| * additional information and styling specific to our standard library. |
| * |
| * Usage: |
| * |
| * $ dart apidoc.dart [--out=<output directory>] |
| */ |
| library apidoc; |
| |
| import 'dart:io'; |
| import 'dart:json'; |
| import 'html_diff.dart'; |
| // TODO(rnystrom): Use "package:" URL (#4968). |
| import '../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors.dart'; |
| import '../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors_util.dart'; |
| import '../../sdk/lib/_internal/dartdoc/lib/dartdoc.dart' as doc; |
| import '../../sdk/lib/_internal/libraries.dart'; |
| |
| HtmlDiff _diff; |
| |
| void main() { |
| final args = new Options().arguments; |
| |
| int mode = doc.MODE_STATIC; |
| Path outputDir = new Path('docs'); |
| bool generateAppCache = false; |
| |
| List<String> excludedLibraries = <String>[]; |
| List<String> includedLibraries = <String>[]; |
| |
| |
| // Parse the command-line arguments. |
| for (int i = 0; i < args.length; i++) { |
| final arg = args[i]; |
| |
| switch (arg) { |
| case '--mode=static': |
| mode = doc.MODE_STATIC; |
| break; |
| |
| case '--mode=live-nav': |
| mode = doc.MODE_LIVE_NAV; |
| break; |
| |
| case '--generate-app-cache=true': |
| generateAppCache = true; |
| break; |
| |
| default: |
| if (arg.startsWith('--exclude-lib=')) { |
| excludedLibraries.add(arg.substring('--exclude-lib='.length)); |
| } else if (arg.startsWith('--include-lib=')) { |
| includedLibraries.add(arg.substring('--include-lib='.length)); |
| } else if (arg.startsWith('--out=')) { |
| outputDir = new Path.fromNative(arg.substring('--out='.length)); |
| } else { |
| print('Unknown option: $arg'); |
| return; |
| } |
| break; |
| } |
| } |
| |
| final libPath = doc.scriptDir.append('../../sdk/'); |
| final pkgPath = doc.scriptDir.append('../../pkg/'); |
| |
| doc.cleanOutputDirectory(outputDir); |
| |
| // The basic dartdoc-provided static content. |
| final copiedStatic = doc.copyDirectory( |
| doc.scriptDir.append('../../sdk/lib/_internal/dartdoc/static'), |
| outputDir); |
| |
| // The apidoc-specific static content. |
| final copiedApiDocStatic = doc.copyDirectory( |
| doc.scriptDir.append('static'), |
| outputDir); |
| |
| print('Parsing MDN data...'); |
| final mdnFile = new File.fromPath(doc.scriptDir.append('mdn/database.json')); |
| final mdn = JSON.parse(mdnFile.readAsStringSync()); |
| |
| print('Cross-referencing dart:html...'); |
| HtmlDiff.initialize(libPath); |
| _diff = new HtmlDiff(printWarnings:false); |
| _diff.run(); |
| |
| // Process handwritten HTML documentation. |
| print('Processing handwritten HTML documentation...'); |
| final htmldoc = new Htmldoc(); |
| htmldoc.includeApi = true; |
| htmldoc.documentLibraries( |
| <Path>[doc.scriptDir.append('../../sdk/lib/html/doc/html.dartdoc')], |
| libPath, pkgPath); |
| |
| // Process libraries. |
| |
| // TODO(johnniwinther): Libraries for the compilation seem to be more like |
| // URIs. Perhaps Path should have a toURI() method. |
| // Add all of the core libraries. |
| final apidocLibraries = <Path>[]; |
| LIBRARIES.forEach((String name, LibraryInfo info) { |
| if (info.documented) { |
| apidocLibraries.add(new Path('dart:$name')); |
| } |
| }); |
| |
| var lister = new Directory.fromPath(doc.scriptDir.append('../../pkg')).list(); |
| lister.onDir = (dirPath) { |
| var path = new Path.fromNative(dirPath); |
| var libName = path.filename; |
| |
| // TODO(rnystrom): Get rid of oldStylePath support when all packages are |
| // using new layout. See #5106. |
| var oldStylePath = path.append('${libName}.dart'); |
| var newStylePath = path.append('lib/${libName}.dart'); |
| |
| if (new File.fromPath(oldStylePath).existsSync()) { |
| apidocLibraries.add(oldStylePath); |
| includedLibraries.add(libName); |
| } else if (new File.fromPath(newStylePath).existsSync()) { |
| apidocLibraries.add(newStylePath); |
| includedLibraries.add(libName); |
| } else { |
| print('Warning: could not find package at $path'); |
| } |
| }; |
| |
| lister.onDone = (success) { |
| print('Generating docs...'); |
| final apidoc = new Apidoc(mdn, htmldoc, outputDir, mode, generateAppCache, |
| excludedLibraries); |
| apidoc.dartdocPath = |
| doc.scriptDir.append('../../sdk/lib/_internal/dartdoc/'); |
| // Select the libraries to include in the produced documentation: |
| apidoc.includeApi = true; |
| apidoc.includedLibraries = includedLibraries; |
| |
| Futures.wait([copiedStatic, copiedApiDocStatic]).then((_) { |
| apidoc.documentLibraries(apidocLibraries, libPath, pkgPath); |
| |
| final compiled = doc.compileScript(mode, outputDir, libPath); |
| |
| Futures.wait([compiled, copiedStatic, copiedApiDocStatic]).then((_) { |
| apidoc.cleanup(); |
| }); |
| }); |
| }; |
| } |
| |
| /** |
| * This class is purely here to scrape handwritten HTML documentation. |
| * This scraped documentation will later be merged with the generated |
| * HTML library. |
| */ |
| class Htmldoc extends doc.Dartdoc { |
| doc.DocComment libraryComment; |
| |
| /** |
| * Map from qualified type names to comments. |
| */ |
| Map<String, doc.DocComment> typeComments; |
| |
| /** |
| * Map from qualified member names to comments. |
| */ |
| Map<String, doc.DocComment> memberComments; |
| |
| Htmldoc() { |
| typeComments = new Map<String, doc.DocComment>(); |
| memberComments = new Map<String, doc.DocComment>(); |
| } |
| |
| // Suppress any actual writing to file. This is only for analysis. |
| void endFile() { |
| } |
| |
| void write(String s) { |
| } |
| |
| doc.DocComment getRecordedLibraryComment(LibraryMirror library) { |
| if (HTML_LIBRARY_NAMES.contains(doc.displayName(library))) { |
| return libraryComment; |
| } |
| return null; |
| } |
| |
| doc.DocComment getRecordedTypeComment(TypeMirror type) { |
| if (typeComments.containsKey(type.qualifiedName)) { |
| return typeComments[type.qualifiedName]; |
| } |
| return null; |
| } |
| |
| doc.DocComment getRecordedMemberComment(MemberMirror member) { |
| if (memberComments.containsKey(member.qualifiedName)) { |
| return memberComments[member.qualifiedName]; |
| } |
| return null; |
| } |
| |
| // These methods are subclassed and used for internal processing. |
| // Do not invoke outside of this class. |
| doc.DocComment getLibraryComment(LibraryMirror library) { |
| doc.DocComment comment = super.getLibraryComment(library); |
| libraryComment = comment; |
| return comment; |
| } |
| |
| doc.DocComment getTypeComment(TypeMirror type) { |
| doc.DocComment comment = super.getTypeComment(type); |
| recordTypeComment(type, comment); |
| return comment; |
| } |
| |
| doc.DocComment getMemberComment(MemberMirror member) { |
| doc.DocComment comment = super.getMemberComment(member); |
| recordMemberComment(member, comment); |
| return comment; |
| } |
| |
| void recordTypeComment(TypeMirror type, doc.DocComment comment) { |
| if (comment != null && comment.text.contains('@domName')) { |
| // This is not a handwritten comment. |
| return; |
| } |
| typeComments[type.qualifiedName] = comment; |
| } |
| |
| void recordMemberComment(MemberMirror member, doc.DocComment comment) { |
| if (comment != null && comment.text.contains('@domName')) { |
| // This is not a handwritten comment. |
| return; |
| } |
| memberComments[member.qualifiedName] = comment; |
| } |
| } |
| |
| class Apidoc extends doc.Dartdoc { |
| /** Big ball of JSON containing the scraped MDN documentation. */ |
| final Map mdn; |
| |
| final Htmldoc htmldoc; |
| |
| static const disqusShortname = 'dartapidocs'; |
| |
| // A set of type names (TypeMirror.simpleName values) to ignore while |
| // looking up information from MDN data. TODO(eub, jacobr): fix up the MDN |
| // import scripts so they run correctly and generate data that doesn't have |
| // any entries that need to be ignored. |
| static Set<String> _mdnTypeNamesToSkip = null; |
| |
| /** |
| * The URL to the page on MDN that content was pulled from for the current |
| * type being documented. Will be `null` if the type doesn't use any MDN |
| * content. |
| */ |
| String mdnUrl = null; |
| |
| Apidoc(this.mdn, this.htmldoc, Path outputDir, int mode, |
| bool generateAppCache, [excludedLibraries]) { |
| if (?excludedLibraries) { |
| this.excludedLibraries = excludedLibraries; |
| } |
| |
| this.outputDir = outputDir; |
| this.mode = mode; |
| this.generateAppCache = generateAppCache; |
| |
| // Skip bad entries in the checked-in mdn/database.json: |
| // * UnknownElement has a top-level Gecko DOM page in German. |
| if (_mdnTypeNamesToSkip == null) |
| _mdnTypeNamesToSkip = new Set.from(['UnknownElement']); |
| |
| mainTitle = 'Dart API Reference'; |
| mainUrl = 'http://dartlang.org'; |
| |
| final note = 'http://code.google.com/policies.html#restrictions'; |
| final cca = 'http://creativecommons.org/licenses/by/3.0/'; |
| final bsd = 'http://code.google.com/google_bsd_license.html'; |
| final tos = 'http://www.dartlang.org/tos.html'; |
| final privacy = 'http://www.google.com/intl/en/privacy/privacy-policy.html'; |
| |
| footerText = |
| ''' |
| <p>Except as otherwise <a href="$note">noted</a>, the content of this |
| page is licensed under the <a href="$cca">Creative Commons Attribution |
| 3.0 License</a>, and code samples are licensed under the |
| <a href="$bsd">BSD License</a>.</p> |
| <p> |
| Comments that are not specifically about the API libraries will |
| be moderated and possibly deleted. |
| Because we may incorporate information from comments into the |
| documentation, any comment submitted here is under the same |
| license as the documentation. |
| </p> |
| <p><a href="$tos">Terms of Service</a> | |
| <a href="$privacy">Privacy Policy</a></p> |
| '''; |
| |
| preFooterText = |
| ''' |
| <div id="comments"> |
| <div id="disqus_thread"></div> |
| <script type="text/javascript"> |
| /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */ |
| var disqus_shortname = "$disqusShortname"; // required: replace example with your forum shortname |
| |
| /* * * DON\'T EDIT BELOW THIS LINE * * */ |
| (function() { |
| var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true; |
| dsq.src = "http://" + disqus_shortname + ".disqus.com/embed.js"; |
| (document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq); |
| })(); |
| </script> |
| <noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript> |
| </div> <!-- #comments --> |
| '''; |
| |
| searchEngineId = '011220921317074318178:i4mscbaxtru'; |
| searchResultsUrl = 'http://www.dartlang.org/search.html'; |
| } |
| |
| void writeHeadContents(String title) { |
| super.writeHeadContents(title); |
| |
| // Include the apidoc-specific CSS. |
| // TODO(rnystrom): Use our CSS pre-processor to combine these. |
| writeln( |
| ''' |
| <link rel="stylesheet" type="text/css" |
| href="${relativePath('apidoc-styles.css')}" /> |
| '''); |
| |
| // Add the analytics code. |
| writeln( |
| ''' |
| <script type="text/javascript"> |
| var _gaq = _gaq || []; |
| _gaq.push(["_setAccount", "UA-26406144-9"]); |
| _gaq.push(["_trackPageview"]); |
| |
| (function() { |
| var ga = document.createElement("script"); |
| ga.type = "text/javascript"; ga.async = true; |
| ga.src = ("https:" == document.location.protocol ? |
| "https://ssl" : "http://www") + ".google-analytics.com/ga.js"; |
| var s = document.getElementsByTagName("script")[0]; |
| s.parentNode.insertBefore(ga, s); |
| })(); |
| </script> |
| '''); |
| } |
| |
| void docIndexLibrary(LibraryMirror library) { |
| // TODO(rnystrom): Hackish. The IO libraries reference this but we don't |
| // want it in the docs. |
| if (doc.displayName(library) == 'dart:nativewrappers') return; |
| super.docIndexLibrary(library); |
| } |
| |
| void docLibraryNavigationJson(LibraryMirror library, List libraryList) { |
| // TODO(rnystrom): Hackish. The IO libraries reference this but we don't |
| // want it in the docs. |
| if (doc.displayName(library) == 'dart:nativewrappers') return; |
| super.docLibraryNavigationJson(library, libraryList); |
| } |
| |
| void docLibrary(LibraryMirror library) { |
| // TODO(rnystrom): Hackish. The IO libraries reference this but we don't |
| // want it in the docs. |
| if (doc.displayName(library) == 'dart:nativewrappers') return; |
| super.docLibrary(library); |
| } |
| |
| /** Override definition from parent class to strip out annotation tags. */ |
| doc.DocComment createDocComment(String text, |
| [ClassMirror inheritedFrom]) { |
| String strippedText = |
| text.replaceAll(new RegExp("@([a-zA-Z]+) ([^;]+)(?:;|\$)"), |
| '').trim(); |
| if (strippedText.isEmpty) return null; |
| return super.createDocComment(strippedText, inheritedFrom); |
| } |
| |
| doc.DocComment getLibraryComment(LibraryMirror library) { |
| if (HTML_LIBRARY_NAMES.contains(doc.displayName(library))) { |
| return htmldoc.libraryComment; |
| } |
| return super.getLibraryComment(library); |
| } |
| |
| doc.DocComment getTypeComment(TypeMirror type) { |
| return _mergeDocs( |
| includeMdnTypeComment(type), super.getTypeComment(type), |
| htmldoc.getRecordedTypeComment(type)); |
| } |
| |
| doc.DocComment getMemberComment(MemberMirror member) { |
| return _mergeDocs( |
| includeMdnMemberComment(member), super.getMemberComment(member), |
| htmldoc.getRecordedMemberComment(member)); |
| } |
| |
| doc.DocComment _mergeDocs(MdnComment mdnComment, |
| doc.DocComment fileComment, |
| doc.DocComment handWrittenComment) { |
| // Prefer the hand-written comment first. |
| if (handWrittenComment != null) return handWrittenComment; |
| |
| // Otherwise, prefer comment from the (possibly generated) Dart file. |
| if (fileComment != null) return fileComment; |
| |
| // Finally, fallback on MDN if available. |
| if (mdnComment != null) { |
| mdnUrl = mdnComment.mdnUrl; |
| return mdnComment; |
| } |
| |
| // We got nothing! |
| return null; |
| } |
| |
| void docType(TypeMirror type) { |
| // Track whether we've inserted MDN content into this page. |
| mdnUrl = null; |
| |
| super.docType(type); |
| } |
| |
| void writeTypeFooter() { |
| if (mdnUrl != null) { |
| final MOZ = 'http://www.mozilla.org/'; |
| final MDN = 'https://developer.mozilla.org'; |
| final CCA = 'http://creativecommons.org/licenses/by-sa/2.5/'; |
| final CONTRIB = 'https://developer.mozilla.org/Project:en/How_to_Help'; |
| writeln( |
| ''' |
| <p class="mdn-attribution"> |
| <a href="$MDN"> |
| <img src="${relativePath('mdn-logo-tiny.png')}" class="mdn-logo" /> |
| </a> |
| This page includes <a href="$mdnUrl">content</a> from the |
| <a href="$MOZ">Mozilla Foundation</a> that is graciously |
| <a href="$MDN/Project:Copyrights">licensed</a> under a |
| <a href="$CCA">Creative Commons: Attribution-Sharealike license</a>. |
| Mozilla has no other association with Dart or dartlang.org. We |
| encourage you to improve the web by |
| <a href="$CONTRIB">contributing</a> to |
| <a href="$MDN">The Mozilla Developer Network</a>. |
| </p> |
| '''); |
| } |
| } |
| |
| /** |
| * Gets the MDN-scraped docs for [type], or `null` if this type isn't |
| * scraped from MDN. |
| */ |
| MdnComment includeMdnTypeComment(TypeMirror type) { |
| if (_mdnTypeNamesToSkip.contains(type.simpleName)) { |
| print('Skipping MDN type ${type.simpleName}'); |
| return null; |
| } |
| |
| var typeString = ''; |
| if (HTML_LIBRARY_NAMES.contains(doc.displayName(type.library))) { |
| // If it's an HTML type, try to map it to a base DOM type so we can find |
| // the MDN docs. |
| final domTypes = _diff.htmlTypesToDom[type.qualifiedName]; |
| |
| // Couldn't find a DOM type. |
| if ((domTypes == null) || (domTypes.length != 1)) return null; |
| |
| // Use the corresponding DOM type when searching MDN. |
| // TODO(rnystrom): Shame there isn't a simpler way to get the one item |
| // out of a singleton Set. |
| typeString = domTypes.iterator().next(); |
| } else { |
| // Not a DOM type. |
| return null; |
| } |
| |
| final mdnType = mdn[typeString]; |
| if (mdnType == null) return null; |
| if (mdnType['skipped'] != null) return null; |
| if (mdnType['summary'] == null) return null; |
| if (mdnType['summary'].trim().isEmpty) return null; |
| |
| // Remember which MDN page we're using so we can attribute it. |
| return new MdnComment(mdnType['summary'], mdnType['srcUrl']); |
| } |
| |
| /** |
| * Gets the MDN-scraped docs for [member], or `null` if this type isn't |
| * scraped from MDN. |
| */ |
| MdnComment includeMdnMemberComment(MemberMirror member) { |
| var library = findLibrary(member); |
| var memberString = ''; |
| if (HTML_LIBRARY_NAMES.contains(doc.displayName(library))) { |
| // If it's an HTML type, try to map it to a DOM type name so we can find |
| // the MDN docs. |
| final domMembers = _diff.htmlToDom[member.qualifiedName]; |
| |
| // Couldn't find a DOM type. |
| if ((domMembers == null) || (domMembers.length != 1)) return null; |
| |
| // Use the corresponding DOM member when searching MDN. |
| // TODO(rnystrom): Shame there isn't a simpler way to get the one item |
| // out of a singleton Set. |
| memberString = domMembers.iterator().next(); |
| } else { |
| // Not a DOM type. |
| return null; |
| } |
| |
| // Ignore top-level functions. |
| if (member.isTopLevel) return null; |
| |
| var mdnMember = null; |
| var mdnType = null; |
| var pieces = memberString.split('.'); |
| if (pieces.length == 2) { |
| mdnType = mdn[pieces[0]]; |
| if (mdnType == null) return null; |
| var nameToFind = pieces[1]; |
| for (final candidateMember in mdnType['members']) { |
| if (candidateMember['name'] == nameToFind) { |
| mdnMember = candidateMember; |
| break; |
| } |
| } |
| } |
| |
| if (mdnMember == null) return null; |
| if (mdnMember['help'] == null) return null; |
| if (mdnMember['help'].trim().isEmpty) return null; |
| |
| // Remember which MDN page we're using so we can attribute it. |
| return new MdnComment(mdnMember['help'], mdnType['srcUrl']); |
| } |
| |
| /** |
| * Returns a link to [member], relative to a type page that may be in a |
| * different library than [member]. |
| */ |
| String _linkMember(MemberMirror member) { |
| final typeName = member.owner.simpleName; |
| var memberName = '$typeName.${member.simpleName}'; |
| if (member is MethodMirror && (member.isConstructor || member.isFactory)) { |
| final separator = member.constructorName == '' ? '' : '.'; |
| memberName = 'new $typeName$separator${member.constructorName}'; |
| } |
| |
| return a(memberUrl(member), memberName); |
| } |
| } |
| |
| class MdnComment implements doc.DocComment { |
| final String mdnComment; |
| final String mdnUrl; |
| |
| MdnComment(String this.mdnComment, String this.mdnUrl); |
| |
| String get text => mdnComment; |
| |
| ClassMirror get inheritedFrom => null; |
| |
| String get html { |
| // Wrap the mdn comment so we can highlight it and so we handle MDN scraped |
| // content that lacks a top-level block tag. |
| return ''' |
| <div class="mdn"> |
| $mdnComment |
| <div class="mdn-note"><a href="$mdnUrl">from MDN</a></div> |
| </div> |
| '''; |
| } |
| |
| String toString() => mdnComment; |
| } |