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 import sys 26 27 class Viewer(ASTVisitor): 28 29 """ 30 A viewing visitor for AST nodes. 31 """ 32 33 def __init__(self, stream): 34 ASTVisitor.__init__(self) 35 self.cached_files = {} 36 self.printed_lines = {} 37 self.visitor = self 38 self.stream = stream 39 40 def process(self, module): 41 self.dispatch(module) 42 43 def dispatch(self, node): 44 self.dispatch_only(node) 45 ASTVisitor.dispatch(self, node) 46 47 def dispatch_only(self, node, every_time=0): 48 self.print_line(getattr(node, "filename", None), getattr(node, "lineno", None), every_time) 49 50 def print_line(self, filename, lineno, every_time): 51 last_printed = self.printed_lines.get(filename, 0) 52 if lineno > last_printed or every_time: 53 self.stream.write(self.get_line(filename, lineno)) 54 self.printed_lines[filename] = lineno 55 56 def get_line(self, filename, lineno): 57 if filename is None or lineno is None: 58 return "" 59 60 if self.cached_files.has_key(filename): 61 lines = self.cached_files[filename] 62 else: 63 f = open(filename) 64 try: 65 self.cached_files[filename] = lines = f.readlines() 66 finally: 67 f.close() 68 69 try: 70 return lines[lineno - 1] 71 except IndexError: 72 return "" 73 74 def report(self, exc): 75 self.stream.write("Exception was:\n\n" + str(exc.exc) + "\n\n") 76 self.stream.write("Nodes:\n\n") 77 for node in exc.nodes: 78 self.stream.write(repr(node) + "\n") 79 self.dispatch_only(node.original, every_time=1) 80 self.stream.write("\nOriginal node was:\n\n" + repr(exc.nodes[0].original) + "\n") 81 self.stream.write("\nSimplified node was:\n\n") 82 exc.nodes[0].pprint(stream=self.stream) 83 84 # HTML-related output production. 85 86 html_header = """<?xml version="1.0" encoding="iso-8859-15"?> 87 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 88 <html xmlns="http://www.w3.org/1999/xhtml"> 89 <head> 90 <title>Module</title> 91 <style type="text/css"> 92 body { 93 padding-top: 4em; padding-bottom: 4em; 94 font-size: 14pt; font-family: monospace; 95 background-color: black; color: white; 96 } 97 98 .class { margin-bottom: 1em; } 99 .function { margin-bottom: 1em; } 100 .body { padding-left: 2em; } 101 .keyword { color: yellow; } 102 .comment { color: blue; } 103 .ref { color: cyan; } 104 .ref a { color: cyan; text-decoration: none; } 105 106 .name { 107 position: relative; 108 } 109 110 .types { 111 display: none; z-index: 2; 112 position: absolute; top: 1em; left: 6.5em; 113 padding: 0.5em; background-color: #0000FF; 114 } 115 116 .scopes { 117 display: none; z-index: 2; 118 position: absolute; top: 1em; left: 0.5em; 119 padding: 0.5em; background-color: #007700; 120 } 121 122 .name:hover > .types, 123 .name:hover > .scopes 124 { 125 display: block; 126 } 127 128 </style> 129 </head> 130 <body> 131 """ 132 133 html_footer = """</body> 134 </html> 135 """ 136 137 class Browser(ASTVisitor): 138 139 """ 140 A browsing visitor for AST nodes. 141 """ 142 143 def __init__(self, stream): 144 ASTVisitor.__init__(self) 145 self.visitor = self 146 self.stream = stream 147 148 def process(self, module): 149 self.stream.write(html_header) 150 self.dispatch(module) 151 self.stream.write(html_footer) 152 153 def visitModule(self, node): 154 self.default(node) 155 156 # Statements. 157 158 def visitPass(self, node): 159 self._keyword("pass") 160 161 def visitClass(self, node): 162 definition = node._node 163 structure = definition.expr.types[0].type 164 self.stream.write("<div class='class' id='%s'>\n" % self._url(structure.full_name())) 165 self.stream.write("<p>\n") 166 self._keyword("class") 167 self._name_start(structure) 168 self._scopes(definition) 169 self._name_end() 170 bases = structure.bases 171 if bases: 172 self.stream.write("(") 173 first = 1 174 for base in bases: 175 if not first: 176 self.stream.write(",\n") 177 self._name_start(base) 178 self._types(base) 179 self._scopes(base) 180 self._name_end() 181 first = 0 182 self.stream.write(")") 183 self.stream.write(":\n") 184 self._comment(self._text(structure.full_name())) 185 self.stream.write("</p>\n") 186 187 self.stream.write("<div class='body'>\n") 188 self._doc(node) 189 self.dispatch(node.code) 190 self.stream.write("</div>\n") 191 self.stream.write("</div>\n") 192 193 def visitAssign(self, node): 194 self.stream.write("<div class='assign'>\n") 195 for lvalue in node.nodes: 196 self.dispatch(lvalue) 197 self.stream.write("=\n") 198 self.dispatch(node.expr) 199 self.stream.write("</div>\n") 200 201 # Expressions. 202 203 def visitTuple(self, node): 204 self.stream.write("<span class='tuple'>\n") 205 self.stream.write("(") 206 self._sequence(node) 207 self.stream.write(")\n") 208 self.stream.write("</span>\n") 209 210 visitAssTuple = visitTuple 211 212 def visitList(self, node): 213 self.stream.write("<span class='list'>\n") 214 self.stream.write("[") 215 self._sequence(node) 216 self.stream.write("]\n") 217 self.stream.write("</span>\n") 218 219 visitAssList = visitList 220 221 def visitName(self, node): 222 self._name_start(node._node) 223 self._types(node._node) 224 self._scopes(node._node) 225 self._name_end() 226 227 def visitAssName(self, node): 228 self._name_start(node._node) 229 self._types(node._node.expr) 230 self._scopes(node._node) 231 self._name_end() 232 233 def visitConst(self, node): 234 self.stream.write(repr(node.value)) 235 236 # Output preparation methods. 237 238 def _text(self, text): 239 return text.replace("&", "&").replace("<", "<").replace(">", ">") 240 241 def _attr(self, attr): 242 return self._text(attr).replace("'", "'").replace('"', """) 243 244 def _url(self, url): 245 return self._attr(url).replace("#", "%23").replace("-", "%2d") 246 247 def _comment(self, comment): 248 self.stream.write("<span class='comment'># %s</span>\n" % comment) 249 250 def _keyword(self, kw): 251 self.stream.write("<span class='keyword'>%s</span> " % kw) 252 253 def _doc(self, node): 254 if node.doc is not None: 255 self.stream.write("<div class='doc'>%s</div>\n" % self._text(node.doc)) 256 257 def _sequence(self, node): 258 first = 1 259 for n in node.nodes: 260 if not first: 261 self.stream.write(",\n") 262 self.dispatch(n) 263 first = 0 264 265 def _name(self, node): 266 self.stream.write("<span class='name'>%s</span>\n" % node.name) 267 268 def _name_start(self, node): 269 self.stream.write("<span class='name'>%s\n" % node.name) 270 271 def _name_end(self): 272 self.stream.write("</span>\n") 273 274 def _types(self, node): 275 self.stream.write("<div class='types'>\n") 276 for type in node.types: 277 fn = type.type.full_name() 278 self.stream.write("<div class='type'>") 279 self.stream.write(self._text(fn)) 280 self.stream.write("</div>\n") 281 self.stream.write("</div>\n") 282 283 def _scopes(self, node): 284 self.stream.write("<div class='scopes'>\n") 285 if not hasattr(node, "writes") and not hasattr(node, "accesses"): 286 raise AttributeError, node 287 for ref in getattr(node, "writes", getattr(node, "accesses", {})).keys(): 288 fn = ref.full_name() 289 self.stream.write("<div class='scope'>") 290 self.stream.write(self._text(fn)) 291 self.stream.write("</div>\n") 292 self.stream.write("</div>\n") 293 294 # Convenience functions. 295 296 def view(module, stream=None): 297 viewer = Viewer(stream or sys.stdout) 298 viewer.process(module.original) 299 300 def browse(module, stream=None): 301 browser = Browser(stream or sys.stdout) 302 browser.process(module.original) 303 304 def makedoc(module, filename): 305 stream = open(filename, "wb") 306 try: 307 browser = Browser(stream) 308 browser.process(module.original) 309 finally: 310 stream.close() 311 312 # vim: tabstop=4 expandtab shiftwidth=4