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