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