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 28 # Exceptions. 29 30 class ViewerError(SimplifiedError): 31 32 "An error in viewing." 33 34 pass 35 36 # Classes. 37 38 class Viewer(ASTVisitor): 39 40 """ 41 A viewing visitor for AST nodes. 42 """ 43 44 def __init__(self, stream): 45 ASTVisitor.__init__(self) 46 self.cached_files = {} 47 self.printed_lines = {} 48 self.visitor = self 49 self.stream = stream 50 51 def process(self, module): 52 self.dispatch(module) 53 54 def dispatch(self, node): 55 self.dispatch_only(node) 56 ASTVisitor.dispatch(self, node) 57 58 def dispatch_only(self, node, every_time=0): 59 self.print_line(getattr(node, "filename", None), getattr(node, "lineno", None), every_time) 60 61 def print_line(self, filename, lineno, every_time): 62 last_printed = self.printed_lines.get(filename, 0) 63 if lineno > last_printed or every_time: 64 self.stream.write(self.get_line(filename, lineno)) 65 self.printed_lines[filename] = lineno 66 67 def get_line(self, filename, lineno): 68 if filename is None or lineno is None: 69 return "" 70 71 if self.cached_files.has_key(filename): 72 lines = self.cached_files[filename] 73 else: 74 f = open(filename) 75 try: 76 self.cached_files[filename] = lines = f.readlines() 77 finally: 78 f.close() 79 80 try: 81 return lines[lineno - 1] 82 except IndexError: 83 return "" 84 85 def report(self, exc): 86 self.stream.write("Exception was:\n\n" + str(exc.exc) + "\n\n") 87 self.stream.write("Nodes:\n\n") 88 for node in exc.nodes: 89 self.stream.write(repr(node) + "\n") 90 self.dispatch_only(node.original, every_time=1) 91 self.stream.write("\nOriginal node was:\n\n" + repr(exc.nodes[0].original) + "\n") 92 self.stream.write("\nSimplified node was:\n\n") 93 exc.nodes[0].pprint(stream=self.stream) 94 95 # HTML-related output production. 96 97 html_header = """<?xml version="1.0" encoding="iso-8859-15"?> 98 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 99 <html xmlns="http://www.w3.org/1999/xhtml"> 100 <head> 101 <title>Module</title> 102 <style type="text/css"> 103 body { 104 padding-top: 4em; padding-bottom: 4em; 105 font-size: 14pt; font-family: monospace; 106 background-color: black; color: white; 107 } 108 109 .class { margin-bottom: 1em; } 110 .function { margin-bottom: 1em; } 111 .body { padding-left: 2em; } 112 .keyword { color: yellow; } 113 .comment { color: blue; } 114 .str { color: #FF00FF; } 115 .doc { color: #FF00FF; margin-bottom: 1em; } 116 .ref { color: cyan; } 117 .ref a { color: cyan; text-decoration: none; } 118 119 .popup { 120 display: none; z-index: 2; 121 position: absolute; top: 1em; left: 0.5em; 122 padding: 0.5em; background-color: #000000; 123 } 124 125 .types { 126 padding: 0.5em; background-color: #0000FF; 127 float: right; 128 } 129 130 .scopes { 131 padding: 0.5em; background-color: #007700; 132 float: left; 133 } 134 135 .name, 136 .attr 137 { 138 position: relative; 139 } 140 141 .name:hover > .popup, 142 .attr:hover > .popup 143 { 144 display: block; 145 } 146 147 </style> 148 </head> 149 <body> 150 """ 151 152 html_footer = """</body> 153 </html> 154 """ 155 156 class Browser(ASTVisitor): 157 158 """ 159 A browsing visitor for AST nodes. 160 """ 161 162 def __init__(self, stream): 163 ASTVisitor.__init__(self) 164 self.visitor = self 165 self.stream = stream 166 167 def process(self, module): 168 self.stream.write(html_header) 169 self.dispatch(module) 170 self.stream.write(html_footer) 171 172 def dispatch(self, node): 173 try: 174 ASTVisitor.dispatch(self, node) 175 except ViewerError, exc: 176 exc.add(node) 177 raise 178 except Exception, exc: 179 raise ViewerError(exc, node) 180 181 def visitModule(self, node): 182 self.default(node) 183 184 # Statements. 185 186 def visitPass(self, node): 187 self._keyword("pass") 188 189 def visitClass(self, node): 190 definition = node._node 191 structure = definition.expr.ref 192 self.stream.write("<div class='class' id='%s'>\n" % self._url(structure.full_name())) 193 self.stream.write("<p>\n") 194 self._keyword("class") 195 self._name_start(structure.name) 196 self._popup_start() 197 self._scopes(definition) 198 self._popup_end() 199 self._name_end() 200 bases = structure.bases 201 if bases: 202 self.stream.write("(") 203 first = 1 204 for base in bases: 205 if not first: 206 self.stream.write(",\n") 207 self._name_start(base.name) 208 self._popup_start() 209 self._types(base) 210 self._scopes(base) 211 self._popup_end() 212 self._name_end() 213 first = 0 214 self.stream.write(")") 215 self.stream.write(":\n") 216 self._comment(self._text(structure.full_name())) 217 self.stream.write("</p>\n") 218 219 self.stream.write("<div class='body'>\n") 220 self._doc(node) 221 self.dispatch(node.code) 222 self.stream.write("</div>\n") 223 self.stream.write("</div>\n") 224 225 def visitFunction(self, node): 226 definition = node._node 227 subprogram = definition.expr.ref 228 self.stream.write("<div class='def' id='%s'>\n" % self._url(subprogram.full_name())) 229 self.stream.write("<p>\n") 230 self._keyword("def") 231 self._name_start(subprogram.name) 232 self._popup_start() 233 self._scopes(definition) 234 self._popup_end() 235 self._name_end() 236 self.stream.write("(") 237 first = 1 238 for param, default in subprogram.params: 239 if not first: 240 self.stream.write(",\n") 241 self._parameter(subprogram, param) 242 first = 0 243 if subprogram.star is not None: 244 if not first: 245 self.stream.write(", *\n") 246 param, default = subprogram.star 247 self._parameter(subprogram, param) 248 first = 0 249 if subprogram.dstar is not None: 250 if not first: 251 self.stream.write(", **\n") 252 param, default = subprogram.dstar 253 self._parameter(subprogram, param) 254 first = 0 255 self.stream.write(")") 256 self.stream.write(":\n") 257 self._comment(self._text(subprogram.full_name())) 258 self.stream.write("</p>\n") 259 260 self.stream.write("<div class='body'>\n") 261 self._doc(node) 262 self.dispatch(node.code) 263 self.stream.write("</div>\n") 264 self.stream.write("</div>\n") 265 266 def visitStmt(self, node): 267 self.stream.write("<div class='stmt'>\n") 268 self.default(node) 269 self.stream.write("</div>\n") 270 271 def visitAssign(self, node): 272 self.stream.write("<div class='assign'>\n") 273 for lvalue in node.nodes: 274 self.dispatch(lvalue) 275 self.stream.write("=\n") 276 self.dispatch(node.expr) 277 self.stream.write("</div>\n") 278 279 # Expressions. 280 281 def visitCallFunc(self, node): 282 invocation = node._node 283 self.stream.write("<span class='callfunc'>\n") 284 self.dispatch(node.node) 285 self.stream.write("(") 286 first = 1 287 for arg in node.args: 288 if not first: 289 self.stream.write(",\n") 290 self.dispatch(arg) 291 first = 0 292 if node.star_args is not None: 293 if not first: 294 self.stream.write(", *\n") 295 self.dispatch(node.star_args) 296 first = 0 297 if node.dstar_args is not None: 298 if not first: 299 self.stream.write(", **\n") 300 self.dispatch(node.dstar_args) 301 first = 0 302 self.stream.write(")\n") 303 self.stream.write("</span>\n") 304 305 def visitTuple(self, node): 306 self.stream.write("<span class='tuple'>\n") 307 self.stream.write("(") 308 self._sequence(node) 309 self.stream.write(")\n") 310 self.stream.write("</span>\n") 311 312 visitAssTuple = visitTuple 313 314 def visitList(self, node): 315 self.stream.write("<span class='list'>\n") 316 self.stream.write("[") 317 self._sequence(node) 318 self.stream.write("]\n") 319 self.stream.write("</span>\n") 320 321 visitAssList = visitList 322 323 def visitName(self, node): 324 self._name_start(node._node.name) 325 self._popup_start() 326 self._types(node._node) 327 self._scopes(node._node) 328 self._popup_end() 329 self._name_end() 330 331 def visitAssName(self, node): 332 self._name_start(node._node.name) 333 self._popup_start() 334 self._types(node._node.expr) 335 self._scopes(node._node) 336 self._popup_end() 337 self._name_end() 338 339 def visitConst(self, node): 340 self.stream.write(repr(node.value)) 341 342 def visitGetattr(self, node): 343 self.stream.write("<span class='getattr'>\n") 344 self.dispatch(node.expr) 345 self.stream.write("<span class='attr'>\n") 346 self.stream.write(".%s\n" % self._text(node.attrname)) 347 self._popup_start() 348 self._types(node._node) 349 self._scopes(node._node) 350 self._popup_end() 351 self.stream.write("</span>\n") 352 self.stream.write("</span>\n") 353 354 def visitAssAttr(self, node): 355 self.stream.write("<span class='assattr'>\n") 356 self.dispatch(node.expr) 357 self.stream.write("<span class='attr'>\n") 358 self.stream.write(".%s\n" % self._text(node.attrname)) 359 self._popup_start() 360 self._types(node._node) 361 self._scopes(node._node) 362 self._popup_end() 363 self.stream.write("</span>\n") 364 self.stream.write("</span>\n") 365 366 # Output preparation methods. 367 368 def _text(self, text): 369 return text.replace("&", "&").replace("<", "<").replace(">", ">") 370 371 def _attr(self, attr): 372 return self._text(attr).replace("'", "'").replace('"', """) 373 374 def _url(self, url): 375 return self._attr(url).replace("#", "%23").replace("-", "%2d") 376 377 def _comment(self, comment): 378 self.stream.write("<span class='comment'># %s</span>\n" % comment) 379 380 def _keyword(self, kw): 381 self.stream.write("<span class='keyword'>%s</span> " % kw) 382 383 def _doc(self, node): 384 if node.doc is not None: 385 self.stream.write("<div class='doc'>%s</div>\n" % self._text(repr(node.doc))) 386 387 def _sequence(self, node): 388 first = 1 389 for n in node.nodes: 390 if not first: 391 self.stream.write(",\n") 392 self.dispatch(n) 393 first = 0 394 395 def _parameter(self, subprogram, param): 396 self._name_start(param) 397 if hasattr(subprogram, "paramtypes"): 398 self._popup_start() 399 self._types_list(subprogram.paramtypes[param]) 400 self._popup_end() 401 self._name_end() 402 403 def _name(self, name): 404 self.stream.write("<span class='name'>%s</span>\n" % name) 405 406 def _name_start(self, name): 407 self.stream.write("<span class='name'>%s\n" % name) 408 409 def _name_end(self): 410 self.stream.write("</span>\n") 411 412 def _popup_start(self): 413 self.stream.write("<span class='popup'>\n") 414 415 def _popup_end(self): 416 self.stream.write("</span>\n") 417 418 def _types(self, node): 419 if hasattr(node, "types"): 420 self._types_list(node.types) 421 elif hasattr(node, "writes"): 422 self._types_list(flatten(node.writes.values())) 423 elif hasattr(node, "accesses"): 424 self._types_list(flatten(node.accesses.values())) 425 else: 426 self.stream.write("<div class='types'>\n") 427 self.stream.write("No types!\n") 428 self.stream.write("</div>\n") 429 430 def _types_list(self, types): 431 self.stream.write("<div class='types'>\n") 432 for type in types: 433 fn = type.type.full_name() 434 self.stream.write("<div class='type'>") 435 self.stream.write(self._text(fn)) 436 self.stream.write("</div>\n") 437 self.stream.write("</div>\n") 438 439 def _scopes(self, node): 440 if not isinstance(node, LoadName): 441 self.stream.write("<div class='scopes'>\n") 442 if not hasattr(node, "writes") and not hasattr(node, "accesses"): 443 self.stream.write("No scopes!\n") 444 else: 445 for ref in getattr(node, "writes", getattr(node, "accesses", {})).keys(): 446 fn = ref.full_name() 447 self.stream.write("<div class='scope'>") 448 self.stream.write(self._text(fn)) 449 self.stream.write("</div>\n") 450 self.stream.write("</div>\n") 451 452 # Utility functions. 453 454 def flatten(lists): 455 result = [] 456 for l in lists: 457 for attr in l: 458 if attr not in result: 459 result.append(attr) 460 return result 461 462 # Convenience functions. 463 464 def view(module, stream=None): 465 viewer = Viewer(stream or sys.stdout) 466 viewer.process(module.original) 467 468 def browse(module, stream=None): 469 browser = Browser(stream or sys.stdout) 470 browser.process(module.original) 471 472 def makedoc(module, filename): 473 stream = open(filename, "wb") 474 try: 475 browser = Browser(stream) 476 browser.process(module.original) 477 finally: 478 stream.close() 479 480 # vim: tabstop=4 expandtab shiftwidth=4