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