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