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