1 #!/usr/bin/env python 2 3 """ 4 View annotated sources. 5 6 Copyright (C) 2006, 2007, 2010 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from micropython.data import * 23 from compiler.visitor import ASTVisitor 24 import sys 25 import os 26 import textwrap 27 28 # Classes. 29 30 # HTML-related output production. 31 32 html_header = """<?xml version="1.0" encoding="iso-8859-15"?> 33 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 34 <html xmlns="http://www.w3.org/1999/xhtml"> 35 <head> 36 <title>Module</title> 37 <style type="text/css"> 38 html { 39 background-color: black; color: white; 40 } 41 42 body { 43 padding-bottom: 4em; 44 font-size: 14pt; font-family: monospace; 45 background-color: black; color: white; 46 } 47 48 .nowrap { white-space: nowrap; } 49 50 .class { margin-top: 1em; margin-bottom: 1em; } 51 .function { margin-top: 1em; margin-bottom: 1em; } 52 .body { padding-left: 2em; } 53 .keyword { color: yellow; } 54 .comment { color: blue; } 55 .class-name { color: cyan; } 56 .function-name { color: cyan; } 57 .str { color: #FF00FF; } 58 .doc { color: #FF00FF; margin-top: 1em; margin-bottom: 1em; } 59 .invocation a { color: white; text-decoration: none; } 60 61 .summary-class { 62 background-color: #004400; 63 } 64 65 .summary-instance { 66 background-color: #0000FF; 67 } 68 69 .summary-attr { 70 background-color: #007700; 71 } 72 73 </style> 74 </head> 75 <body> 76 """ 77 78 html_footer = """</body> 79 </html> 80 """ 81 82 # Utility classes. 83 84 class Writer: 85 86 "A utility class providing useful HTML output methods." 87 88 # Methods which return strings. 89 90 def _text(self, text): 91 return text.replace("&", "&").replace("<", "<").replace(">", ">") 92 93 def _attr(self, attr): 94 return self._text(attr).replace("'", "'").replace('"', """) 95 96 def _url(self, url): 97 return self._attr(url).replace("#", "%23").replace("-", "%2d") 98 99 # Methods which write to the stream. 100 101 def _comment(self, comment): 102 self.stream.write("<span class='comment'># %s</span>\n" % comment) 103 104 def _keyword(self, kw): 105 self.stream.write("<span class='keyword'>%s</span> " % kw) 106 107 def _doc(self, node): 108 if node.doc is not None: 109 self.stream.write("<pre class='doc'>\n") 110 self.stream.write('"""') 111 output = textwrap.dedent(node.doc.replace('"""', '\\"\\"\\"')) 112 self.stream.write(self._text(output)) 113 self.stream.write('"""') 114 self.stream.write("</pre>\n") 115 116 def _name(self, name, classes=None): 117 self.stream.write("<span class='%s'>%s</span>" % (classes or "name", name)) 118 119 def _name_link(self, module_name, full_name, name, classes=None): 120 self.stream.write("<a class='%s' href='%s%sxhtml#%s'>%s</a>" % ( 121 classes or "name", module_name, os.path.extsep, 122 self._attr(full_name), self._text(name))) 123 124 def _summary_link(self, module_name, full_name, name, classes=None): 125 self._name_link("%s-summary" % module_name, full_name, name, classes) 126 127 def _object_name_def(self, module, obj, classes=None): 128 129 """ 130 Link to the summary for 'module' using 'obj'. The optional 'classes' 131 can be used to customise the CSS classes employed. 132 """ 133 134 self._summary_link(module.full_name(), obj.full_name(), obj.name, classes) 135 136 # Summary classes. 137 138 class Summary(Writer): 139 140 "Summarise classes and attributes in modules." 141 142 def __init__(self, module): 143 self.module = module 144 145 def to_stream(self, stream): 146 147 "Write the summary to the given 'stream'." 148 149 self.stream = stream 150 self._init_details() 151 self.stream.write(html_header) 152 self._write_classes(self.module) 153 self.stream.write(html_footer) 154 155 def _write_classes(self, module): 156 self.stream.write("<table cellspacing='5' cellpadding='5'>\n") 157 158 all_classes = {} 159 160 for obj in self.module.all_objects: 161 if isinstance(obj, Class): 162 all_classes[obj.name] = obj 163 164 all_class_names = all_classes.keys() 165 all_class_names.sort() 166 167 for name in all_class_names: 168 self._write_class(all_classes[name]) 169 170 self.stream.write("</table>\n") 171 172 def _write_class(self, obj): 173 174 # Write the class... 175 176 self.stream.write("<tbody class='class'>\n") 177 self.stream.write("<tr>\n") 178 self.stream.write("<th class='summary-class' id='%s'>\n" % obj.full_name()) 179 self._keyword("class") 180 self.stream.write(obj.name) 181 self.stream.write("</th>\n") 182 183 # ...and all known attribute names. 184 185 obj_attributes = obj.all_attribute_names() 186 187 for name in self.attribute_names: 188 if name in obj_attributes: 189 self.stream.write("<th class='summary-attr'>%s</th>\n" % self._text(name)) 190 else: 191 self.stream.write("<th></th>\n") 192 self.stream.write("</tr>\n") 193 194 self.stream.write("</tbody>\n") 195 196 def _init_details(self): 197 names = set() 198 199 # Visit all classes. 200 201 for obj in self.module.all_objects: 202 if isinstance(obj, Class): 203 names.update(obj.all_attribute_names()) 204 205 self.attribute_names = list(names) 206 self.attribute_names.sort() 207 208 # Source code classes. 209 210 class AnnotatedSource(ASTVisitor, Writer): 211 212 "A module source code browser." 213 214 def __init__(self, module): 215 ASTVisitor.__init__(self) 216 self.visitor = self 217 self.module = module 218 219 def to_stream(self, stream): 220 221 "Write the annotated code to the given 'stream'." 222 223 self.stream = stream 224 self.stream.write(html_header) 225 self.dispatch(self.module.module) 226 self.stream.write(html_footer) 227 228 def visitModule(self, node): 229 self.default(node) 230 231 # Statements. 232 233 def visitAssert(self, node): 234 self.stream.write("<div class='assert nowrap'>\n") 235 self._keyword("assert") 236 self.dispatch(node.test) 237 if node.fail: 238 self.stream.write(", ") 239 self.dispatch(node.fail) 240 self.stream.write("</div>\n") 241 242 def visitAssign(self, node): 243 self.stream.write("<div class='assign nowrap'>\n") 244 for lvalue in node.nodes: 245 self.dispatch(lvalue) 246 self.stream.write("= ") 247 self.dispatch(node.expr) 248 self.stream.write("</div>\n") 249 250 def visitAugAssign(self, node): 251 self.stream.write("<div class='augassign nowrap'>\n") 252 self.dispatch(node.node) 253 self.stream.write("%s " % node.op) 254 self.dispatch(node.expr) 255 self.stream.write("</div>\n") 256 257 def visitBreak(self, node): 258 self.stream.write("<div class='break nowrap'>\n") 259 self._keyword("break") 260 self.stream.write("</div>\n") 261 262 def visitClass(self, node): 263 264 # Use inspected details where possible. 265 266 if hasattr(node, "_def"): 267 cls = node._def 268 bases = cls.bases 269 self.stream.write("<div class='class nowrap' id='%s'>\n" % cls.full_name()) 270 else: 271 print "Warning: class %s not recognised!" % node.name 272 return 273 274 # Write the declaration line. 275 276 self.stream.write("<div>\n") 277 self._keyword("class") 278 self._object_name_def(self.module, cls, "class-name") 279 280 # Suppress the "object" class appearing alone. 281 282 if bases and not (len(bases) == 1 and bases[0].name == "object"): 283 self.stream.write("(") 284 first = 1 285 for base in bases: 286 if not first: 287 self.stream.write(", ") 288 289 self._object_name_def(base.module, base) 290 291 first = 0 292 self.stream.write(")") 293 294 self.stream.write(":\n") 295 self.stream.write("</div>\n") 296 297 # Write the docstring and class body. 298 299 self.stream.write("<div class='body nowrap'>\n") 300 self._doc(node) 301 self.dispatch(node.code) 302 self.stream.write("</div>\n") 303 self.stream.write("</div>\n") 304 305 def visitContinue(self, node): 306 self.stream.write("<div class='continue nowrap'>\n") 307 self._keyword("continue") 308 self.stream.write("</div>\n") 309 310 def visitDiscard(self, node): 311 self.stream.write("<div class='discard nowrap'>\n") 312 self.default(node) 313 self.stream.write("</div>\n") 314 315 def visitFor(self, node): 316 self.stream.write("<div class='if nowrap'>\n") 317 self.stream.write("<div>\n") 318 self._keyword("for") 319 self.dispatch(node.assign) 320 self._keyword("in") 321 self.dispatch(node.list) 322 self.stream.write(":\n") 323 self.stream.write("</div>\n") 324 self.stream.write("<div class='body nowrap'>\n") 325 self.dispatch(node.body) 326 self.stream.write("</div>\n") 327 if node.else_ is not None: 328 self.stream.write("<div>\n") 329 self._keyword("else") 330 self.stream.write(":\n") 331 self.stream.write("</div>\n") 332 self.stream.write("<div class='body nowrap'>\n") 333 self.dispatch(node.else_) 334 self.stream.write("</div>\n") 335 self.stream.write("</div>\n") 336 337 def visitFrom(self, node): 338 self.stream.write("<div class='from nowrap'>\n") 339 self._keyword("from") 340 self._name(node.modname) 341 self._keyword("import") 342 first = 1 343 for (name, alias), _name in map(None, node.names, node._names): 344 if not first: 345 self.stream.write(", ") 346 if alias: 347 self.stream.write(name + " ") 348 self._keyword("as") 349 self._name(alias or name) 350 first = 0 351 self.stream.write("</div>\n") 352 353 def visitFunction(self, node): 354 if hasattr(node, "_def"): 355 fn = node._def 356 self.stream.write("<div class='function nowrap' id='%s'>\n" % fn.full_name()) 357 else: 358 print "Warning: function %s not recognised!" % node.name 359 return 360 361 # Write the declaration line. 362 363 self.stream.write("<div>\n") 364 self._keyword("def") 365 self._object_name_def(self.module, fn, "function-name") 366 367 self.stream.write("(") 368 self._parameters(fn) 369 self.stream.write(")") 370 self.stream.write(":\n") 371 self.stream.write("</div>\n") 372 373 self.stream.write("<div class='body nowrap'>\n") 374 self._doc(node) 375 self.dispatch(node.code) 376 self.stream.write("</div>\n") 377 self.stream.write("</div>\n") 378 379 def visitGlobal(self, node): 380 self.stream.write("<div class='global nowrap'>\n") 381 self._keyword("global") 382 first = 1 383 for name in node.names: 384 if not first: 385 self.stream.write(", ") 386 self.stream.write(name) 387 first = 0 388 self.stream.write("</div>\n") 389 390 def visitIf(self, node): 391 self.stream.write("<div class='if nowrap'>\n") 392 first = 1 393 for compare, stmt in node.tests: 394 self.stream.write("<div>\n") 395 if first: 396 self._keyword("if") 397 else: 398 self._keyword("elif") 399 self.dispatch(compare) 400 self.stream.write(":\n") 401 self.stream.write("</div>\n") 402 self.stream.write("<div class='body nowrap'>\n") 403 self.dispatch(stmt) 404 self.stream.write("</div>\n") 405 first = 0 406 if node.else_ is not None: 407 self.stream.write("<div>\n") 408 self._keyword("else") 409 self.stream.write(":\n") 410 self.stream.write("</div>\n") 411 self.stream.write("<div class='body nowrap'>\n") 412 self.dispatch(node.else_) 413 self.stream.write("</div>\n") 414 self.stream.write("</div>\n") 415 416 # Output preparation methods. 417 418 def _sequence(self, node): 419 first = 1 420 for n in node.nodes: 421 if not first: 422 self.stream.write(", ") 423 self.dispatch(n) 424 first = 0 425 426 def _mapping(self, node): 427 first = 1 428 for k, v in node.items: 429 if not first: 430 self.stream.write(", ") 431 self.dispatch(k) 432 self.stream.write(" : ") 433 self.dispatch(v) 434 first = 0 435 436 def _parameters(self, fn): 437 nparams = len(fn.positional_names) 438 ndefaults = len(fn.defaults) 439 first_with_default = nparams - ndefaults 440 441 first = 1 442 for n, param in enumerate(fn.positional_names): 443 if not first: 444 self.stream.write(", ") 445 self._name(param) 446 n_default = n - first_with_default 447 if n_default >= 0: 448 self._default(fn.defaults[n_default]) 449 first = 0 450 451 if fn.has_star: 452 if not first: 453 self.stream.write(", *") 454 self._name(fn.star_name) 455 456 if fn.has_dstar: 457 if not first: 458 self.stream.write(", **") 459 self._name(fn.dstar_name) 460 461 def _default(self, default): 462 self.stream.write("=") 463 self.dispatch(default) 464 465 # Convenience functions. 466 467 def summarise(module, filename): 468 stream = open(filename, "wb") 469 try: 470 summary = Summary(module) 471 summary.to_stream(stream) 472 finally: 473 stream.close() 474 475 def annotate(module, filename): 476 stream = open(filename, "wb") 477 try: 478 source = AnnotatedSource(module) 479 source.to_stream(stream) 480 finally: 481 stream.close() 482 483 # vim: tabstop=4 expandtab shiftwidth=4