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.5em; 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 # Validation functions. 158 159 def hasnode(fn): 160 def _hasnode(self, node): 161 if not hasattr(node, "_node"): 162 return 163 else: 164 fn(self, node) 165 return _hasnode 166 167 # Browser classes. 168 169 class Browser(ASTVisitor): 170 171 """ 172 A browsing visitor for AST nodes. 173 """ 174 175 def __init__(self, stream): 176 ASTVisitor.__init__(self) 177 self.visitor = self 178 self.stream = stream 179 180 def process(self, module): 181 self.stream.write(html_header) 182 self.dispatch(module) 183 self.stream.write(html_footer) 184 185 def dispatch(self, node): 186 try: 187 ASTVisitor.dispatch(self, node) 188 except ViewerError, exc: 189 exc.add(node) 190 raise 191 except Exception, exc: 192 raise ViewerError(exc, node) 193 194 def visitModule(self, node): 195 self.default(node) 196 197 # Statements. 198 199 def visitAssign(self, node): 200 self.stream.write("<div class='assign'>\n") 201 for lvalue in node.nodes: 202 self.dispatch(lvalue) 203 self.stream.write("=\n") 204 self.dispatch(node.expr) 205 self.stream.write("</div>\n") 206 207 def visitClass(self, node): 208 definition = node._node 209 structure = definition.expr.ref 210 self.stream.write("<div class='class' id='%s'>\n" % self._url(structure.full_name())) 211 self.stream.write("<p>\n") 212 self._keyword("class") 213 self._name_start(structure.name) 214 self._popup_start() 215 self._scopes(definition) 216 self._popup_end() 217 self._name_end() 218 bases = structure.bases 219 if bases: 220 self.stream.write("(") 221 first = 1 222 for base in bases: 223 if not first: 224 self.stream.write(",\n") 225 self._name_start(base.name) 226 self._popup_start() 227 self._types(base) 228 self._scopes(base) 229 self._popup_end() 230 self._name_end() 231 first = 0 232 self.stream.write(")") 233 self.stream.write(":\n") 234 self._comment(self._text(structure.full_name())) 235 self.stream.write("</p>\n") 236 237 self.stream.write("<div class='body'>\n") 238 self._doc(node) 239 self.dispatch(node.code) 240 self.stream.write("</div>\n") 241 self.stream.write("</div>\n") 242 243 visitClass = hasnode(visitClass) # Remove unannotated nodes. 244 245 def visitFunction(self, node): 246 definition = node._node 247 subprogram = definition.expr.ref 248 self.stream.write("<div class='def' id='%s'>\n" % self._url(subprogram.full_name())) 249 self.stream.write("<p>\n") 250 self._keyword("def") 251 self._name_start(subprogram.name) 252 self._popup_start() 253 self._scopes(definition) 254 self._popup_end() 255 self._name_end() 256 self.stream.write("(") 257 first = 1 258 for param, default in subprogram.params: 259 if not first: 260 self.stream.write(",\n") 261 self._parameter(subprogram, param) 262 first = 0 263 if subprogram.star is not None: 264 if not first: 265 self.stream.write(", *\n") 266 param, default = subprogram.star 267 self._parameter(subprogram, param) 268 first = 0 269 if subprogram.dstar is not None: 270 if not first: 271 self.stream.write(", **\n") 272 param, default = subprogram.dstar 273 self._parameter(subprogram, param) 274 first = 0 275 self.stream.write(")") 276 self.stream.write(":\n") 277 self._comment(self._text(subprogram.full_name())) 278 self.stream.write("</p>\n") 279 280 self.stream.write("<div class='body'>\n") 281 self._doc(node) 282 self.dispatch(node.code) 283 self.stream.write("</div>\n") 284 self.stream.write("</div>\n") 285 286 visitFunction = hasnode(visitFunction) # Remove unannotated nodes. 287 288 def visitPass(self, node): 289 self._keyword("pass") 290 291 def visitReturn(self, node): 292 self._keyword("return") 293 self.dispatch(node.value) 294 295 def visitStmt(self, node): 296 self.stream.write("<div class='stmt'>\n") 297 self.default(node) 298 self.stream.write("</div>\n") 299 300 # Expressions. 301 302 def visitCallFunc(self, node): 303 self.stream.write("<span class='callfunc'>\n") 304 self.dispatch(node.node) 305 self.stream.write("(") 306 first = 1 307 for arg in node.args: 308 if not first: 309 self.stream.write(",\n") 310 self.dispatch(arg) 311 first = 0 312 if node.star_args is not None: 313 if not first: 314 self.stream.write(", *\n") 315 self.dispatch(node.star_args) 316 first = 0 317 if node.dstar_args is not None: 318 if not first: 319 self.stream.write(", **\n") 320 self.dispatch(node.dstar_args) 321 first = 0 322 self.stream.write(")\n") 323 self.stream.write("</span>\n") 324 325 def visitTuple(self, node): 326 self.stream.write("<span class='tuple'>\n") 327 self.stream.write("(") 328 self._sequence(node) 329 self.stream.write(")\n") 330 self.stream.write("</span>\n") 331 332 visitAssTuple = visitTuple 333 334 def visitList(self, node): 335 self.stream.write("<span class='list'>\n") 336 self.stream.write("[") 337 self._sequence(node) 338 self.stream.write("]\n") 339 self.stream.write("</span>\n") 340 341 visitAssList = visitList 342 343 def visitName(self, node): 344 if hasattr(node, "_node"): 345 self._name_start(node._node.name) 346 self._popup_start() 347 self._types(node._node) 348 self._scopes(node._node) 349 self._popup_end() 350 self._name_end() 351 else: 352 self._name(node.name) 353 354 def visitAssName(self, node): 355 if hasattr(node, "_node"): 356 self._name_start(node._node.name) 357 self._popup_start() 358 self._types(node._node.expr) 359 self._scopes(node._node) 360 self._popup_end() 361 self._name_end() 362 else: 363 self._name(node.name) 364 365 def visitConst(self, node): 366 self.stream.write(repr(node.value)) 367 368 def visitGetattr(self, node): 369 self.stream.write("<span class='getattr'>\n") 370 self.dispatch(node.expr) 371 self.stream.write("<span class='attr'>\n") 372 self.stream.write(".%s\n" % self._text(node.attrname)) 373 if hasattr(node, "_node"): 374 self._popup_start() 375 self._types(node._node) 376 self._scopes(node._node) 377 self._popup_end() 378 self.stream.write("</span>\n") 379 self.stream.write("</span>\n") 380 381 def visitAssAttr(self, node): 382 self.stream.write("<span class='assattr'>\n") 383 self.dispatch(node.expr) 384 self.stream.write("<span class='attr'>\n") 385 self.stream.write(".%s\n" % self._text(node.attrname)) 386 if hasattr(node, "_node"): 387 self._popup_start() 388 self._types(node._node) 389 self._scopes(node._node) 390 self._popup_end() 391 self.stream.write("</span>\n") 392 self.stream.write("</span>\n") 393 394 # Output preparation methods. 395 396 def _text(self, text): 397 return text.replace("&", "&").replace("<", "<").replace(">", ">") 398 399 def _attr(self, attr): 400 return self._text(attr).replace("'", "'").replace('"', """) 401 402 def _url(self, url): 403 return self._attr(url).replace("#", "%23").replace("-", "%2d") 404 405 def _comment(self, comment): 406 self.stream.write("<span class='comment'># %s</span>\n" % comment) 407 408 def _keyword(self, kw): 409 self.stream.write("<span class='keyword'>%s</span> " % kw) 410 411 def _doc(self, node): 412 if node.doc is not None: 413 self.stream.write("<pre class='doc'>\n") 414 self.stream.write('"""') 415 output = textwrap.dedent(node.doc.replace('"""', '\\"\\"\\"')) 416 self.stream.write(self._text(output)) 417 self.stream.write('"""') 418 self.stream.write("</pre>\n") 419 420 def _sequence(self, node): 421 first = 1 422 for n in node.nodes: 423 if not first: 424 self.stream.write(",\n") 425 self.dispatch(n) 426 first = 0 427 428 def _parameter(self, subprogram, param): 429 self._name_start(param) 430 if hasattr(subprogram, "paramtypes"): 431 self._popup_start() 432 self._types_list(subprogram.paramtypes[param]) 433 self._popup_end() 434 self._name_end() 435 436 def _name(self, name): 437 self.stream.write("<span class='name'>%s</span>\n" % name) 438 439 def _name_start(self, name): 440 self.stream.write("<span class='name'>%s\n" % name) 441 442 def _name_end(self): 443 self.stream.write("</span>\n") 444 445 def _popup_start(self): 446 self.stream.write("<span class='popup'>\n") 447 448 def _popup_end(self): 449 self.stream.write("</span>\n") 450 451 def _types(self, node): 452 if hasattr(node, "types"): 453 self._types_list(node.types) 454 elif hasattr(node, "writes"): 455 self._types_list(flatten(node.writes.values())) 456 elif hasattr(node, "accesses"): 457 self._types_list(flatten(node.accesses.values())) 458 else: 459 self.stream.write("<div class='types'>\n") 460 self.stream.write("No types!\n") 461 self.stream.write("</div>\n") 462 463 def _types_list(self, types): 464 self.stream.write("<div class='types'>\n") 465 for type in types: 466 fn = type.type.full_name() 467 self.stream.write("<div class='type'>") 468 self.stream.write(self._text(fn)) 469 self.stream.write("</div>\n") 470 self.stream.write("</div>\n") 471 472 def _scopes(self, node): 473 if not isinstance(node, LoadName): 474 self.stream.write("<div class='scopes'>\n") 475 if not hasattr(node, "writes") and not hasattr(node, "accesses"): 476 self.stream.write("No scopes!\n") 477 else: 478 for ref in getattr(node, "writes", getattr(node, "accesses", {})).keys(): 479 fn = ref.full_name() 480 self.stream.write("<div class='scope'>") 481 self.stream.write(self._text(fn)) 482 self.stream.write("</div>\n") 483 self.stream.write("</div>\n") 484 485 # Utility functions. 486 487 def flatten(lists): 488 result = [] 489 for l in lists: 490 for attr in l: 491 if attr not in result: 492 result.append(attr) 493 return result 494 495 # Convenience functions. 496 497 def view(module, stream=None): 498 viewer = Viewer(stream or sys.stdout) 499 viewer.process(module.original) 500 501 def browse(module, stream=None): 502 browser = Browser(stream or sys.stdout) 503 browser.process(module.original) 504 505 def makedoc(module, filename): 506 stream = open(filename, "wb") 507 try: 508 browser = Browser(stream) 509 browser.process(module.original) 510 finally: 511 stream.close() 512 513 # vim: tabstop=4 expandtab shiftwidth=4