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