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