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, 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(module_name))) 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 self._span_start("assname") 311 if not self._attrcombined(name, node): 312 self._span(name) 313 self._span_end() 314 315 def _op(self, symbol, name=None, leading=0, trailing=1): 316 if leading: 317 self.stream.write(" ") 318 self._span_start(name and "operation" or None) 319 self._span(symbol, "operator") 320 if name is not None: 321 self._popup_start() 322 self.stream.write("<div class='opnames'>") 323 self._name_link("operator", "operator.%s" % name, name) 324 self.stream.write("</div>\n") 325 self._popup_end() 326 # NOTE: Handle "is" and "in". 327 self._span_end() 328 if trailing: 329 self.stream.write(" ") 330 331 def _names_list_start(self, label, classes): 332 self.stream.write("<div class='%s'><span class='label'>%s</span><br />" % (classes, label)) 333 334 def _names_list_end(self): 335 self.stream.write("</div>\n") 336 337 def _names_list(self, names, label, classes): 338 if not names: 339 return 340 names = list(names) 341 names.sort() 342 343 self._names_list_start(label, classes) 344 first = True 345 for name in names: 346 if not first: 347 self.stream.write("<br />") 348 self.stream.write(self._text(name)) 349 first = False 350 self._names_list_end() 351 352 def _attrcombined(self, name, node): 353 attrcombined = node._attrcombined and node._attrcombined.get(name) or [] 354 355 # Since assigned values will not be shown, produce a sorted list of 356 # distinct attribute name lists. 357 358 all_attrnames = set() 359 for usage in attrcombined: 360 if usage: 361 all_attrnames.add(tuple(usage.keys())) 362 363 if not all_attrnames: 364 return False 365 366 all_attrnames = list(all_attrnames) 367 all_attrnames.sort() 368 369 # Write the lists of attribute names. 370 371 self._name_start() 372 self.stream.write(name) 373 self._popup_start() 374 for attrnames in all_attrnames: 375 self._attrnames(attrnames) 376 self._popup_end() 377 self._name_end() 378 return True 379 380 def _attrnames(self, attrnames): 381 self._names_list(attrnames, "attributes", "attrnames") 382 383 def _typenames(self, typenames): 384 self._names_list(typenames, "types", "typenames") 385 386 def _accessor_start(self, target_names): 387 if target_names: 388 self._span_start("accessor") 389 self._popup_start("types-popup") 390 self._typenames(target_names) 391 self._popup_end() 392 393 def _accessor_end(self, target_names): 394 if target_names: 395 self._span_end() 396 397 def _values_to_attribute_names(self, attr): 398 399 "Get the output form of the values referenced by 'attr'." 400 401 if isinstance(attr, Const): 402 return [(repr(attr.get_value()), attr)] 403 elif isinstance(attr, Instance): 404 return [] 405 406 have_instances = False 407 values = [] 408 for v in attr.get_values(): 409 if isinstance(v, Const): 410 values.append((repr(v.get_value()), v)) 411 elif not isinstance(v, Instance): 412 values.append((v.full_name(), v)) 413 else: 414 have_instances = True 415 416 if have_instances: 417 values.append(("...", None)) 418 419 values.sort() 420 return values 421 422 def _attribute_value_to_name(self, attr, value, target=False): 423 if value and not isinstance(value, Instance): 424 fullname = value.full_name() 425 elif isinstance(attr.parent, Instance): 426 fullname = "%s%s" % (attr.parent_type.full_name(), not target and ".%s" % attr.name or "") 427 else: 428 fullname = "%s%s" % (attr.parent.full_name(), not target and ".%s" % attr.name or "") 429 return fullname 430 431 def _attributes_to_target_names(self, attributes): 432 433 "Get the target names for the 'attributes'." 434 435 output = [] 436 437 if attributes: 438 for attr, value in attributes: 439 fullname = self._attribute_value_to_name(attr, value, True) 440 output.append(fullname) 441 442 output.sort() 443 return output 444 445 def _attributes_to_attribute_names(self, attributes): 446 447 "Get the output form of the 'attributes'." 448 449 output = [] 450 451 if attributes: 452 for attr, value in attributes: 453 fullname = self._attribute_value_to_name(attr, value, False) 454 output.append((fullname, value)) 455 456 output.sort() 457 return output 458 459 def _attribute_start(self, attrname, attributes): 460 if attributes: 461 self._span_start("attr") 462 self._popup_start("attributes-popup") 463 self._names_list_start("attributes", "attrnames") 464 self._attribute_list(attributes) 465 self._names_list_end() 466 self._popup_end() 467 468 def _attribute_list(self, attributes): 469 470 # Mix links to attributes with labels indicating undetermined 471 # attributes. 472 473 last = None 474 for fullname, value in attributes: 475 if fullname != last: 476 if last is not None: 477 self.stream.write("<br />") 478 if value is not None and not isinstance(value, Instance): 479 self._object_name_ref(value.module, value, fullname, classes="attribute-name") 480 else: 481 self.stream.write(self._text(fullname)) 482 last = fullname 483 484 def _attribute_end(self, attributes): 485 if attributes: 486 self._span_end() 487 488 def _get_possible_types(self, attrname): 489 objtable = self.program.get_object_table() 490 return objtable.any_possible_objects([attrname]) 491 492 def _get_attributes(self, possible_types, attrname): 493 objtable = self.program.get_object_table() 494 attributes = [] 495 for target_name in possible_types: 496 target = objtable.get_object(target_name) 497 try: 498 attr = objtable.access(target_name, attrname) 499 except TableError: 500 continue 501 if attr.is_static_attribute(): 502 for v in attr.get_values(): 503 attributes.append((v, target, target_name)) 504 else: 505 attributes.append((None, target, target_name)) 506 507 return attributes 508 509 # Summary classes. 510 511 class Summary(Writer): 512 513 "Summarise classes and attributes in modules." 514 515 def __init__(self, module, program): 516 self.module = module 517 self.program = program 518 519 def to_stream(self, stream): 520 521 "Write the summary to the given 'stream'." 522 523 self.stream = stream 524 self.stream.write(html_header % { 525 "title" : "Module: %s" % self.module.full_name() 526 }) 527 self._write_classes(self.module) 528 self.stream.write(html_footer) 529 530 def _write_classes(self, module): 531 532 all_classes = {} 533 534 for obj in self.module.all_objects: 535 if isinstance(obj, Class): 536 all_classes[obj.name] = obj 537 538 if all_classes: 539 540 all_class_names = all_classes.keys() 541 all_class_names.sort() 542 543 self.stream.write("<table cellspacing='5' cellpadding='5'>\n") 544 self.stream.write("<thead>\n") 545 self.stream.write("<tr>\n") 546 self.stream.write("<th>Classes</th><th>Attributes</th>\n") 547 self.stream.write("</tr>\n") 548 self.stream.write("</thead>\n") 549 550 for name in all_class_names: 551 self._write_class(all_classes[name]) 552 553 self.stream.write("</table>\n") 554 555 def _write_class(self, obj): 556 557 # Write the class... 558 559 self.stream.write("<tbody class='class'>\n") 560 self.stream.write("<tr>\n") 561 self.stream.write("<th class='summary-class' id='%s' rowspan='2'>" % self._attr(obj.full_name())) 562 self._object_name_ref(self.module, obj, classes="class-name") 563 self.stream.write("</th>\n") 564 565 # ...and instance attribute names in order... 566 567 attrs = obj.instance_attributes().values() 568 attrs.sort(cmp=lambda x, y: cmp(x.position, y.position)) 569 570 if attrs: 571 for attr in attrs: 572 self.stream.write("<td class='summary-attr'>%s</td>\n" % self._text(attr.name)) 573 else: 574 self.stream.write("<td class='summary-attr-absent'>None</td>\n") 575 576 self.stream.write("</tr>\n") 577 self.stream.write("<tr>\n") 578 579 # ...and class attribute names in order. 580 581 attrs = obj.class_attributes().values() 582 attrs.sort(cmp=lambda x, y: cmp(x.position, y.position)) 583 584 if attrs: 585 for attr in attrs: 586 if attr.is_strict_constant(): 587 value = attr.get_value() 588 if not isinstance(value, Const): 589 self.stream.write("<td class='summary-class-attr' id='%s'>" % self._attr(value.full_name())) 590 self._object_name_ref(self.module, value, attr.name, classes="summary-ref") 591 self.stream.write("</td>\n") 592 else: 593 self.stream.write("<td class='summary-class-attr'>%s</td>\n" % self._text(attr.name)) 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-absent'>None</td>\n") 598 599 self.stream.write("</tr>\n") 600 self.stream.write("</tbody>\n") 601 602 class Interfaces(Writer): 603 604 "Summarise the interfaces used by reading the object table cache." 605 606 def __init__(self, program): 607 self.program = program 608 609 def to_stream(self, stream): 610 611 "Write the summary to the given 'stream'." 612 613 self.stream = stream 614 self.stream.write(html_header % { 615 "title" : "Interfaces" 616 }) 617 self._write_interfaces() 618 self.stream.write(html_footer) 619 620 def _write_interfaces(self): 621 objtable = self.program.get_object_table() 622 all_interfaces = objtable.all_cache.items() 623 all_interfaces.sort() 624 625 self.stream.write("<table cellspacing='5' cellpadding='5'>\n") 626 self.stream.write("<thead>\n") 627 self.stream.write("<tr>\n") 628 self.stream.write("<th>Complete Interfaces</th>\n") 629 self.stream.write("</tr>\n") 630 self.stream.write("</thead>\n") 631 self._write_interface_type(all_interfaces, "complete") 632 self.stream.write("</table>\n") 633 634 def _write_interface_type(self, interfaces, classes=""): 635 self.stream.write("<tbody>\n") 636 637 for names, objects in interfaces: 638 if names: 639 names = list(names) 640 names.sort() 641 self.stream.write("<tr>\n") 642 self.stream.write("<td class='summary-interface %s'>%s</td>" % (classes, ", ".join(names))) 643 self.stream.write("</tr>\n") 644 645 self.stream.write("</tbody>\n") 646 647 # Source code classes. 648 649 class AnnotatedSource(ASTVisitor, Writer): 650 651 "A module source code browser." 652 653 def __init__(self, module, program): 654 self.visitor = self 655 self.module = module 656 self.program = program 657 self.units = [] 658 659 def get_unit(self): 660 return self.units[-1] 661 662 def to_stream(self, stream): 663 664 "Write the annotated code to the given 'stream'." 665 666 self.stream = stream 667 self.stream.write(html_header % { 668 "title" : "Module: %s" % self.module.full_name() 669 }) 670 self.dispatch(self.module.astnode) 671 self.stream.write(html_footer) 672 673 def visitModule(self, node): 674 self.units.append(node.unit) 675 676 self._doc(node, "module") 677 self.default(node) 678 679 self.units.pop() 680 681 # Statements. 682 683 def visitAssert(self, node): 684 self.stream.write("<div class='assert nowrap'>\n") 685 self._keyword("assert") 686 self.dispatch(node.test) 687 if node.fail: 688 self.stream.write(", ") 689 self.dispatch(node.fail) 690 self.stream.write("</div>\n") 691 692 def visitAssign(self, node): 693 self.stream.write("<div class='assign nowrap'>\n") 694 for lvalue in node.nodes: 695 self.dispatch(lvalue) 696 self.stream.write(" = ") 697 self.dispatch(node.expr) 698 self.stream.write("</div>\n") 699 700 def visitAugAssign(self, node): 701 self.stream.write("<div class='augassign nowrap'>\n") 702 self.dispatch(node.node) 703 self._op(node.op, operator_functions[node.op], 1) 704 self.dispatch(node.expr) 705 self.stream.write("</div>\n") 706 707 def visitBreak(self, node): 708 self.stream.write("<div class='break nowrap'>\n") 709 self._keyword("break") 710 self.stream.write("</div>\n") 711 712 def visitClass(self, node): 713 if not used_by_unit(node): 714 self._docstring('"Class %s not generated."' % node.name) 715 return 716 717 # Use inspected details where possible. 718 719 cls = node.unit 720 self.units.append(cls) 721 722 bases = cls.bases 723 self.stream.write("<div class='class nowrap' id='%s'>\n" % cls.full_name()) 724 725 # Write the declaration line. 726 727 self.stream.write("<div>\n") 728 self._keyword("class") 729 self._object_name_def(self.module, cls, "class-name") 730 731 # Suppress the "object" class appearing alone. 732 733 if bases and not (len(bases) == 1 and bases[0].name == "object"): 734 self.stream.write("(") 735 first = True 736 for base in bases: 737 if not first: 738 self.stream.write(", ") 739 740 self._object_name_ref(base.module, base) 741 742 first = False 743 self.stream.write(")") 744 745 self.stream.write(":\n") 746 self.stream.write("</div>\n") 747 748 # Write the docstring and class body. 749 750 self.stream.write("<div class='body nowrap'>\n") 751 self._doc(node) 752 753 # NOTE: Some streams may not support tell. 754 755 x = self.stream.tell() 756 757 self.default(node.code) 758 759 # Check for no output. 760 761 if x == self.stream.tell(): 762 self.visitPass(None) 763 764 self.stream.write("</div>\n") 765 self.stream.write("</div>\n") 766 767 self.units.pop() 768 769 def visitContinue(self, node): 770 self.stream.write("<div class='continue nowrap'>\n") 771 self._keyword("continue") 772 self.stream.write("</div>\n") 773 774 def visitDiscard(self, node): 775 self.stream.write("<div class='discard nowrap'>\n") 776 self.default(node) 777 self.stream.write("</div>\n") 778 779 def visitFor(self, node): 780 self.stream.write("<div class='if nowrap'>\n") 781 self.stream.write("<div>\n") 782 self._keyword("for") 783 self.dispatch(node.assign) 784 self._keyword("in", 1) 785 self.dispatch(node.list) 786 self.stream.write(":\n") 787 self.stream.write("</div>\n") 788 self.stream.write("<div class='body nowrap'>\n") 789 self.dispatch(node.body) 790 self.stream.write("</div>\n") 791 if node.else_ is not None: 792 self.stream.write("<div>\n") 793 self._keyword("else", trailing=0) 794 self.stream.write(":\n") 795 self.stream.write("</div>\n") 796 self.stream.write("<div class='body nowrap'>\n") 797 self.dispatch(node.else_) 798 self.stream.write("</div>\n") 799 self.stream.write("</div>\n") 800 801 def visitFrom(self, node): 802 self.stream.write("<div class='from nowrap'>\n") 803 self._keyword("from") 804 self._module_link(node.modname) 805 self._keyword("import", 1) 806 first = True 807 for name, alias in node.names: 808 if not first: 809 self.stream.write(", ") 810 self._name(name) 811 if alias: 812 self._keyword("as", 1) 813 self._name(alias) 814 first = False 815 self.stream.write("</div>\n") 816 817 def visitFunction(self, node): 818 if not used_by_unit(node): 819 self._docstring('"Function %s not generated."' % node.name) 820 return 821 822 fn = node.unit 823 self.units.append(fn) 824 825 self.stream.write("<div class='function nowrap' id='%s'>\n" % fn.full_name()) 826 827 # Write the declaration line. 828 829 self.stream.write("<div>\n") 830 self._keyword("def") 831 self._object_name_def(self.module, fn, "function-name") 832 833 self.stream.write("(") 834 self._parameters(fn, node) 835 self.stream.write(")") 836 self.stream.write(":\n") 837 self.stream.write("</div>\n") 838 839 self.stream.write("<div class='body nowrap'>\n") 840 self._doc(node) 841 self.dispatch(node.code) 842 self.stream.write("</div>\n") 843 self.stream.write("</div>\n") 844 845 self.units.pop() 846 847 def visitGlobal(self, node): 848 self.stream.write("<div class='global nowrap'>\n") 849 self._keyword("global") 850 first = True 851 for name in node.names: 852 if not first: 853 self.stream.write(", ") 854 self.stream.write(name) 855 first = False 856 self.stream.write("</div>\n") 857 858 def visitIf(self, node): 859 self.stream.write("<div class='if nowrap'>\n") 860 first = True 861 for compare, stmt in node.tests: 862 self.stream.write("<div>\n") 863 if first: 864 self._keyword("if") 865 else: 866 self._keyword("elif") 867 self.dispatch(compare) 868 self.stream.write(":\n") 869 self.stream.write("</div>\n") 870 self.stream.write("<div class='body nowrap'>\n") 871 self.dispatch(stmt) 872 self.stream.write("</div>\n") 873 first = False 874 if node.else_ is not None: 875 self.stream.write("<div>\n") 876 self._keyword("else", trailing=0) 877 self.stream.write(":\n") 878 self.stream.write("</div>\n") 879 self.stream.write("<div class='body nowrap'>\n") 880 self.dispatch(node.else_) 881 self.stream.write("</div>\n") 882 self.stream.write("</div>\n") 883 884 def visitImport(self, node): 885 self.stream.write("<div class='import nowrap'>\n") 886 self._keyword("import") 887 first = True 888 for name, alias in node.names: 889 if not first: 890 self.stream.write(",\n") 891 self._module_link(name) 892 if alias: 893 self._keyword("as", 1) 894 self._name(alias) 895 first = False 896 self.stream.write("</div>\n") 897 898 def visitPass(self, node): 899 self.stream.write("<div class='pass nowrap'>\n") 900 self._keyword("pass") 901 self.stream.write("</div>\n") 902 903 def visitPrint(self, node): 904 self.stream.write("<div class='print nowrap'>\n") 905 self._keyword("print") 906 if node.dest is not None: 907 self.stream.write(">>\n") 908 self.dispatch(node.dest) 909 self.stream.write(",\n") 910 for n in node.nodes: 911 self.dispatch(n) 912 self.stream.write(",\n") 913 self.stream.write("</div>\n") 914 915 def visitPrintnl(self, node): 916 self.stream.write("<div class='printnl nowrap'>\n") 917 self._keyword("print") 918 if node.dest is not None: 919 self.stream.write(">>\n") 920 self.dispatch(node.dest) 921 first = False 922 else: 923 first = True 924 for n in node.nodes: 925 if not first: 926 self.stream.write(",\n") 927 self.dispatch(n) 928 first = False 929 self.stream.write("</div>\n") 930 931 def visitRaise(self, node): 932 self.stream.write("<div class='raise nowrap'>\n") 933 self._keyword("raise") 934 if node.expr1 is not None: 935 self.dispatch(node.expr1) 936 if node.expr2 is not None: 937 self.stream.write(",\n") 938 self.dispatch(node.expr2) 939 if node.expr3 is not None: 940 self.stream.write(",\n") 941 self.dispatch(node.expr3) 942 self.stream.write("</div>\n") 943 944 def visitReturn(self, node): 945 self.stream.write("<div class='return nowrap'>\n") 946 self._keyword("return") 947 self.dispatch(node.value) 948 self.stream.write("</div>\n") 949 950 def visitStmt(self, node): 951 self.stream.write("<div class='stmt nowrap'>\n") 952 self.default(node) 953 self.stream.write("</div>\n") 954 955 def visitTryExcept(self, node): 956 self.stream.write("<div class='tryexcept 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 for spec, assign, statement in node.handlers: 965 self.stream.write("<div>\n") 966 self._keyword("except") 967 if spec is not None: 968 self.dispatch(spec) 969 if assign is not None: 970 self.stream.write(",\n") 971 self.dispatch(assign) 972 self.stream.write(":\n") 973 self.stream.write("</div>\n") 974 self.stream.write("<div class='body nowrap'>\n") 975 self.dispatch(statement) 976 self.stream.write("</div>\n") 977 if node.else_ is not None: 978 self.stream.write("<div>\n") 979 self._keyword("else", trailing=0) 980 self.stream.write(":\n") 981 self.stream.write("</div>\n") 982 self.stream.write("<div class='body nowrap'>\n") 983 self.dispatch(node.else_) 984 self.stream.write("</div>\n") 985 self.stream.write("</div>\n") 986 987 def visitTryFinally(self, node): 988 self.stream.write("<div class='tryfinally 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 self.stream.write("<div>\n") 997 self._keyword("finally", trailing=0) 998 self.stream.write(":\n") 999 self.stream.write("</div>\n") 1000 self.stream.write("<div class='body nowrap'>\n") 1001 self.dispatch(node.final) 1002 self.stream.write("</div>\n") 1003 self.stream.write("</div>\n") 1004 1005 def visitWhile(self, node): 1006 self.stream.write("<div class='while nowrap'>\n") 1007 self.stream.write("<div>\n") 1008 self._keyword("while") 1009 self.dispatch(node.test) 1010 self.stream.write(":\n") 1011 self.stream.write("</div>\n") 1012 self.stream.write("<div class='body nowrap'>\n") 1013 self.dispatch(node.body) 1014 self.stream.write("</div>\n") 1015 if node.else_ is not None: 1016 self.stream.write("<div>\n") 1017 self._keyword("else", trailing=0) 1018 self.stream.write(":\n") 1019 self.stream.write("</div>\n") 1020 self.stream.write("<div class='body nowrap'>\n") 1021 self.dispatch(node.else_) 1022 self.stream.write("</div>\n") 1023 self.stream.write("</div>\n") 1024 1025 def visitYield(self, node): 1026 self.stream.write("<div class='yield nowrap'>\n") 1027 self._keyword("yield") 1028 self.dispatch(node.value) 1029 self.stream.write("</div>\n") 1030 1031 # Expression-related helper methods. 1032 1033 def _visitBitBinary(self, node, symbol): 1034 name = operator_functions[node.__class__.__name__] 1035 self._span_start(name) 1036 first = True 1037 for node in node.nodes: 1038 if not first: 1039 self._op(symbol, name, 1) 1040 self.dispatch(node) 1041 first = False 1042 self._span_end() 1043 1044 def _visitBinary(self, node, symbol): 1045 name = operator_functions[node.__class__.__name__] 1046 self._span_start(name) 1047 self.dispatch(node.left) 1048 self._op(symbol, name, 1) 1049 self.dispatch(node.right) 1050 self._span_end() 1051 1052 def _visitUnary(self, node, symbol): 1053 name = operator_functions[node.__class__.__name__] 1054 self._span_start(name) 1055 self._op(symbol, name, trailing=0) 1056 self.dispatch(node.expr) 1057 self._span_end() 1058 1059 # Expressions. 1060 1061 def visitAdd(self, node): 1062 self._visitBinary(node, "+") 1063 1064 def visitAnd(self, node): 1065 self._span_start("and") 1066 first = True 1067 for n in node.nodes: 1068 if not first: 1069 self._keyword("and", 1) 1070 self.dispatch(n) 1071 first = False 1072 self._span_end() 1073 1074 def _visitAttr(self, node, label): 1075 self.record_unknown_targets(node) 1076 1077 attributes = node._value_deduced and [self.get_attribute_and_value(node._value_deduced)] or \ 1078 node._attr_deduced and [self.get_attribute_and_value(node._attr_deduced)] or \ 1079 node._attrs_deduced or \ 1080 map(self.get_attribute_and_value, node._attrs_deduced_from_specific_usage or []) 1081 1082 possible_types = self._attributes_to_target_names(attributes) 1083 1084 wraps_getattr = self._has_descendant(node.expr, compiler.ast.Getattr) 1085 1086 if not wraps_getattr: 1087 self._span_start(label) 1088 self._accessor_start(possible_types) 1089 self.dispatch(node.expr) 1090 if not wraps_getattr: 1091 self._accessor_end(possible_types) 1092 1093 self.stream.write(".") 1094 self._attribute_start(node.attrname, self._attributes_to_attribute_names(attributes)) 1095 self._span(node.attrname, "attrname" + (not possible_types and " no-targets" or "")) 1096 self._attribute_end(attributes) 1097 1098 if not wraps_getattr: 1099 self._span_end() 1100 1101 def visitAssAttr(self, node): 1102 self._visitAttr(node, "assattr") 1103 1104 def visitAssList(self, node): 1105 self._span_start("list") 1106 self.stream.write("[") 1107 self._sequence(node) 1108 self.stream.write("]") 1109 self._span_end() 1110 1111 def visitAssName(self, node): 1112 self._assname(node.name, node) 1113 1114 def visitAssTuple(self, node): 1115 self._span_start("tuple") 1116 self.stream.write("(") 1117 self._sequence(node) 1118 self.stream.write(")") 1119 self._span_end() 1120 1121 def visitBitand(self, node): 1122 self._visitBitBinary(node, "&") 1123 1124 def visitBitor(self, node): 1125 self._visitBitBinary(node, "|") 1126 1127 def visitBitxor(self, node): 1128 self._visitBitBinary(node, "^") 1129 1130 def visitCallFunc(self, node): 1131 self._span_start("callfunc") 1132 self.dispatch(node.node) 1133 self._span_start("call") 1134 self.stream.write("(") 1135 first = True 1136 for arg in node.args: 1137 if not first: 1138 self.stream.write(", ") 1139 self.dispatch(arg) 1140 first = False 1141 if node.star_args is not None: 1142 if not first: 1143 self.stream.write(", *") 1144 self.dispatch(node.star_args) 1145 first = False 1146 if node.dstar_args is not None: 1147 if not first: 1148 self.stream.write(", **") 1149 self.dispatch(node.dstar_args) 1150 first = False 1151 self.stream.write(")") 1152 self._span_end() 1153 self._span_end() 1154 1155 def visitCompare(self, node): 1156 self._span_start("compare") 1157 self.dispatch(node.expr) 1158 for op_name, expr in node.ops: 1159 self._op(op_name, operator_functions.get(op_name), 1) 1160 self.dispatch(expr) 1161 self._span_end() 1162 1163 def visitConst(self, node): 1164 if isinstance(node.value, (str, unicode)): 1165 self._span_start("str") 1166 self.stream.write(self._text(repr(node.value))) 1167 if isinstance(node.value, (str, unicode)): 1168 self._span_end() 1169 1170 def visitDict(self, node): 1171 self._span_start("dict") 1172 self.stream.write("{") 1173 self._mapping(node) 1174 self.stream.write("}") 1175 self._span_end() 1176 1177 def visitDiv(self, node): 1178 self._visitBinary(node, "/") 1179 1180 def visitFloorDiv(self, node): 1181 self._visitBinary(node, "//") 1182 1183 def visitGetattr(self, node): 1184 self._visitAttr(node, "getattr") 1185 1186 def visitGenExpr(self, node): 1187 self._span_start("genexpr") 1188 self.stream.write("(") 1189 self.dispatch(node.code) 1190 self.stream.write(")") 1191 self._span_end() 1192 1193 def visitGenExprFor(self, node): 1194 self._span_start("genexprfor") 1195 self._keyword("for", 1) 1196 self._span_start("item") 1197 self.dispatch(node.assign) 1198 self._span_end() 1199 self._keyword("in", 1) 1200 self._span_start("collection") 1201 self.dispatch(node.iter) 1202 self._span_end() 1203 for if_ in node.ifs: 1204 self.dispatch(if_) 1205 self._span_end() 1206 1207 def visitGenExprIf(self, node): 1208 self._span_start("genexprif") 1209 self._span_start("conditional") 1210 self._keyword("if", 1) 1211 self.dispatch(node.test) 1212 self._span_end() 1213 self._span_end() 1214 1215 def visitGenExprInner(self, node): 1216 self._span_start("genexprinner") 1217 self.dispatch(node.expr) 1218 for qual in node.quals: 1219 self.dispatch(qual) 1220 self._span_end() 1221 1222 def visitIfExp(self, node): 1223 self._span_start("ifexp") 1224 self.dispatch(node.then) 1225 self._keyword("if", 1) 1226 self.dispatch(node.test) 1227 self._keyword("else", 1) 1228 self.dispatch(node.else_) 1229 self._span_end() 1230 1231 def visitInvert(self, node): 1232 self._visitUnary(node, "~") 1233 1234 def visitKeyword(self, node): 1235 self._span_start("keyword-arg") 1236 self.stream.write(node.name) 1237 self.stream.write("=") 1238 self.dispatch(node.expr) 1239 self._span_end() 1240 1241 def visitLambda(self, node): 1242 fn = node.unit 1243 self.units.append(fn) 1244 1245 self._span_start("lambda") 1246 self._keyword("lambda") 1247 self._parameters(fn, node) 1248 self.stream.write(": ") 1249 self._span_start("code") 1250 self.dispatch(node.code) 1251 self._span_end() 1252 self._span_end() 1253 1254 self.units.pop() 1255 1256 def visitLeftShift(self, node): 1257 self._visitBinary(node, "<<") 1258 1259 visitList = visitAssList 1260 1261 def visitListComp(self, node): 1262 self._span_start("listcomp") 1263 self.stream.write("[") 1264 self.dispatch(node.expr) 1265 for qual in node.quals: 1266 self.dispatch(qual) 1267 self.stream.write("]") 1268 self._span_end() 1269 1270 def visitListCompFor(self, node): 1271 self._span_start("listcompfor") 1272 self._keyword("for", 1) 1273 self._span_start("item") 1274 self.dispatch(node.assign) 1275 self._span_end() 1276 self._keyword("in", 1) 1277 self._span_start("collection") 1278 self.dispatch(node.list) 1279 self._span_end() 1280 for if_ in node.ifs: 1281 self.dispatch(if_) 1282 self._span_end() 1283 1284 def visitListCompIf(self, node): 1285 self._span_start("listcompif") 1286 self._span_start("conditional") 1287 self._keyword("if", 1) 1288 self.dispatch(node.test) 1289 self._span_end() 1290 self._span_end() 1291 1292 def visitMod(self, node): 1293 self._visitBinary(node, "%") 1294 1295 def visitMul(self, node): 1296 self._visitBinary(node, "*") 1297 1298 def visitName(self, node): 1299 if node._scope: 1300 scope = node._scope 1301 self._name_start() 1302 self.stream.write(node.name) 1303 self._popup_start() 1304 self._scope(node._scope, node._attr) 1305 self._popup_end() 1306 self._name_end() 1307 else: 1308 self._span(node.name) 1309 1310 def visitNot(self, node): 1311 self._span_start("not") 1312 self._keyword("not") 1313 self.dispatch(node.expr) 1314 self._span_end() 1315 1316 def visitOr(self, node): 1317 self._span_start("or") 1318 first = True 1319 for n in node.nodes: 1320 if not first: 1321 self._keyword("or", 1) 1322 self.dispatch(n) 1323 first = False 1324 self._span_end() 1325 1326 def visitPower(self, node): 1327 self._visitBinary(node, "**") 1328 1329 def visitRightShift(self, node): 1330 self._visitBinary(node, ">>") 1331 1332 def visitSlice(self, node): 1333 self._span_start("slice") 1334 self.dispatch(node.expr) 1335 self.stream.write("[") 1336 if node.lower: 1337 self.dispatch(node.lower) 1338 self.stream.write(":") 1339 if node.upper: 1340 self.dispatch(node.upper) 1341 # NOTE: Step? 1342 self.stream.write("]") 1343 self._span_end() 1344 1345 def visitSliceobj(self, node): 1346 self._span_start("sliceobj") 1347 first = True 1348 for n in node.nodes: 1349 if not first: 1350 self.stream.write(":") 1351 self.dispatch(n) 1352 self._span_end() 1353 1354 def visitSub(self, node): 1355 self._visitBinary(node, "-") 1356 1357 def visitSubscript(self, node): 1358 self._span_start("subscript") 1359 self.dispatch(node.expr) 1360 self.stream.write("[") 1361 first = True 1362 for sub in node.subs: 1363 if not first: 1364 self.stream.write(", ") 1365 self.dispatch(sub) 1366 first = False 1367 self.stream.write("]") 1368 self._span_end() 1369 1370 visitTuple = visitAssTuple 1371 1372 def visitUnaryAdd(self, node): 1373 self._visitUnary(node, "+") 1374 1375 def visitUnarySub(self, node): 1376 self._visitUnary(node, "-") 1377 1378 # Output preparation methods. 1379 1380 def _sequence(self, node): 1381 first = True 1382 for n in node.nodes: 1383 if not first: 1384 self.stream.write(", ") 1385 self.dispatch(n) 1386 first = False 1387 1388 def _mapping(self, node): 1389 first = True 1390 for k, v in node.items: 1391 if not first: 1392 self.stream.write(", ") 1393 self.dispatch(k) 1394 self.stream.write(" : ") 1395 self.dispatch(v) 1396 first = False 1397 1398 def _parameters(self, fn, node): 1399 nparams = len(fn.positional_names) 1400 ndefaults = len(fn.defaults) 1401 first_with_default = nparams - ndefaults 1402 1403 first = True 1404 for n, param in enumerate(fn.positional_names): 1405 if not first: 1406 self.stream.write(", ") 1407 1408 # Handle tuple parameters. 1409 1410 if isinstance(param, tuple): 1411 self._tuple_parameter(param, node) 1412 else: 1413 self._assname(param, node) 1414 1415 n_default = n - first_with_default 1416 if n_default >= 0: 1417 self._default(fn.defaults[n_default]) 1418 first = False 1419 1420 if fn.has_star: 1421 if not first: 1422 self.stream.write(", *") 1423 self._name(fn.star_name) 1424 1425 if fn.has_dstar: 1426 if not first: 1427 self.stream.write(", **") 1428 self._name(fn.dstar_name) 1429 1430 def _tuple_parameter(self, parameters, node): 1431 self.stream.write("(") 1432 1433 first = True 1434 for param in parameters: 1435 if not first: 1436 self.stream.write(", ") 1437 1438 # Handle tuples. 1439 1440 if isinstance(param, tuple): 1441 self._tuple_parameter(param, node) 1442 else: 1443 self._assname(param, node) 1444 1445 first = False 1446 1447 self.stream.write(")") 1448 1449 def _default(self, default): 1450 self.stream.write("=") 1451 self.dispatch(default) 1452 1453 # Statistics gathering methods. 1454 1455 def record_unknown_targets(self, node): 1456 if not node._attrs_deduced: 1457 self.program.unknown_target_nodes.append((self.units[-1], node)) 1458 elif not node._attrs_deduced_from_specific_usage: 1459 self.program.independent_target_nodes.append((self.units[-1], node)) 1460 1461 # Utility methods. 1462 1463 def _has_descendant(self, node, nodetype): 1464 if isinstance(node, nodetype): 1465 return True 1466 else: 1467 for n in node.getChildNodes(): 1468 if self._has_descendant(n, nodetype): 1469 return True 1470 return False 1471 1472 # Convenience functions. 1473 1474 def summarise(module, program, filename): 1475 stream = open(filename, "wb") 1476 try: 1477 summary = Summary(module, program) 1478 summary.to_stream(stream) 1479 finally: 1480 stream.close() 1481 1482 def annotate(module, program, filename): 1483 stream = open(filename, "wb") 1484 try: 1485 source = AnnotatedSource(module, program) 1486 source.to_stream(stream) 1487 finally: 1488 stream.close() 1489 1490 def interfaces(program, filename): 1491 stream = open(filename, "wb") 1492 try: 1493 source = Interfaces(program) 1494 source.to_stream(stream) 1495 finally: 1496 stream.close() 1497 1498 def report(program, directory): 1499 if not exists(directory): 1500 os.mkdir(directory) 1501 1502 for module in program.get_importer().get_modules(): 1503 annotate(module, program, join(directory, "%s%sxhtml" % (module.full_name(), extsep))) 1504 summarise(module, program, join(directory, "%s-summary%sxhtml" % (module.full_name(), extsep))) 1505 1506 interfaces(program, join(directory, "-interfaces%sxhtml" % extsep)) 1507 1508 # vim: tabstop=4 expandtab shiftwidth=4