1 #!/usr/bin/env python 2 3 """ 4 View annotated sources. 5 6 Copyright (C) 2006, 2007, 2010, 2011 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 ASTVisitor.__init__(self) 506 self.visitor = self 507 self.module = module 508 self.program = program 509 self.objtable = self.program.get_object_table() 510 self.paramtable = self.program.get_parameter_table() 511 512 def to_stream(self, stream): 513 514 "Write the annotated code to the given 'stream'." 515 516 self.stream = stream 517 self.stream.write(html_header % { 518 "title" : "Module: %s" % self.module.full_name() 519 }) 520 self.dispatch(self.module.astnode) 521 self.stream.write(html_footer) 522 523 def visitModule(self, node): 524 self.default(node) 525 526 # Statements. 527 528 def visitAssert(self, node): 529 self.stream.write("<div class='assert nowrap'>\n") 530 self._keyword("assert") 531 self.dispatch(node.test) 532 if node.fail: 533 self.stream.write(", ") 534 self.dispatch(node.fail) 535 self.stream.write("</div>\n") 536 537 def visitAssign(self, node): 538 self.stream.write("<div class='assign nowrap'>\n") 539 for lvalue in node.nodes: 540 self.dispatch(lvalue) 541 self.stream.write(" = ") 542 self.dispatch(node.expr) 543 self.stream.write("</div>\n") 544 545 def visitAugAssign(self, node): 546 self.stream.write("<div class='augassign nowrap'>\n") 547 self.dispatch(node.node) 548 self._op(node.op, operator_functions[node.op], 1) 549 self.dispatch(node.expr) 550 self.stream.write("</div>\n") 551 552 def visitBreak(self, node): 553 self.stream.write("<div class='break nowrap'>\n") 554 self._keyword("break") 555 self.stream.write("</div>\n") 556 557 def visitClass(self, node): 558 if not used_by_unit(node): 559 self._docstring('"Class %s not generated."' % node.name) 560 return 561 562 # Use inspected details where possible. 563 564 if hasattr(node, "unit"): 565 cls = node.unit 566 bases = cls.bases 567 self.stream.write("<div class='class nowrap' id='%s'>\n" % cls.full_name()) 568 else: 569 print "Warning: class %s not recognised!" % node.name 570 return 571 572 # Write the declaration line. 573 574 self.stream.write("<div>\n") 575 self._keyword("class") 576 self._object_name_def(self.module, cls, "class-name") 577 578 # Suppress the "object" class appearing alone. 579 580 if bases and not (len(bases) == 1 and bases[0].name == "object"): 581 self.stream.write("(") 582 first = 1 583 for base in bases: 584 if not first: 585 self.stream.write(", ") 586 587 self._object_name_ref(base.module, base) 588 589 first = 0 590 self.stream.write(")") 591 592 self.stream.write(":\n") 593 self.stream.write("</div>\n") 594 595 # Write the docstring and class body. 596 597 self.stream.write("<div class='body nowrap'>\n") 598 self._doc(node) 599 600 # NOTE: Some streams may not support tell. 601 602 x = self.stream.tell() 603 604 self.default(node.code) 605 606 # Check for no output. 607 608 if x == self.stream.tell(): 609 self.visitPass(None) 610 611 self.stream.write("</div>\n") 612 self.stream.write("</div>\n") 613 614 def visitContinue(self, node): 615 self.stream.write("<div class='continue nowrap'>\n") 616 self._keyword("continue") 617 self.stream.write("</div>\n") 618 619 def visitDiscard(self, node): 620 self.stream.write("<div class='discard nowrap'>\n") 621 self.default(node) 622 self.stream.write("</div>\n") 623 624 def visitFor(self, node): 625 self.stream.write("<div class='if nowrap'>\n") 626 self.stream.write("<div>\n") 627 self._keyword("for") 628 self.dispatch(node.assign) 629 self._keyword("in", 1) 630 self.dispatch(node.list) 631 self.stream.write(":\n") 632 self.stream.write("</div>\n") 633 self.stream.write("<div class='body nowrap'>\n") 634 self.dispatch(node.body) 635 self.stream.write("</div>\n") 636 if node.else_ is not None: 637 self.stream.write("<div>\n") 638 self._keyword("else", trailing=0) 639 self.stream.write(":\n") 640 self.stream.write("</div>\n") 641 self.stream.write("<div class='body nowrap'>\n") 642 self.dispatch(node.else_) 643 self.stream.write("</div>\n") 644 self.stream.write("</div>\n") 645 646 def visitFrom(self, node): 647 self.stream.write("<div class='from nowrap'>\n") 648 self._keyword("from") 649 self._name(node.modname) 650 self._keyword("import", 1) 651 first = 1 652 for name, alias in node.names: 653 if not first: 654 self.stream.write(", ") 655 if alias: 656 self.stream.write(name + " ") 657 self._keyword("as", 1) 658 self._name(alias or name) 659 first = 0 660 self.stream.write("</div>\n") 661 662 def visitFunction(self, node): 663 if not used_by_unit(node): 664 self._docstring('"Function %s not generated."' % node.name) 665 return 666 667 if hasattr(node, "unit"): 668 fn = node.unit 669 self.stream.write("<div class='function nowrap' id='%s'>\n" % fn.full_name()) 670 else: 671 print "Warning: function %s not recognised!" % node.name 672 return 673 674 # Write the declaration line. 675 676 self.stream.write("<div>\n") 677 self._keyword("def") 678 self._object_name_def(self.module, fn, "function-name") 679 680 self.stream.write("(") 681 self._parameters(fn, node) 682 self.stream.write(")") 683 self.stream.write(":\n") 684 self.stream.write("</div>\n") 685 686 self.stream.write("<div class='body nowrap'>\n") 687 self._doc(node) 688 self.dispatch(node.code) 689 self.stream.write("</div>\n") 690 self.stream.write("</div>\n") 691 692 def visitGlobal(self, node): 693 self.stream.write("<div class='global nowrap'>\n") 694 self._keyword("global") 695 first = 1 696 for name in node.names: 697 if not first: 698 self.stream.write(", ") 699 self.stream.write(name) 700 first = 0 701 self.stream.write("</div>\n") 702 703 def visitIf(self, node): 704 self.stream.write("<div class='if nowrap'>\n") 705 first = 1 706 for compare, stmt in node.tests: 707 self.stream.write("<div>\n") 708 if first: 709 self._keyword("if") 710 else: 711 self._keyword("elif") 712 self.dispatch(compare) 713 self.stream.write(":\n") 714 self.stream.write("</div>\n") 715 self.stream.write("<div class='body nowrap'>\n") 716 self.dispatch(stmt) 717 self.stream.write("</div>\n") 718 first = 0 719 if node.else_ is not None: 720 self.stream.write("<div>\n") 721 self._keyword("else", trailing=0) 722 self.stream.write(":\n") 723 self.stream.write("</div>\n") 724 self.stream.write("<div class='body nowrap'>\n") 725 self.dispatch(node.else_) 726 self.stream.write("</div>\n") 727 self.stream.write("</div>\n") 728 729 def visitImport(self, node): 730 self.stream.write("<div class='import nowrap'>\n") 731 self._keyword("import") 732 first = 1 733 for name, alias in node.names: 734 if not first: 735 self.stream.write(",\n") 736 if alias: 737 self.stream.write(name + " ") 738 self._keyword("as", 1) 739 self._name(alias or name) 740 first = 0 741 self.stream.write("</div>\n") 742 743 def visitPass(self, node): 744 self.stream.write("<div class='pass nowrap'>\n") 745 self._keyword("pass") 746 self.stream.write("</div>\n") 747 748 def visitPrint(self, node): 749 self.stream.write("<div class='print nowrap'>\n") 750 self._keyword("print") 751 if node.dest is not None: 752 self.stream.write(">>\n") 753 self.dispatch(node.dest) 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 = 1 766 for n in node.nodes: 767 if not first: 768 self.stream.write(",\n") 769 self.dispatch(n) 770 first = 0 771 self.stream.write("</div>\n") 772 773 def visitRaise(self, node): 774 self.stream.write("<div class='raise nowrap'>\n") 775 self._keyword("raise") 776 if node.expr1 is not None: 777 self.dispatch(node.expr1) 778 if node.expr2 is not None: 779 self.stream.write(",\n") 780 self.dispatch(node.expr2) 781 if node.expr3 is not None: 782 self.stream.write(",\n") 783 self.dispatch(node.expr3) 784 self.stream.write("</div>\n") 785 786 def visitReturn(self, node): 787 self.stream.write("<div class='return nowrap'>\n") 788 self._keyword("return") 789 self.dispatch(node.value) 790 self.stream.write("</div>\n") 791 792 def visitStmt(self, node): 793 self.stream.write("<div class='stmt nowrap'>\n") 794 self.default(node) 795 self.stream.write("</div>\n") 796 797 def visitTryExcept(self, node): 798 self.stream.write("<div class='tryexcept nowrap'>\n") 799 self.stream.write("<div>\n") 800 self._keyword("try", trailing=0) 801 self.stream.write(":\n") 802 self.stream.write("</div>\n") 803 self.stream.write("<div class='body nowrap'>\n") 804 self.dispatch(node.body) 805 self.stream.write("</div>\n") 806 for spec, assign, statement in node.handlers: 807 self.stream.write("<div>\n") 808 self._keyword("except") 809 if spec is not None: 810 self.dispatch(spec) 811 if assign is not None: 812 self.stream.write(",\n") 813 self.dispatch(assign) 814 self.stream.write(":\n") 815 self.stream.write("</div>\n") 816 self.stream.write("<div class='body nowrap'>\n") 817 self.dispatch(statement) 818 self.stream.write("</div>\n") 819 if node.else_ is not None: 820 self.stream.write("<div>\n") 821 self._keyword("else", trailing=0) 822 self.stream.write(":\n") 823 self.stream.write("</div>\n") 824 self.stream.write("<div class='body nowrap'>\n") 825 self.dispatch(node.else_) 826 self.stream.write("</div>\n") 827 self.stream.write("</div>\n") 828 829 def visitTryFinally(self, node): 830 self.stream.write("<div class='tryfinally nowrap'>\n") 831 self.stream.write("<div>\n") 832 self._keyword("try", trailing=0) 833 self.stream.write(":\n") 834 self.stream.write("</div>\n") 835 self.stream.write("<div class='body nowrap'>\n") 836 self.dispatch(node.body) 837 self.stream.write("</div>\n") 838 self.stream.write("<div>\n") 839 self._keyword("finally", trailing=0) 840 self.stream.write(":\n") 841 self.stream.write("</div>\n") 842 self.stream.write("<div class='body nowrap'>\n") 843 self.dispatch(node.final) 844 self.stream.write("</div>\n") 845 self.stream.write("</div>\n") 846 847 def visitWhile(self, node): 848 self.stream.write("<div class='while nowrap'>\n") 849 self.stream.write("<div>\n") 850 self._keyword("while") 851 self.dispatch(node.test) 852 self.stream.write(":\n") 853 self.stream.write("</div>\n") 854 self.stream.write("<div class='body nowrap'>\n") 855 self.dispatch(node.body) 856 self.stream.write("</div>\n") 857 if node.else_ is not None: 858 self.stream.write("<div>\n") 859 self._keyword("else", trailing=0) 860 self.stream.write(":\n") 861 self.stream.write("</div>\n") 862 self.stream.write("<div class='body nowrap'>\n") 863 self.dispatch(node.else_) 864 self.stream.write("</div>\n") 865 self.stream.write("</div>\n") 866 867 # Expression-related helper methods. 868 869 def _visitBitBinary(self, node, name, symbol): 870 self._span_start(name) 871 first = 1 872 for node in node.nodes: 873 if not first: 874 self._op(symbol, name, 1) 875 self.dispatch(node) 876 first = 0 877 self._span_end() 878 879 def _visitBinary(self, node, name, symbol): 880 self._span_start(name) 881 self.dispatch(node.left) 882 self._op(symbol, name, 1) 883 self.dispatch(node.right) 884 self._span_end() 885 886 def _visitUnary(self, node, name, symbol): 887 self._span_start(name) 888 self._op(symbol, name) 889 self.dispatch(node.expr) 890 self._span_end() 891 892 # Expressions. 893 894 def visitAdd(self, node): 895 self._visitBinary(node, "add", "+") 896 897 def visitAnd(self, node): 898 self._span_start("and") 899 first = 1 900 for n in node.nodes: 901 if not first: 902 self._keyword("and", 1) 903 self.dispatch(n) 904 first = 0 905 self._span_end() 906 907 def visitAssAttr(self, node): 908 target_names = ["%s%s" % (is_static and "class " or "", target_name) 909 for target_name, is_static in self.possible_accessor_types(node)] 910 self._span_start("assattr") 911 self._accessor_start(target_names) 912 self.dispatch(node.expr) 913 self._accessor_end(target_names) 914 self._span_start("attr") 915 self.stream.write(".") 916 self._span(node.attrname, "attrname") 917 self._span_end() 918 self._span_end() 919 920 def visitAssList(self, node): 921 self._span_start("list") 922 self.stream.write("[") 923 self._sequence(node) 924 self.stream.write("]") 925 self._span_end() 926 927 def visitAssName(self, node): 928 self._assname(node.name, node) 929 930 def visitAssTuple(self, node): 931 self._span_start("tuple") 932 self.stream.write("(") 933 self._sequence(node) 934 self.stream.write(")") 935 self._span_end() 936 937 def visitBitand(self, node): 938 self._visitBitBinary(node, "bitand", "&") 939 940 def visitBitor(self, node): 941 self._visitBitBinary(node, "bitor", "|") 942 943 def visitBitxor(self, node): 944 self._visitBitBinary(node, "bitxor", "^") 945 946 def visitCallFunc(self, node): 947 self._span_start("callfunc") 948 self.dispatch(node.node) 949 self._span_start("call") 950 self.stream.write("(") 951 first = 1 952 for arg in node.args: 953 if not first: 954 self.stream.write(", ") 955 self.dispatch(arg) 956 first = 0 957 if node.star_args is not None: 958 if not first: 959 self.stream.write(", *") 960 self.dispatch(node.star_args) 961 first = 0 962 if node.dstar_args is not None: 963 if not first: 964 self.stream.write(", **") 965 self.dispatch(node.dstar_args) 966 first = 0 967 self.stream.write(")") 968 self._span_end() 969 self._span_end() 970 971 def visitCompare(self, node): 972 self._span_start("compare") 973 self.dispatch(node.expr) 974 for op_name, expr in node.ops: 975 self._op(op_name, operator_functions.get(op_name), 1) 976 self.dispatch(expr) 977 self._span_end() 978 979 def visitConst(self, node): 980 if isinstance(node.value, (str, unicode)): 981 self._span_start("str") 982 self.stream.write(self._text(repr(node.value))) 983 if isinstance(node.value, (str, unicode)): 984 self._span_end() 985 986 def visitDict(self, node): 987 self._span_start("dict") 988 self.stream.write("{") 989 self._mapping(node) 990 self.stream.write("}") 991 self._span_end() 992 993 def visitDiv(self, node): 994 self._visitBinary(node, "div", "/") 995 996 def visitFloorDiv(self, node): 997 self._visitBinary(node, "floordiv", "//") 998 999 def visitGetattr(self, node): 1000 target_names = ["%s%s" % (is_static and "class " or "", target_name) 1001 for target_name, is_static in self.possible_accessor_types(node)] 1002 self._span_start("getattr") 1003 self._accessor_start(target_names) 1004 self.dispatch(node.expr) 1005 self._accessor_end(target_names) 1006 self._span_start("attr") 1007 self.stream.write(".") 1008 self._span(node.attrname, "attrname") 1009 self._span_end() 1010 self._span_end() 1011 1012 def visitKeyword(self, node): 1013 self._span_start("keyword-arg") 1014 self.stream.write(node.name) 1015 self.stream.write("=") 1016 self.dispatch(node.expr) 1017 self._span_end() 1018 1019 def visitLambda(self, node): 1020 if hasattr(node, "unit"): 1021 fn = node.unit 1022 else: 1023 print "Warning: function %s not recognised!" % node.name 1024 return 1025 1026 self._span_start("lambda") 1027 self._keyword("lambda") 1028 self._parameters(fn, node) 1029 self.stream.write(": ") 1030 self._span_start("code") 1031 self.dispatch(node.code) 1032 self._span_end() 1033 self._span_end() 1034 1035 def visitLeftShift(self, node): 1036 self._visitBinary(node, "lshift", "<<") 1037 1038 visitList = visitAssList 1039 1040 def visitListComp(self, node): 1041 self._span_start("listcomp") 1042 self.stream.write("[") 1043 self.dispatch(node.expr) 1044 for qual in node.quals: 1045 self.dispatch(qual) 1046 self.stream.write("]") 1047 self._span_end() 1048 1049 def visitListCompFor(self, node): 1050 self._span_start("listcompfor") 1051 self._keyword("for", 1) 1052 self._span_start("item") 1053 self.dispatch(node.assign) 1054 self._span_end() 1055 self._keyword("in", 1) 1056 self._span_start("collection") 1057 self.dispatch(node.list) 1058 self._span_end() 1059 for if_ in node.ifs: 1060 self.dispatch(if_) 1061 self._span_end() 1062 1063 def visitListCompIf(self, node): 1064 self._span_start("listcompif") 1065 self._span_start("conditional") 1066 self._keyword("if", 1) 1067 self.dispatch(node.test) 1068 self._span_end() 1069 self._span_end() 1070 1071 def visitMod(self, node): 1072 self._visitBinary(node, "mod", "%") 1073 1074 def visitMul(self, node): 1075 self._visitBinary(node, "mul", "*") 1076 1077 def visitName(self, node): 1078 if hasattr(node, "_scope"): 1079 scope = node._scope 1080 self._name_start() 1081 self.stream.write(node.name) 1082 self._popup_start() 1083 self._scope(scope) 1084 self._popup_end() 1085 self._name_end() 1086 else: 1087 self._span(node.name) 1088 1089 def visitNot(self, node): 1090 self._span_start("not") 1091 self._keyword("not") 1092 self.dispatch(node.expr) 1093 self._span_end() 1094 1095 def visitOr(self, node): 1096 self._span_start("or") 1097 first = 1 1098 for n in node.nodes: 1099 if not first: 1100 self._keyword("or", 1) 1101 self.dispatch(n) 1102 first = 0 1103 self._span_end() 1104 1105 def visitPower(self, node): 1106 self._visitBinary(node, "pow", "**") 1107 1108 def visitRightShift(self, node): 1109 self._visitBinary(node, "rshift", ">>") 1110 1111 def visitSlice(self, node): 1112 self._span_start("slice") 1113 self.dispatch(node.expr) 1114 self.stream.write("[") 1115 if node.lower: 1116 self.dispatch(node.lower) 1117 self.stream.write(":") 1118 if node.upper: 1119 self.dispatch(node.upper) 1120 # NOTE: Step? 1121 self.stream.write("]") 1122 self._span_end() 1123 1124 def visitSliceobj(self, node): 1125 self._span_start("sliceobj") 1126 first = 1 1127 for n in node.nodes: 1128 if not first: 1129 self.stream.write(":") 1130 self.dispatch(n) 1131 self._span_end() 1132 1133 def visitSub(self, node): 1134 self._visitBinary(node, "sub", "-") 1135 1136 def visitSubscript(self, node): 1137 self._span_start("subscript") 1138 self.dispatch(node.expr) 1139 self.stream.write("[") 1140 first = 1 1141 for sub in node.subs: 1142 if not first: 1143 self.stream.write(", ") 1144 self.dispatch(sub) 1145 first = 0 1146 self.stream.write("]") 1147 self._span_end() 1148 1149 visitTuple = visitAssTuple 1150 1151 def visitUnaryAdd(self, node): 1152 self._visitUnary(node, "add", "+") 1153 1154 def visitUnarySub(self, node): 1155 self._visitUnary(node, "sub", "-") 1156 1157 # Output preparation methods. 1158 1159 def _sequence(self, node): 1160 first = 1 1161 for n in node.nodes: 1162 if not first: 1163 self.stream.write(", ") 1164 self.dispatch(n) 1165 first = 0 1166 1167 def _mapping(self, node): 1168 first = 1 1169 for k, v in node.items: 1170 if not first: 1171 self.stream.write(", ") 1172 self.dispatch(k) 1173 self.stream.write(" : ") 1174 self.dispatch(v) 1175 first = 0 1176 1177 def _parameters(self, fn, node): 1178 nparams = len(fn.positional_names) 1179 ndefaults = len(fn.defaults) 1180 first_with_default = nparams - ndefaults 1181 1182 first = 1 1183 for n, param in enumerate(fn.positional_names): 1184 if not first: 1185 self.stream.write(", ") 1186 1187 # Handle tuple parameters. 1188 1189 if isinstance(param, tuple): 1190 self._tuple_parameter(param, node) 1191 else: 1192 self._assname(param, node) 1193 1194 n_default = n - first_with_default 1195 if n_default >= 0: 1196 self._default(fn.defaults[n_default]) 1197 first = 0 1198 1199 if fn.has_star: 1200 if not first: 1201 self.stream.write(", *") 1202 self._name(fn.star_name) 1203 1204 if fn.has_dstar: 1205 if not first: 1206 self.stream.write(", **") 1207 self._name(fn.dstar_name) 1208 1209 def _tuple_parameter(self, parameters, node): 1210 self.stream.write("(") 1211 1212 first = 1 1213 for param in parameters: 1214 if not first: 1215 self.stream.write(", ") 1216 1217 # Handle tuples. 1218 1219 if isinstance(param, tuple): 1220 self._tuple_parameter(param, node) 1221 else: 1222 self._assname(param, node) 1223 1224 first = 0 1225 1226 self.stream.write(")") 1227 1228 def _default(self, default): 1229 self.stream.write("=") 1230 self.dispatch(default) 1231 1232 # Convenience functions. 1233 1234 def summarise(module, program, filename): 1235 stream = open(filename, "wb") 1236 try: 1237 summary = Summary(module, program) 1238 summary.to_stream(stream) 1239 finally: 1240 stream.close() 1241 1242 def annotate(module, program, filename): 1243 stream = open(filename, "wb") 1244 try: 1245 source = AnnotatedSource(module, program) 1246 source.to_stream(stream) 1247 finally: 1248 stream.close() 1249 1250 def interfaces(program, filename): 1251 stream = open(filename, "wb") 1252 try: 1253 source = Interfaces(program) 1254 source.to_stream(stream) 1255 finally: 1256 stream.close() 1257 1258 def report(program, directory): 1259 if not exists(directory): 1260 os.mkdir(directory) 1261 1262 for module in program.get_importer().get_modules(): 1263 annotate(module, program, join(directory, "%s%sxhtml" % (module.full_name(), extsep))) 1264 summarise(module, program, join(directory, "%s-summary%sxhtml" % (module.full_name(), extsep))) 1265 1266 interfaces(program, join(directory, "-interfaces%sxhtml" % extsep)) 1267 1268 # vim: tabstop=4 expandtab shiftwidth=4