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