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 textwrap 28 29 # Exceptions. 30 31 class ViewerError(SimplifiedError): 32 33 "An error in viewing." 34 35 pass 36 37 # Classes. 38 39 class Viewer(ASTVisitor): 40 41 """ 42 A viewing visitor for AST nodes. 43 """ 44 45 def __init__(self, stream): 46 ASTVisitor.__init__(self) 47 self.cached_files = {} 48 self.printed_lines = {} 49 self.visitor = self 50 self.stream = stream 51 52 def process(self, module): 53 self.dispatch(module) 54 55 def dispatch(self, node): 56 self.dispatch_only(node) 57 ASTVisitor.dispatch(self, node) 58 59 def dispatch_only(self, node, every_time=0): 60 self.print_line(getattr(node, "filename", None), getattr(node, "lineno", None), every_time) 61 62 def print_line(self, filename, lineno, every_time): 63 last_printed = self.printed_lines.get(filename, 0) 64 if lineno > last_printed or every_time: 65 self.stream.write(self.get_line(filename, lineno)) 66 self.printed_lines[filename] = lineno 67 68 def get_line(self, filename, lineno): 69 if filename is None or lineno is None: 70 return "" 71 72 if self.cached_files.has_key(filename): 73 lines = self.cached_files[filename] 74 else: 75 f = open(filename) 76 try: 77 self.cached_files[filename] = lines = f.readlines() 78 finally: 79 f.close() 80 81 try: 82 return lines[lineno - 1] 83 except IndexError: 84 return "" 85 86 def report(self, exc): 87 self.stream.write("Exception was:\n\n" + str(exc.exc) + "\n\n") 88 self.stream.write("Nodes:\n\n") 89 for node in exc.nodes: 90 self.stream.write(repr(node) + "\n") 91 if node is not None: 92 self.dispatch_only(node.original, every_time=1) 93 self.stream.write("\nOriginal node was:\n\n" + repr(exc.nodes[0].original) + "\n") 94 self.stream.write("\nSimplified node was:\n\n") 95 exc.nodes[0].pprint(stream=self.stream) 96 97 # HTML-related output production. 98 99 html_header = """<?xml version="1.0" encoding="iso-8859-15"?> 100 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 101 <html xmlns="http://www.w3.org/1999/xhtml"> 102 <head> 103 <title>Module</title> 104 <style type="text/css"> 105 body { 106 padding-top: 4em; padding-bottom: 4em; 107 font-size: 14pt; font-family: monospace; 108 background-color: black; color: white; 109 } 110 111 .class { margin-bottom: 1em; } 112 .function { margin-bottom: 1em; } 113 .body { padding-left: 2em; } 114 .keyword { color: yellow; } 115 .comment { color: blue; } 116 .str { color: #FF00FF; } 117 .doc { color: #FF00FF; margin-bottom: 1em; } 118 .invocation a { color: white; text-decoration: none; } 119 120 .popup { 121 display: none; z-index: 2; 122 position: absolute; top: 1em; left: 0.5em; 123 padding: 0.2em; background-color: #000000; 124 } 125 126 .invocations { 127 padding: 0.5em; background-color: #770000; 128 clear: all; 129 } 130 131 .types { 132 padding: 0.5em; background-color: #0000FF; 133 float: right; 134 } 135 136 .raises { 137 padding: 0.5em; background-color: #7700FF; 138 float: right; 139 } 140 141 .scopes { 142 padding: 0.5em; background-color: #007700; 143 float: left; 144 } 145 146 .op, 147 .name, 148 .attr, 149 .conditional 150 { 151 position: relative; 152 } 153 154 .op:hover > .popup, 155 .name:hover > .popup, 156 .attr:hover > .popup, 157 .conditional:hover > .popup 158 { 159 display: block; 160 } 161 162 </style> 163 </head> 164 <body> 165 """ 166 167 html_footer = """</body> 168 </html> 169 """ 170 171 # Browser classes. 172 173 class Browser(ASTVisitor): 174 175 """ 176 A browsing visitor for AST nodes. 177 178 Covered: AssAttr, AssList, AssName, AssTuple, Assign, AugAssign, Break, 179 CallFunc, Class, Compare, Const, Continue, Dict, Discard, For, 180 Function, Getattr, If, Keyword, Lambda, List, Module, Name, Pass, Raise, Return, Slice, 181 Stmt, Subscript, Tuple, While. 182 183 Missing: And, Add, Assert, Backquote, Bitand, Bitor, Bitxor, Decorators, Div, 184 Ellipsis, Exec, FloorDiv, From, Global, Import, Invert, LeftShift, ListComp, ListCompFor, 185 ListCompIf, Mod, Mul, Not, Or, Power, Print, Printnl, RightShift, Sliceobj, 186 Sub, TryExcept, TryFinally, UnaryAdd, UnarySub, Yield. 187 """ 188 189 def __init__(self, stream): 190 ASTVisitor.__init__(self) 191 self.visitor = self 192 self.stream = stream 193 194 def process(self, module): 195 self.stream.write(html_header) 196 self.dispatch(module) 197 self.stream.write(html_footer) 198 199 def dispatch(self, node): 200 try: 201 ASTVisitor.dispatch(self, node) 202 except ViewerError, exc: 203 exc.add(node) 204 raise 205 except Exception, exc: 206 raise ViewerError(exc, node) 207 208 def visitModule(self, node): 209 self.default(node) 210 211 # Statements. 212 213 def visitAssign(self, node): 214 self.stream.write("<div class='assign'>\n") 215 for lvalue in node.nodes: 216 self.dispatch(lvalue) 217 self.stream.write("=\n") 218 self.dispatch(node.expr) 219 self.stream.write("</div>\n") 220 221 def visitAugAssign(self, node): 222 self.stream.write("<div class='augassign'>\n") 223 self.dispatch(node.node) 224 self.stream.write("%s\n" % node.op) 225 self.dispatch(node.expr) 226 self.stream.write("</div>\n") 227 228 def visitBreak(self, node): 229 self.stream.write("<div class='break'>\n") 230 self._keyword("break") 231 self.stream.write("</div>\n") 232 233 def visitClass(self, node): 234 definition = node._node 235 structure = definition.expr.ref 236 self.stream.write("<div class='class' id='%s'>\n" % self._url(structure.full_name())) 237 self.stream.write("<div>\n") 238 self._keyword("class") 239 self._name_start(structure.name) 240 self._popup_start() 241 self._scopes(definition) 242 self._popup_end() 243 self._name_end() 244 bases = structure.bases 245 if bases: 246 self.stream.write("(") 247 first = 1 248 for base in bases: 249 if not first: 250 self.stream.write(",\n") 251 self._name_start(base.name) 252 self._popup_start() 253 self._types(base) 254 self._scopes(base) 255 self._popup_end() 256 self._name_end() 257 first = 0 258 self.stream.write(")") 259 self.stream.write(":\n") 260 self._comment(self._text(structure.full_name())) 261 self.stream.write("</div>\n") 262 263 self.stream.write("<div class='body'>\n") 264 self._doc(node) 265 self.dispatch(node.code) 266 self.stream.write("</div>\n") 267 self.stream.write("</div>\n") 268 269 def visitContinue(self, node): 270 self.stream.write("<div class='continue'>\n") 271 self._keyword("continue") 272 self.stream.write("</div>\n") 273 274 def visitDiscard(self, node): 275 self.stream.write("<div class='discard'>\n") 276 self.default(node) 277 self.stream.write("</div>\n") 278 279 def visitFor(self, node): 280 self.stream.write("<div class='if'>\n") 281 self.stream.write("<div>\n") 282 self._keyword("for") 283 self.dispatch(node.assign) 284 self._keyword("in") 285 self.dispatch(node.list) 286 self.stream.write(":\n") 287 self.stream.write("</div>\n") 288 self.stream.write("<div class='body'>\n") 289 self.dispatch(node.body) 290 self.stream.write("</div>\n") 291 if node.else_ is not None: 292 self.stream.write("<div>\n") 293 self._keyword("else") 294 self.stream.write(":\n") 295 self.stream.write("</div>\n") 296 self.stream.write("<div class='body'>\n") 297 self.dispatch(node.else_) 298 self.stream.write("</div>\n") 299 self.stream.write("</div>\n") 300 301 def visitFunction(self, node): 302 definition = node._node 303 subprogram = definition.expr.ref 304 self.stream.write("<div class='function' id='%s'>\n" % self._url(subprogram.full_name())) 305 self.stream.write("<div>\n") 306 self._keyword("def") 307 self._name_start(subprogram.name) 308 self._popup_start() 309 self._scopes(definition) 310 self._raises(subprogram) 311 self._popup_end() 312 self._name_end() 313 self.stream.write("(") 314 self._parameters(subprogram) 315 self.stream.write(")") 316 self.stream.write(":\n") 317 self._comment(self._text(subprogram.full_name())) 318 self.stream.write("</div>\n") 319 320 self.stream.write("<div class='body'>\n") 321 self._doc(node) 322 self.dispatch(node.code) 323 self.stream.write("</div>\n") 324 self.stream.write("</div>\n") 325 326 def visitIf(self, node): 327 self.stream.write("<div class='if'>\n") 328 first = 1 329 conditional = node._node 330 for compare, stmt in node.tests: 331 self.stream.write("<div>\n") 332 self.stream.write("<span class='conditional'>\n") 333 if first: 334 self._keyword("if") 335 else: 336 self._keyword("elif") 337 self._popup_start() 338 self._invocations(conditional.test) 339 self._popup_end() 340 self.stream.write("<span>\n") 341 self.dispatch(compare) 342 self.stream.write(":\n") 343 self.stream.write("</div>\n") 344 self.stream.write("<div class='body'>\n") 345 self.dispatch(stmt) 346 self.stream.write("</div>\n") 347 if conditional.else_: 348 conditional = conditional.else_[0] 349 else: 350 conditional = None 351 first = 0 352 if node.else_ is not None: 353 self.stream.write("<div>\n") 354 self._keyword("else") 355 self.stream.write(":\n") 356 self.stream.write("</div>\n") 357 self.stream.write("<div class='body'>\n") 358 self.dispatch(node.else_) 359 self.stream.write("</div>\n") 360 self.stream.write("</div>\n") 361 362 def visitPass(self, node): 363 self.stream.write("<div class='pass'>\n") 364 self._keyword("pass") 365 self.stream.write("</div>\n") 366 367 def visitRaise(self, node): 368 self.stream.write("<div class='raise'>\n") 369 self._keyword("raise") 370 self.dispatch(node.expr1) 371 if node.expr2 is not None: 372 self.stream.write(",\n") 373 self.dispatch(node.expr2) 374 if node.expr3 is not None: 375 self.stream.write(",\n") 376 self.dispatch(node.expr3) 377 self.stream.write("</div>\n") 378 379 def visitReturn(self, node): 380 self.stream.write("<div class='return'>\n") 381 self._keyword("return") 382 self.dispatch(node.value) 383 self.stream.write("</div>\n") 384 385 def visitStmt(self, node): 386 self.stream.write("<div class='stmt'>\n") 387 self.default(node) 388 self.stream.write("</div>\n") 389 390 def visitTryExcept(self, node): 391 self.stream.write("<div class='tryexcept'>\n") 392 self.stream.write("<div>\n") 393 self._keyword("try") 394 self.stream.write(":\n") 395 self.stream.write("</div>\n") 396 self.stream.write("<div class='body'>\n") 397 self.dispatch(node.body) 398 self.stream.write("</div>\n") 399 for spec, assign, statement in node.handlers: 400 self.stream.write("<div>\n") 401 self._keyword("except") 402 if spec is not None: 403 self.dispatch(spec) 404 if assign is not None: 405 self.stream.write(",\n") 406 self.dispatch(assign) 407 self.stream.write(":\n") 408 self.stream.write("</div>\n") 409 self.stream.write("<div class='body'>\n") 410 self.dispatch(statement) 411 self.stream.write("</div>\n") 412 if node.else_ is not None: 413 self.stream.write("<div>\n") 414 self._keyword("else") 415 self.stream.write(":\n") 416 self.stream.write("</div>\n") 417 self.stream.write("<div class='body'>\n") 418 self.dispatch(node.else_) 419 self.stream.write("</div>\n") 420 self.stream.write("</div>\n") 421 422 def visitTryFinally(self, node): 423 self.stream.write("<div class='tryfinally'>\n") 424 self.stream.write("<div>\n") 425 self._keyword("try") 426 self.stream.write(":\n") 427 self.stream.write("</div>\n") 428 self.stream.write("<div class='body'>\n") 429 self.dispatch(node.body) 430 self.stream.write("</div>\n") 431 self.stream.write("<div>\n") 432 self._keyword("finally") 433 self.stream.write(":\n") 434 self.stream.write("</div>\n") 435 self.stream.write("<div class='body'>\n") 436 self.dispatch(node.final) 437 self.stream.write("</div>\n") 438 self.stream.write("</div>\n") 439 440 def visitWhile(self, node): 441 self.stream.write("<div class='while'>\n") 442 self.stream.write("<div>\n") 443 self._keyword("while") 444 self.dispatch(node.test) 445 self.stream.write(":\n") 446 self.stream.write("</div>\n") 447 self.stream.write("<div class='body'>\n") 448 self.dispatch(node.body) 449 self.stream.write("</div>\n") 450 if node.else_ is not None: 451 self.stream.write("<div>\n") 452 self._keyword("else") 453 self.stream.write(":\n") 454 self.stream.write("</div>\n") 455 self.stream.write("<div class='body'>\n") 456 self.dispatch(node.else_) 457 self.stream.write("</div>\n") 458 self.stream.write("</div>\n") 459 460 # Expressions. 461 462 def visitAssAttr(self, node): 463 self.stream.write("<span class='assattr'>\n") 464 self.dispatch(node.expr) 465 self.stream.write("<span class='attr'>\n") 466 self.stream.write(".%s\n" % self._text(node.attrname)) 467 if hasattr(node, "_node"): 468 self._popup_start() 469 self._types(node._node) 470 self._scopes(node._node) 471 self._popup_end() 472 else: 473 raise ValueError, node 474 self.stream.write("</span>\n") 475 self.stream.write("</span>\n") 476 477 def visitAssList(self, node): 478 self.stream.write("<span class='list'>\n") 479 self.stream.write("[") 480 self._sequence(node) 481 self.stream.write("]\n") 482 self.stream.write("</span>\n") 483 484 def visitAssName(self, node): 485 if hasattr(node, "_node"): 486 self._name_start(node._node.name) 487 self._popup_start() 488 self._types(node._node.expr) 489 self._scopes(node._node) 490 self._popup_end() 491 self._name_end() 492 else: 493 raise ValueError, node 494 self._name(node.name) 495 496 def visitAssTuple(self, node): 497 self.stream.write("<span class='tuple'>\n") 498 self.stream.write("(") 499 self._sequence(node) 500 self.stream.write(")\n") 501 self.stream.write("</span>\n") 502 503 def visitCallFunc(self, node): 504 self.stream.write("<span class='callfunc'>\n") 505 self.dispatch(node.node) 506 self.stream.write("(") 507 first = 1 508 for arg in node.args: 509 if not first: 510 self.stream.write(",\n") 511 self.dispatch(arg) 512 first = 0 513 if node.star_args is not None: 514 if not first: 515 self.stream.write(", *\n") 516 self.dispatch(node.star_args) 517 first = 0 518 if node.dstar_args is not None: 519 if not first: 520 self.stream.write(", **\n") 521 self.dispatch(node.dstar_args) 522 first = 0 523 self.stream.write(")\n") 524 self.stream.write("</span>\n") 525 526 def visitCompare(self, node): 527 self.stream.write("<span class='compare'>\n") 528 self.dispatch(node.expr) 529 for (op_name, expr), _op in map(None, node.ops, node._ops): 530 self.stream.write("<span class='op'>\n") 531 self.stream.write(op_name) 532 self._popup_start() 533 self._op(op_name, _op) 534 self._popup_end() 535 self.stream.write("</span>\n") 536 self.dispatch(expr) 537 self.stream.write("</span>\n") 538 539 def visitConst(self, node): 540 self.stream.write(repr(node.value)) 541 542 def visitGetattr(self, node): 543 self.stream.write("<span class='getattr'>\n") 544 self.dispatch(node.expr) 545 self.stream.write("<span class='attr'>\n") 546 self.stream.write(".%s\n" % self._text(node.attrname)) 547 if hasattr(node, "_node"): 548 self._popup_start() 549 self._types(node._node) 550 self._scopes(node._node) 551 self._popup_end() 552 else: 553 raise ValueError, node 554 self.stream.write("</span>\n") 555 self.stream.write("</span>\n") 556 557 def visitKeyword(self, node): 558 self.stream.write("<span class='keyword'>\n") 559 self.stream.write(node.name) 560 self.stream.write("=") 561 self.dispatch(node.expr) 562 self.stream.write("</span>\n") 563 564 def visitLambda(self, node): 565 definition = node._node 566 subprogram = definition.expr.ref 567 self.stream.write("<span class='lambda'>\n") 568 self._keyword("lambda") 569 self._parameters(subprogram) 570 self.dispatch(node.code) 571 self.stream.write("</span>\n") 572 573 visitList = visitAssList 574 575 def visitName(self, node): 576 if hasattr(node, "_node"): 577 self._name_start(node._node.name) 578 self._popup_start() 579 self._types(node._node) 580 self._scopes(node._node) 581 self._popup_end() 582 self._name_end() 583 else: 584 raise ValueError, node 585 self._name(node.name) 586 587 def visitSlice(self, node): 588 self.stream.write("<span class='slice'>\n") 589 self.dispatch(node.expr) 590 self.stream.write("[") 591 if node.lower: 592 self.dispatch(node.lower) 593 self.stream.write(":") 594 if node.upper: 595 self.dispatch(node.upper) 596 # NOTE: Step? 597 self.stream.write("]") 598 self.stream.write("</span>\n") 599 600 def visitSubscript(self, node): 601 self.stream.write("<span class='subscript'>\n") 602 self.dispatch(node.expr) 603 self.stream.write("[") 604 first = 1 605 for sub in node.subs: 606 if not first: 607 self.stream.write(", ") 608 self.dispatch(sub) 609 first = 0 610 self.stream.write("]") 611 self.stream.write("</span>\n") 612 613 visitTuple = visitAssTuple 614 615 # Output preparation methods. 616 617 def _text(self, text): 618 return text.replace("&", "&").replace("<", "<").replace(">", ">") 619 620 def _attr(self, attr): 621 return self._text(attr).replace("'", "'").replace('"', """) 622 623 def _url(self, url): 624 return self._attr(url).replace("#", "%23").replace("-", "%2d") 625 626 def _comment(self, comment): 627 self.stream.write("<span class='comment'># %s</span>\n" % comment) 628 629 def _keyword(self, kw): 630 self.stream.write("<span class='keyword'>%s</span> " % kw) 631 632 def _doc(self, node): 633 if node.doc is not None: 634 self.stream.write("<pre class='doc'>\n") 635 self.stream.write('"""') 636 output = textwrap.dedent(node.doc.replace('"""', '\\"\\"\\"')) 637 self.stream.write(self._text(output)) 638 self.stream.write('"""') 639 self.stream.write("</pre>\n") 640 641 def _sequence(self, node): 642 first = 1 643 for n in node.nodes: 644 if not first: 645 self.stream.write(",\n") 646 self.dispatch(n) 647 first = 0 648 649 def _parameters(self, subprogram): 650 first = 1 651 for param, default in subprogram.params: 652 if not first: 653 self.stream.write(",\n") 654 self._parameter(subprogram, param, default) 655 first = 0 656 if subprogram.star is not None: 657 if not first: 658 self.stream.write(", *\n") 659 param, default = subprogram.star 660 self._parameter(subprogram, param, default) 661 first = 0 662 if subprogram.dstar is not None: 663 if not first: 664 self.stream.write(", **\n") 665 param, default = subprogram.dstar 666 self._parameter(subprogram, param, default) 667 first = 0 668 669 def _parameter(self, subprogram, param, default): 670 self._name_start(param) 671 if hasattr(subprogram, "paramtypes"): 672 self._popup_start() 673 self._types_list(subprogram.paramtypes[param]) 674 self._popup_end() 675 self._name_end() 676 if default is not None and default.original is not None: 677 self.stream.write("=\n") 678 self.dispatch(default.original) 679 680 def _name(self, name): 681 self.stream.write("<span class='name'>%s</span>\n" % name) 682 683 def _name_start(self, name): 684 self.stream.write("<span class='name'>%s\n" % name) 685 686 def _name_end(self): 687 self.stream.write("</span>\n") 688 689 def _popup_start(self): 690 self.stream.write("<span class='popup'>\n") 691 692 def _popup_end(self): 693 self.stream.write("</span>\n") 694 695 def _op(self, op_name, op): 696 if op is not None: 697 self._invocations(op) 698 699 def _invocations(self, node): 700 if hasattr(node, "invocations"): 701 self._invocations_list(node.invocations) 702 703 def _invocations_list(self, invocations): 704 self.stream.write("<div class='invocations'>\n") 705 for invocation in invocations: 706 fn = invocation.full_name() 707 module = invocation.module.name 708 name = invocation.name 709 self.stream.write("<div class='invocation'>") 710 self.stream.write("<a href='%s.html#%s'>" % (self._url(module), self._url(fn))) 711 self.stream.write("%s.%s" % (self._text(module), self._text(name))) 712 self.stream.write("</a>") 713 self.stream.write("</div>\n") 714 self.stream.write("</div>\n") 715 716 def _types(self, node): 717 if hasattr(node, "types"): 718 self._types_list(node.types) 719 elif hasattr(node, "writes"): 720 self._types_list(flatten(node.writes.values())) 721 elif hasattr(node, "accesses"): 722 self._types_list(flatten(node.accesses.values())) 723 else: 724 self.stream.write("<div class='types'>\n") 725 self.stream.write("unvisited\n") 726 self.stream.write("</div>\n") 727 728 def _types_list(self, types, style_class="types"): 729 self.stream.write("<div class='%s'>\n" % style_class) 730 for type in types: 731 fn = type.type.full_name() 732 self.stream.write("<div class='type'>") 733 self.stream.write(self._text(fn)) 734 self.stream.write("</div>\n") 735 self.stream.write("</div>\n") 736 737 def _raises(self, node): 738 if hasattr(node, "namespace") and hasattr(node.namespace, "raises") and node.namespace.raises: 739 self._types_list(node.namespace.raises, style_class="raises") 740 741 def _scopes(self, node): 742 if not isinstance(node, LoadName): 743 if hasattr(node, "writes") or hasattr(node, "accesses"): 744 self.stream.write("<div class='scopes'>\n") 745 for ref in getattr(node, "writes", getattr(node, "accesses", {})).keys(): 746 fn = ref.full_name() 747 self.stream.write("<div class='scope'>") 748 self.stream.write(self._text(fn)) 749 self.stream.write("</div>\n") 750 self.stream.write("</div>\n") 751 752 # Utility functions. 753 754 def flatten(lists): 755 result = [] 756 for l in lists: 757 for attr in l: 758 if attr not in result: 759 result.append(attr) 760 return result 761 762 # Convenience functions. 763 764 def view(module, stream=None): 765 viewer = Viewer(stream or sys.stdout) 766 viewer.process(module.original) 767 768 def browse(module, stream=None): 769 browser = Browser(stream or sys.stdout) 770 browser.process(module.original) 771 772 def makedoc(module, filename): 773 stream = open(filename, "wb") 774 try: 775 browser = Browser(stream) 776 browser.process(module.original) 777 finally: 778 stream.close() 779 780 # vim: tabstop=4 expandtab shiftwidth=4