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 .popup { 62 display: none; z-index: 2; 63 position: absolute; top: 2ex; left: 0; 64 padding: 0.2em; background-color: #000000; color: white; 65 border: 2px solid #dddddd; 66 } 67 68 .invocations { 69 padding: 0.5em; background-color: #770000; 70 clear: all; 71 } 72 73 .types { 74 padding: 0.5em; background-color: #0000FF; 75 float: right; 76 } 77 78 .raises { 79 padding: 0.5em; background-color: #7700FF; 80 float: right; 81 } 82 83 .scopes { 84 padding: 0.5em; background-color: #007700; 85 float: left; 86 } 87 88 .non-scopes { 89 padding: 0.5em; background-color: #FF0000; 90 float: left; 91 } 92 93 .no-types { 94 background-color: #FF0000; 95 } 96 97 .op, 98 .name, 99 .attr, 100 .conditional, 101 .operator, 102 .iterator, 103 .call, 104 .returns, 105 .failure 106 { 107 position: relative; 108 } 109 110 .op:hover > .popup, 111 .name:hover > .popup, 112 .attr:hover > .popup, 113 .conditional:hover > .popup, 114 .operator:hover > .popup, 115 .iterator:hover > .popup, 116 .call:hover > .popup, 117 .returns:hover > .popup, 118 .failure:hover > .popup 119 { 120 display: block; 121 } 122 123 .summary-class { 124 background-color: #004400; 125 } 126 127 .summary-instance { 128 background-color: #0000FF; 129 } 130 131 .summary-attr { 132 background-color: #007700; 133 } 134 135 </style> 136 </head> 137 <body> 138 """ 139 140 html_footer = """</body> 141 </html> 142 """ 143 144 # Utility classes. 145 146 class Writer: 147 148 "A utility class providing useful HTML output methods." 149 150 # Methods which return strings. 151 152 def _text(self, text): 153 return text.replace("&", "&").replace("<", "<").replace(">", ">") 154 155 def _attr(self, attr): 156 return self._text(attr).replace("'", "'").replace('"', """) 157 158 def _url(self, url): 159 return self._attr(url).replace("#", "%23").replace("-", "%2d") 160 161 # Methods which write to the stream. 162 163 def _comment(self, comment): 164 self.stream.write("<span class='comment'># %s</span>\n" % comment) 165 166 def _keyword(self, kw): 167 self.stream.write("<span class='keyword'>%s</span> " % kw) 168 169 def _doc(self, node): 170 if node.doc is not None: 171 self.stream.write("<pre class='doc'>\n") 172 self.stream.write('"""') 173 output = textwrap.dedent(node.doc.replace('"""', '\\"\\"\\"')) 174 self.stream.write(self._text(output)) 175 self.stream.write('"""') 176 self.stream.write("</pre>\n") 177 178 def _name(self, name): 179 self.stream.write("<span class='name'>%s</span>\n" % name) 180 181 def _name_link(self, module_name, full_name, name): 182 self.stream.write("<a class='name' href='%s%sxhtml#%s'>%s</a>" % ( 183 module_name, os.path.extsep, self._attr(full_name), self._text(name))) 184 185 def _summary_link(self, module_name, full_name, name): 186 self.stream.write("<a class='name' href='%s-summary%sxhtml#%s'>%s</a>" % ( 187 module_name, os.path.extsep, self._attr(full_name), self._text(name))) 188 189 def _object_name_def(self, module, obj): 190 191 """ 192 Link to the summary for 'module' using 'obj'. 193 """ 194 195 self._summary_link(module.full_name(), obj.full_name(), obj.name) 196 197 # Summary classes. 198 199 class Summary(Writer): 200 201 "Summarise classes and attributes in modules." 202 203 def __init__(self, module): 204 self.module = module 205 206 def to_stream(self, stream): 207 208 "Write the summary to the given 'stream'." 209 210 self.stream = stream 211 self._init_details() 212 self.stream.write(html_header) 213 self._write_classes(self.module) 214 self.stream.write(html_footer) 215 216 def _write_classes(self, module): 217 self.stream.write("<table cellspacing='5' cellpadding='5'>\n") 218 219 all_classes = {} 220 221 for obj in self.module.all_objects: 222 if isinstance(obj, Class): 223 all_classes[obj.name] = obj 224 225 all_class_names = all_classes.keys() 226 all_class_names.sort() 227 228 for name in all_class_names: 229 self._write_class(all_classes[name]) 230 231 self.stream.write("</table>\n") 232 233 def _write_class(self, obj): 234 235 # Write the class... 236 237 self.stream.write("<tbody class='class'>\n") 238 self.stream.write("<tr>\n") 239 self.stream.write("<th class='summary-class' id='%s'>\n" % obj.full_name()) 240 self._keyword("class") 241 self.stream.write(obj.name) 242 self.stream.write("</th>\n") 243 244 # ...and all known attribute names. 245 246 obj_attributes = obj.all_attribute_names() 247 248 for name in self.attribute_names: 249 if name in obj_attributes: 250 self.stream.write("<th class='summary-attr'>%s</th>\n" % self._text(name)) 251 else: 252 self.stream.write("<th></th>\n") 253 self.stream.write("</tr>\n") 254 255 self.stream.write("</tbody>\n") 256 257 def _init_details(self): 258 names = set() 259 260 # Visit all classes. 261 262 for obj in self.module.all_objects: 263 if isinstance(obj, Class): 264 names.update(obj.all_attribute_names()) 265 266 self.attribute_names = list(names) 267 self.attribute_names.sort() 268 269 # Source code classes. 270 271 class AnnotatedSource(ASTVisitor, Writer): 272 273 "A module source code browser." 274 275 def __init__(self, module): 276 ASTVisitor.__init__(self) 277 self.visitor = self 278 self.module = module 279 280 def to_stream(self, stream): 281 282 "Write the annotated code to the given 'stream'." 283 284 self.stream = stream 285 self.stream.write(html_header) 286 self.dispatch(self.module.module) 287 self.stream.write(html_footer) 288 289 def visitModule(self, node): 290 self.default(node) 291 292 # Statements. 293 294 def visitAssert(self, node): 295 self.stream.write("<div class='assert nowrap'>\n") 296 self._keyword("assert") 297 self.dispatch(node.test) 298 if node.fail: 299 self.stream.write(", ") 300 self.dispatch(node.fail) 301 self.stream.write("</div>\n") 302 303 def visitAssign(self, node): 304 self.stream.write("<div class='assign nowrap'>\n") 305 for lvalue in node.nodes: 306 self.dispatch(lvalue) 307 self.stream.write("= ") 308 self.dispatch(node.expr) 309 self.stream.write("</div>\n") 310 311 def visitAugAssign(self, node): 312 self.stream.write("<div class='augassign nowrap'>\n") 313 self.dispatch(node.node) 314 self.stream.write("%s " % node.op) 315 self.dispatch(node.expr) 316 self.stream.write("</div>\n") 317 318 def visitBreak(self, node): 319 self.stream.write("<div class='break nowrap'>\n") 320 self._keyword("break") 321 self.stream.write("</div>\n") 322 323 def visitClass(self, node): 324 325 # Use inspected details where possible. 326 327 if hasattr(node, "_def"): 328 cls = node._def 329 bases = cls.bases 330 self.stream.write("<div class='class nowrap' id='%s'>\n" % cls.full_name()) 331 else: 332 print "Warning: class %s not recognised!" % node.name 333 return 334 335 # Write the declaration line. 336 337 self.stream.write("<div>\n") 338 self._keyword("class") 339 self._object_name_def(self.module, cls) 340 341 # Suppress the "object" class appearing alone. 342 343 if bases and not (len(bases) == 1 and bases[0].name == "object"): 344 self.stream.write("(") 345 first = 1 346 for base in bases: 347 if not first: 348 self.stream.write(",\n") 349 350 self._object_name_def(base.module, base) 351 352 first = 0 353 self.stream.write(")") 354 355 self.stream.write(":\n") 356 self.stream.write("</div>\n") 357 358 # Write the docstring and class body. 359 360 self.stream.write("<div class='body nowrap'>\n") 361 self._doc(node) 362 self.dispatch(node.code) 363 self.stream.write("</div>\n") 364 self.stream.write("</div>\n") 365 366 def visitContinue(self, node): 367 self.stream.write("<div class='continue nowrap'>\n") 368 self._keyword("continue") 369 self.stream.write("</div>\n") 370 371 def visitDiscard(self, node): 372 self.stream.write("<div class='discard nowrap'>\n") 373 self.default(node) 374 self.stream.write("</div>\n") 375 376 def visitFor(self, node): 377 self.stream.write("<div class='if nowrap'>\n") 378 self.stream.write("<div>\n") 379 self._keyword("for") 380 self.dispatch(node.assign) 381 self._keyword("in") 382 self.dispatch(node.list) 383 self.stream.write(":\n") 384 self.stream.write("</div>\n") 385 self.stream.write("<div class='body nowrap'>\n") 386 self.dispatch(node.body) 387 self.stream.write("</div>\n") 388 if node.else_ is not None: 389 self.stream.write("<div>\n") 390 self._keyword("else") 391 self.stream.write(":\n") 392 self.stream.write("</div>\n") 393 self.stream.write("<div class='body nowrap'>\n") 394 self.dispatch(node.else_) 395 self.stream.write("</div>\n") 396 self.stream.write("</div>\n") 397 398 def visitFrom(self, node): 399 self.stream.write("<div class='from nowrap'>\n") 400 self._keyword("from") 401 self._name(node.modname) 402 self._keyword("import") 403 first = 1 404 for (name, alias), _name in map(None, node.names, node._names): 405 if not first: 406 self.stream.write(",\n") 407 if alias: 408 self.stream.write(name + " ") 409 self._keyword("as") 410 self._name(alias or name) 411 first = 0 412 self.stream.write("</div>\n") 413 414 def visitFunction(self, node): 415 if hasattr(node, "_def"): 416 fn = node._def 417 self.stream.write("<div class='function nowrap' id='%s'>\n" % fn.full_name()) 418 else: 419 print "Warning: function %s not recognised!" % node.name 420 return 421 422 # Write the declaration line. 423 424 self.stream.write("<div>\n") 425 self._keyword("def") 426 self._object_name_def(self.module, fn) 427 428 self.stream.write("(") 429 self._parameters(fn) 430 self.stream.write(")") 431 self.stream.write(":\n") 432 self.stream.write("</div>\n") 433 434 self.stream.write("<div class='body nowrap'>\n") 435 self._doc(node) 436 self.dispatch(node.code) 437 self.stream.write("</div>\n") 438 self.stream.write("</div>\n") 439 440 def visitGlobal(self, node): 441 self.stream.write("<div class='global nowrap'>\n") 442 self._keyword("global") 443 first = 1 444 for name in node.names: 445 if not first: 446 self.stream.write(",\n") 447 self.stream.write(name) 448 first = 0 449 self.stream.write("</div>\n") 450 451 def visitIf(self, node): 452 self.stream.write("<div class='if nowrap'>\n") 453 first = 1 454 for compare, stmt in node.tests: 455 self.stream.write("<div>\n") 456 if first: 457 self._keyword("if") 458 else: 459 self._keyword("elif") 460 self.dispatch(compare) 461 self.stream.write(":\n") 462 self.stream.write("</div>\n") 463 self.stream.write("<div class='body nowrap'>\n") 464 self.dispatch(stmt) 465 self.stream.write("</div>\n") 466 first = 0 467 if node.else_ is not None: 468 self.stream.write("<div>\n") 469 self._keyword("else") 470 self.stream.write(":\n") 471 self.stream.write("</div>\n") 472 self.stream.write("<div class='body nowrap'>\n") 473 self.dispatch(node.else_) 474 self.stream.write("</div>\n") 475 self.stream.write("</div>\n") 476 477 # Output preparation methods. 478 479 def _sequence(self, node): 480 first = 1 481 for n in node.nodes: 482 if not first: 483 self.stream.write(",\n") 484 self.dispatch(n) 485 first = 0 486 487 def _mapping(self, node): 488 first = 1 489 for k, v in node.items: 490 if not first: 491 self.stream.write(",\n") 492 self.dispatch(k) 493 self.stream.write(":\n") 494 self.dispatch(v) 495 first = 0 496 497 def _parameters(self, fn): 498 first = 1 499 nparams = len(fn.positional_names) 500 ndefaults = len(fn.defaults) 501 first_with_default = nparams - ndefaults 502 503 for n, param in enumerate(fn.positional_names): 504 if not first: 505 self.stream.write(",\n") 506 self._name(param) 507 n_default = n - first_with_default 508 if n_default >= 0: 509 self._default(fn.defaults[n_default]) 510 511 if fn.has_star: 512 if not first: 513 self.stream.write(", *\n") 514 self._name(fn.star_name) 515 516 if fn.has_dstar: 517 if not first: 518 self.stream.write(", **\n") 519 self._name(fn.dstar_name) 520 521 def _default(self, default): 522 self.stream.write("=\n") 523 self.dispatch(default) 524 525 # Convenience functions. 526 527 def summarise(module, filename): 528 stream = open(filename, "wb") 529 try: 530 summary = Summary(module) 531 summary.to_stream(stream) 532 finally: 533 stream.close() 534 535 def annotate(module, filename): 536 stream = open(filename, "wb") 537 try: 538 source = AnnotatedSource(module) 539 source.to_stream(stream) 540 finally: 541 stream.close() 542 543 # vim: tabstop=4 expandtab shiftwidth=4