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