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