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