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