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