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._name_start(param) 242 if hasattr(subprogram, "paramtypes"): 243 self._popup_start() 244 self._types_list(subprogram.paramtypes[param]) 245 self._popup_end() 246 self._name_end() 247 first = 0 248 self.stream.write(")") 249 self.stream.write(":\n") 250 self._comment(self._text(subprogram.full_name())) 251 self.stream.write("</p>\n") 252 253 self.stream.write("<div class='body'>\n") 254 self._doc(node) 255 self.dispatch(node.code) 256 self.stream.write("</div>\n") 257 self.stream.write("</div>\n") 258 259 def visitStmt(self, node): 260 self.stream.write("<div class='stmt'>\n") 261 self.default(node) 262 self.stream.write("</div>\n") 263 264 def visitAssign(self, node): 265 self.stream.write("<div class='assign'>\n") 266 for lvalue in node.nodes: 267 self.dispatch(lvalue) 268 self.stream.write("=\n") 269 self.dispatch(node.expr) 270 self.stream.write("</div>\n") 271 272 # Expressions. 273 274 def visitTuple(self, node): 275 self.stream.write("<span class='tuple'>\n") 276 self.stream.write("(") 277 self._sequence(node) 278 self.stream.write(")\n") 279 self.stream.write("</span>\n") 280 281 visitAssTuple = visitTuple 282 283 def visitList(self, node): 284 self.stream.write("<span class='list'>\n") 285 self.stream.write("[") 286 self._sequence(node) 287 self.stream.write("]\n") 288 self.stream.write("</span>\n") 289 290 visitAssList = visitList 291 292 def visitName(self, node): 293 self._name_start(node._node.name) 294 self._popup_start() 295 self._types(node._node) 296 self._scopes(node._node) 297 self._popup_end() 298 self._name_end() 299 300 def visitAssName(self, node): 301 self._name_start(node._node.name) 302 self._popup_start() 303 self._types(node._node.expr) 304 self._scopes(node._node) 305 self._popup_end() 306 self._name_end() 307 308 def visitConst(self, node): 309 self.stream.write(repr(node.value)) 310 311 def visitGetattr(self, node): 312 self.stream.write("<span class='getattr'>\n") 313 self.dispatch(node.expr) 314 self.stream.write("<span class='attr'>\n") 315 self.stream.write(".%s\n" % self._text(node.attrname)) 316 self._popup_start() 317 self._types(node._node) 318 self._scopes(node._node) 319 self._popup_end() 320 self.stream.write("</span>\n") 321 self.stream.write("</span>\n") 322 323 def visitAssAttr(self, node): 324 self.stream.write("<span class='assattr'>\n") 325 self.dispatch(node.expr) 326 self.stream.write("<span class='attr'>\n") 327 self.stream.write(".%s\n" % self._text(node.attrname)) 328 self._popup_start() 329 self._types(node._node) 330 self._scopes(node._node) 331 self._popup_end() 332 self.stream.write("</span>\n") 333 self.stream.write("</span>\n") 334 335 # Output preparation methods. 336 337 def _text(self, text): 338 return text.replace("&", "&").replace("<", "<").replace(">", ">") 339 340 def _attr(self, attr): 341 return self._text(attr).replace("'", "'").replace('"', """) 342 343 def _url(self, url): 344 return self._attr(url).replace("#", "%23").replace("-", "%2d") 345 346 def _comment(self, comment): 347 self.stream.write("<span class='comment'># %s</span>\n" % comment) 348 349 def _keyword(self, kw): 350 self.stream.write("<span class='keyword'>%s</span> " % kw) 351 352 def _doc(self, node): 353 if node.doc is not None: 354 self.stream.write("<div class='doc'>%s</div>\n" % self._text(repr(node.doc))) 355 356 def _sequence(self, node): 357 first = 1 358 for n in node.nodes: 359 if not first: 360 self.stream.write(",\n") 361 self.dispatch(n) 362 first = 0 363 364 def _name(self, name): 365 self.stream.write("<span class='name'>%s</span>\n" % name) 366 367 def _name_start(self, name): 368 self.stream.write("<span class='name'>%s\n" % name) 369 370 def _name_end(self): 371 self.stream.write("</span>\n") 372 373 def _popup_start(self): 374 self.stream.write("<span class='popup'>\n") 375 376 def _popup_end(self): 377 self.stream.write("</span>\n") 378 379 def _types(self, node): 380 if hasattr(node, "types"): 381 self._types_list(node.types) 382 elif hasattr(node, "writes"): 383 self._types_list(flatten(node.writes.values())) 384 elif hasattr(node, "accesses"): 385 self._types_list(flatten(node.accesses.values())) 386 else: 387 self.stream.write("<div class='types'>\n") 388 self.stream.write("No types!\n") 389 self.stream.write("</div>\n") 390 391 def _types_list(self, types): 392 self.stream.write("<div class='types'>\n") 393 for type in types: 394 fn = type.type.full_name() 395 self.stream.write("<div class='type'>") 396 self.stream.write(self._text(fn)) 397 self.stream.write("</div>\n") 398 self.stream.write("</div>\n") 399 400 def _scopes(self, node): 401 if not isinstance(node, LoadName): 402 self.stream.write("<div class='scopes'>\n") 403 if not hasattr(node, "writes") and not hasattr(node, "accesses"): 404 self.stream.write("No scopes!\n") 405 else: 406 for ref in getattr(node, "writes", getattr(node, "accesses", {})).keys(): 407 fn = ref.full_name() 408 self.stream.write("<div class='scope'>") 409 self.stream.write(self._text(fn)) 410 self.stream.write("</div>\n") 411 self.stream.write("</div>\n") 412 413 # Utility functions. 414 415 def flatten(lists): 416 result = [] 417 for l in lists: 418 for attr in l: 419 if attr not in result: 420 result.append(attr) 421 return result 422 423 # Convenience functions. 424 425 def view(module, stream=None): 426 viewer = Viewer(stream or sys.stdout) 427 viewer.process(module.original) 428 429 def browse(module, stream=None): 430 browser = Browser(stream or sys.stdout) 431 browser.process(module.original) 432 433 def makedoc(module, filename): 434 stream = open(filename, "wb") 435 try: 436 browser = Browser(stream) 437 browser.process(module.original) 438 finally: 439 stream.close() 440 441 # vim: tabstop=4 expandtab shiftwidth=4