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.stream.write(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 visitWith(self, node): 1058 self.stream.write("<div class='with nowrap'>\n") 1059 self._keyword("with") 1060 self.dispatch(node.expr) 1061 if node.vars: 1062 self._keyword("as", 1) 1063 first = True 1064 for var in node.vars: 1065 if not first: 1066 self.stream.write(", ") 1067 self._name(var) 1068 first = False 1069 self.stream.write("<div>\n") 1070 self.dispatch(node.body) 1071 self.stream.write("</div>\n") 1072 self.stream.write("</div>\n") 1073 1074 def visitYield(self, node): 1075 self.stream.write("<div class='yield nowrap'>\n") 1076 self._keyword("yield") 1077 self.dispatch(node.value) 1078 self.stream.write("</div>\n") 1079 1080 # Expression-related helper methods. 1081 1082 def _visitBitBinary(self, node, symbol): 1083 name = operator_functions[node.__class__.__name__] 1084 self._span_start(name) 1085 first = True 1086 for node in node.nodes: 1087 if not first: 1088 self._op(symbol, name, 1) 1089 self.dispatch(node) 1090 first = False 1091 self._span_end() 1092 1093 def _visitBinary(self, node, symbol): 1094 name = operator_functions[node.__class__.__name__] 1095 self._span_start(name) 1096 self.dispatch(node.left) 1097 self._op(symbol, name, 1) 1098 self.dispatch(node.right) 1099 self._span_end() 1100 1101 def _visitUnary(self, node, symbol): 1102 name = operator_functions[node.__class__.__name__] 1103 self._span_start(name) 1104 self._op(symbol, name, trailing=0) 1105 self.dispatch(node.expr) 1106 self._span_end() 1107 1108 # Expressions. 1109 1110 def visitAdd(self, node): 1111 self._visitBinary(node, "+") 1112 1113 def visitAnd(self, node): 1114 self._span_start("and") 1115 first = True 1116 for n in node.nodes: 1117 if not first: 1118 self._keyword("and", 1) 1119 self.dispatch(n) 1120 first = False 1121 self._span_end() 1122 1123 def _visitAttr(self, node, label): 1124 self.record_unknown_targets(node) 1125 1126 attributes = node._value_deduced and [self.get_attribute_and_value(node._value_deduced)] or \ 1127 node._attr_deduced and [self.get_attribute_and_value(node._attr_deduced)] or \ 1128 node._attrs_deduced or \ 1129 map(self.get_attribute_and_value, node._attrs_deduced_from_specific_usage or []) 1130 1131 possible_types = self._attributes_to_target_names(attributes) 1132 1133 wraps_getattr = self._has_descendant(node.expr, compiler.ast.Getattr) 1134 1135 if not wraps_getattr: 1136 self._span_start(label) 1137 self._accessor_start(possible_types) 1138 self.dispatch(node.expr) 1139 if not wraps_getattr: 1140 self._accessor_end(possible_types) 1141 1142 self.stream.write(".") 1143 self._attribute_start(node.attrname, self._attributes_to_attribute_names(attributes)) 1144 self._span(node.attrname, "attrname" + (not possible_types and " no-targets" or "")) 1145 self._attribute_end(attributes) 1146 1147 if not wraps_getattr: 1148 self._span_end() 1149 1150 def visitAssAttr(self, node): 1151 if node.flags == "OP_DELETE": 1152 self._keyword("del") 1153 self._visitAttr(node, "assattr") 1154 1155 def visitAssList(self, node): 1156 self._span_start("list") 1157 self.stream.write("[") 1158 self._sequence(node) 1159 self.stream.write("]") 1160 self._span_end() 1161 1162 def visitAssName(self, node): 1163 self._assname(node.name, node) 1164 1165 def visitAssTuple(self, node): 1166 self._span_start("tuple") 1167 self.stream.write("(") 1168 self._sequence(node) 1169 self.stream.write(")") 1170 self._span_end() 1171 1172 def visitBackquote(self, node): 1173 self._span_start("backquote") 1174 self.stream.write("`") 1175 self.dispatch(node.expr) 1176 self.stream.write("`") 1177 self._span_end() 1178 1179 def visitBitand(self, node): 1180 self._visitBitBinary(node, "&") 1181 1182 def visitBitor(self, node): 1183 self._visitBitBinary(node, "|") 1184 1185 def visitBitxor(self, node): 1186 self._visitBitBinary(node, "^") 1187 1188 def visitCallFunc(self, node): 1189 self._span_start("callfunc") 1190 self.dispatch(node.node) 1191 self._span_start("call") 1192 self.stream.write("(") 1193 first = True 1194 for arg in node.args: 1195 if not first: 1196 self.stream.write(", ") 1197 self.dispatch(arg) 1198 first = False 1199 if node.star_args is not None: 1200 if not first: 1201 self.stream.write(", *") 1202 self.dispatch(node.star_args) 1203 first = False 1204 if node.dstar_args is not None: 1205 if not first: 1206 self.stream.write(", **") 1207 self.dispatch(node.dstar_args) 1208 first = False 1209 self.stream.write(")") 1210 self._span_end() 1211 self._span_end() 1212 1213 def visitCompare(self, node): 1214 self._span_start("compare") 1215 self.dispatch(node.expr) 1216 for op_name, expr in node.ops: 1217 self._op(op_name, operator_functions.get(op_name), 1) 1218 self.dispatch(expr) 1219 self._span_end() 1220 1221 def visitConst(self, node): 1222 if isinstance(node.value, (str, unicode)): 1223 self._span_start("str") 1224 self.stream.write(self._text(repr(node.value))) 1225 if isinstance(node.value, (str, unicode)): 1226 self._span_end() 1227 1228 def visitDict(self, node): 1229 self._span_start("dict") 1230 self.stream.write("{") 1231 self._mapping(node) 1232 self.stream.write("}") 1233 self._span_end() 1234 1235 def visitDiv(self, node): 1236 self._visitBinary(node, "/") 1237 1238 def visitFloorDiv(self, node): 1239 self._visitBinary(node, "//") 1240 1241 def visitGetattr(self, node): 1242 self._visitAttr(node, "getattr") 1243 1244 def visitGenExpr(self, node): 1245 self._span_start("genexpr") 1246 self.stream.write("(") 1247 self.dispatch(node.code) 1248 self.stream.write(")") 1249 self._span_end() 1250 1251 def visitGenExprFor(self, node): 1252 self._span_start("genexprfor") 1253 self._keyword("for", 1) 1254 self._span_start("item") 1255 self.dispatch(node.assign) 1256 self._span_end() 1257 self._keyword("in", 1) 1258 self._span_start("collection") 1259 self.dispatch(node.iter) 1260 self._span_end() 1261 for if_ in node.ifs: 1262 self.dispatch(if_) 1263 self._span_end() 1264 1265 def visitGenExprIf(self, node): 1266 self._span_start("genexprif") 1267 self._span_start("conditional") 1268 self._keyword("if", 1) 1269 self.dispatch(node.test) 1270 self._span_end() 1271 self._span_end() 1272 1273 def visitGenExprInner(self, node): 1274 self._span_start("genexprinner") 1275 self.dispatch(node.expr) 1276 for qual in node.quals: 1277 self.dispatch(qual) 1278 self._span_end() 1279 1280 def visitIfExp(self, node): 1281 self._span_start("ifexp") 1282 self.dispatch(node.then) 1283 self._keyword("if", 1) 1284 self.dispatch(node.test) 1285 self._keyword("else", 1) 1286 self.dispatch(node.else_) 1287 self._span_end() 1288 1289 def visitInvert(self, node): 1290 self._visitUnary(node, "~") 1291 1292 def visitKeyword(self, node): 1293 self._span_start("keyword-arg") 1294 self.stream.write(node.name) 1295 self.stream.write("=") 1296 self.dispatch(node.expr) 1297 self._span_end() 1298 1299 def visitLambda(self, node): 1300 fn = node.unit 1301 self.units.append(fn) 1302 1303 self._span_start("lambda") 1304 self._keyword("lambda") 1305 self._parameters(fn, node) 1306 self.stream.write(": ") 1307 self._span_start("code") 1308 self.dispatch(node.code) 1309 self._span_end() 1310 self._span_end() 1311 1312 self.units.pop() 1313 1314 def visitLeftShift(self, node): 1315 self._visitBinary(node, "<<") 1316 1317 visitList = visitAssList 1318 1319 def visitListComp(self, node): 1320 self._span_start("listcomp") 1321 self.stream.write("[") 1322 self.dispatch(node.expr) 1323 for qual in node.quals: 1324 self.dispatch(qual) 1325 self.stream.write("]") 1326 self._span_end() 1327 1328 def visitListCompFor(self, node): 1329 self._span_start("listcompfor") 1330 self._keyword("for", 1) 1331 self._span_start("item") 1332 self.dispatch(node.assign) 1333 self._span_end() 1334 self._keyword("in", 1) 1335 self._span_start("collection") 1336 self.dispatch(node.list) 1337 self._span_end() 1338 for if_ in node.ifs: 1339 self.dispatch(if_) 1340 self._span_end() 1341 1342 def visitListCompIf(self, node): 1343 self._span_start("listcompif") 1344 self._span_start("conditional") 1345 self._keyword("if", 1) 1346 self.dispatch(node.test) 1347 self._span_end() 1348 self._span_end() 1349 1350 def visitMod(self, node): 1351 self._visitBinary(node, "%") 1352 1353 def visitMul(self, node): 1354 self._visitBinary(node, "*") 1355 1356 def visitName(self, node): 1357 if node._scope: 1358 scope = node._scope 1359 self._name_start() 1360 self.stream.write(node.name) 1361 self._popup_start() 1362 self._scope(node._scope, node._attr) 1363 self._popup_end() 1364 self._name_end() 1365 else: 1366 self._span(node.name) 1367 1368 def visitNot(self, node): 1369 self._span_start("not") 1370 self._keyword("not") 1371 self.dispatch(node.expr) 1372 self._span_end() 1373 1374 def visitOr(self, node): 1375 self._span_start("or") 1376 first = True 1377 for n in node.nodes: 1378 if not first: 1379 self._keyword("or", 1) 1380 self.dispatch(n) 1381 first = False 1382 self._span_end() 1383 1384 def visitPower(self, node): 1385 self._visitBinary(node, "**") 1386 1387 def visitRightShift(self, node): 1388 self._visitBinary(node, ">>") 1389 1390 def visitSlice(self, node): 1391 self._span_start("slice") 1392 self.dispatch(node.expr) 1393 self.stream.write("[") 1394 if node.lower: 1395 self.dispatch(node.lower) 1396 self.stream.write(":") 1397 if node.upper: 1398 self.dispatch(node.upper) 1399 # NOTE: Step? 1400 self.stream.write("]") 1401 self._span_end() 1402 1403 def visitSliceobj(self, node): 1404 self._span_start("sliceobj") 1405 first = True 1406 for n in node.nodes: 1407 if not first: 1408 self.stream.write(":") 1409 self.dispatch(n) 1410 self._span_end() 1411 1412 def visitSub(self, node): 1413 self._visitBinary(node, "-") 1414 1415 def visitSubscript(self, node): 1416 self._span_start("subscript") 1417 self.dispatch(node.expr) 1418 self.stream.write("[") 1419 first = True 1420 for sub in node.subs: 1421 if not first: 1422 self.stream.write(", ") 1423 self.dispatch(sub) 1424 first = False 1425 self.stream.write("]") 1426 self._span_end() 1427 1428 visitTuple = visitAssTuple 1429 1430 def visitUnaryAdd(self, node): 1431 self._visitUnary(node, "+") 1432 1433 def visitUnarySub(self, node): 1434 self._visitUnary(node, "-") 1435 1436 # Output preparation methods. 1437 1438 def _sequence(self, node): 1439 first = True 1440 for n in node.nodes: 1441 if not first: 1442 self.stream.write(", ") 1443 self.dispatch(n) 1444 first = False 1445 1446 def _mapping(self, node): 1447 first = True 1448 for k, v in node.items: 1449 if not first: 1450 self.stream.write(", ") 1451 self.dispatch(k) 1452 self.stream.write(" : ") 1453 self.dispatch(v) 1454 first = False 1455 1456 def _parameters(self, fn, node): 1457 nparams = len(fn.positional_names) 1458 ndefaults = len(fn.defaults) 1459 first_with_default = nparams - ndefaults 1460 1461 first = True 1462 for n, param in enumerate(fn.positional_names): 1463 if not first: 1464 self.stream.write(", ") 1465 1466 # Handle tuple parameters. 1467 1468 if isinstance(param, tuple): 1469 self._tuple_parameter(param, node) 1470 else: 1471 self._assname(param, node) 1472 1473 n_default = n - first_with_default 1474 if n_default >= 0: 1475 self._default(fn.defaults[n_default]) 1476 first = False 1477 1478 if fn.has_star: 1479 if not first: 1480 self.stream.write(", *") 1481 self._name(fn.star_name) 1482 1483 if fn.has_dstar: 1484 if not first: 1485 self.stream.write(", **") 1486 self._name(fn.dstar_name) 1487 1488 def _tuple_parameter(self, parameters, node): 1489 self.stream.write("(") 1490 1491 first = True 1492 for param in parameters: 1493 if not first: 1494 self.stream.write(", ") 1495 1496 # Handle tuples. 1497 1498 if isinstance(param, tuple): 1499 self._tuple_parameter(param, node) 1500 else: 1501 self._assname(param, node) 1502 1503 first = False 1504 1505 self.stream.write(")") 1506 1507 def _default(self, default): 1508 self.stream.write("=") 1509 self.dispatch(default) 1510 1511 # Statistics gathering methods. 1512 1513 def record_unknown_targets(self, node): 1514 if not node._attrs_deduced: 1515 self.program.unknown_target_nodes.append((self.units[-1], node)) 1516 elif not node._attrs_deduced_from_specific_usage: 1517 self.program.independent_target_nodes.append((self.units[-1], node)) 1518 1519 # Utility methods. 1520 1521 def _has_descendant(self, node, nodetype): 1522 if isinstance(node, nodetype): 1523 return True 1524 else: 1525 for n in node.getChildNodes(): 1526 if self._has_descendant(n, nodetype): 1527 return True 1528 return False 1529 1530 # Convenience functions. 1531 1532 def summarise(module, program, filename): 1533 stream = open(filename, "wb") 1534 try: 1535 summary = Summary(module, program) 1536 summary.to_stream(stream) 1537 finally: 1538 stream.close() 1539 1540 def annotate(module, program, filename): 1541 stream = open(filename, "wb") 1542 try: 1543 source = AnnotatedSource(module, program) 1544 source.to_stream(stream) 1545 finally: 1546 stream.close() 1547 1548 def interfaces(program, filename): 1549 stream = open(filename, "wb") 1550 try: 1551 source = Interfaces(program) 1552 source.to_stream(stream) 1553 finally: 1554 stream.close() 1555 1556 def report(program, directory): 1557 if not exists(directory): 1558 os.mkdir(directory) 1559 1560 for module in program.get_importer().get_modules(): 1561 annotate(module, program, join(directory, "%s%sxhtml" % (module.full_name(), extsep))) 1562 summarise(module, program, join(directory, "%s-summary%sxhtml" % (module.full_name(), extsep))) 1563 1564 interfaces(program, join(directory, "-interfaces%sxhtml" % extsep)) 1565 1566 # vim: tabstop=4 expandtab shiftwidth=4