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 * Generates the complete set of corelib reference documentation. | 6 * Generates the complete set of corelib reference documentation. |
7 */ | 7 */ |
8 #library('apidoc'); | 8 #library('apidoc'); |
9 | 9 |
| 10 #import('dart:json'); |
10 #import('html_diff.dart'); | 11 #import('html_diff.dart'); |
11 #import('../../frog/lang.dart'); | 12 #import('../../frog/lang.dart'); |
12 #import('../../frog/file_system_node.dart'); | 13 #import('../../frog/file_system_node.dart'); |
13 #import('../../frog/file_system.dart'); | 14 #import('../../frog/file_system.dart'); |
14 #import('../dartdoc/dartdoc.dart', prefix: 'doc'); | 15 #import('../dartdoc/dartdoc.dart', prefix: 'doc'); |
15 | 16 |
16 HtmlDiff _diff; | 17 HtmlDiff _diff; |
17 | 18 |
18 void main() { | 19 void main() { |
19 var files = new NodeFileSystem(); | 20 var files = new NodeFileSystem(); |
20 parseOptions('../../frog', [] /* args */, files); | 21 parseOptions('../../frog', [] /* args */, files); |
21 initializeWorld(files); | 22 initializeWorld(files); |
22 final apidoc = new Apidoc(); | |
23 | 23 |
| 24 print('Parsing MDN data...'); |
| 25 final mdn = JSON.parse(files.readAll('mdn/database.json')); |
| 26 |
| 27 print('Cross-referencing dart:dom and dart:html...'); |
24 HtmlDiff.initialize(); | 28 HtmlDiff.initialize(); |
25 | |
26 _diff = new HtmlDiff(); | 29 _diff = new HtmlDiff(); |
27 _diff.run(); | 30 _diff.run(); |
28 world.reset(); | 31 world.reset(); |
29 | 32 |
| 33 print('Generating docs...'); |
| 34 final apidoc = new Apidoc(mdn); |
30 apidoc.document('html'); | 35 apidoc.document('html'); |
31 } | 36 } |
32 | 37 |
33 class Apidoc extends doc.Dartdoc { | 38 class Apidoc extends doc.Dartdoc { |
34 Apidoc() { | 39 /** Big ball of JSON containing the scraped MDN documentation. */ |
| 40 final Map mdn; |
| 41 |
| 42 /** |
| 43 * The URL to the page on MDN that content was pulled from for the current |
| 44 * type being documented. Will be `null` if the type doesn't use any MDN |
| 45 * content. |
| 46 */ |
| 47 String mdnUrl; |
| 48 |
| 49 Apidoc(this.mdn) { |
35 mainTitle = 'Dart API Reference'; | 50 mainTitle = 'Dart API Reference'; |
36 mainUrl = 'http://dartlang.org'; | 51 mainUrl = 'http://dartlang.org'; |
37 | 52 |
38 final note = 'http://code.google.com/policies.html#restrictions'; | 53 final note = 'http://code.google.com/policies.html#restrictions'; |
39 final cca = 'http://creativecommons.org/licenses/by/3.0/'; | 54 final cca = 'http://creativecommons.org/licenses/by/3.0/'; |
40 final bsd = 'http://code.google.com/google_bsd_license.html'; | 55 final bsd = 'http://code.google.com/google_bsd_license.html'; |
41 final tos = 'http://www.dartlang.org/tos.html'; | 56 final tos = 'http://www.dartlang.org/tos.html'; |
42 final privacy = 'http://www.google.com/intl/en/privacy/privacy-policy.html'; | 57 final privacy = 'http://www.google.com/intl/en/privacy/privacy-policy.html'; |
43 | 58 |
44 footerText = | 59 footerText = |
45 ''' | 60 ''' |
46 <p>Except as otherwise <a href="$note">noted</a>, the content of this | 61 <p>Except as otherwise <a href="$note">noted</a>, the content of this |
47 page is licensed under the <a href="$cca">Creative Commons Attribution | 62 page is licensed under the <a href="$cca">Creative Commons Attribution |
48 3.0 License</a>, and code samples are licensed under the | 63 3.0 License</a>, and code samples are licensed under the |
49 <a href="$bsd">BSD License</a>.</p> | 64 <a href="$bsd">BSD License</a>.</p> |
50 <p><a href="$tos">Terms of Service</a> | | 65 <p><a href="$tos">Terms of Service</a> | |
51 <a href="$privacy">Privacy Policy</a></p> | 66 <a href="$privacy">Privacy Policy</a></p> |
52 '''; | 67 '''; |
53 } | 68 } |
54 | 69 |
55 void writeHeadContents(String title) { | 70 void writeHeadContents(String title) { |
56 super.writeHeadContents(title); | 71 super.writeHeadContents(title); |
57 | 72 |
| 73 // Include the apidoc-specific CSS. |
| 74 // TODO(rnystrom): Use our CSS pre-processor to combine these. |
| 75 writeln( |
| 76 ''' |
| 77 <link rel="stylesheet" type="text/css" |
| 78 href="${relativePath('apidoc-styles.css')}" /> |
| 79 '''); |
| 80 |
58 // Add the analytics code. | 81 // Add the analytics code. |
59 doc.writeln( | 82 writeln( |
60 ''' | 83 ''' |
61 <script type="text/javascript"> | 84 <script type="text/javascript"> |
62 var _gaq = _gaq || []; | 85 var _gaq = _gaq || []; |
63 _gaq.push(['_setAccount', 'UA-26406144-4']); | 86 _gaq.push(["_setAccount", "UA-26406144-4"]); |
64 _gaq.push(['_setDomainName', 'dartlang.org']); | 87 _gaq.push(["_setDomainName", "dartlang.org"]); |
65 _gaq.push(['_trackPageview']); | 88 _gaq.push(["_trackPageview"]); |
66 | 89 |
67 (function() { | 90 (function() { |
68 var ga = document.createElement('script'); ga.type = 'text/javascrip
t'; ga.async = true; | 91 var ga = document.createElement("script"); |
69 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : '
http://www') + '.google-analytics.com/ga.js'; | 92 ga.type = "text/javascript"; ga.async = true; |
70 var s = document.getElementsByTagName('script')[0]; s.parentNode.ins
ertBefore(ga, s); | 93 ga.src = ("https:" == document.location.protocol ? |
| 94 "https://ssl" : "http://www") + ".google-analytics.com/ga.js"; |
| 95 var s = document.getElementsByTagName("script")[0]; |
| 96 s.parentNode.insertBefore(ga, s); |
71 })(); | 97 })(); |
72 </script> | 98 </script> |
73 '''); | 99 '''); |
74 } | 100 } |
75 | 101 |
76 getTypeComment(Type type) { | 102 String getTypeComment(Type type) { |
77 return _mergeComments(super.getTypeComment(type), getTypeDoc(type)); | 103 return _mergeDocs( |
78 } | 104 includeMdnTypeComment(type), |
79 | 105 super.getTypeComment(type), |
80 getMethodComment(MethodMember method) { | 106 getTypeDoc(type)); |
81 return _mergeComments(super.getMethodComment(method), getMemberDoc(method)); | 107 } |
82 } | 108 |
83 | 109 String getMethodComment(MethodMember method) { |
84 getFieldComment(FieldMember field) { | 110 // TODO(rnystrom): Disabling cross-linking between individual members. |
85 return _mergeComments(super.getFieldComment(field), getMemberDoc(field)); | 111 // Now that we include MDN content for most members, it doesn't add much |
86 } | 112 // value and adds a lot of clutter. Once we're sure we want to do this, |
87 | 113 // we can delete this code entirely. |
88 String _mergeComments(String comment, String extra) { | 114 return _mergeDocs( |
89 if (comment == null) return extra; | 115 includeMdnMemberComment(method), |
90 return '$comment\n\n$extra'; | 116 super.getMethodComment(method), |
| 117 /* getMemberDoc(method) */ null); |
| 118 } |
| 119 |
| 120 String getFieldComment(FieldMember field) { |
| 121 // TODO(rnystrom): Disabling cross-linking between individual members. |
| 122 // Now that we include MDN content for most members, it doesn't add much |
| 123 // value and adds a lot of clutter. Once we're sure we want to do this, |
| 124 // we can delete this code entirely. |
| 125 return _mergeDocs( |
| 126 includeMdnMemberComment(field), |
| 127 super.getFieldComment(field), |
| 128 /* getMemberDoc(field) */ null); |
| 129 } |
| 130 |
| 131 bool isNonEmpty(String string) => (string != null) && (string.trim() != ''); |
| 132 |
| 133 String _mergeDocs(String mdnComment, String dartComment, String diffComment) { |
| 134 // Prefer hand-written Dart comments over stuff from MDN. |
| 135 if (isNonEmpty(dartComment)) { |
| 136 // Also include the diff comment if provided. |
| 137 if (isNonEmpty(diffComment)) return dartComment + diffComment; |
| 138 return dartComment; |
| 139 } else if (isNonEmpty(mdnComment)) { |
| 140 // Wrap it so we can highlight it and so we handle MDN scraped content |
| 141 // that lacks a top-level block tag. |
| 142 mdnComment = |
| 143 ''' |
| 144 <div class="mdn"> |
| 145 $mdnComment |
| 146 <div class="mdn-note"><a href="$mdnUrl">from MDN</a></div> |
| 147 </div> |
| 148 '''; |
| 149 |
| 150 // Also include the diff comment if provided. |
| 151 if (isNonEmpty(diffComment)) return mdnComment + diffComment; |
| 152 return mdnComment; |
| 153 } else if (isNonEmpty(diffComment)) { |
| 154 // All we have is the diff comment. |
| 155 return diffComment; |
| 156 } else { |
| 157 // We got nothing! |
| 158 return ''; |
| 159 } |
| 160 } |
| 161 |
| 162 void docType(Type type) { |
| 163 // Track whether we've inserted MDN content into this page. |
| 164 mdnUrl = null; |
| 165 |
| 166 super.docType(type); |
| 167 } |
| 168 |
| 169 void writeTypeFooter() { |
| 170 if (mdnUrl != null) { |
| 171 final MOZ = 'http://www.mozilla.org/'; |
| 172 final MDN = 'https://developer.mozilla.org'; |
| 173 final CCA = 'http://creativecommons.org/licenses/by-sa/2.5/'; |
| 174 final CONTRIB = 'https://developer.mozilla.org/Project:en/How_to_Help'; |
| 175 writeln( |
| 176 ''' |
| 177 <p class="mdn-attribution"> |
| 178 <a href="$MDN"> |
| 179 <img src="${relativePath('mdn-logo-tiny.png')}" class="mdn-logo" /> |
| 180 </a> |
| 181 This page includes <a href="$mdnUrl">content</a> from the |
| 182 <a href="$MOZ">Mozilla Foundation</a> that is graciously |
| 183 <a href="$MDN/Project:Copyrights">licensed</a> under a |
| 184 <a href="$CCA">Creative Commons: Attribution-Sharealike license</a>. |
| 185 Mozilla has no other association with Dart or dartlang.org. We |
| 186 encourage you to improve the web by |
| 187 <a href="$CONTRIB">contributing</a> to |
| 188 <a href="$MDN">The Mozilla Developer Network</a>. |
| 189 </p> |
| 190 '''); |
| 191 } |
| 192 } |
| 193 |
| 194 /** |
| 195 * Gets the MDN-scraped docs for [type], or `null` if this type isn't |
| 196 * scraped from MDN. |
| 197 */ |
| 198 includeMdnTypeComment(Type type) { |
| 199 if (type.library.name == 'html') { |
| 200 // If it's an HTML type, try to map it to a base DOM type so we can find |
| 201 // the MDN docs. |
| 202 final domTypes = _diff.htmlTypesToDom[type]; |
| 203 |
| 204 // Couldn't find a DOM type. |
| 205 if ((domTypes == null) || (domTypes.length != 1)) return null; |
| 206 |
| 207 // Use the corresponding DOM type when searching MDN. |
| 208 // TODO(rnystrom): Shame there isn't a simpler way to get the one item |
| 209 // out of a singleton Set. |
| 210 type = domTypes.iterator().next(); |
| 211 } else if (type.library.name != 'dom') { |
| 212 // Not a DOM type. |
| 213 return null; |
| 214 } |
| 215 |
| 216 final mdnType = mdn[type.name]; |
| 217 if (mdnType == null) return null; |
| 218 if (mdnType['skipped'] != null) return null; |
| 219 |
| 220 // Remember which MDN page we're using so we can attribute it. |
| 221 mdnUrl = mdnType['srcUrl']; |
| 222 return mdnType['summary']; |
| 223 } |
| 224 |
| 225 /** |
| 226 * Gets the MDN-scraped docs for [member], or `null` if this type isn't |
| 227 * scraped from MDN. |
| 228 */ |
| 229 includeMdnMemberComment(Member member) { |
| 230 if (member.library.name == 'html') { |
| 231 // If it's an HTML type, try to map it to a base DOM type so we can find |
| 232 // the MDN docs. |
| 233 final domMembers = _diff.htmlToDom[member]; |
| 234 |
| 235 // Couldn't find a DOM type. |
| 236 if ((domMembers == null) || (domMembers.length != 1)) return null; |
| 237 |
| 238 // Use the corresponding DOM member when searching MDN. |
| 239 // TODO(rnystrom): Shame there isn't a simpler way to get the one item |
| 240 // out of a singleton Set. |
| 241 member = domMembers.iterator().next(); |
| 242 } else if (member.library.name != 'dom') { |
| 243 // Not a DOM type. |
| 244 return null; |
| 245 } |
| 246 |
| 247 // Ignore top-level functions. |
| 248 if (member.declaringType.isTop) return null; |
| 249 |
| 250 final mdnType = mdn[member.declaringType.name]; |
| 251 if (mdnType == null) return null; |
| 252 |
| 253 var mdnMember = null; |
| 254 for (final thisMember in mdnType['members']) { |
| 255 if (thisMember['name'] == member.name) { |
| 256 mdnMember = thisMember; |
| 257 break; |
| 258 } |
| 259 } |
| 260 |
| 261 if (mdnMember == null) return null; |
| 262 |
| 263 // Remember which MDN page we're using so we can attribute it. |
| 264 mdnUrl = mdnType['srcUrl']; |
| 265 return mdnMember['help']; |
| 266 } |
| 267 |
| 268 /** |
| 269 * Returns a link to [member], relative to a type page that may be in a |
| 270 * different library than [member]. |
| 271 */ |
| 272 String _linkMember(Member member) { |
| 273 final GET_PREFIX = 'get:'; |
| 274 |
| 275 final typeName = member.declaringType.name; |
| 276 var memberName = '$typeName.${member.name}'; |
| 277 if (member.isConstructor || member.isFactory) { |
| 278 final separator = member.constructorName == '' ? '' : '.'; |
| 279 memberName = 'new $typeName$separator${member.constructorName}'; |
| 280 } else if (member.name.startsWith(GET_PREFIX)) { |
| 281 memberName = '$typeName.${member.name.substring(GET_PREFIX.length)}'; |
| 282 } |
| 283 |
| 284 return a(memberUrl(member), memberName); |
| 285 } |
| 286 |
| 287 /** |
| 288 * Unify getters and setters of the same property. We only want to print |
| 289 * explicit setters if no getter exists. |
| 290 * |
| 291 * If [members] contains no setters, returns it unmodified. |
| 292 */ |
| 293 Set<Member> _unifyProperties(Set<Member> members) { |
| 294 // Only print setters if the getter doesn't exist. |
| 295 return members.filter((m) { |
| 296 if (!m.name.startsWith('set:')) return true; |
| 297 var getName = m.name.replaceFirst('set:', 'get:'); |
| 298 return !members.some((maybeGet) => maybeGet.name == getName); |
| 299 }); |
| 300 } |
| 301 |
| 302 /** |
| 303 * Returns additional documentation for [member], linking it to the |
| 304 * corresponding `dart:html` or `dart:dom` [Member](s). If [member] is not in |
| 305 * `dart:html` or `dart:dom`, returns no additional documentation. |
| 306 */ |
| 307 String getMemberDoc(Member member) { |
| 308 if (_diff.domToHtml.containsKey(member)) { |
| 309 final htmlMemberSet = _unifyProperties(_diff.domToHtml[member]); |
| 310 final allSameName = htmlMemberSet.every((m) => _diff.sameName(member, m)); |
| 311 final phrase = allSameName ? 'available as' : 'renamed to'; |
| 312 final htmlMembers = doc.joinWithCommas(map(htmlMemberSet, _linkMember)); |
| 313 return |
| 314 '''<p class="correspond">This is $phrase $htmlMembers in the |
| 315 ${a("html.html", "dart:html")} library.</p> |
| 316 '''; |
| 317 } else if (_diff.htmlToDom.containsKey(member)) { |
| 318 final domMemberSet = _unifyProperties(_diff.htmlToDom[member]); |
| 319 final allSameName = domMemberSet.every((m) => _diff.sameName(m, member)); |
| 320 final phrase = allSameName ? 'is the same as' : 'renames'; |
| 321 final domMembers = doc.joinWithCommas(map(domMemberSet, _linkMember)); |
| 322 return |
| 323 ''' |
| 324 <p class="correspond">This $phrase $domMembers in the |
| 325 ${a("dom.html", "dart:dom")} library.</p> |
| 326 '''; |
| 327 } else { |
| 328 return ''; |
| 329 } |
| 330 } |
| 331 |
| 332 /** |
| 333 * Returns additional Markdown-formatted documentation for [type], linking it |
| 334 * to the corresponding `dart:html` or `dart:dom` [Type](s). If [type] is not |
| 335 * in `dart:html` or `dart:dom`, returns no additional documentation. |
| 336 */ |
| 337 String getTypeDoc(Type type) { |
| 338 final htmlTypes = _diff.domTypesToHtml[type]; |
| 339 if ((htmlTypes != null) && (htmlTypes.length > 0)) { |
| 340 var htmlTypesText = doc.joinWithCommas(map(htmlTypes, typeReference)); |
| 341 return |
| 342 ''' |
| 343 <p class="correspond">This corresponds to $htmlTypesText in the |
| 344 ${a("html.html", "dart:html")} library.</p> |
| 345 '''; |
| 346 } |
| 347 |
| 348 final domTypes = _diff.htmlTypesToDom[type]; |
| 349 if ((domTypes != null) && (domTypes.length > 0)) { |
| 350 var domTypesText = doc.joinWithCommas(map(domTypes, typeReference)); |
| 351 return |
| 352 ''' |
| 353 <p class="correspond">This corresponds to $domTypesText in the |
| 354 ${a("dom.html", "dart:dom")} library.</p> |
| 355 '''; |
| 356 } |
| 357 |
| 358 return ''; |
91 } | 359 } |
92 } | 360 } |
93 | |
94 /** | |
95 * Returns a Markdown-formatted link to [member], relative to a type page that | |
96 * may be in a different library than [member]. | |
97 */ | |
98 String _linkMember(Member member) { | |
99 final typeName = member.declaringType.name; | |
100 var memberName = "$typeName.${member.name}"; | |
101 if (member.isConstructor || member.isFactory) { | |
102 final separator = member.constructorName == '' ? '' : '.'; | |
103 memberName = 'new $typeName$separator${member.constructorName}'; | |
104 } else if (member.name.startsWith('get:')) { | |
105 memberName = "$typeName.${member.name.substring(4)}"; | |
106 } | |
107 | |
108 return "[$memberName](../${doc.memberUrl(member)})"; | |
109 } | |
110 | |
111 /** | |
112 * Returns a Markdown-formatted link to [type], relative to a type page that | |
113 * may be in a different library than [type]. | |
114 */ | |
115 String _linkType(Type type) => "[${type.name}](../${doc.typeUrl(type)})"; | |
116 | |
117 /** | |
118 * Unify getters and setters of the same property. We only want to print | |
119 * explicit setters if no getter exists. | |
120 * | |
121 * If [members] contains no setters, returns it unmodified. | |
122 */ | |
123 Set<Member> _unifyProperties(Set<Member> members) { | |
124 // Only print setters if the getter doesn't exist. | |
125 return members.filter((m) { | |
126 if (!m.name.startsWith('set:')) return true; | |
127 var getName = m.name.replaceFirst('set:', 'get:'); | |
128 return !members.some((maybeGet) => maybeGet.name == getName); | |
129 }); | |
130 } | |
131 | |
132 /** | |
133 * Returns additional Markdown-formatted documentation for [member], linking it | |
134 * to the corresponding `dart:html` or `dart:dom` [Member](s). If [member] is | |
135 * not in `dart:html` or `dart:dom`, returns no additional documentation. | |
136 */ | |
137 String getMemberDoc(Member member) { | |
138 if (_diff.domToHtml.containsKey(member)) { | |
139 final htmlMemberSet = _unifyProperties(_diff.domToHtml[member]); | |
140 final allSameName = htmlMemberSet.every((m) => _diff.sameName(member, m)); | |
141 final phrase = allSameName ? "available as" : "renamed to"; | |
142 final htmlMembers = doc.joinWithCommas(map(htmlMemberSet, _linkMember)); | |
143 return "_This is $phrase $htmlMembers in the " + | |
144 "[dart:html](../html.html) library._"; | |
145 } else if (_diff.htmlToDom.containsKey(member)) { | |
146 final domMemberSet = _unifyProperties(_diff.htmlToDom[member]); | |
147 final allSameName = domMemberSet.every((m) => _diff.sameName(m, member)); | |
148 final phrase = allSameName ? "is the same as" : "renames"; | |
149 final domMembers = doc.joinWithCommas(map(domMemberSet, _linkMember)); | |
150 return "_This $phrase $domMembers in the [dart:dom](../dom.html) " + | |
151 "library._"; | |
152 } else { | |
153 return ""; | |
154 } | |
155 } | |
156 | |
157 /** | |
158 * Returns additional Markdown-formatted documentation for [type], linking it to | |
159 * the corresponding `dart:html` or `dart:dom` [Type](s). If [type] is not in | |
160 * `dart:html` or `dart:dom`, returns no additional documentation. | |
161 */ | |
162 String getTypeDoc(Type type) { | |
163 var types = _diff.domTypesToHtml[type]; | |
164 if (types != null && types.length > 0) { | |
165 final text = doc.joinWithCommas(map(types, _linkType)); | |
166 return '_This corresponds to $text in the [dart:html](../html.html) ' + | |
167 'library._'; | |
168 } | |
169 | |
170 types = _diff.htmlTypesToDom[type]; | |
171 if (types != null && types.length > 0) { | |
172 final text = doc.joinWithCommas(map(types, _linkType)); | |
173 return '_This corresponds to $text in the [dart:dom](../dom.html) ' + | |
174 'library._'; | |
175 } | |
176 | |
177 return ''; | |
178 } | |
OLD | NEW |