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