1 #!/usr/bin/env python 2 3 """ 4 View annotated sources. 5 6 Copyright (C) 2006, 2007, 2010, 2011, 2012 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from micropython.common import * 23 from micropython.data import * 24 from os.path import exists, extsep, join 25 import sys 26 import os 27 import textwrap 28 29 # Classes. 30 31 # HTML-related output production. 32 33 html_header = """<?xml version="1.0" encoding="iso-8859-15"?> 34 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 35 <html xmlns="http://www.w3.org/1999/xhtml"> 36 <head> 37 <title>%(title)s</title> 38 <style type="text/css"> 39 html { 40 background-color: black; color: white; 41 } 42 43 body { 44 padding-bottom: 4em; 45 font-size: 14pt; font-family: monospace; 46 background-color: black; color: white; 47 margin-top: 2em; 48 } 49 50 a { 51 text-decoration: none; 52 } 53 54 .nowrap { white-space: nowrap; } 55 .label { font-size: smaller; } 56 57 .class { margin-top: 1em; margin-bottom: 1em; } 58 .function { margin-top: 1em; margin-bottom: 1em; } 59 .body { padding-left: 2em; } 60 .for, .if, .tryexcept, .tryfinally, .while { margin-bottom: 1em; } 61 .keyword { color: yellow; } 62 .comment { color: blue; } 63 .class-name { color: cyan; } 64 .function-name { color: cyan; } 65 .specific-ref { color: #07F; } 66 .str { color: #FF00FF; } 67 .doc { color: #FF00FF; margin-top: 1em; margin-bottom: 1em; } 68 69 .popup { 70 display: none; 71 position: absolute; 72 top: 3ex; left: 0; 73 color: white; 74 border-bottom: 0.5ex solid #000; 75 border-right: 0.5ex solid #000; 76 z-index: 3; 77 } 78 79 .types-popup { 80 display: none; 81 position: absolute; 82 bottom: 3ex; left: 0; 83 color: white; 84 border-bottom: 0.5ex solid #000; 85 border-right: 0.5ex solid #000; 86 z-index: 3; 87 } 88 89 .no-targets { 90 background-color: #d00; 91 color: white; 92 } 93 94 .accessor, 95 .name, 96 .operation { 97 position: relative; 98 background-color: #300; 99 color: white; 100 } 101 102 .accessor:hover, 103 .name:hover, 104 .operation:hover { 105 background-color: #500; 106 padding-top: 0.5ex; 107 padding-bottom: 0.5ex; 108 z-index: 2; 109 } 110 111 .accessor:hover .types-popup, 112 .name:hover .popup, 113 .operation:hover .popup { 114 display: block; 115 } 116 117 .attrnames, 118 .opnames, 119 .scope, 120 .typenames { 121 padding: 0.5em; 122 background-color: #700; 123 } 124 125 .opnames a { 126 color: white; 127 } 128 129 .summary-class { 130 vertical-align: top; 131 } 132 133 th.summary-class { 134 font-weight: normal; 135 } 136 137 .summary-attr { 138 background-color: #070; 139 } 140 141 .summary-interface, 142 .summary-attr { 143 font-size: smaller; 144 } 145 146 .summary-interface.complete { 147 background-color: #050; 148 } 149 150 .summary-interface.partial { 151 background-color: #005; 152 } 153 154 .summary-attr-absent { 155 border-left: 0.2em solid #070; 156 font-size: small; 157 } 158 159 .summary-class-attr { 160 background-color: #007; 161 font-size: smaller; 162 } 163 164 .summary-class-attr-absent { 165 border-left: 0.2em solid #007; 166 font-size: small; 167 } 168 169 .summary-ref { 170 color: white; 171 } 172 173 </style> 174 </head> 175 <body> 176 """ 177 178 html_footer = """</body> 179 </html> 180 """ 181 182 # Utility classes. 183 184 class Writer: 185 186 "A utility class providing useful HTML output methods." 187 188 # Methods which return strings. 189 190 def _text(self, text): 191 return text.replace("&", "&").replace("<", "<").replace(">", ">") 192 193 def _attr(self, attr): 194 return self._text(attr).replace("'", "'").replace('"', """) 195 196 def _url(self, url): 197 return self._attr(url).replace("#", "%23").replace("-", "%2d") 198 199 # Methods which write to the stream. 200 201 def _span_start(self, classes=None): 202 self.stream.write("<span class='%s'>" % (classes or "")) 203 204 def _span_end(self): 205 self.stream.write("</span>") 206 207 def _span(self, value, classes=None): 208 self._span_start(classes) 209 self.stream.write(self._text(value)) 210 self._span_end() 211 212 def _name_start(self, classes=None): 213 self._span_start(classes or "name") 214 215 _name_end = _span_end 216 217 def _name(self, name, classes=None): 218 self._name_start(classes) 219 self.stream.write(self._text(name)) 220 self._name_end() 221 222 def _popup_start(self, classes=None): 223 self._span_start(classes or "popup") 224 225 _popup_end = _span_end 226 227 def _comment(self, comment): 228 self._span("# %s" % comment, "comment") 229 self.stream.write("\n") 230 231 def _reserved(self, token, classes, leading=0, trailing=1): 232 if leading: 233 self.stream.write(" ") 234 self._span(token, classes) 235 if trailing: 236 self.stream.write(" ") 237 238 def _keyword(self, kw, leading=0, trailing=1): 239 self._reserved(kw, "keyword", leading, trailing) 240 241 def _doc(self, node): 242 if node.doc is not None: 243 self._docstring(node.doc) 244 245 def _docstring(self, s): 246 self.stream.write("<pre class='doc'>") 247 self.stream.write('"""') 248 output = textwrap.dedent(s.replace('"""', '\\"\\"\\"')) 249 self.stream.write(self._text(output)) 250 self.stream.write('"""') 251 self.stream.write("</pre>\n") 252 253 def _object_name_def(self, module, obj, classes=None): 254 255 """ 256 Link to the summary for 'module' using 'obj'. The optional 'classes' 257 can be used to customise the CSS classes employed. 258 """ 259 260 if isinstance(obj, Class) or (isinstance(obj, Function) and obj.is_method()): 261 self._summary_link(module.full_name(), obj.full_name(), obj.name, classes) 262 else: 263 self._span(obj.name, classes) 264 265 def _object_name_ref(self, module, obj, name=None, classes=None): 266 267 """ 268 Link to the definition for 'module' using 'obj' with the optional 'name' 269 used as the label (instead of the name of 'obj'). The optional 'classes' 270 can be used to customise the CSS classes employed. 271 """ 272 273 self._name_link(module.full_name(), obj.full_name(), name or obj.name, classes) 274 275 def _summary_link(self, module_name, full_name, name, classes=None): 276 self._name_link("%s-summary" % module_name, full_name, name, classes) 277 278 def _name_link(self, module_name, full_name, name, classes=None): 279 self.stream.write("<a class='%s' href='%s%sxhtml#%s'>%s</a>" % ( 280 classes or "specific-ref", module_name, os.path.extsep, 281 self._attr(full_name), self._text(name))) 282 283 def _scope(self, scope): 284 self.stream.write("<div class='scope'><span class='label'>scope</span><br />%s</div>\n" % scope) 285 286 def _assname(self, name, node): 287 self._span_start("assname") 288 if not self._attrcombined(name, node): 289 self._span(name) 290 self._span_end() 291 292 def _op(self, symbol, name=None, leading=0, trailing=1): 293 if leading: 294 self.stream.write(" ") 295 self._span_start(name and "operation" or None) 296 self._span(symbol, "operator") 297 if name is not None: 298 self._popup_start() 299 self.stream.write("<div class='opnames'>") 300 self._name_link("operator", "operator.%s" % name, name) 301 self.stream.write("</div>\n") 302 self._popup_end() 303 self._span_end() 304 if trailing: 305 self.stream.write(" ") 306 307 def _names_list(self, names, label, classes): 308 if not names: 309 return 310 names = list(names) 311 names.sort() 312 313 self.stream.write("<div class='%s'><span class='label'>%s</span><br />" % (classes, label)) 314 first = 1 315 for name in names: 316 if not first: 317 self.stream.write("<br />") 318 self.stream.write(name) 319 first = 0 320 self.stream.write("</div>\n") 321 322 def _attrcombined(self, name, node): 323 attrcombined = hasattr(node, "_attrcombined") and node._attrcombined.get(name) or [] 324 325 for attrnames in attrcombined: 326 if attrnames: 327 break 328 else: 329 return 0 330 331 self._name_start() 332 self.stream.write(name) 333 self._popup_start() 334 for attrnames in attrcombined: 335 self._attrnames(attrnames) 336 self._popup_end() 337 self._name_end() 338 return 1 339 340 def _attrnames(self, attrnames): 341 self._names_list(attrnames, "attributes", "attrnames") 342 343 def _typenames(self, typenames): 344 self._names_list(typenames, "types", "typenames") 345 346 def _accessor_start(self, target_names): 347 if target_names: 348 self._span_start("accessor") 349 self._popup_start("types-popup") 350 self._typenames(target_names) 351 self._popup_end() 352 353 def _accessor_end(self, target_names): 354 if target_names: 355 self._span_end() 356 357 # Summary classes. 358 359 class Summary(Writer): 360 361 "Summarise classes and attributes in modules." 362 363 def __init__(self, module, program): 364 self.module = module 365 self.program = program 366 367 def to_stream(self, stream): 368 369 "Write the summary to the given 'stream'." 370 371 self.stream = stream 372 self.stream.write(html_header % { 373 "title" : "Module: %s" % self.module.full_name() 374 }) 375 self._write_classes(self.module) 376 self.stream.write(html_footer) 377 378 def _write_classes(self, module): 379 380 all_classes = {} 381 382 for obj in self.module.all_objects: 383 if isinstance(obj, Class): 384 all_classes[obj.name] = obj 385 386 if all_classes: 387 388 all_class_names = all_classes.keys() 389 all_class_names.sort() 390 391 self.stream.write("<table cellspacing='5' cellpadding='5'>\n") 392 self.stream.write("<thead>\n") 393 self.stream.write("<tr>\n") 394 self.stream.write("<th>Classes</th><th>Attributes</th>\n") 395 self.stream.write("</tr>\n") 396 self.stream.write("</thead>\n") 397 398 for name in all_class_names: 399 self._write_class(all_classes[name]) 400 401 self.stream.write("</table>\n") 402 403 def _write_class(self, obj): 404 405 # Write the class... 406 407 self.stream.write("<tbody class='class'>\n") 408 self.stream.write("<tr>\n") 409 self.stream.write("<th class='summary-class' id='%s' rowspan='2'>" % self._attr(obj.full_name())) 410 self._object_name_ref(self.module, obj, classes="class-name") 411 self.stream.write("</th>\n") 412 413 # ...and instance attribute names in order... 414 415 attrs = obj.instance_attributes().values() 416 attrs.sort(cmp=lambda x, y: cmp(x.position, y.position)) 417 418 if attrs: 419 for attr in attrs: 420 self.stream.write("<td class='summary-attr'>%s</td>\n" % self._text(attr.name)) 421 else: 422 self.stream.write("<td class='summary-attr-absent'>None</td>\n") 423 424 self.stream.write("</tr>\n") 425 self.stream.write("<tr>\n") 426 427 # ...and class attribute names in order. 428 429 attrs = obj.class_attributes().values() 430 attrs.sort(cmp=lambda x, y: cmp(x.position, y.position)) 431 432 if attrs: 433 for attr in attrs: 434 if attr.is_strict_constant(): 435 value = attr.get_value() 436 if not isinstance(value, Const): 437 self.stream.write("<td class='summary-class-attr' id='%s'>" % self._attr(value.full_name())) 438 self._object_name_ref(self.module, value, attr.name, classes="summary-ref") 439 self.stream.write("</td>\n") 440 else: 441 self.stream.write("<td class='summary-class-attr'>%s</td>\n" % self._text(attr.name)) 442 else: 443 self.stream.write("<td class='summary-class-attr'>%s</td>\n" % self._text(attr.name)) 444 else: 445 self.stream.write("<td class='summary-class-attr-absent'>None</td>\n") 446 447 self.stream.write("</tr>\n") 448 self.stream.write("</tbody>\n") 449 450 class Interfaces(Writer): 451 452 "Summarise the interfaces used by reading the object table cache." 453 454 def __init__(self, program): 455 self.program = program 456 457 def to_stream(self, stream): 458 459 "Write the summary to the given 'stream'." 460 461 self.stream = stream 462 self.stream.write(html_header % { 463 "title" : "Interfaces" 464 }) 465 self._write_interfaces() 466 self.stream.write(html_footer) 467 468 def _write_interfaces(self): 469 objtable = self.program.get_object_table() 470 all_interfaces = objtable.all_cache.items() 471 any_interfaces = objtable.any_cache.items() 472 all_interfaces.sort() 473 any_interfaces.sort() 474 475 self.stream.write("<table cellspacing='5' cellpadding='5'>\n") 476 self.stream.write("<thead>\n") 477 self.stream.write("<tr>\n") 478 self.stream.write("<th>Complete Interfaces</th>\n") 479 self.stream.write("</tr>\n") 480 self.stream.write("</thead>\n") 481 self._write_interface_type(all_interfaces, "complete") 482 self.stream.write("<thead>\n") 483 self.stream.write("<tr>\n") 484 self.stream.write("<th>Partial Interfaces</th>\n") 485 self.stream.write("</tr>\n") 486 self.stream.write("</thead>\n") 487 self._write_interface_type(any_interfaces, "partial") 488 self.stream.write("</table>\n") 489 490 def _write_interface_type(self, interfaces, classes=""): 491 self.stream.write("<tbody>\n") 492 493 for names, objects in interfaces: 494 if names: 495 names = list(names) 496 names.sort() 497 self.stream.write("<tr>\n") 498 self.stream.write("<td class='summary-interface %s'>%s</td>" % (classes, ", ".join(names))) 499 self.stream.write("</tr>\n") 500 501 self.stream.write("</tbody>\n") 502 503 # Source code classes. 504 505 class AnnotatedSource(ASTVisitor, Writer): 506 507 "A module source code browser." 508 509 def __init__(self, module, program): 510 self.visitor = self 511 self.module = module 512 self.program = program 513 self.objtable = self.program.get_object_table() 514 self.paramtable = self.program.get_parameter_table() 515 516 def to_stream(self, stream): 517 518 "Write the annotated code to the given 'stream'." 519 520 self.stream = stream 521 self.stream.write(html_header % { 522 "title" : "Module: %s" % self.module.full_name() 523 }) 524 self.dispatch(self.module.astnode) 525 self.stream.write(html_footer) 526 527 def visitModule(self, node): 528 self.default(node) 529 530 # Statements. 531 532 def visitAssert(self, node): 533 self.stream.write("<div class='assert nowrap'>\n") 534 self._keyword("assert") 535 self.dispatch(node.test) 536 if node.fail: 537 self.stream.write(", ") 538 self.dispatch(node.fail) 539 self.stream.write("</div>\n") 540 541 def visitAssign(self, node): 542 self.stream.write("<div class='assign nowrap'>\n") 543 for lvalue in node.nodes: 544 self.dispatch(lvalue) 545 self.stream.write(" = ") 546 self.dispatch(node.expr) 547 self.stream.write("</div>\n") 548 549 def visitAugAssign(self, node): 550 self.stream.write("<div class='augassign nowrap'>\n") 551 self.dispatch(node.node) 552 self._op(node.op, operator_functions[node.op], 1) 553 self.dispatch(node.expr) 554 self.stream.write("</div>\n") 555 556 def visitBreak(self, node): 557 self.stream.write("<div class='break nowrap'>\n") 558 self._keyword("break") 559 self.stream.write("</div>\n") 560 561 def visitClass(self, node): 562 if not used_by_unit(node): 563 self._docstring('"Class %s not generated."' % node.name) 564 return 565 566 # Use inspected details where possible. 567 568 if hasattr(node, "unit"): 569 cls = node.unit 570 bases = cls.bases 571 self.stream.write("<div class='class nowrap' id='%s'>\n" % cls.full_name()) 572 else: 573 print >>sys.stderr, "Warning: class %s not recognised!" % node.name 574 return 575 576 # Write the declaration line. 577 578 self.stream.write("<div>\n") 579 self._keyword("class") 580 self._object_name_def(self.module, cls, "class-name") 581 582 # Suppress the "object" class appearing alone. 583 584 if bases and not (len(bases) == 1 and bases[0].name == "object"): 585 self.stream.write("(") 586 first = 1 587 for base in bases: 588 if not first: 589 self.stream.write(", ") 590 591 self._object_name_ref(base.module, base) 592 593 first = 0 594 self.stream.write(")") 595 596 self.stream.write(":\n") 597 self.stream.write("</div>\n") 598 599 # Write the docstring and class body. 600 601 self.stream.write("<div class='body nowrap'>\n") 602 self._doc(node) 603 604 # NOTE: Some streams may not support tell. 605 606 x = self.stream.tell() 607 608 self.default(node.code) 609 610 # Check for no output. 611 612 if x == self.stream.tell(): 613 self.visitPass(None) 614 615 self.stream.write("</div>\n") 616 self.stream.write("</div>\n") 617 618 def visitContinue(self, node): 619 self.stream.write("<div class='continue nowrap'>\n") 620 self._keyword("continue") 621 self.stream.write("</div>\n") 622 623 def visitDiscard(self, node): 624 self.stream.write("<div class='discard nowrap'>\n") 625 self.default(node) 626 self.stream.write("</div>\n") 627 628 def visitFor(self, node): 629 self.stream.write("<div class='if nowrap'>\n") 630 self.stream.write("<div>\n") 631 self._keyword("for") 632 self.dispatch(node.assign) 633 self._keyword("in", 1) 634 self.dispatch(node.list) 635 self.stream.write(":\n") 636 self.stream.write("</div>\n") 637 self.stream.write("<div class='body nowrap'>\n") 638 self.dispatch(node.body) 639 self.stream.write("</div>\n") 640 if node.else_ is not None: 641 self.stream.write("<div>\n") 642 self._keyword("else", trailing=0) 643 self.stream.write(":\n") 644 self.stream.write("</div>\n") 645 self.stream.write("<div class='body nowrap'>\n") 646 self.dispatch(node.else_) 647 self.stream.write("</div>\n") 648 self.stream.write("</div>\n") 649 650 def visitFrom(self, node): 651 self.stream.write("<div class='from nowrap'>\n") 652 self._keyword("from") 653 self._name(node.modname) 654 self._keyword("import", 1) 655 first = 1 656 for name, alias in node.names: 657 if not first: 658 self.stream.write(", ") 659 if alias: 660 self.stream.write(name + " ") 661 self._keyword("as", 1) 662 self._name(alias or name) 663 first = 0 664 self.stream.write("</div>\n") 665 666 def visitFunction(self, node): 667 if not used_by_unit(node): 668 self._docstring('"Function %s not generated."' % node.name) 669 return 670 671 if hasattr(node, "unit"): 672 fn = node.unit 673 self.stream.write("<div class='function nowrap' id='%s'>\n" % fn.full_name()) 674 else: 675 print >>sys.stderr, "Warning: function %s not recognised!" % node.name 676 return 677 678 # Write the declaration line. 679 680 self.stream.write("<div>\n") 681 self._keyword("def") 682 self._object_name_def(self.module, fn, "function-name") 683 684 self.stream.write("(") 685 self._parameters(fn, node) 686 self.stream.write(")") 687 self.stream.write(":\n") 688 self.stream.write("</div>\n") 689 690 self.stream.write("<div class='body nowrap'>\n") 691 self._doc(node) 692 self.dispatch(node.code) 693 self.stream.write("</div>\n") 694 self.stream.write("</div>\n") 695 696 def visitGlobal(self, node): 697 self.stream.write("<div class='global nowrap'>\n") 698 self._keyword("global") 699 first = 1 700 for name in node.names: 701 if not first: 702 self.stream.write(", ") 703 self.stream.write(name) 704 first = 0 705 self.stream.write("</div>\n") 706 707 def visitIf(self, node): 708 self.stream.write("<div class='if nowrap'>\n") 709 first = 1 710 for compare, stmt in node.tests: 711 self.stream.write("<div>\n") 712 if first: 713 self._keyword("if") 714 else: 715 self._keyword("elif") 716 self.dispatch(compare) 717 self.stream.write(":\n") 718 self.stream.write("</div>\n") 719 self.stream.write("<div class='body nowrap'>\n") 720 self.dispatch(stmt) 721 self.stream.write("</div>\n") 722 first = 0 723 if node.else_ is not None: 724 self.stream.write("<div>\n") 725 self._keyword("else", trailing=0) 726 self.stream.write(":\n") 727 self.stream.write("</div>\n") 728 self.stream.write("<div class='body nowrap'>\n") 729 self.dispatch(node.else_) 730 self.stream.write("</div>\n") 731 self.stream.write("</div>\n") 732 733 def visitImport(self, node): 734 self.stream.write("<div class='import nowrap'>\n") 735 self._keyword("import") 736 first = 1 737 for name, alias in node.names: 738 if not first: 739 self.stream.write(",\n") 740 if alias: 741 self.stream.write(name + " ") 742 self._keyword("as", 1) 743 self._name(alias or name) 744 first = 0 745 self.stream.write("</div>\n") 746 747 def visitPass(self, node): 748 self.stream.write("<div class='pass nowrap'>\n") 749 self._keyword("pass") 750 self.stream.write("</div>\n") 751 752 def visitPrint(self, node): 753 self.stream.write("<div class='print nowrap'>\n") 754 self._keyword("print") 755 if node.dest is not None: 756 self.stream.write(">>\n") 757 self.dispatch(node.dest) 758 self.stream.write(",\n") 759 for n in node.nodes: 760 self.dispatch(n) 761 self.stream.write(",\n") 762 self.stream.write("</div>\n") 763 764 def visitPrintnl(self, node): 765 self.stream.write("<div class='printnl nowrap'>\n") 766 self._keyword("print") 767 if node.dest is not None: 768 self.stream.write(">>\n") 769 self.dispatch(node.dest) 770 first = 0 771 else: 772 first = 1 773 for n in node.nodes: 774 if not first: 775 self.stream.write(",\n") 776 self.dispatch(n) 777 first = 0 778 self.stream.write("</div>\n") 779 780 def visitRaise(self, node): 781 self.stream.write("<div class='raise nowrap'>\n") 782 self._keyword("raise") 783 if node.expr1 is not None: 784 self.dispatch(node.expr1) 785 if node.expr2 is not None: 786 self.stream.write(",\n") 787 self.dispatch(node.expr2) 788 if node.expr3 is not None: 789 self.stream.write(",\n") 790 self.dispatch(node.expr3) 791 self.stream.write("</div>\n") 792 793 def visitReturn(self, node): 794 self.stream.write("<div class='return nowrap'>\n") 795 self._keyword("return") 796 self.dispatch(node.value) 797 self.stream.write("</div>\n") 798 799 def visitStmt(self, node): 800 self.stream.write("<div class='stmt nowrap'>\n") 801 self.default(node) 802 self.stream.write("</div>\n") 803 804 def visitTryExcept(self, node): 805 self.stream.write("<div class='tryexcept nowrap'>\n") 806 self.stream.write("<div>\n") 807 self._keyword("try", trailing=0) 808 self.stream.write(":\n") 809 self.stream.write("</div>\n") 810 self.stream.write("<div class='body nowrap'>\n") 811 self.dispatch(node.body) 812 self.stream.write("</div>\n") 813 for spec, assign, statement in node.handlers: 814 self.stream.write("<div>\n") 815 self._keyword("except") 816 if spec is not None: 817 self.dispatch(spec) 818 if assign is not None: 819 self.stream.write(",\n") 820 self.dispatch(assign) 821 self.stream.write(":\n") 822 self.stream.write("</div>\n") 823 self.stream.write("<div class='body nowrap'>\n") 824 self.dispatch(statement) 825 self.stream.write("</div>\n") 826 if node.else_ is not None: 827 self.stream.write("<div>\n") 828 self._keyword("else", trailing=0) 829 self.stream.write(":\n") 830 self.stream.write("</div>\n") 831 self.stream.write("<div class='body nowrap'>\n") 832 self.dispatch(node.else_) 833 self.stream.write("</div>\n") 834 self.stream.write("</div>\n") 835 836 def visitTryFinally(self, node): 837 self.stream.write("<div class='tryfinally nowrap'>\n") 838 self.stream.write("<div>\n") 839 self._keyword("try", trailing=0) 840 self.stream.write(":\n") 841 self.stream.write("</div>\n") 842 self.stream.write("<div class='body nowrap'>\n") 843 self.dispatch(node.body) 844 self.stream.write("</div>\n") 845 self.stream.write("<div>\n") 846 self._keyword("finally", trailing=0) 847 self.stream.write(":\n") 848 self.stream.write("</div>\n") 849 self.stream.write("<div class='body nowrap'>\n") 850 self.dispatch(node.final) 851 self.stream.write("</div>\n") 852 self.stream.write("</div>\n") 853 854 def visitWhile(self, node): 855 self.stream.write("<div class='while nowrap'>\n") 856 self.stream.write("<div>\n") 857 self._keyword("while") 858 self.dispatch(node.test) 859 self.stream.write(":\n") 860 self.stream.write("</div>\n") 861 self.stream.write("<div class='body nowrap'>\n") 862 self.dispatch(node.body) 863 self.stream.write("</div>\n") 864 if node.else_ is not None: 865 self.stream.write("<div>\n") 866 self._keyword("else", trailing=0) 867 self.stream.write(":\n") 868 self.stream.write("</div>\n") 869 self.stream.write("<div class='body nowrap'>\n") 870 self.dispatch(node.else_) 871 self.stream.write("</div>\n") 872 self.stream.write("</div>\n") 873 874 def visitYield(self, node): 875 self.stream.write("<div class='yield nowrap'>\n") 876 self._keyword("yield") 877 self.dispatch(node.value) 878 self.stream.write("</div>\n") 879 880 # Expression-related helper methods. 881 882 def _visitBitBinary(self, node, name, symbol): 883 self._span_start(name) 884 first = 1 885 for node in node.nodes: 886 if not first: 887 self._op(symbol, name, 1) 888 self.dispatch(node) 889 first = 0 890 self._span_end() 891 892 def _visitBinary(self, node, name, symbol): 893 self._span_start(name) 894 self.dispatch(node.left) 895 self._op(symbol, name, 1) 896 self.dispatch(node.right) 897 self._span_end() 898 899 def _visitUnary(self, node, name, symbol): 900 self._span_start(name) 901 self._op(symbol, name) 902 self.dispatch(node.expr) 903 self._span_end() 904 905 # Expressions. 906 907 def visitAdd(self, node): 908 self._visitBinary(node, "add", "+") 909 910 def visitAnd(self, node): 911 self._span_start("and") 912 first = 1 913 for n in node.nodes: 914 if not first: 915 self._keyword("and", 1) 916 self.dispatch(n) 917 first = 0 918 self._span_end() 919 920 def visitAssAttr(self, node): 921 target_names = ["%s%s" % (is_static and "static " or "", target_name) 922 for target_name, is_static in self.possible_accessor_types(node, defining_users=0)] 923 self._span_start("assattr") 924 self._accessor_start(target_names) 925 self.dispatch(node.expr) 926 self._accessor_end(target_names) 927 self._span_start("attr") 928 self.stream.write(".") 929 self._span(node.attrname, "attrname" + (not target_names and " no-targets" or "")) 930 self._span_end() 931 self._span_end() 932 933 def visitAssList(self, node): 934 self._span_start("list") 935 self.stream.write("[") 936 self._sequence(node) 937 self.stream.write("]") 938 self._span_end() 939 940 def visitAssName(self, node): 941 self._assname(node.name, node) 942 943 def visitAssTuple(self, node): 944 self._span_start("tuple") 945 self.stream.write("(") 946 self._sequence(node) 947 self.stream.write(")") 948 self._span_end() 949 950 def visitBitand(self, node): 951 self._visitBitBinary(node, "bitand", "&") 952 953 def visitBitor(self, node): 954 self._visitBitBinary(node, "bitor", "|") 955 956 def visitBitxor(self, node): 957 self._visitBitBinary(node, "bitxor", "^") 958 959 def visitCallFunc(self, node): 960 self._span_start("callfunc") 961 self.dispatch(node.node) 962 self._span_start("call") 963 self.stream.write("(") 964 first = 1 965 for arg in node.args: 966 if not first: 967 self.stream.write(", ") 968 self.dispatch(arg) 969 first = 0 970 if node.star_args is not None: 971 if not first: 972 self.stream.write(", *") 973 self.dispatch(node.star_args) 974 first = 0 975 if node.dstar_args is not None: 976 if not first: 977 self.stream.write(", **") 978 self.dispatch(node.dstar_args) 979 first = 0 980 self.stream.write(")") 981 self._span_end() 982 self._span_end() 983 984 def visitCompare(self, node): 985 self._span_start("compare") 986 self.dispatch(node.expr) 987 for op_name, expr in node.ops: 988 self._op(op_name, operator_functions.get(op_name), 1) 989 self.dispatch(expr) 990 self._span_end() 991 992 def visitConst(self, node): 993 if isinstance(node.value, (str, unicode)): 994 self._span_start("str") 995 self.stream.write(self._text(repr(node.value))) 996 if isinstance(node.value, (str, unicode)): 997 self._span_end() 998 999 def visitDict(self, node): 1000 self._span_start("dict") 1001 self.stream.write("{") 1002 self._mapping(node) 1003 self.stream.write("}") 1004 self._span_end() 1005 1006 def visitDiv(self, node): 1007 self._visitBinary(node, "div", "/") 1008 1009 def visitFloorDiv(self, node): 1010 self._visitBinary(node, "floordiv", "//") 1011 1012 def visitGetattr(self, node): 1013 target_names = ["%s%s" % (is_static and "static " or "", target_name) 1014 for target_name, is_static in self.possible_accessor_types(node, defining_users=0)] 1015 self._span_start("getattr") 1016 self._accessor_start(target_names) 1017 self.dispatch(node.expr) 1018 self._accessor_end(target_names) 1019 self._span_start("attr") 1020 self.stream.write(".") 1021 self._span(node.attrname, "attrname" + (not target_names and " no-targets" or "")) 1022 self._span_end() 1023 self._span_end() 1024 1025 def visitGenExpr(self, node): 1026 self._span_start("genexpr") 1027 self.stream.write("(") 1028 self.dispatch(node.code) 1029 self.stream.write(")") 1030 self._span_end() 1031 1032 def visitGenExprFor(self, node): 1033 self._span_start("genexprfor") 1034 self._keyword("for", 1) 1035 self._span_start("item") 1036 self.dispatch(node.assign) 1037 self._span_end() 1038 self._keyword("in", 1) 1039 self._span_start("collection") 1040 self.dispatch(node.iter) 1041 self._span_end() 1042 for if_ in node.ifs: 1043 self.dispatch(if_) 1044 self._span_end() 1045 1046 def visitGenExprIf(self, node): 1047 self._span_start("genexprif") 1048 self._span_start("conditional") 1049 self._keyword("if", 1) 1050 self.dispatch(node.test) 1051 self._span_end() 1052 self._span_end() 1053 1054 def visitGenExprInner(self, node): 1055 self._span_start("genexprinner") 1056 self.dispatch(node.expr) 1057 for qual in node.quals: 1058 self.dispatch(qual) 1059 self._span_end() 1060 1061 def visitIfExp(self, node): 1062 self._span_start("ifexp") 1063 self.dispatch(node.then) 1064 self._keyword("if", 1) 1065 self.dispatch(node.test) 1066 self._keyword("else", 1) 1067 self.dispatch(node.else_) 1068 self._span_end() 1069 1070 def visitInvert(self, node): 1071 self._visitUnary(node, "invert", "~") 1072 1073 def visitKeyword(self, node): 1074 self._span_start("keyword-arg") 1075 self.stream.write(node.name) 1076 self.stream.write("=") 1077 self.dispatch(node.expr) 1078 self._span_end() 1079 1080 def visitLambda(self, node): 1081 if hasattr(node, "unit"): 1082 fn = node.unit 1083 else: 1084 print >>sys.stderr, "Warning: function %s not recognised!" % node.name 1085 return 1086 1087 self._span_start("lambda") 1088 self._keyword("lambda") 1089 self._parameters(fn, node) 1090 self.stream.write(": ") 1091 self._span_start("code") 1092 self.dispatch(node.code) 1093 self._span_end() 1094 self._span_end() 1095 1096 def visitLeftShift(self, node): 1097 self._visitBinary(node, "lshift", "<<") 1098 1099 visitList = visitAssList 1100 1101 def visitListComp(self, node): 1102 self._span_start("listcomp") 1103 self.stream.write("[") 1104 self.dispatch(node.expr) 1105 for qual in node.quals: 1106 self.dispatch(qual) 1107 self.stream.write("]") 1108 self._span_end() 1109 1110 def visitListCompFor(self, node): 1111 self._span_start("listcompfor") 1112 self._keyword("for", 1) 1113 self._span_start("item") 1114 self.dispatch(node.assign) 1115 self._span_end() 1116 self._keyword("in", 1) 1117 self._span_start("collection") 1118 self.dispatch(node.list) 1119 self._span_end() 1120 for if_ in node.ifs: 1121 self.dispatch(if_) 1122 self._span_end() 1123 1124 def visitListCompIf(self, node): 1125 self._span_start("listcompif") 1126 self._span_start("conditional") 1127 self._keyword("if", 1) 1128 self.dispatch(node.test) 1129 self._span_end() 1130 self._span_end() 1131 1132 def visitMod(self, node): 1133 self._visitBinary(node, "mod", "%") 1134 1135 def visitMul(self, node): 1136 self._visitBinary(node, "mul", "*") 1137 1138 def visitName(self, node): 1139 if hasattr(node, "_scope"): 1140 scope = node._scope 1141 self._name_start() 1142 self.stream.write(node.name) 1143 self._popup_start() 1144 self._scope(scope) 1145 self._popup_end() 1146 self._name_end() 1147 else: 1148 self._span(node.name) 1149 1150 def visitNot(self, node): 1151 self._span_start("not") 1152 self._keyword("not") 1153 self.dispatch(node.expr) 1154 self._span_end() 1155 1156 def visitOr(self, node): 1157 self._span_start("or") 1158 first = 1 1159 for n in node.nodes: 1160 if not first: 1161 self._keyword("or", 1) 1162 self.dispatch(n) 1163 first = 0 1164 self._span_end() 1165 1166 def visitPower(self, node): 1167 self._visitBinary(node, "pow", "**") 1168 1169 def visitRightShift(self, node): 1170 self._visitBinary(node, "rshift", ">>") 1171 1172 def visitSlice(self, node): 1173 self._span_start("slice") 1174 self.dispatch(node.expr) 1175 self.stream.write("[") 1176 if node.lower: 1177 self.dispatch(node.lower) 1178 self.stream.write(":") 1179 if node.upper: 1180 self.dispatch(node.upper) 1181 # NOTE: Step? 1182 self.stream.write("]") 1183 self._span_end() 1184 1185 def visitSliceobj(self, node): 1186 self._span_start("sliceobj") 1187 first = 1 1188 for n in node.nodes: 1189 if not first: 1190 self.stream.write(":") 1191 self.dispatch(n) 1192 self._span_end() 1193 1194 def visitSub(self, node): 1195 self._visitBinary(node, "sub", "-") 1196 1197 def visitSubscript(self, node): 1198 self._span_start("subscript") 1199 self.dispatch(node.expr) 1200 self.stream.write("[") 1201 first = 1 1202 for sub in node.subs: 1203 if not first: 1204 self.stream.write(", ") 1205 self.dispatch(sub) 1206 first = 0 1207 self.stream.write("]") 1208 self._span_end() 1209 1210 visitTuple = visitAssTuple 1211 1212 def visitUnaryAdd(self, node): 1213 self._visitUnary(node, "add", "+") 1214 1215 def visitUnarySub(self, node): 1216 self._visitUnary(node, "sub", "-") 1217 1218 # Output preparation methods. 1219 1220 def _sequence(self, node): 1221 first = 1 1222 for n in node.nodes: 1223 if not first: 1224 self.stream.write(", ") 1225 self.dispatch(n) 1226 first = 0 1227 1228 def _mapping(self, node): 1229 first = 1 1230 for k, v in node.items: 1231 if not first: 1232 self.stream.write(", ") 1233 self.dispatch(k) 1234 self.stream.write(" : ") 1235 self.dispatch(v) 1236 first = 0 1237 1238 def _parameters(self, fn, node): 1239 nparams = len(fn.positional_names) 1240 ndefaults = len(fn.defaults) 1241 first_with_default = nparams - ndefaults 1242 1243 first = 1 1244 for n, param in enumerate(fn.positional_names): 1245 if not first: 1246 self.stream.write(", ") 1247 1248 # Handle tuple parameters. 1249 1250 if isinstance(param, tuple): 1251 self._tuple_parameter(param, node) 1252 else: 1253 self._assname(param, node) 1254 1255 n_default = n - first_with_default 1256 if n_default >= 0: 1257 self._default(fn.defaults[n_default]) 1258 first = 0 1259 1260 if fn.has_star: 1261 if not first: 1262 self.stream.write(", *") 1263 self._name(fn.star_name) 1264 1265 if fn.has_dstar: 1266 if not first: 1267 self.stream.write(", **") 1268 self._name(fn.dstar_name) 1269 1270 def _tuple_parameter(self, parameters, node): 1271 self.stream.write("(") 1272 1273 first = 1 1274 for param in parameters: 1275 if not first: 1276 self.stream.write(", ") 1277 1278 # Handle tuples. 1279 1280 if isinstance(param, tuple): 1281 self._tuple_parameter(param, node) 1282 else: 1283 self._assname(param, node) 1284 1285 first = 0 1286 1287 self.stream.write(")") 1288 1289 def _default(self, default): 1290 self.stream.write("=") 1291 self.dispatch(default) 1292 1293 # Convenience functions. 1294 1295 def summarise(module, program, filename): 1296 stream = open(filename, "wb") 1297 try: 1298 summary = Summary(module, program) 1299 summary.to_stream(stream) 1300 finally: 1301 stream.close() 1302 1303 def annotate(module, program, filename): 1304 stream = open(filename, "wb") 1305 try: 1306 source = AnnotatedSource(module, program) 1307 source.to_stream(stream) 1308 finally: 1309 stream.close() 1310 1311 def interfaces(program, filename): 1312 stream = open(filename, "wb") 1313 try: 1314 source = Interfaces(program) 1315 source.to_stream(stream) 1316 finally: 1317 stream.close() 1318 1319 def report(program, directory): 1320 if not exists(directory): 1321 os.mkdir(directory) 1322 1323 for module in program.get_importer().get_modules(): 1324 annotate(module, program, join(directory, "%s%sxhtml" % (module.full_name(), extsep))) 1325 summarise(module, program, join(directory, "%s-summary%sxhtml" % (module.full_name(), extsep))) 1326 1327 interfaces(program, join(directory, "-interfaces%sxhtml" % extsep)) 1328 1329 # vim: tabstop=4 expandtab shiftwidth=4