1 #!/usr/bin/env python 2 3 """ 4 View annotated sources. 5 6 Copyright (C) 2006 Paul Boddie <paul@boddie.org.uk> 7 8 This software is free software; you can redistribute it and/or 9 modify it under the terms of the GNU General Public License as 10 published by the Free Software Foundation; either version 2 of 11 the License, or (at your option) any later version. 12 13 This software is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 GNU General Public License for more details. 17 18 You should have received a copy of the GNU General Public 19 License along with this library; see the file LICENCE.txt 20 If not, write to the Free Software Foundation, Inc., 21 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 22 """ 23 24 from compiler.visitor import ASTVisitor 25 from simplified import * 26 import sys 27 import os 28 import textwrap 29 30 # Exceptions. 31 32 class ViewerError(SimplifiedError): 33 34 "An error in viewing." 35 36 pass 37 38 # Classes. 39 40 class Viewer(ASTVisitor): 41 42 """ 43 A viewing visitor for AST nodes. 44 """ 45 46 def __init__(self, stream): 47 ASTVisitor.__init__(self) 48 self.cached_files = {} 49 self.printed_lines = {} 50 self.visitor = self 51 self.stream = stream 52 53 def process(self, module): 54 self.dispatch(module) 55 56 def dispatch(self, node): 57 self.dispatch_only(node) 58 ASTVisitor.dispatch(self, node) 59 60 def dispatch_only(self, node, every_time=0): 61 self.print_line(getattr(node, "filename", None), getattr(node, "lineno", None), every_time) 62 63 def print_line(self, filename, lineno, every_time): 64 last_printed = self.printed_lines.get(filename, 0) 65 if lineno > last_printed or every_time: 66 self.stream.write(self.get_line(filename, lineno)) 67 self.printed_lines[filename] = lineno 68 69 def get_line(self, filename, lineno): 70 if filename is None or lineno is None: 71 return "" 72 73 if self.cached_files.has_key(filename): 74 lines = self.cached_files[filename] 75 else: 76 f = open(filename) 77 try: 78 self.cached_files[filename] = lines = f.readlines() 79 finally: 80 f.close() 81 82 try: 83 return lines[lineno - 1] 84 except IndexError: 85 return "" 86 87 def report(self, exc): 88 self.stream.write("Exception was:\n\n" + str(exc.exc) + "\n\n") 89 self.stream.write("Nodes:\n\n") 90 for node in exc.nodes: 91 self.stream.write(repr(node) + "\n") 92 if node is not None and hasattr(node, "original"): 93 self.dispatch_only(node.original, every_time=1) 94 if hasattr(exc.nodes[0], "original"): 95 self.stream.write("\nOriginal node was:\n\n" + repr(exc.nodes[0].original) + "\n") 96 self.stream.write("\nSimplified node was:\n\n") 97 exc.nodes[0].pprint(stream=self.stream) 98 99 # HTML-related output production. 100 101 html_header = """<?xml version="1.0" encoding="iso-8859-15"?> 102 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 103 <html xmlns="http://www.w3.org/1999/xhtml"> 104 <head> 105 <title>Module</title> 106 <style type="text/css"> 107 body { 108 padding-top: 4em; padding-bottom: 4em; 109 font-size: 14pt; font-family: monospace; 110 background-color: black; color: white; 111 } 112 113 .class { margin-bottom: 1em; } 114 .function { margin-bottom: 1em; } 115 .body { padding-left: 2em; } 116 .keyword { color: yellow; } 117 .comment { color: blue; } 118 .str { color: #FF00FF; } 119 .doc { color: #FF00FF; margin-bottom: 1em; } 120 .invocation a { color: white; text-decoration: none; } 121 122 .popup { 123 display: none; z-index: 2; 124 position: absolute; top: 1em; left: 0.5em; 125 padding: 0.2em; background-color: #000000; 126 border: 2px solid #dddddd; 127 } 128 129 .invocations { 130 padding: 0.5em; background-color: #770000; 131 clear: all; 132 } 133 134 .types { 135 padding: 0.5em; background-color: #0000FF; 136 float: right; 137 } 138 139 .raises { 140 padding: 0.5em; background-color: #7700FF; 141 float: right; 142 } 143 144 .scopes { 145 padding: 0.5em; background-color: #007700; 146 float: left; 147 } 148 149 .non-writes, .non-accesses { 150 padding: 0.5em; background-color: #FF0000; 151 float: right; 152 } 153 154 .op, 155 .name, 156 .attr, 157 .conditional, 158 .operator, 159 .iterator, 160 .call, 161 .returns 162 { 163 position: relative; 164 } 165 166 .op:hover > .popup, 167 .name:hover > .popup, 168 .attr:hover > .popup, 169 .conditional:hover > .popup, 170 .operator:hover > .popup, 171 .iterator:hover > .popup, 172 .call:hover > .popup, 173 .returns:hover > .popup 174 { 175 display: block; 176 } 177 178 </style> 179 </head> 180 <body> 181 """ 182 183 html_footer = """</body> 184 </html> 185 """ 186 187 # Browser classes. 188 189 class Browser(ASTVisitor): 190 191 """ 192 A browsing visitor for AST nodes. 193 194 Covered: Add, And, AssAttr, AssList, AssName, AssTuple, Assign, AugAssign, 195 Break, CallFunc, Class, Compare, Const, Continue, Dict, Discard, 196 Div, FloorDiv, For, Function, Getattr, If, Keyword, Lambda, List, 197 Module, Mul, Name, Not, Or, Pass, Print, Printnl, Raise, Return, 198 Slice, Stmt, Sub, Subscript, TryExcept, TryFinally, Tuple, 199 UnaryAdd, UnarySub, While. 200 201 Missing: Assert, Backquote, Bitand, Bitor, Bitxor, Decorators, Ellipsis, 202 Exec, From, Global, Import, Invert, LeftShift, ListComp, 203 ListCompFor, ListCompIf, Mod, Power, RightShift, Sliceobj, Yield. 204 """ 205 206 def __init__(self, stream): 207 ASTVisitor.__init__(self) 208 self.visitor = self 209 self.stream = stream 210 211 def process(self, module): 212 self.stream.write(html_header) 213 self.dispatch(module) 214 self.stream.write(html_footer) 215 216 def dispatch(self, node): 217 ASTVisitor.dispatch(self, node) 218 #try: 219 # ASTVisitor.dispatch(self, node) 220 #except ViewerError, exc: 221 # exc.add(node) 222 # raise 223 #except Exception, exc: 224 # raise ViewerError(exc, node) 225 226 def visitModule(self, node): 227 self.default(node) 228 229 # Statements. 230 231 def visitAssign(self, node): 232 self.stream.write("<div class='assign'>\n") 233 for lvalue in node.nodes: 234 self.dispatch(lvalue) 235 self.stream.write("=\n") 236 self.dispatch(node.expr) 237 self.stream.write("</div>\n") 238 239 def visitAugAssign(self, node): 240 self.stream.write("<div class='augassign'>\n") 241 self.dispatch(node.node) 242 self.stream.write("%s\n" % node.op) 243 self.dispatch(node.expr) 244 self.stream.write("</div>\n") 245 246 def visitBreak(self, node): 247 self.stream.write("<div class='break'>\n") 248 self._keyword("break") 249 self.stream.write("</div>\n") 250 251 def visitClass(self, node): 252 definition = node._node 253 structure = definition.expr.ref 254 self.stream.write("<div class='class' id='%s'>\n" % self._url(structure.full_name())) 255 self.stream.write("<div>\n") 256 self._keyword("class") 257 self._name_start(structure.name) 258 self._popup_start() 259 self._scopes(definition) 260 self._popup_end() 261 self._name_end() 262 bases = structure.bases 263 264 # Suppress the "object" class appearing alone. 265 266 if bases and not (len(bases) == 1 and bases[0].name == "object"): 267 self.stream.write("(") 268 first = 1 269 for base in bases: 270 if not first: 271 self.stream.write(",\n") 272 self._name_start(base.name) 273 self._popup_start() 274 self._types(base) 275 self._scopes(base) 276 self._popup_end() 277 self._name_end() 278 first = 0 279 self.stream.write(")") 280 281 self.stream.write(":\n") 282 self._comment(self._text(structure.full_name())) 283 self.stream.write("</div>\n") 284 285 self.stream.write("<div class='body'>\n") 286 self._doc(node) 287 self.dispatch(node.code) 288 self.stream.write("</div>\n") 289 self.stream.write("</div>\n") 290 291 def visitContinue(self, node): 292 self.stream.write("<div class='continue'>\n") 293 self._keyword("continue") 294 self.stream.write("</div>\n") 295 296 def visitDiscard(self, node): 297 self.stream.write("<div class='discard'>\n") 298 self.default(node) 299 self.stream.write("</div>\n") 300 301 def visitFor(self, node): 302 self.stream.write("<div class='if'>\n") 303 self.stream.write("<div>\n") 304 self.stream.write("<span class='iterator'>\n") 305 self._keyword("for") 306 self._popup_start() 307 self._invocations(node._node.code[1].expr.ref.code[0].body[0].code[0].expr) # Link to next call in subprogram. 308 self._popup_end() 309 self.stream.write("</span>\n") 310 self.dispatch(node.assign) 311 self.stream.write("<span class='iterator'>\n") 312 self._keyword("in") 313 self._popup_start() 314 self._invocations(node._node.code[0].expr) # Link to __iter__ call. 315 self._popup_end() 316 self.stream.write("</span>\n") 317 self.dispatch(node.list) 318 self.stream.write(":\n") 319 self.stream.write("</div>\n") 320 self.stream.write("<div class='body'>\n") 321 self.dispatch(node.body) 322 self.stream.write("</div>\n") 323 if node.else_ is not None: 324 self.stream.write("<div>\n") 325 self._keyword("else") 326 self.stream.write(":\n") 327 self.stream.write("</div>\n") 328 self.stream.write("<div class='body'>\n") 329 self.dispatch(node.else_) 330 self.stream.write("</div>\n") 331 self.stream.write("</div>\n") 332 333 def visitFunction(self, node): 334 definition = node._node 335 subprogram = definition.expr.ref 336 self.stream.write("<div class='function' id='%s'>\n" % self._url(subprogram.full_name())) 337 self.stream.write("<div>\n") 338 self._keyword("def") 339 self._name_start(subprogram.name) 340 self._popup_start() 341 self._scopes(definition) 342 self._raises(subprogram) 343 self._popup_end() 344 self._name_end() 345 self.stream.write("(") 346 self._parameters(subprogram) 347 self.stream.write(")") 348 self.stream.write(":\n") 349 self._comment(self._text(subprogram.full_name())) 350 self.stream.write("</div>\n") 351 352 self.stream.write("<div class='body'>\n") 353 self._doc(node) 354 self.dispatch(node.code) 355 self.stream.write("</div>\n") 356 self.stream.write("</div>\n") 357 358 def visitIf(self, node): 359 self.stream.write("<div class='if'>\n") 360 first = 1 361 conditional = node._node 362 for compare, stmt in node.tests: 363 self.stream.write("<div>\n") 364 self.stream.write("<span class='conditional'>\n") 365 if first: 366 self._keyword("if") 367 else: 368 self._keyword("elif") 369 self._popup_start() 370 self._invocations(conditional.test) 371 self._popup_end() 372 self.stream.write("</span>\n") 373 self.dispatch(compare) 374 self.stream.write(":\n") 375 self.stream.write("</div>\n") 376 self.stream.write("<div class='body'>\n") 377 self.dispatch(stmt) 378 self.stream.write("</div>\n") 379 if conditional.else_: 380 conditional = conditional.else_[0] 381 else: 382 conditional = None 383 first = 0 384 if node.else_ is not None: 385 self.stream.write("<div>\n") 386 self._keyword("else") 387 self.stream.write(":\n") 388 self.stream.write("</div>\n") 389 self.stream.write("<div class='body'>\n") 390 self.dispatch(node.else_) 391 self.stream.write("</div>\n") 392 self.stream.write("</div>\n") 393 394 def visitPass(self, node): 395 self.stream.write("<div class='pass'>\n") 396 self._keyword("pass") 397 self.stream.write("</div>\n") 398 399 def visitPrint(self, node): 400 self.stream.write("<div class='print'>\n") 401 self._keyword("print") 402 if node.dest is not None: 403 self.stream.write(">>\n") 404 self.dispatch(node.dest) 405 for n in node.nodes: 406 self.dispatch(n) 407 self.stream.write(",\n") 408 self.stream.write("</div>\n") 409 410 def visitPrintnl(self, node): 411 self.stream.write("<div class='printnl'>\n") 412 self._keyword("print") 413 if node.dest is not None: 414 self.stream.write(">>\n") 415 self.dispatch(node.dest) 416 first = 1 417 for n in node.nodes: 418 if not first: 419 self.stream.write(",\n") 420 self.dispatch(n) 421 first = 0 422 self.stream.write("</div>\n") 423 424 def visitRaise(self, node): 425 self.stream.write("<div class='raise'>\n") 426 self._keyword("raise") 427 self.dispatch(node.expr1) 428 if node.expr2 is not None: 429 self.stream.write(",\n") 430 self.dispatch(node.expr2) 431 if node.expr3 is not None: 432 self.stream.write(",\n") 433 self.dispatch(node.expr3) 434 self.stream.write("</div>\n") 435 436 def visitReturn(self, node): 437 self.stream.write("<div class='return'>\n") 438 self.stream.write("<span class='returns'>\n") 439 self._keyword("return") 440 self._popup_start() 441 self._types(node._node) 442 self._popup_end() 443 self.stream.write("</span>\n") 444 self.dispatch(node.value) 445 self.stream.write("</div>\n") 446 447 def visitStmt(self, node): 448 self.stream.write("<div class='stmt'>\n") 449 self.default(node) 450 self.stream.write("</div>\n") 451 452 def visitTryExcept(self, node): 453 self.stream.write("<div class='tryexcept'>\n") 454 self.stream.write("<div>\n") 455 self._keyword("try") 456 self.stream.write(":\n") 457 self.stream.write("</div>\n") 458 self.stream.write("<div class='body'>\n") 459 self.dispatch(node.body) 460 self.stream.write("</div>\n") 461 for spec, assign, statement in node.handlers: 462 self.stream.write("<div>\n") 463 self._keyword("except") 464 if spec is not None: 465 self.dispatch(spec) 466 if assign is not None: 467 self.stream.write(",\n") 468 self.dispatch(assign) 469 self.stream.write(":\n") 470 self.stream.write("</div>\n") 471 self.stream.write("<div class='body'>\n") 472 self.dispatch(statement) 473 self.stream.write("</div>\n") 474 if node.else_ is not None: 475 self.stream.write("<div>\n") 476 self._keyword("else") 477 self.stream.write(":\n") 478 self.stream.write("</div>\n") 479 self.stream.write("<div class='body'>\n") 480 self.dispatch(node.else_) 481 self.stream.write("</div>\n") 482 self.stream.write("</div>\n") 483 484 def visitTryFinally(self, node): 485 self.stream.write("<div class='tryfinally'>\n") 486 self.stream.write("<div>\n") 487 self._keyword("try") 488 self.stream.write(":\n") 489 self.stream.write("</div>\n") 490 self.stream.write("<div class='body'>\n") 491 self.dispatch(node.body) 492 self.stream.write("</div>\n") 493 self.stream.write("<div>\n") 494 self._keyword("finally") 495 self.stream.write(":\n") 496 self.stream.write("</div>\n") 497 self.stream.write("<div class='body'>\n") 498 self.dispatch(node.final) 499 self.stream.write("</div>\n") 500 self.stream.write("</div>\n") 501 502 def visitWhile(self, node): 503 self.stream.write("<div class='while'>\n") 504 self.stream.write("<div>\n") 505 self.stream.write("<span class='conditional'>\n") 506 self._keyword("while") 507 self._popup_start() 508 self._invocations(node._node.expr.ref.code[0].test) 509 self._popup_end() 510 self.stream.write("</span>\n") 511 self.dispatch(node.test) 512 self.stream.write(":\n") 513 self.stream.write("</div>\n") 514 self.stream.write("<div class='body'>\n") 515 self.dispatch(node.body) 516 self.stream.write("</div>\n") 517 if node.else_ is not None: 518 self.stream.write("<div>\n") 519 self._keyword("else") 520 self.stream.write(":\n") 521 self.stream.write("</div>\n") 522 self.stream.write("<div class='body'>\n") 523 self.dispatch(node.else_) 524 self.stream.write("</div>\n") 525 self.stream.write("</div>\n") 526 527 # Expression-related helper methods. 528 529 def _visitBinary(self, node, name, symbol): 530 self.stream.write("<span class='%s'>\n" % name) 531 self.dispatch(node.left) 532 self.stream.write("<span class='operator'>\n") 533 self.stream.write(symbol) 534 self._popup_start() 535 self.stream.write("<div class='invocations'>\n") 536 self._invocations_list(node._node.body[0].expr) # NOTE: See visitAdd in simplify. 537 self._invocations_list(node._node.handler[0].body[0].expr) # NOTE: See visitAdd in simplify. 538 self.stream.write("</div>\n") 539 self._popup_end() 540 self.stream.write("</span>\n") 541 self.dispatch(node.right) 542 self.stream.write("</span>") 543 544 def _visitUnary(self, node, name, symbol): 545 self.stream.write("<span class='%s'>\n" % name) 546 self.stream.write("<span class='operator'>\n") 547 self.stream.write(symbol) 548 self._popup_start() 549 self.stream.write("<div class='invocations'>\n") 550 self._invocations_list(node._node) # NOTE: See _visitUnary in simplify. 551 self.stream.write("</div>\n") 552 self._popup_end() 553 self.stream.write("</span>\n") 554 self.dispatch(node.expr) 555 self.stream.write("</span>") 556 557 # Expressions. 558 559 def visitAdd(self, node): 560 self._visitBinary(node, "add", "+") 561 562 def visitAnd(self, node): 563 self.stream.write("<span class='and'>\n") 564 first = 1 565 for n in node.nodes: 566 if not first: 567 self._keyword("and") 568 self.dispatch(n) 569 first = 0 570 self.stream.write("</span>") 571 572 def visitAssAttr(self, node): 573 self.stream.write("<span class='assattr'>\n") 574 self.dispatch(node.expr) 575 self.stream.write("<span class='attr'>\n") 576 self.stream.write(".%s\n" % self._text(node.attrname)) 577 if hasattr(node, "_node"): 578 self._popup_start() 579 self._types(node._node) 580 self._scopes(node._node) 581 self._popup_end() 582 else: 583 raise ValueError, node 584 self.stream.write("</span>\n") 585 self.stream.write("</span>\n") 586 587 def visitAssList(self, node): 588 self.stream.write("<span class='list'>\n") 589 self.stream.write("[") 590 self._sequence(node) 591 self.stream.write("]\n") 592 self.stream.write("</span>\n") 593 594 def visitAssName(self, node): 595 if hasattr(node, "_node"): 596 self._name_start(node._node.name) 597 self._popup_start() 598 self._types(node._node.expr) 599 self._scopes(node._node) 600 self._popup_end() 601 self._name_end() 602 else: 603 raise ValueError, node 604 self._name(node.name) 605 606 def visitAssTuple(self, node): 607 self.stream.write("<span class='tuple'>\n") 608 self.stream.write("(") 609 self._sequence(node) 610 self.stream.write(")\n") 611 self.stream.write("</span>\n") 612 613 def visitCallFunc(self, node): 614 self.stream.write("<span class='callfunc'>\n") 615 self.dispatch(node.node) 616 self.stream.write("<span class='call'>\n") 617 self.stream.write("(") 618 self._popup_start() 619 self._invocations(node._node) 620 self._popup_end() 621 self.stream.write("</span>\n") 622 first = 1 623 for arg in node.args: 624 if not first: 625 self.stream.write(",\n") 626 self.dispatch(arg) 627 first = 0 628 if node.star_args is not None: 629 if not first: 630 self.stream.write(", *\n") 631 self.dispatch(node.star_args) 632 first = 0 633 if node.dstar_args is not None: 634 if not first: 635 self.stream.write(", **\n") 636 self.dispatch(node.dstar_args) 637 first = 0 638 self.stream.write(")\n") 639 self.stream.write("</span>\n") 640 641 def visitCompare(self, node): 642 self.stream.write("<span class='compare'>\n") 643 self.dispatch(node.expr) 644 for (op_name, expr), _op in map(None, node.ops, node._ops): 645 self.stream.write("<span class='op'>\n") 646 self.stream.write(op_name) 647 self._popup_start() 648 self._op(op_name, _op) 649 self._popup_end() 650 self.stream.write("</span>\n") 651 self.dispatch(expr) 652 self.stream.write("</span>\n") 653 654 def visitConst(self, node): 655 self.stream.write(repr(node.value)) 656 657 def visitDiv(self, node): 658 self._visitBinary(node, "div", "/") 659 660 def visitFloorDiv(self, node): 661 self._visitBinary(node, "floordiv", "//") 662 663 def visitGetattr(self, node): 664 self.stream.write("<span class='getattr'>\n") 665 self.dispatch(node.expr) 666 self.stream.write("<span class='attr'>\n") 667 self.stream.write(".%s\n" % self._text(node.attrname)) 668 if hasattr(node, "_node"): 669 self._popup_start() 670 self._types(node._node) 671 self._scopes(node._node) 672 self._popup_end() 673 else: 674 raise ValueError, node 675 self.stream.write("</span>\n") 676 self.stream.write("</span>\n") 677 678 def visitKeyword(self, node): 679 self.stream.write("<span class='keyword'>\n") 680 self.stream.write(node.name) 681 self.stream.write("=") 682 self.dispatch(node.expr) 683 self.stream.write("</span>\n") 684 685 def visitLambda(self, node): 686 definition = node._node 687 subprogram = definition.expr.ref 688 self.stream.write("<span class='lambda'>\n") 689 self._keyword("lambda") 690 self._parameters(subprogram) 691 self.dispatch(node.code) 692 self.stream.write("</span>\n") 693 694 visitList = visitAssList 695 696 def visitMul(self, node): 697 self._visitBinary(node, "mul", "*") 698 699 def visitName(self, node): 700 if hasattr(node, "_node"): 701 self._name_start(node._node.name) 702 self._popup_start() 703 self._types(node._node) 704 self._scopes(node._node) 705 self._popup_end() 706 self._name_end() 707 else: 708 raise ValueError, node 709 self._name(node.name) 710 711 def visitNot(self, node): 712 self.stream.write("<span class='not'>\n") 713 self._keyword("not") 714 self.dispatch(node.expr) 715 self.stream.write("</span>") 716 717 def visitOr(self, node): 718 self.stream.write("<span class='or'>\n") 719 first = 1 720 for n in node.nodes: 721 if not first: 722 self._keyword("or") 723 self.dispatch(n) 724 first = 0 725 self.stream.write("</span>") 726 727 def visitPower(self, node): 728 self._visitBinary(node, "power", "**") 729 730 def visitSlice(self, node): 731 self.stream.write("<span class='slice'>\n") 732 self.dispatch(node.expr) 733 self.stream.write("[") 734 if node.lower: 735 self.dispatch(node.lower) 736 self.stream.write(":") 737 if node.upper: 738 self.dispatch(node.upper) 739 # NOTE: Step? 740 self.stream.write("]") 741 self.stream.write("</span>\n") 742 743 def visitSub(self, node): 744 self._visitBinary(node, "sub", "-") 745 746 def visitSubscript(self, node): 747 self.stream.write("<span class='subscript'>\n") 748 self.dispatch(node.expr) 749 self.stream.write("[") 750 first = 1 751 for sub in node.subs: 752 if not first: 753 self.stream.write(", ") 754 self.dispatch(sub) 755 first = 0 756 self.stream.write("]") 757 self.stream.write("</span>\n") 758 759 visitTuple = visitAssTuple 760 761 def visitUnaryAdd(self, node): 762 self._visitUnary(node, "add", "+") 763 764 def visitUnarySub(self, node): 765 self._visitUnary(node, "sub", "-") 766 767 # Output preparation methods. 768 769 def _text(self, text): 770 return text.replace("&", "&").replace("<", "<").replace(">", ">") 771 772 def _attr(self, attr): 773 return self._text(attr).replace("'", "'").replace('"', """) 774 775 def _url(self, url): 776 return self._attr(url).replace("#", "%23").replace("-", "%2d") 777 778 def _comment(self, comment): 779 self.stream.write("<span class='comment'># %s</span>\n" % comment) 780 781 def _keyword(self, kw): 782 self.stream.write("<span class='keyword'>%s</span> " % kw) 783 784 def _doc(self, node): 785 if node.doc is not None: 786 self.stream.write("<pre class='doc'>\n") 787 self.stream.write('"""') 788 output = textwrap.dedent(node.doc.replace('"""', '\\"\\"\\"')) 789 self.stream.write(self._text(output)) 790 self.stream.write('"""') 791 self.stream.write("</pre>\n") 792 793 def _sequence(self, node): 794 first = 1 795 for n in node.nodes: 796 if not first: 797 self.stream.write(",\n") 798 self.dispatch(n) 799 first = 0 800 801 def _parameters(self, subprogram): 802 first = 1 803 for param, default in subprogram.params: 804 if not first: 805 self.stream.write(",\n") 806 self._parameter(subprogram, param, default) 807 first = 0 808 if subprogram.star is not None: 809 if not first: 810 self.stream.write(", *\n") 811 param, default = subprogram.star 812 self._parameter(subprogram, param, default) 813 first = 0 814 if subprogram.dstar is not None: 815 if not first: 816 self.stream.write(", **\n") 817 param, default = subprogram.dstar 818 self._parameter(subprogram, param, default) 819 first = 0 820 821 def _parameter(self, subprogram, param, default): 822 self._name_start(param) 823 if hasattr(subprogram, "paramtypes"): 824 self._popup_start() 825 self._types_list(subprogram.paramtypes[param]) 826 self._popup_end() 827 self._name_end() 828 if default is not None and default.original is not None: 829 self.stream.write("=\n") 830 self.dispatch(default.original) 831 832 def _name(self, name): 833 self.stream.write("<span class='name'>%s</span>\n" % name) 834 835 def _name_start(self, name): 836 self.stream.write("<span class='name'>%s\n" % name) 837 838 def _name_end(self): 839 self.stream.write("</span>\n") 840 841 def _popup_start(self): 842 self.stream.write("<span class='popup'>\n") 843 844 def _popup_end(self): 845 self.stream.write("</span>\n") 846 847 def _op(self, op_name, op): 848 if op is not None: 849 if isinstance(op, Not): 850 self._invocations(op.expr) 851 else: 852 self._invocations(op) 853 854 def _invocations(self, node): 855 self.stream.write("<div class='invocations'>\n") 856 self._invocations_list(node) 857 self.stream.write("</div>\n") 858 859 def _invocations_list(self, node): 860 if hasattr(node, "invocations"): 861 for invocation in node.invocations: 862 fn = invocation.full_name() 863 module = invocation.module.name 864 name = invocation.name 865 structures = [x.name for x in invocation.structures] 866 self.stream.write("<div class='invocation'>") 867 self.stream.write("<a href='%s.html#%s'>" % (self._url(module), self._url(fn))) 868 self.stream.write(self._text(".".join([module] + structures + [name]))) 869 self.stream.write("</a>") 870 self.stream.write("</div>\n") 871 872 def _types(self, node): 873 if hasattr(node, "types"): 874 if node.types: 875 self._types_list(node.types) 876 else: 877 self.stream.write("<div class='types'>\n") 878 self.stream.write("no types\n") 879 self.stream.write("</div>\n") 880 else: 881 self.stream.write("<div class='types'>\n") 882 self.stream.write("unvisited\n") 883 self.stream.write("</div>\n") 884 885 def _types_list(self, types, style_class="types"): 886 self.stream.write("<div class='%s'>\n" % style_class) 887 for type in types: 888 fn = type.type.full_name() 889 self.stream.write("<div class='type'>") 890 self.stream.write(self._text(fn)) 891 self.stream.write("</div>\n") 892 self.stream.write("</div>\n") 893 894 def _raises(self, node): 895 if hasattr(node, "namespace") and hasattr(node.namespace, "raises") and node.namespace.raises: 896 self._types_list(node.namespace.raises, style_class="raises") 897 898 def _scopes(self, node): 899 if not isinstance(node, LoadName): 900 if hasattr(node, "writes") and node.writes or hasattr(node, "accesses") and node.accesses: 901 self.stream.write("<div class='scopes'>\n") 902 for ref in getattr(node, "writes", getattr(node, "accesses", {})).keys(): 903 fn = ref.full_name() 904 self.stream.write("<div class='scope'>") 905 self.stream.write(self._text(fn)) 906 self.stream.write("</div>\n") 907 self.stream.write("</div>\n") 908 if hasattr(node, "non_accesses") and node.non_accesses: 909 self._types_list(node.non_accesses, style_class="non-accesses") 910 if hasattr(node, "non_writes") and node.non_writes: 911 self._types_list(node.non_writes, style_class="non-writes") 912 913 # Utility functions. 914 915 def flatten(lists): 916 result = [] 917 for l in lists: 918 for attr in l: 919 if attr not in result: 920 result.append(attr) 921 return result 922 923 # Convenience functions. 924 925 def view(module, stream=None): 926 viewer = Viewer(stream or sys.stdout) 927 viewer.process(module.original) 928 929 def report(exc): 930 viewer = Viewer(stream or sys.stdout) 931 viewer.report(exc) 932 933 def browse(module, stream=None): 934 browser = Browser(stream or sys.stdout) 935 browser.process(module.original) 936 937 def makedoc(module, filename): 938 stream = open(filename, "wb") 939 try: 940 browser = Browser(stream) 941 browser.process(module.original) 942 finally: 943 stream.close() 944 945 def makedocs(module, builtins): 946 try: 947 dirname = "%s-docs" % module.name 948 if not os.path.exists(dirname): 949 os.mkdir(dirname) 950 makedoc(module, os.path.join(dirname, "%s%shtml" % (module.name, os.path.extsep))) 951 makedoc(builtins, os.path.join(dirname, "%s%shtml" % (builtins.name, os.path.extsep))) 952 except ViewerError, exc: 953 raise 954 955 # vim: tabstop=4 expandtab shiftwidth=4