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 .types { 120 display: none; z-index: 2; 121 position: absolute; top: 1em; left: 7.5em; 122 padding: 0.5em; background-color: #0000FF; 123 } 124 125 .scopes { 126 display: none; z-index: 2; 127 position: absolute; top: 1em; left: 0.5em; 128 padding: 0.5em; background-color: #007700; 129 } 130 131 .name, 132 .attr 133 { 134 position: relative; 135 } 136 137 .name:hover > .types, 138 .name:hover > .scopes, 139 .attr:hover > .types, 140 .attr:hover > .scopes 141 { 142 display: block; 143 } 144 145 </style> 146 </head> 147 <body> 148 """ 149 150 html_footer = """</body> 151 </html> 152 """ 153 154 class Browser(ASTVisitor): 155 156 """ 157 A browsing visitor for AST nodes. 158 """ 159 160 def __init__(self, stream): 161 ASTVisitor.__init__(self) 162 self.visitor = self 163 self.stream = stream 164 165 def process(self, module): 166 self.stream.write(html_header) 167 self.dispatch(module) 168 self.stream.write(html_footer) 169 170 def dispatch(self, node): 171 try: 172 ASTVisitor.dispatch(self, node) 173 except ViewerError, exc: 174 exc.add(node) 175 raise 176 except Exception, exc: 177 raise ViewerError(exc, node) 178 179 def visitModule(self, node): 180 self.default(node) 181 182 # Statements. 183 184 def visitPass(self, node): 185 self._keyword("pass") 186 187 def visitClass(self, node): 188 definition = node._node 189 structure = definition.expr.ref 190 self.stream.write("<div class='class' id='%s'>\n" % self._url(structure.full_name())) 191 self.stream.write("<p>\n") 192 self._keyword("class") 193 self._name_start(structure.name) 194 self._scopes(definition) 195 self._name_end() 196 bases = structure.bases 197 if bases: 198 self.stream.write("(") 199 first = 1 200 for base in bases: 201 if not first: 202 self.stream.write(",\n") 203 self._name_start(base.name) 204 self._types(base) 205 self._scopes(base) 206 self._name_end() 207 first = 0 208 self.stream.write(")") 209 self.stream.write(":\n") 210 self._comment(self._text(structure.full_name())) 211 self.stream.write("</p>\n") 212 213 self.stream.write("<div class='body'>\n") 214 self._doc(node) 215 self.dispatch(node.code) 216 self.stream.write("</div>\n") 217 self.stream.write("</div>\n") 218 219 def visitFunction(self, node): 220 definition = node._node 221 subprogram = definition.expr.ref 222 self.stream.write("<div class='def' id='%s'>\n" % self._url(subprogram.full_name())) 223 self.stream.write("<p>\n") 224 self._keyword("def") 225 self._name_start(subprogram.name) 226 self._scopes(definition) 227 self._name_end() 228 self.stream.write("(") 229 first = 1 230 for param, default in subprogram.params: 231 if not first: 232 self.stream.write(",\n") 233 self._name_start(param) 234 if hasattr(subprogram, "paramtypes"): 235 self._types_list(subprogram.paramtypes[param]) 236 self._name_end() 237 first = 0 238 self.stream.write(")") 239 self.stream.write(":\n") 240 self._comment(self._text(subprogram.full_name())) 241 self.stream.write("</p>\n") 242 243 self.stream.write("<div class='body'>\n") 244 self._doc(node) 245 self.dispatch(node.code) 246 self.stream.write("</div>\n") 247 self.stream.write("</div>\n") 248 249 def visitStmt(self, node): 250 self.stream.write("<div class='stmt'>\n") 251 self.default(node) 252 self.stream.write("</div>\n") 253 254 def visitAssign(self, node): 255 self.stream.write("<div class='assign'>\n") 256 for lvalue in node.nodes: 257 self.dispatch(lvalue) 258 self.stream.write("=\n") 259 self.dispatch(node.expr) 260 self.stream.write("</div>\n") 261 262 # Expressions. 263 264 def visitTuple(self, node): 265 self.stream.write("<span class='tuple'>\n") 266 self.stream.write("(") 267 self._sequence(node) 268 self.stream.write(")\n") 269 self.stream.write("</span>\n") 270 271 visitAssTuple = visitTuple 272 273 def visitList(self, node): 274 self.stream.write("<span class='list'>\n") 275 self.stream.write("[") 276 self._sequence(node) 277 self.stream.write("]\n") 278 self.stream.write("</span>\n") 279 280 visitAssList = visitList 281 282 def visitName(self, node): 283 self._name_start(node._node.name) 284 self._types(node._node) 285 self._scopes(node._node) 286 self._name_end() 287 288 def visitAssName(self, node): 289 self._name_start(node._node.name) 290 self._types(node._node.expr) 291 self._scopes(node._node) 292 self._name_end() 293 294 def visitConst(self, node): 295 self.stream.write(repr(node.value)) 296 297 def visitGetattr(self, node): 298 self.stream.write("<span class='getattr'>\n") 299 self.dispatch(node.expr) 300 self.stream.write("<span class='attr'>\n") 301 self.stream.write(".%s\n" % self._text(node.attrname)) 302 self._types(node._node) 303 self._scopes(node._node) 304 self.stream.write("</span>\n") 305 self.stream.write("</span>\n") 306 307 def visitAssAttr(self, node): 308 self.stream.write("<span class='assattr'>\n") 309 self.dispatch(node.expr) 310 self.stream.write("<span class='attr'>\n") 311 self.stream.write(".%s\n" % self._text(node.attrname)) 312 self._types(node._node) 313 self._scopes(node._node) 314 self.stream.write("</span>\n") 315 self.stream.write("</span>\n") 316 317 # Output preparation methods. 318 319 def _text(self, text): 320 return text.replace("&", "&").replace("<", "<").replace(">", ">") 321 322 def _attr(self, attr): 323 return self._text(attr).replace("'", "'").replace('"', """) 324 325 def _url(self, url): 326 return self._attr(url).replace("#", "%23").replace("-", "%2d") 327 328 def _comment(self, comment): 329 self.stream.write("<span class='comment'># %s</span>\n" % comment) 330 331 def _keyword(self, kw): 332 self.stream.write("<span class='keyword'>%s</span> " % kw) 333 334 def _doc(self, node): 335 if node.doc is not None: 336 self.stream.write("<div class='doc'>%s</div>\n" % self._text(repr(node.doc))) 337 338 def _sequence(self, node): 339 first = 1 340 for n in node.nodes: 341 if not first: 342 self.stream.write(",\n") 343 self.dispatch(n) 344 first = 0 345 346 def _name(self, name): 347 self.stream.write("<span class='name'>%s</span>\n" % name) 348 349 def _name_start(self, name): 350 self.stream.write("<span class='name'>%s\n" % name) 351 352 def _name_end(self): 353 self.stream.write("</span>\n") 354 355 def _types(self, node): 356 if not hasattr(node, "types"): 357 self.stream.write("<div class='types'>\n") 358 self.stream.write("No types!\n") 359 self.stream.write("</div>\n") 360 else: 361 self._types_list(node.types) 362 363 def _types_list(self, types): 364 self.stream.write("<div class='types'>\n") 365 for type in types: 366 fn = type.type.full_name() 367 self.stream.write("<div class='type'>") 368 self.stream.write(self._text(fn)) 369 self.stream.write("</div>\n") 370 self.stream.write("</div>\n") 371 372 def _scopes(self, node): 373 if not isinstance(node, LoadName): 374 self.stream.write("<div class='scopes'>\n") 375 if not hasattr(node, "writes") and not hasattr(node, "accesses"): 376 self.stream.write("No scopes!\n") 377 else: 378 for ref in getattr(node, "writes", getattr(node, "accesses", {})).keys(): 379 fn = ref.full_name() 380 self.stream.write("<div class='scope'>") 381 self.stream.write(self._text(fn)) 382 self.stream.write("</div>\n") 383 self.stream.write("</div>\n") 384 385 # Convenience functions. 386 387 def view(module, stream=None): 388 viewer = Viewer(stream or sys.stdout) 389 viewer.process(module.original) 390 391 def browse(module, stream=None): 392 browser = Browser(stream or sys.stdout) 393 browser.process(module.original) 394 395 def makedoc(module, filename): 396 stream = open(filename, "wb") 397 try: 398 browser = Browser(stream) 399 browser.process(module.original) 400 finally: 401 stream.close() 402 403 # vim: tabstop=4 expandtab shiftwidth=4