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 self.dispatch_only(node.original, every_time=1) 92 self.stream.write("\nOriginal node was:\n\n" + repr(exc.nodes[0].original) + "\n") 93 self.stream.write("\nSimplified node was:\n\n") 94 exc.nodes[0].pprint(stream=self.stream) 95 96 # HTML-related output production. 97 98 html_header = """<?xml version="1.0" encoding="iso-8859-15"?> 99 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 100 <html xmlns="http://www.w3.org/1999/xhtml"> 101 <head> 102 <title>Module</title> 103 <style type="text/css"> 104 body { 105 padding-top: 4em; padding-bottom: 4em; 106 font-size: 14pt; font-family: monospace; 107 background-color: black; color: white; 108 } 109 110 .class { margin-bottom: 1em; } 111 .function { margin-bottom: 1em; } 112 .body { padding-left: 2em; } 113 .keyword { color: yellow; } 114 .comment { color: blue; } 115 .str { color: #FF00FF; } 116 .doc { color: #FF00FF; margin-bottom: 1em; } 117 .ref { color: cyan; } 118 .ref a { color: cyan; 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 .types { 127 padding: 0.5em; background-color: #0000FF; 128 float: right; 129 } 130 131 .scopes { 132 padding: 0.5em; background-color: #007700; 133 float: left; 134 } 135 136 .name, 137 .attr 138 { 139 position: relative; 140 } 141 142 .name:hover > .popup, 143 .attr:hover > .popup 144 { 145 display: block; 146 } 147 148 </style> 149 </head> 150 <body> 151 """ 152 153 html_footer = """</body> 154 </html> 155 """ 156 157 # Browser classes. 158 159 class Browser(ASTVisitor): 160 161 """ 162 A browsing visitor for AST nodes. 163 """ 164 165 def __init__(self, stream): 166 ASTVisitor.__init__(self) 167 self.visitor = self 168 self.stream = stream 169 170 def process(self, module): 171 self.stream.write(html_header) 172 self.dispatch(module) 173 self.stream.write(html_footer) 174 175 def dispatch(self, node): 176 try: 177 ASTVisitor.dispatch(self, node) 178 except ViewerError, exc: 179 exc.add(node) 180 raise 181 except Exception, exc: 182 raise ViewerError(exc, node) 183 184 def visitModule(self, node): 185 self.default(node) 186 187 # Statements. 188 189 def visitAssign(self, node): 190 self.stream.write("<div class='assign'>\n") 191 for lvalue in node.nodes: 192 self.dispatch(lvalue) 193 self.stream.write("=\n") 194 self.dispatch(node.expr) 195 self.stream.write("</div>\n") 196 197 def visitAugAssign(self, node): 198 self.stream.write("<div class='augassign'>\n") 199 self.dispatch(node.node) 200 self.stream.write("%s\n" % node.op) 201 self.dispatch(node.expr) 202 self.stream.write("</div>\n") 203 204 def visitClass(self, node): 205 definition = node._node 206 structure = definition.expr.ref 207 self.stream.write("<div class='class' id='%s'>\n" % self._url(structure.full_name())) 208 self.stream.write("<div>\n") 209 self._keyword("class") 210 self._name_start(structure.name) 211 self._popup_start() 212 self._scopes(definition) 213 self._popup_end() 214 self._name_end() 215 bases = structure.bases 216 if bases: 217 self.stream.write("(") 218 first = 1 219 for base in bases: 220 if not first: 221 self.stream.write(",\n") 222 self._name_start(base.name) 223 self._popup_start() 224 self._types(base) 225 self._scopes(base) 226 self._popup_end() 227 self._name_end() 228 first = 0 229 self.stream.write(")") 230 self.stream.write(":\n") 231 self._comment(self._text(structure.full_name())) 232 self.stream.write("</div>\n") 233 234 self.stream.write("<div class='body'>\n") 235 self._doc(node) 236 self.dispatch(node.code) 237 self.stream.write("</div>\n") 238 self.stream.write("</div>\n") 239 240 def visitFor(self, node): 241 self.stream.write("<div class='if'>\n") 242 self.stream.write("<div>\n") 243 self._keyword("for") 244 self.dispatch(node.assign) 245 self._keyword("in") 246 self.dispatch(node.list) 247 self.stream.write(":\n") 248 self.stream.write("</div>\n") 249 self.stream.write("<div class='body'>\n") 250 self.dispatch(node.body) 251 self.stream.write("</div>\n") 252 if node.else_ is not None: 253 self.stream.write("<div>\n") 254 self._keyword("else") 255 self.stream.write(":\n") 256 self.stream.write("</div>\n") 257 self.stream.write("<div class='body'>\n") 258 self.dispatch(node.else_) 259 self.stream.write("</div>\n") 260 self.stream.write("</div>\n") 261 262 def visitFunction(self, node): 263 definition = node._node 264 subprogram = definition.expr.ref 265 self.stream.write("<div class='function' id='%s'>\n" % self._url(subprogram.full_name())) 266 self.stream.write("<div>\n") 267 self._keyword("def") 268 self._name_start(subprogram.name) 269 self._popup_start() 270 self._scopes(definition) 271 self._popup_end() 272 self._name_end() 273 self.stream.write("(") 274 first = 1 275 for param, default in subprogram.params: 276 if not first: 277 self.stream.write(",\n") 278 self._parameter(subprogram, param, default) 279 first = 0 280 if subprogram.star is not None: 281 if not first: 282 self.stream.write(", *\n") 283 param, default = subprogram.star 284 self._parameter(subprogram, param, default) 285 first = 0 286 if subprogram.dstar is not None: 287 if not first: 288 self.stream.write(", **\n") 289 param, default = subprogram.dstar 290 self._parameter(subprogram, param, default) 291 first = 0 292 self.stream.write(")") 293 self.stream.write(":\n") 294 self._comment(self._text(subprogram.full_name())) 295 self.stream.write("</div>\n") 296 297 self.stream.write("<div class='body'>\n") 298 self._doc(node) 299 self.dispatch(node.code) 300 self.stream.write("</div>\n") 301 self.stream.write("</div>\n") 302 303 def visitIf(self, node): 304 self.stream.write("<div class='if'>\n") 305 first = 1 306 for compare, stmt in node.tests: 307 self.stream.write("<div>\n") 308 if first: 309 self._keyword("if") 310 else: 311 self._keyword("elif") 312 self.dispatch(compare) 313 self.stream.write(":\n") 314 self.stream.write("</div>\n") 315 self.stream.write("<div class='body'>\n") 316 self.dispatch(stmt) 317 self.stream.write("</div>\n") 318 first = 0 319 if node.else_ is not None: 320 self.stream.write("<div>\n") 321 self._keyword("else") 322 self.stream.write(":\n") 323 self.stream.write("</div>\n") 324 self.stream.write("<div class='body'>\n") 325 self.dispatch(node.else_) 326 self.stream.write("</div>\n") 327 self.stream.write("</div>\n") 328 329 def visitPass(self, node): 330 self.stream.write("<div class='pass'>\n") 331 self._keyword("pass") 332 self.stream.write("</div>\n") 333 334 def visitRaise(self, node): 335 self.stream.write("<div class='raise'>\n") 336 self._keyword("raise") 337 self.dispatch(node.expr1) 338 if node.expr2 is not None: 339 self.stream.write(",\n") 340 self.dispatch(node.expr2) 341 if node.expr3 is not None: 342 self.stream.write(",\n") 343 self.dispatch(node.expr3) 344 self.stream.write("</div>\n") 345 346 def visitReturn(self, node): 347 self.stream.write("<div class='return'>\n") 348 self._keyword("return") 349 self.dispatch(node.value) 350 self.stream.write("</div>\n") 351 352 def visitStmt(self, node): 353 self.stream.write("<div class='stmt'>\n") 354 self.default(node) 355 self.stream.write("</div>\n") 356 357 # Expressions. 358 359 def visitAssAttr(self, node): 360 self.stream.write("<span class='assattr'>\n") 361 self.dispatch(node.expr) 362 self.stream.write("<span class='attr'>\n") 363 self.stream.write(".%s\n" % self._text(node.attrname)) 364 if hasattr(node, "_node"): 365 self._popup_start() 366 self._types(node._node) 367 self._scopes(node._node) 368 self._popup_end() 369 else: 370 raise ValueError, node 371 self.stream.write("</span>\n") 372 self.stream.write("</span>\n") 373 374 def visitAssList(self, node): 375 self.stream.write("<span class='list'>\n") 376 self.stream.write("[") 377 self._sequence(node) 378 self.stream.write("]\n") 379 self.stream.write("</span>\n") 380 381 def visitAssName(self, node): 382 if hasattr(node, "_node"): 383 self._name_start(node._node.name) 384 self._popup_start() 385 self._types(node._node.expr) 386 self._scopes(node._node) 387 self._popup_end() 388 self._name_end() 389 else: 390 raise ValueError, node 391 self._name(node.name) 392 393 def visitAssTuple(self, node): 394 self.stream.write("<span class='tuple'>\n") 395 self.stream.write("(") 396 self._sequence(node) 397 self.stream.write(")\n") 398 self.stream.write("</span>\n") 399 400 def visitCallFunc(self, node): 401 self.stream.write("<span class='callfunc'>\n") 402 self.dispatch(node.node) 403 self.stream.write("(") 404 first = 1 405 for arg in node.args: 406 if not first: 407 self.stream.write(",\n") 408 self.dispatch(arg) 409 first = 0 410 if node.star_args is not None: 411 if not first: 412 self.stream.write(", *\n") 413 self.dispatch(node.star_args) 414 first = 0 415 if node.dstar_args is not None: 416 if not first: 417 self.stream.write(", **\n") 418 self.dispatch(node.dstar_args) 419 first = 0 420 self.stream.write(")\n") 421 self.stream.write("</span>\n") 422 423 def visitCompare(self, node): 424 self.stream.write("<span class='compare'>\n") 425 self.dispatch(node.expr) 426 for name, op in node.ops: 427 self.stream.write("<span class='op'>%s</span>\n" % name) 428 self.dispatch(op) 429 self.stream.write("</span>\n") 430 431 def visitConst(self, node): 432 self.stream.write(repr(node.value)) 433 434 def visitGetattr(self, node): 435 self.stream.write("<span class='getattr'>\n") 436 self.dispatch(node.expr) 437 self.stream.write("<span class='attr'>\n") 438 self.stream.write(".%s\n" % self._text(node.attrname)) 439 if hasattr(node, "_node"): 440 self._popup_start() 441 self._types(node._node) 442 self._scopes(node._node) 443 self._popup_end() 444 else: 445 raise ValueError, node 446 self.stream.write("</span>\n") 447 self.stream.write("</span>\n") 448 449 def visitKeyword(self, node): 450 self.stream.write("<span class='keyword'>\n") 451 self.stream.write(node.name) 452 self.stream.write("=") 453 self.dispatch(node.expr) 454 self.stream.write("</span>\n") 455 456 visitList = visitAssList 457 458 def visitName(self, node): 459 if hasattr(node, "_node"): 460 self._name_start(node._node.name) 461 self._popup_start() 462 self._types(node._node) 463 self._scopes(node._node) 464 self._popup_end() 465 self._name_end() 466 else: 467 raise ValueError, node 468 self._name(node.name) 469 470 def visitSlice(self, node): 471 self.stream.write("<span class='slice'>\n") 472 self.dispatch(node.expr) 473 self.stream.write("[") 474 if node.lower: 475 self.dispatch(node.lower) 476 self.stream.write(":") 477 if node.upper: 478 self.dispatch(node.upper) 479 # NOTE: Step? 480 self.stream.write("]") 481 self.stream.write("</span>\n") 482 483 def visitSubscript(self, node): 484 self.stream.write("<span class='subscript'>\n") 485 self.dispatch(node.expr) 486 self.stream.write("[") 487 first = 1 488 for sub in node.subs: 489 if not first: 490 self.stream.write(", ") 491 self.dispatch(sub) 492 first = 0 493 self.stream.write("]") 494 self.stream.write("</span>\n") 495 496 visitTuple = visitAssTuple 497 498 # Output preparation methods. 499 500 def _text(self, text): 501 return text.replace("&", "&").replace("<", "<").replace(">", ">") 502 503 def _attr(self, attr): 504 return self._text(attr).replace("'", "'").replace('"', """) 505 506 def _url(self, url): 507 return self._attr(url).replace("#", "%23").replace("-", "%2d") 508 509 def _comment(self, comment): 510 self.stream.write("<span class='comment'># %s</span>\n" % comment) 511 512 def _keyword(self, kw): 513 self.stream.write("<span class='keyword'>%s</span> " % kw) 514 515 def _doc(self, node): 516 if node.doc is not None: 517 self.stream.write("<pre class='doc'>\n") 518 self.stream.write('"""') 519 output = textwrap.dedent(node.doc.replace('"""', '\\"\\"\\"')) 520 self.stream.write(self._text(output)) 521 self.stream.write('"""') 522 self.stream.write("</pre>\n") 523 524 def _sequence(self, node): 525 first = 1 526 for n in node.nodes: 527 if not first: 528 self.stream.write(",\n") 529 self.dispatch(n) 530 first = 0 531 532 def _parameter(self, subprogram, param, default): 533 self._name_start(param) 534 if hasattr(subprogram, "paramtypes"): 535 self._popup_start() 536 self._types_list(subprogram.paramtypes[param]) 537 self._popup_end() 538 self._name_end() 539 if default is not None and default.original is not None: 540 self.stream.write("=\n") 541 self.dispatch(default.original) 542 543 def _name(self, name): 544 self.stream.write("<span class='name'>%s</span>\n" % name) 545 546 def _name_start(self, name): 547 self.stream.write("<span class='name'>%s\n" % name) 548 549 def _name_end(self): 550 self.stream.write("</span>\n") 551 552 def _popup_start(self): 553 self.stream.write("<span class='popup'>\n") 554 555 def _popup_end(self): 556 self.stream.write("</span>\n") 557 558 def _types(self, node): 559 if hasattr(node, "types"): 560 self._types_list(node.types) 561 elif hasattr(node, "writes"): 562 self._types_list(flatten(node.writes.values())) 563 elif hasattr(node, "accesses"): 564 self._types_list(flatten(node.accesses.values())) 565 else: 566 self.stream.write("<div class='types'>\n") 567 self.stream.write("unvisited\n") 568 self.stream.write("</div>\n") 569 570 def _types_list(self, types): 571 self.stream.write("<div class='types'>\n") 572 for type in types: 573 fn = type.type.full_name() 574 self.stream.write("<div class='type'>") 575 self.stream.write(self._text(fn)) 576 self.stream.write("</div>\n") 577 self.stream.write("</div>\n") 578 579 def _scopes(self, node): 580 if not isinstance(node, LoadName): 581 if hasattr(node, "writes") or hasattr(node, "accesses"): 582 self.stream.write("<div class='scopes'>\n") 583 for ref in getattr(node, "writes", getattr(node, "accesses", {})).keys(): 584 fn = ref.full_name() 585 self.stream.write("<div class='scope'>") 586 self.stream.write(self._text(fn)) 587 self.stream.write("</div>\n") 588 self.stream.write("</div>\n") 589 590 # Utility functions. 591 592 def flatten(lists): 593 result = [] 594 for l in lists: 595 for attr in l: 596 if attr not in result: 597 result.append(attr) 598 return result 599 600 # Convenience functions. 601 602 def view(module, stream=None): 603 viewer = Viewer(stream or sys.stdout) 604 viewer.process(module.original) 605 606 def browse(module, stream=None): 607 browser = Browser(stream or sys.stdout) 608 browser.process(module.original) 609 610 def makedoc(module, filename): 611 stream = open(filename, "wb") 612 try: 613 browser = Browser(stream) 614 browser.process(module.original) 615 finally: 616 stream.close() 617 618 # vim: tabstop=4 expandtab shiftwidth=4