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 value and isinstance(value, Const): 426 fullname = "%s" % value.get_value() 427 elif isinstance(attr.parent, Instance): 428 fullname = "%s%s" % (attr.parent_type.full_name(), not target and ".%s" % attr.name or "") 429 else: 430 fullname = "%s%s" % (attr.parent.full_name(), not target and ".%s" % attr.name or "") 431 return fullname 432 433 def _attributes_to_target_names(self, attributes): 434 435 "Get the target names for the 'attributes'." 436 437 output = [] 438 439 if attributes: 440 for attr, value in attributes: 441 fullname = self._attribute_value_to_name(attr, value, True) 442 output.append(fullname) 443 444 output.sort() 445 return output 446 447 def _attributes_to_attribute_names(self, attributes): 448 449 "Get the output form of the 'attributes'." 450 451 output = [] 452 453 if attributes: 454 for attr, value in attributes: 455 fullname = self._attribute_value_to_name(attr, value, False) 456 output.append((fullname, value)) 457 458 output.sort() 459 return output 460 461 def _attribute_start(self, attrname, attributes): 462 if attributes: 463 self._span_start("attr") 464 self._popup_start("attributes-popup") 465 self._names_list_start("attributes", "attrnames") 466 self._attribute_list(attributes) 467 self._names_list_end() 468 self._popup_end() 469 470 def _attribute_list(self, attributes): 471 472 # Mix links to attributes with labels indicating undetermined 473 # attributes. 474 475 last = None 476 for fullname, value in attributes: 477 if fullname != last: 478 if last is not None: 479 self.stream.write("<br />") 480 if value is not None and not isinstance(value, Instance): 481 self._object_name_ref(value.module, value, fullname, classes="attribute-name") 482 else: 483 self.stream.write(self._text(fullname)) 484 last = fullname 485 486 def _attribute_end(self, attributes): 487 if attributes: 488 self._span_end() 489 490 def _get_possible_types(self, attrname): 491 objtable = self.program.get_object_table() 492 return objtable.any_possible_objects([attrname]) 493 494 def _get_attributes(self, possible_types, attrname): 495 objtable = self.program.get_object_table() 496 attributes = [] 497 for target_name in possible_types: 498 target = objtable.get_object(target_name) 499 try: 500 attr = objtable.access(target_name, attrname) 501 except TableError: 502 continue 503 if attr.is_static_attribute(): 504 for v in attr.get_values(): 505 attributes.append((v, target, target_name)) 506 else: 507 attributes.append((None, target, target_name)) 508 509 return attributes 510 511 # Summary classes. 512 513 class Summary(Writer): 514 515 "Summarise classes and attributes in modules." 516 517 def __init__(self, module, program): 518 self.module = module 519 self.program = program 520 521 def to_stream(self, stream): 522 523 "Write the summary to the given 'stream'." 524 525 self.stream = stream 526 self.stream.write(html_header % { 527 "title" : "Module: %s" % self.module.full_name() 528 }) 529 self._write_classes(self.module) 530 self.stream.write(html_footer) 531 532 def _write_classes(self, module): 533 534 all_classes = {} 535 536 for obj in self.module.all_objects: 537 if isinstance(obj, Class): 538 all_classes[obj.name] = obj 539 540 if all_classes: 541 542 all_class_names = all_classes.keys() 543 all_class_names.sort() 544 545 self.stream.write("<table cellspacing='5' cellpadding='5'>\n") 546 self.stream.write("<thead>\n") 547 self.stream.write("<tr>\n") 548 self.stream.write("<th>Classes</th><th>Attributes</th>\n") 549 self.stream.write("</tr>\n") 550 self.stream.write("</thead>\n") 551 552 for name in all_class_names: 553 self._write_class(all_classes[name]) 554 555 self.stream.write("</table>\n") 556 557 def _write_class(self, obj): 558 559 # Write the class... 560 561 self.stream.write("<tbody class='class'>\n") 562 self.stream.write("<tr>\n") 563 self.stream.write("<th class='summary-class' id='%s' rowspan='2'>" % self._attr(obj.full_name())) 564 self._object_name_ref(self.module, obj, classes="class-name") 565 self.stream.write("</th>\n") 566 567 # ...and instance attribute names in order... 568 569 attrs = obj.instance_attributes().values() 570 attrs.sort(cmp=lambda x, y: cmp(x.position, y.position)) 571 572 if attrs: 573 for attr in attrs: 574 self.stream.write("<td class='summary-attr'>%s</td>\n" % self._text(attr.name)) 575 else: 576 self.stream.write("<td class='summary-attr-absent'>None</td>\n") 577 578 self.stream.write("</tr>\n") 579 self.stream.write("<tr>\n") 580 581 # ...and class attribute names in order. 582 583 attrs = obj.class_attributes().values() 584 attrs.sort(cmp=lambda x, y: cmp(x.position, y.position)) 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.default(node.expr) 785 if node.locals: 786 self.stream.write(", ") 787 self.default(node.locals) 788 if node.globals: 789 self.stream.write(", ") 790 self.default(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 self._module_link(node.modname) 819 self._keyword("import", 1) 820 first = True 821 for name, alias in node.names: 822 if not first: 823 self.stream.write(", ") 824 self._name(name) 825 if alias: 826 self._keyword("as", 1) 827 self._name(alias) 828 first = False 829 self.stream.write("</div>\n") 830 831 def visitFunction(self, node): 832 if not used_by_unit(node): 833 self._docstring('"Function %s not generated."' % node.name) 834 return 835 836 fn = node.unit 837 self.units.append(fn) 838 839 self.stream.write("<div class='function nowrap' id='%s'>\n" % fn.full_name()) 840 841 # Write the declaration line. 842 843 self.stream.write("<div>\n") 844 self._keyword("def") 845 self._object_name_def(self.module, fn, "function-name") 846 847 self.stream.write("(") 848 self._parameters(fn, node) 849 self.stream.write(")") 850 self.stream.write(":\n") 851 self.stream.write("</div>\n") 852 853 self.stream.write("<div class='body nowrap'>\n") 854 self._doc(node) 855 self.dispatch(node.code) 856 self.stream.write("</div>\n") 857 self.stream.write("</div>\n") 858 859 self.units.pop() 860 861 def visitGlobal(self, node): 862 self.stream.write("<div class='global nowrap'>\n") 863 self._keyword("global") 864 first = True 865 for name in node.names: 866 if not first: 867 self.stream.write(", ") 868 self.stream.write(name) 869 first = False 870 self.stream.write("</div>\n") 871 872 def visitIf(self, node): 873 self.stream.write("<div class='if nowrap'>\n") 874 first = True 875 for compare, stmt in node.tests: 876 self.stream.write("<div>\n") 877 if first: 878 self._keyword("if") 879 else: 880 self._keyword("elif") 881 self.dispatch(compare) 882 self.stream.write(":\n") 883 self.stream.write("</div>\n") 884 self.stream.write("<div class='body nowrap'>\n") 885 self.dispatch(stmt) 886 self.stream.write("</div>\n") 887 first = False 888 if node.else_ is not None: 889 self.stream.write("<div>\n") 890 self._keyword("else", trailing=0) 891 self.stream.write(":\n") 892 self.stream.write("</div>\n") 893 self.stream.write("<div class='body nowrap'>\n") 894 self.dispatch(node.else_) 895 self.stream.write("</div>\n") 896 self.stream.write("</div>\n") 897 898 def visitImport(self, node): 899 self.stream.write("<div class='import nowrap'>\n") 900 self._keyword("import") 901 first = True 902 for name, alias in node.names: 903 if not first: 904 self.stream.write(",\n") 905 self._module_link(name) 906 if alias: 907 self._keyword("as", 1) 908 self._name(alias) 909 first = False 910 self.stream.write("</div>\n") 911 912 def visitPass(self, node): 913 self.stream.write("<div class='pass nowrap'>\n") 914 self._keyword("pass") 915 self.stream.write("</div>\n") 916 917 def visitPrint(self, node): 918 self.stream.write("<div class='print nowrap'>\n") 919 self._keyword("print") 920 if node.dest is not None: 921 self.stream.write(">>\n") 922 self.dispatch(node.dest) 923 self.stream.write(",\n") 924 for n in node.nodes: 925 self.dispatch(n) 926 self.stream.write(",\n") 927 self.stream.write("</div>\n") 928 929 def visitPrintnl(self, node): 930 self.stream.write("<div class='printnl nowrap'>\n") 931 self._keyword("print") 932 if node.dest is not None: 933 self.stream.write(">>\n") 934 self.dispatch(node.dest) 935 first = False 936 else: 937 first = True 938 for n in node.nodes: 939 if not first: 940 self.stream.write(",\n") 941 self.dispatch(n) 942 first = False 943 self.stream.write("</div>\n") 944 945 def visitRaise(self, node): 946 self.stream.write("<div class='raise nowrap'>\n") 947 self._keyword("raise") 948 if node.expr1 is not None: 949 self.dispatch(node.expr1) 950 if node.expr2 is not None: 951 self.stream.write(",\n") 952 self.dispatch(node.expr2) 953 if node.expr3 is not None: 954 self.stream.write(",\n") 955 self.dispatch(node.expr3) 956 self.stream.write("</div>\n") 957 958 def visitReturn(self, node): 959 self.stream.write("<div class='return nowrap'>\n") 960 self._keyword("return") 961 self.dispatch(node.value) 962 self.stream.write("</div>\n") 963 964 def visitStmt(self, node): 965 self.stream.write("<div class='stmt nowrap'>\n") 966 self.default(node) 967 self.stream.write("</div>\n") 968 969 def visitTryExcept(self, node): 970 self.stream.write("<div class='tryexcept nowrap'>\n") 971 self.stream.write("<div>\n") 972 self._keyword("try", trailing=0) 973 self.stream.write(":\n") 974 self.stream.write("</div>\n") 975 self.stream.write("<div class='body nowrap'>\n") 976 self.dispatch(node.body) 977 self.stream.write("</div>\n") 978 for spec, assign, statement in node.handlers: 979 self.stream.write("<div>\n") 980 self._keyword("except") 981 if spec is not None: 982 self.dispatch(spec) 983 if assign is not None: 984 self.stream.write(",\n") 985 self.dispatch(assign) 986 self.stream.write(":\n") 987 self.stream.write("</div>\n") 988 self.stream.write("<div class='body nowrap'>\n") 989 self.dispatch(statement) 990 self.stream.write("</div>\n") 991 if node.else_ is not None: 992 self.stream.write("<div>\n") 993 self._keyword("else", trailing=0) 994 self.stream.write(":\n") 995 self.stream.write("</div>\n") 996 self.stream.write("<div class='body nowrap'>\n") 997 self.dispatch(node.else_) 998 self.stream.write("</div>\n") 999 self.stream.write("</div>\n") 1000 1001 def visitTryFinally(self, node): 1002 self.stream.write("<div class='tryfinally nowrap'>\n") 1003 self.stream.write("<div>\n") 1004 self._keyword("try", trailing=0) 1005 self.stream.write(":\n") 1006 self.stream.write("</div>\n") 1007 self.stream.write("<div class='body nowrap'>\n") 1008 self.dispatch(node.body) 1009 self.stream.write("</div>\n") 1010 self.stream.write("<div>\n") 1011 self._keyword("finally", 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.final) 1016 self.stream.write("</div>\n") 1017 self.stream.write("</div>\n") 1018 1019 def visitWhile(self, node): 1020 self.stream.write("<div class='while nowrap'>\n") 1021 self.stream.write("<div>\n") 1022 self._keyword("while") 1023 self.dispatch(node.test) 1024 self.stream.write(":\n") 1025 self.stream.write("</div>\n") 1026 self.stream.write("<div class='body nowrap'>\n") 1027 self.dispatch(node.body) 1028 self.stream.write("</div>\n") 1029 if node.else_ is not None: 1030 self.stream.write("<div>\n") 1031 self._keyword("else", trailing=0) 1032 self.stream.write(":\n") 1033 self.stream.write("</div>\n") 1034 self.stream.write("<div class='body nowrap'>\n") 1035 self.dispatch(node.else_) 1036 self.stream.write("</div>\n") 1037 self.stream.write("</div>\n") 1038 1039 def visitYield(self, node): 1040 self.stream.write("<div class='yield nowrap'>\n") 1041 self._keyword("yield") 1042 self.dispatch(node.value) 1043 self.stream.write("</div>\n") 1044 1045 # Expression-related helper methods. 1046 1047 def _visitBitBinary(self, node, symbol): 1048 name = operator_functions[node.__class__.__name__] 1049 self._span_start(name) 1050 first = True 1051 for node in node.nodes: 1052 if not first: 1053 self._op(symbol, name, 1) 1054 self.dispatch(node) 1055 first = False 1056 self._span_end() 1057 1058 def _visitBinary(self, node, symbol): 1059 name = operator_functions[node.__class__.__name__] 1060 self._span_start(name) 1061 self.dispatch(node.left) 1062 self._op(symbol, name, 1) 1063 self.dispatch(node.right) 1064 self._span_end() 1065 1066 def _visitUnary(self, node, symbol): 1067 name = operator_functions[node.__class__.__name__] 1068 self._span_start(name) 1069 self._op(symbol, name, trailing=0) 1070 self.dispatch(node.expr) 1071 self._span_end() 1072 1073 # Expressions. 1074 1075 def visitAdd(self, node): 1076 self._visitBinary(node, "+") 1077 1078 def visitAnd(self, node): 1079 self._span_start("and") 1080 first = True 1081 for n in node.nodes: 1082 if not first: 1083 self._keyword("and", 1) 1084 self.dispatch(n) 1085 first = False 1086 self._span_end() 1087 1088 def _visitAttr(self, node, label): 1089 self.record_unknown_targets(node) 1090 1091 attributes = node._value_deduced and [self.get_attribute_and_value(node._value_deduced)] or \ 1092 node._attr_deduced and [self.get_attribute_and_value(node._attr_deduced)] or \ 1093 node._attrs_deduced or \ 1094 map(self.get_attribute_and_value, node._attrs_deduced_from_specific_usage or []) 1095 1096 possible_types = self._attributes_to_target_names(attributes) 1097 1098 wraps_getattr = self._has_descendant(node.expr, compiler.ast.Getattr) 1099 1100 if not wraps_getattr: 1101 self._span_start(label) 1102 self._accessor_start(possible_types) 1103 self.dispatch(node.expr) 1104 if not wraps_getattr: 1105 self._accessor_end(possible_types) 1106 1107 self.stream.write(".") 1108 self._attribute_start(node.attrname, self._attributes_to_attribute_names(attributes)) 1109 self._span(node.attrname, "attrname" + (not possible_types and " no-targets" or "")) 1110 self._attribute_end(attributes) 1111 1112 if not wraps_getattr: 1113 self._span_end() 1114 1115 def visitAssAttr(self, node): 1116 self._visitAttr(node, "assattr") 1117 1118 def visitAssList(self, node): 1119 self._span_start("list") 1120 self.stream.write("[") 1121 self._sequence(node) 1122 self.stream.write("]") 1123 self._span_end() 1124 1125 def visitAssName(self, node): 1126 self._assname(node.name, node) 1127 1128 def visitAssTuple(self, node): 1129 self._span_start("tuple") 1130 self.stream.write("(") 1131 self._sequence(node) 1132 self.stream.write(")") 1133 self._span_end() 1134 1135 def visitBackquote(self, node): 1136 self._span_start("backquote") 1137 self.stream.write("`") 1138 self.dispatch(node.expr) 1139 self.stream.write("`") 1140 self._span_end() 1141 1142 def visitBitand(self, node): 1143 self._visitBitBinary(node, "&") 1144 1145 def visitBitor(self, node): 1146 self._visitBitBinary(node, "|") 1147 1148 def visitBitxor(self, node): 1149 self._visitBitBinary(node, "^") 1150 1151 def visitCallFunc(self, node): 1152 self._span_start("callfunc") 1153 self.dispatch(node.node) 1154 self._span_start("call") 1155 self.stream.write("(") 1156 first = True 1157 for arg in node.args: 1158 if not first: 1159 self.stream.write(", ") 1160 self.dispatch(arg) 1161 first = False 1162 if node.star_args is not None: 1163 if not first: 1164 self.stream.write(", *") 1165 self.dispatch(node.star_args) 1166 first = False 1167 if node.dstar_args is not None: 1168 if not first: 1169 self.stream.write(", **") 1170 self.dispatch(node.dstar_args) 1171 first = False 1172 self.stream.write(")") 1173 self._span_end() 1174 self._span_end() 1175 1176 def visitCompare(self, node): 1177 self._span_start("compare") 1178 self.dispatch(node.expr) 1179 for op_name, expr in node.ops: 1180 self._op(op_name, operator_functions.get(op_name), 1) 1181 self.dispatch(expr) 1182 self._span_end() 1183 1184 def visitConst(self, node): 1185 if isinstance(node.value, (str, unicode)): 1186 self._span_start("str") 1187 self.stream.write(self._text(repr(node.value))) 1188 if isinstance(node.value, (str, unicode)): 1189 self._span_end() 1190 1191 def visitDict(self, node): 1192 self._span_start("dict") 1193 self.stream.write("{") 1194 self._mapping(node) 1195 self.stream.write("}") 1196 self._span_end() 1197 1198 def visitDiv(self, node): 1199 self._visitBinary(node, "/") 1200 1201 def visitFloorDiv(self, node): 1202 self._visitBinary(node, "//") 1203 1204 def visitGetattr(self, node): 1205 self._visitAttr(node, "getattr") 1206 1207 def visitGenExpr(self, node): 1208 self._span_start("genexpr") 1209 self.stream.write("(") 1210 self.dispatch(node.code) 1211 self.stream.write(")") 1212 self._span_end() 1213 1214 def visitGenExprFor(self, node): 1215 self._span_start("genexprfor") 1216 self._keyword("for", 1) 1217 self._span_start("item") 1218 self.dispatch(node.assign) 1219 self._span_end() 1220 self._keyword("in", 1) 1221 self._span_start("collection") 1222 self.dispatch(node.iter) 1223 self._span_end() 1224 for if_ in node.ifs: 1225 self.dispatch(if_) 1226 self._span_end() 1227 1228 def visitGenExprIf(self, node): 1229 self._span_start("genexprif") 1230 self._span_start("conditional") 1231 self._keyword("if", 1) 1232 self.dispatch(node.test) 1233 self._span_end() 1234 self._span_end() 1235 1236 def visitGenExprInner(self, node): 1237 self._span_start("genexprinner") 1238 self.dispatch(node.expr) 1239 for qual in node.quals: 1240 self.dispatch(qual) 1241 self._span_end() 1242 1243 def visitIfExp(self, node): 1244 self._span_start("ifexp") 1245 self.dispatch(node.then) 1246 self._keyword("if", 1) 1247 self.dispatch(node.test) 1248 self._keyword("else", 1) 1249 self.dispatch(node.else_) 1250 self._span_end() 1251 1252 def visitInvert(self, node): 1253 self._visitUnary(node, "~") 1254 1255 def visitKeyword(self, node): 1256 self._span_start("keyword-arg") 1257 self.stream.write(node.name) 1258 self.stream.write("=") 1259 self.dispatch(node.expr) 1260 self._span_end() 1261 1262 def visitLambda(self, node): 1263 fn = node.unit 1264 self.units.append(fn) 1265 1266 self._span_start("lambda") 1267 self._keyword("lambda") 1268 self._parameters(fn, node) 1269 self.stream.write(": ") 1270 self._span_start("code") 1271 self.dispatch(node.code) 1272 self._span_end() 1273 self._span_end() 1274 1275 self.units.pop() 1276 1277 def visitLeftShift(self, node): 1278 self._visitBinary(node, "<<") 1279 1280 visitList = visitAssList 1281 1282 def visitListComp(self, node): 1283 self._span_start("listcomp") 1284 self.stream.write("[") 1285 self.dispatch(node.expr) 1286 for qual in node.quals: 1287 self.dispatch(qual) 1288 self.stream.write("]") 1289 self._span_end() 1290 1291 def visitListCompFor(self, node): 1292 self._span_start("listcompfor") 1293 self._keyword("for", 1) 1294 self._span_start("item") 1295 self.dispatch(node.assign) 1296 self._span_end() 1297 self._keyword("in", 1) 1298 self._span_start("collection") 1299 self.dispatch(node.list) 1300 self._span_end() 1301 for if_ in node.ifs: 1302 self.dispatch(if_) 1303 self._span_end() 1304 1305 def visitListCompIf(self, node): 1306 self._span_start("listcompif") 1307 self._span_start("conditional") 1308 self._keyword("if", 1) 1309 self.dispatch(node.test) 1310 self._span_end() 1311 self._span_end() 1312 1313 def visitMod(self, node): 1314 self._visitBinary(node, "%") 1315 1316 def visitMul(self, node): 1317 self._visitBinary(node, "*") 1318 1319 def visitName(self, node): 1320 if node._scope: 1321 scope = node._scope 1322 self._name_start() 1323 self.stream.write(node.name) 1324 self._popup_start() 1325 self._scope(node._scope, node._attr) 1326 self._popup_end() 1327 self._name_end() 1328 else: 1329 self._span(node.name) 1330 1331 def visitNot(self, node): 1332 self._span_start("not") 1333 self._keyword("not") 1334 self.dispatch(node.expr) 1335 self._span_end() 1336 1337 def visitOr(self, node): 1338 self._span_start("or") 1339 first = True 1340 for n in node.nodes: 1341 if not first: 1342 self._keyword("or", 1) 1343 self.dispatch(n) 1344 first = False 1345 self._span_end() 1346 1347 def visitPower(self, node): 1348 self._visitBinary(node, "**") 1349 1350 def visitRightShift(self, node): 1351 self._visitBinary(node, ">>") 1352 1353 def visitSlice(self, node): 1354 self._span_start("slice") 1355 self.dispatch(node.expr) 1356 self.stream.write("[") 1357 if node.lower: 1358 self.dispatch(node.lower) 1359 self.stream.write(":") 1360 if node.upper: 1361 self.dispatch(node.upper) 1362 # NOTE: Step? 1363 self.stream.write("]") 1364 self._span_end() 1365 1366 def visitSliceobj(self, node): 1367 self._span_start("sliceobj") 1368 first = True 1369 for n in node.nodes: 1370 if not first: 1371 self.stream.write(":") 1372 self.dispatch(n) 1373 self._span_end() 1374 1375 def visitSub(self, node): 1376 self._visitBinary(node, "-") 1377 1378 def visitSubscript(self, node): 1379 self._span_start("subscript") 1380 self.dispatch(node.expr) 1381 self.stream.write("[") 1382 first = True 1383 for sub in node.subs: 1384 if not first: 1385 self.stream.write(", ") 1386 self.dispatch(sub) 1387 first = False 1388 self.stream.write("]") 1389 self._span_end() 1390 1391 visitTuple = visitAssTuple 1392 1393 def visitUnaryAdd(self, node): 1394 self._visitUnary(node, "+") 1395 1396 def visitUnarySub(self, node): 1397 self._visitUnary(node, "-") 1398 1399 # Output preparation methods. 1400 1401 def _sequence(self, node): 1402 first = True 1403 for n in node.nodes: 1404 if not first: 1405 self.stream.write(", ") 1406 self.dispatch(n) 1407 first = False 1408 1409 def _mapping(self, node): 1410 first = True 1411 for k, v in node.items: 1412 if not first: 1413 self.stream.write(", ") 1414 self.dispatch(k) 1415 self.stream.write(" : ") 1416 self.dispatch(v) 1417 first = False 1418 1419 def _parameters(self, fn, node): 1420 nparams = len(fn.positional_names) 1421 ndefaults = len(fn.defaults) 1422 first_with_default = nparams - ndefaults 1423 1424 first = True 1425 for n, param in enumerate(fn.positional_names): 1426 if not first: 1427 self.stream.write(", ") 1428 1429 # Handle tuple parameters. 1430 1431 if isinstance(param, tuple): 1432 self._tuple_parameter(param, node) 1433 else: 1434 self._assname(param, node) 1435 1436 n_default = n - first_with_default 1437 if n_default >= 0: 1438 self._default(fn.defaults[n_default]) 1439 first = False 1440 1441 if fn.has_star: 1442 if not first: 1443 self.stream.write(", *") 1444 self._name(fn.star_name) 1445 1446 if fn.has_dstar: 1447 if not first: 1448 self.stream.write(", **") 1449 self._name(fn.dstar_name) 1450 1451 def _tuple_parameter(self, parameters, node): 1452 self.stream.write("(") 1453 1454 first = True 1455 for param in parameters: 1456 if not first: 1457 self.stream.write(", ") 1458 1459 # Handle tuples. 1460 1461 if isinstance(param, tuple): 1462 self._tuple_parameter(param, node) 1463 else: 1464 self._assname(param, node) 1465 1466 first = False 1467 1468 self.stream.write(")") 1469 1470 def _default(self, default): 1471 self.stream.write("=") 1472 self.dispatch(default) 1473 1474 # Statistics gathering methods. 1475 1476 def record_unknown_targets(self, node): 1477 if not node._attrs_deduced: 1478 self.program.unknown_target_nodes.append((self.units[-1], node)) 1479 elif not node._attrs_deduced_from_specific_usage: 1480 self.program.independent_target_nodes.append((self.units[-1], node)) 1481 1482 # Utility methods. 1483 1484 def _has_descendant(self, node, nodetype): 1485 if isinstance(node, nodetype): 1486 return True 1487 else: 1488 for n in node.getChildNodes(): 1489 if self._has_descendant(n, nodetype): 1490 return True 1491 return False 1492 1493 # Convenience functions. 1494 1495 def summarise(module, program, filename): 1496 stream = open(filename, "wb") 1497 try: 1498 summary = Summary(module, program) 1499 summary.to_stream(stream) 1500 finally: 1501 stream.close() 1502 1503 def annotate(module, program, filename): 1504 stream = open(filename, "wb") 1505 try: 1506 source = AnnotatedSource(module, program) 1507 source.to_stream(stream) 1508 finally: 1509 stream.close() 1510 1511 def interfaces(program, filename): 1512 stream = open(filename, "wb") 1513 try: 1514 source = Interfaces(program) 1515 source.to_stream(stream) 1516 finally: 1517 stream.close() 1518 1519 def report(program, directory): 1520 if not exists(directory): 1521 os.mkdir(directory) 1522 1523 for module in program.get_importer().get_modules(): 1524 annotate(module, program, join(directory, "%s%sxhtml" % (module.full_name(), extsep))) 1525 summarise(module, program, join(directory, "%s-summary%sxhtml" % (module.full_name(), extsep))) 1526 1527 interfaces(program, join(directory, "-interfaces%sxhtml" % extsep)) 1528 1529 # vim: tabstop=4 expandtab shiftwidth=4