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