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