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