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