| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2014, 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 * Code for displaying the API as HTML. This is used both for generating a |
| 7 * full description of the API as a web page, and for generating doc comments |
| 8 * in generated code. |
| 9 */ |
| 10 library toHtml; |
| 11 |
| 12 import 'dart:convert'; |
| 13 import 'dart:io'; |
| 14 |
| 15 import 'package:html5lib/dom.dart' as dom; |
| 16 |
| 17 import 'api.dart'; |
| 18 import 'codegen_tools.dart'; |
| 19 import 'from_html.dart'; |
| 20 import 'html_tools.dart'; |
| 21 |
| 22 /** |
| 23 * Embedded stylesheet |
| 24 */ |
| 25 final String stylesheet = |
| 26 ''' |
| 27 h1 { |
| 28 text-align: center; |
| 29 } |
| 30 pre { |
| 31 margin: 0px; |
| 32 } |
| 33 div.box { |
| 34 border: 1px solid rgb(0, 0, 0); |
| 35 background-color: rgb(207, 226, 243); |
| 36 padding: 0.5em; |
| 37 } |
| 38 dt { |
| 39 margin-top: 1em; |
| 40 margin-bottom: 1em; |
| 41 } |
| 42 '''.trim( |
| 43 ); |
| 44 |
| 45 /** |
| 46 * Helper methods for creating HTML elements. |
| 47 */ |
| 48 abstract class HtmlMixin { |
| 49 void element(String name, Map<dynamic, String> attributes, [void callback()]); |
| 50 |
| 51 void anchor(String id, void callback()) { |
| 52 element('a', { |
| 53 'name': id |
| 54 }, callback); |
| 55 } |
| 56 void link(String id, void callback()) { |
| 57 element('a', { |
| 58 'href': '#$id' |
| 59 }, callback); |
| 60 } |
| 61 void b(void callback()) => element('b', {}, callback); |
| 62 void box(void callback()) { |
| 63 element('div', { |
| 64 'class': 'box' |
| 65 }, callback); |
| 66 } |
| 67 void br() => element('br', {}); |
| 68 void body(void callback()) => element('body', {}, callback); |
| 69 void dd(void callback()) => element('dd', {}, callback); |
| 70 void dl(void callback()) => element('dl', {}, callback); |
| 71 void dt(String cls, void callback()) => element('dt', { |
| 72 'class': cls |
| 73 }, callback); |
| 74 void gray(void callback()) => element('span', { |
| 75 'style': 'color:#999999' |
| 76 }, callback); |
| 77 void h1(void callback()) => element('h1', {}, callback); |
| 78 void h2(void callback()) => element('h2', {}, callback); |
| 79 void h3(void callback()) => element('h3', {}, callback); |
| 80 void h4(void callback()) => element('h4', {}, callback); |
| 81 void head(void callback()) => element('head', {}, callback); |
| 82 void html(void callback()) => element('html', {}, callback); |
| 83 void i(void callback()) => element('i', {}, callback); |
| 84 void p(void callback()) => element('p', {}, callback); |
| 85 void pre(void callback()) => element('pre', {}, callback); |
| 86 void title(void callback()) => element('title', {}, callback); |
| 87 void tt(void callback()) => element('tt', {}, callback); |
| 88 } |
| 89 |
| 90 /** |
| 91 * Visitor that generates a compact representation of a type, such as: |
| 92 * |
| 93 * { |
| 94 * "id": String |
| 95 * "error": optional Error |
| 96 * "result": { |
| 97 * "version": String |
| 98 * } |
| 99 * } |
| 100 */ |
| 101 class TypeVisitor extends HierarchicalApiVisitor with HtmlMixin, |
| 102 HtmlCodeGenerator { |
| 103 /** |
| 104 * Set of fields which should be shown in boldface, or null if no field |
| 105 * should be shown in boldface. |
| 106 */ |
| 107 final Set<String> fieldsToBold; |
| 108 |
| 109 /** |
| 110 * True if a short description should be generated. In a short description, |
| 111 * objects are shown as simply "object", and enums are shown as "String". |
| 112 */ |
| 113 final bool short; |
| 114 |
| 115 TypeVisitor(Api api, {this.fieldsToBold, this.short: false}) : super(api); |
| 116 |
| 117 @override |
| 118 void visitTypeEnum(TypeEnum typeEnum) { |
| 119 if (short) { |
| 120 write('String'); |
| 121 return; |
| 122 } |
| 123 writeln('enum {'); |
| 124 indent(() { |
| 125 for (TypeEnumValue value in typeEnum.values) { |
| 126 writeln(value.value); |
| 127 } |
| 128 }); |
| 129 write('}'); |
| 130 } |
| 131 |
| 132 @override |
| 133 void visitTypeList(TypeList typeList) { |
| 134 write('List<'); |
| 135 visitTypeDecl(typeList.itemType); |
| 136 write('>'); |
| 137 } |
| 138 |
| 139 @override |
| 140 void visitTypeMap(TypeMap typeMap) { |
| 141 write('Map<'); |
| 142 visitTypeDecl(typeMap.keyType); |
| 143 write(', '); |
| 144 visitTypeDecl(typeMap.valueType); |
| 145 write('>'); |
| 146 } |
| 147 |
| 148 @override |
| 149 void visitTypeObject(TypeObject typeObject) { |
| 150 if (short) { |
| 151 write('object'); |
| 152 return; |
| 153 } |
| 154 writeln('{'); |
| 155 indent(() { |
| 156 for (TypeObjectField field in typeObject.fields) { |
| 157 write('"'); |
| 158 if (fieldsToBold != null && fieldsToBold.contains(field.name)) { |
| 159 b(() { |
| 160 write(field.name); |
| 161 }); |
| 162 } else { |
| 163 write(field.name); |
| 164 } |
| 165 write('": '); |
| 166 if (field.value != null) { |
| 167 write(JSON.encode(field.value)); |
| 168 } else { |
| 169 if (field.optional) { |
| 170 gray(() { |
| 171 write('optional'); |
| 172 }); |
| 173 write(' '); |
| 174 } |
| 175 visitTypeDecl(field.type); |
| 176 } |
| 177 writeln(); |
| 178 } |
| 179 }); |
| 180 write('}'); |
| 181 } |
| 182 |
| 183 @override |
| 184 void visitTypeReference(TypeReference typeReference) { |
| 185 String displayName = typeReference.typeName; |
| 186 if (api.types.containsKey(typeReference.typeName)) { |
| 187 link('type_${typeReference.typeName}', () { |
| 188 write(displayName); |
| 189 }); |
| 190 } else { |
| 191 write(displayName); |
| 192 } |
| 193 } |
| 194 } |
| 195 |
| 196 /** |
| 197 * Visitor that records the mapping from HTML elements to various kinds of API |
| 198 * nodes. |
| 199 */ |
| 200 class ApiMappings extends HierarchicalApiVisitor { |
| 201 ApiMappings(Api api) : super(api); |
| 202 |
| 203 Map<dom.Element, Domain> domains = <dom.Element, Domain> {}; |
| 204 |
| 205 @override |
| 206 void visitDomain(Domain domain) { |
| 207 domains[domain.html] = domain; |
| 208 } |
| 209 } |
| 210 |
| 211 /** |
| 212 * Visitor that generates HTML documentation of the API. |
| 213 */ |
| 214 class ToHtmlVisitor extends HierarchicalApiVisitor with HtmlMixin, HtmlGenerator |
| 215 { |
| 216 /** |
| 217 * Set of types defined in the API. |
| 218 */ |
| 219 Set<String> definedTypes = new Set<String>(); |
| 220 |
| 221 /** |
| 222 * Mappings from HTML elements to API nodes. |
| 223 */ |
| 224 ApiMappings apiMappings; |
| 225 |
| 226 ToHtmlVisitor(Api api) |
| 227 : super(api), |
| 228 apiMappings = new ApiMappings(api) { |
| 229 apiMappings.visitApi(); |
| 230 } |
| 231 |
| 232 @override |
| 233 void visitApi() { |
| 234 definedTypes = api.types.keys.toSet(); |
| 235 |
| 236 html(() { |
| 237 translateHtml(api.html); |
| 238 }); |
| 239 } |
| 240 |
| 241 @override |
| 242 void visitRefactorings(Refactorings refactorings) { |
| 243 translateHtml(refactorings.html); |
| 244 dl(() { |
| 245 super.visitRefactorings(refactorings); |
| 246 }); |
| 247 } |
| 248 |
| 249 @override visitRefactoring(Refactoring refactoring) { |
| 250 dt('refactoring', () { |
| 251 write(refactoring.kind); |
| 252 }); |
| 253 dd(() { |
| 254 translateHtml(refactoring.html); |
| 255 describePayload(refactoring.feedback, 'Feedback', force: true); |
| 256 describePayload(refactoring.options, 'Options', force: true); |
| 257 }); |
| 258 } |
| 259 |
| 260 @override |
| 261 void visitTypes(Types types) { |
| 262 translateHtml(types.html); |
| 263 dl(() { |
| 264 super.visitTypes(types); |
| 265 }); |
| 266 } |
| 267 |
| 268 @override |
| 269 void visitDomain(Domain domain) { |
| 270 h2(() { |
| 271 anchor('domain_${domain.name}', () { |
| 272 write('Domain: ${domain.name}'); |
| 273 }); |
| 274 }); |
| 275 translateHtml(domain.html); |
| 276 if (domain.requests.isNotEmpty) { |
| 277 h3(() { |
| 278 write('Requests'); |
| 279 }); |
| 280 dl(() { |
| 281 domain.requests.forEach(visitRequest); |
| 282 }); |
| 283 } |
| 284 if (domain.notifications.isNotEmpty) { |
| 285 h3(() { |
| 286 write('Notifications'); |
| 287 }); |
| 288 dl(() { |
| 289 domain.notifications.forEach(visitNotification); |
| 290 }); |
| 291 } |
| 292 } |
| 293 |
| 294 @override |
| 295 void visitNotification(Notification notification) { |
| 296 dt('notification', () { |
| 297 write(notification.longEvent); |
| 298 }); |
| 299 dd(() { |
| 300 box(() { |
| 301 showType('notification', notification.notificationType, |
| 302 notification.params); |
| 303 }); |
| 304 translateHtml(notification.html); |
| 305 describePayload(notification.params, 'Parameters'); |
| 306 }); |
| 307 } |
| 308 |
| 309 /** |
| 310 * Copy the contents of the given HTML element, translating the special |
| 311 * elements that define the API appropriately. |
| 312 */ |
| 313 void translateHtml(dom.Element html) { |
| 314 for (dom.Node node in html.nodes) { |
| 315 if (node is dom.Element) { |
| 316 switch (node.localName) { |
| 317 case 'api': |
| 318 translateHtml(node); |
| 319 break; |
| 320 case 'domain': |
| 321 visitDomain(apiMappings.domains[node]); |
| 322 break; |
| 323 case 'head': |
| 324 head(() { |
| 325 translateHtml(node); |
| 326 element('style', {}, () { |
| 327 writeln(stylesheet); |
| 328 }); |
| 329 }); |
| 330 break; |
| 331 case 'refactorings': |
| 332 visitRefactorings(api.refactorings); |
| 333 break; |
| 334 case 'types': |
| 335 visitTypes(api.types); |
| 336 break; |
| 337 case 'version': |
| 338 translateHtml(node); |
| 339 break; |
| 340 default: |
| 341 if (!specialElements.contains(node.localName)) { |
| 342 element(node.localName, node.attributes, () { |
| 343 translateHtml(node); |
| 344 }); |
| 345 } |
| 346 } |
| 347 } else if (node is dom.Text) { |
| 348 String text = node.text; |
| 349 write(text); |
| 350 } |
| 351 } |
| 352 } |
| 353 |
| 354 /** |
| 355 * Generate a description of [type] using [TypeVisitor]. |
| 356 * |
| 357 * If [shortDesc] is non-null, the output is prefixed with this string |
| 358 * and a colon. |
| 359 * |
| 360 * If [typeForBolding] is supplied, then fields in this type are shown in |
| 361 * boldface. |
| 362 */ |
| 363 void showType(String shortDesc, TypeDecl type, [TypeObject typeForBolding]) { |
| 364 Set<String> fieldsToBold = new Set<String>(); |
| 365 if (typeForBolding != null) { |
| 366 for (TypeObjectField field in typeForBolding.fields) { |
| 367 fieldsToBold.add(field.name); |
| 368 } |
| 369 } |
| 370 pre(() { |
| 371 if (shortDesc != null) { |
| 372 write('$shortDesc: '); |
| 373 } |
| 374 TypeVisitor typeVisitor = new TypeVisitor(api, fieldsToBold: fieldsToBold |
| 375 ); |
| 376 addAll(typeVisitor.collectHtml(() { |
| 377 typeVisitor.visitTypeDecl(type); |
| 378 })); |
| 379 }); |
| 380 } |
| 381 |
| 382 /** |
| 383 * Describe the payload of request, response, notification, refactoring |
| 384 * feedback, or refactoring options. |
| 385 * |
| 386 * If [force] is true, then a section is inserted even if the payload is |
| 387 * null. |
| 388 */ |
| 389 void describePayload(TypeObject subType, String name, {bool force: false}) { |
| 390 if (force || subType != null) { |
| 391 h4(() { |
| 392 write(name); |
| 393 }); |
| 394 if (subType == null) { |
| 395 p(() { |
| 396 write('none'); |
| 397 }); |
| 398 } else { |
| 399 visitTypeDecl(subType); |
| 400 } |
| 401 } |
| 402 } |
| 403 |
| 404 @override |
| 405 void visitRequest(Request request) { |
| 406 dt('request', () { |
| 407 write(request.longMethod); |
| 408 }); |
| 409 dd(() { |
| 410 box(() { |
| 411 showType('request', request.requestType, request.params); |
| 412 br(); |
| 413 showType('response', request.responseType, request.result); |
| 414 }); |
| 415 translateHtml(request.html); |
| 416 describePayload(request.params, 'Parameters'); |
| 417 describePayload(request.result, 'Returns'); |
| 418 }); |
| 419 } |
| 420 |
| 421 @override |
| 422 void visitTypeDefinition(TypeDefinition typeDefinition) { |
| 423 dt('typeDefinition', () { |
| 424 anchor('type_${typeDefinition.name}', () { |
| 425 write('${typeDefinition.name}: '); |
| 426 TypeVisitor typeVisitor = new TypeVisitor(api, short: true); |
| 427 addAll(typeVisitor.collectHtml(() { |
| 428 typeVisitor.visitTypeDecl(typeDefinition.type); |
| 429 })); |
| 430 }); |
| 431 }); |
| 432 dd(() { |
| 433 translateHtml(typeDefinition.html); |
| 434 visitTypeDecl(typeDefinition.type); |
| 435 }); |
| 436 } |
| 437 |
| 438 @override |
| 439 void visitTypeEnum(TypeEnum typeEnum) { |
| 440 dl(() { |
| 441 super.visitTypeEnum(typeEnum); |
| 442 }); |
| 443 } |
| 444 |
| 445 @override |
| 446 void visitTypeEnumValue(TypeEnumValue typeEnumValue) { |
| 447 bool isDocumented = false; |
| 448 for (dom.Node node in typeEnumValue.html.nodes) { |
| 449 if ((node is dom.Element && node.localName != 'code') || (node is dom.Text |
| 450 && node.text.trim().isNotEmpty)) { |
| 451 isDocumented = true; |
| 452 break; |
| 453 } |
| 454 } |
| 455 dt('value', () { |
| 456 write(typeEnumValue.value); |
| 457 }); |
| 458 if (isDocumented) { |
| 459 dd(() { |
| 460 translateHtml(typeEnumValue.html); |
| 461 }); |
| 462 } |
| 463 } |
| 464 |
| 465 @override |
| 466 void visitTypeList(TypeList typeList) { |
| 467 visitTypeDecl(typeList.itemType); |
| 468 } |
| 469 |
| 470 @override |
| 471 void visitTypeMap(TypeMap typeMap) { |
| 472 visitTypeDecl(typeMap.valueType); |
| 473 } |
| 474 |
| 475 @override |
| 476 void visitTypeObject(TypeObject typeObject) { |
| 477 dl(() { |
| 478 super.visitTypeObject(typeObject); |
| 479 }); |
| 480 } |
| 481 |
| 482 @override |
| 483 void visitTypeObjectField(TypeObjectField typeObjectField) { |
| 484 dt('field', () { |
| 485 b(() { |
| 486 i(() { |
| 487 write(typeObjectField.name); |
| 488 if (typeObjectField.value != null) { |
| 489 write(' = ${typeObjectField.value}'); |
| 490 } else { |
| 491 write(' ( '); |
| 492 if (typeObjectField.optional) { |
| 493 gray(() { |
| 494 write('optional'); |
| 495 }); |
| 496 write(' '); |
| 497 } |
| 498 TypeVisitor typeVisitor = new TypeVisitor(api, short: true); |
| 499 addAll(typeVisitor.collectHtml(() { |
| 500 typeVisitor.visitTypeDecl(typeObjectField.type); |
| 501 })); |
| 502 write(' )'); |
| 503 } |
| 504 }); |
| 505 }); |
| 506 }); |
| 507 dd(() { |
| 508 translateHtml(typeObjectField.html); |
| 509 }); |
| 510 } |
| 511 |
| 512 @override |
| 513 void visitTypeReference(TypeReference typeReference) { |
| 514 } |
| 515 } |
| 516 |
| 517 /** |
| 518 * Translate spec_input.html into api.html. |
| 519 */ |
| 520 main() { |
| 521 ToHtmlVisitor visitor = new ToHtmlVisitor(readApi()); |
| 522 dom.Document document = new dom.Document(); |
| 523 for (dom.Node node in visitor.collectHtml(visitor.visitApi)) { |
| 524 document.append(node); |
| 525 } |
| 526 File outputFile = new File('api.html'); |
| 527 outputFile.writeAsStringSync(document.outerHtml); |
| 528 } |
| OLD | NEW |