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