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