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