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 import textwrap 28 29 # Exceptions. 30 31 class ViewerError(SimplifiedError): 32 33 "An error in viewing." 34 35 pass 36 37 # Classes. 38 39 class Viewer(ASTVisitor): 40 41 """ 42 A viewing visitor for AST nodes. 43 """ 44 45 def __init__(self, stream): 46 ASTVisitor.__init__(self) 47 self.cached_files = {} 48 self.printed_lines = {} 49 self.visitor = self 50 self.stream = stream 51 52 def process(self, module): 53 self.dispatch(module) 54 55 def dispatch(self, node): 56 self.dispatch_only(node) 57 ASTVisitor.dispatch(self, node) 58 59 def dispatch_only(self, node, every_time=0): 60 self.print_line(getattr(node, "filename", None), getattr(node, "lineno", None), every_time) 61 62 def print_line(self, filename, lineno, every_time): 63 last_printed = self.printed_lines.get(filename, 0) 64 if lineno > last_printed or every_time: 65 self.stream.write(self.get_line(filename, lineno)) 66 self.printed_lines[filename] = lineno 67 68 def get_line(self, filename, lineno): 69 if filename is None or lineno is None: 70 return "" 71 72 if self.cached_files.has_key(filename): 73 lines = self.cached_files[filename] 74 else: 75 f = open(filename) 76 try: 77 self.cached_files[filename] = lines = f.readlines() 78 finally: 79 f.close() 80 81 try: 82 return lines[lineno - 1] 83 except IndexError: 84 return "" 85 86 def report(self, exc): 87 self.stream.write("Exception was:\n\n" + str(exc.exc) + "\n\n") 88 self.stream.write("Nodes:\n\n") 89 for node in exc.nodes: 90 self.stream.write(repr(node) + "\n") 91 if node is not None: 92 self.dispatch_only(node.original, every_time=1) 93 self.stream.write("\nOriginal node was:\n\n" + repr(exc.nodes[0].original) + "\n") 94 self.stream.write("\nSimplified node was:\n\n") 95 exc.nodes[0].pprint(stream=self.stream) 96 97 # HTML-related output production. 98 99 html_header = """<?xml version="1.0" encoding="iso-8859-15"?> 100 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 101 <html xmlns="http://www.w3.org/1999/xhtml"> 102 <head> 103 <title>Module</title> 104 <style type="text/css"> 105 body { 106 padding-top: 4em; padding-bottom: 4em; 107 font-size: 14pt; font-family: monospace; 108 background-color: black; color: white; 109 } 110 111 .class { margin-bottom: 1em; } 112 .function { margin-bottom: 1em; } 113 .body { padding-left: 2em; } 114 .keyword { color: yellow; } 115 .comment { color: blue; } 116 .str { color: #FF00FF; } 117 .doc { color: #FF00FF; margin-bottom: 1em; } 118 .invocation a { color: white; text-decoration: none; } 119 120 .popup { 121 display: none; z-index: 2; 122 position: absolute; top: 1em; left: 0.5em; 123 padding: 0.2em; background-color: #000000; 124 } 125 126 .invocations { 127 padding: 0.5em; background-color: #770000; 128 clear: all; 129 } 130 131 .types { 132 padding: 0.5em; background-color: #0000FF; 133 float: right; 134 } 135 136 .raises { 137 padding: 0.5em; background-color: #7700FF; 138 float: right; 139 } 140 141 .scopes { 142 padding: 0.5em; background-color: #007700; 143 float: left; 144 } 145 146 .non-writes, .non-accesses { 147 padding: 0.5em; background-color: #FF0000; 148 float: right; 149 } 150 151 .op, 152 .name, 153 .attr, 154 .conditional 155 { 156 position: relative; 157 } 158 159 .op:hover > .popup, 160 .name:hover > .popup, 161 .attr:hover > .popup, 162 .conditional:hover > .popup 163 { 164 display: block; 165 } 166 167 </style> 168 </head> 169 <body> 170 """ 171 172 html_footer = """</body> 173 </html> 174 """ 175 176 # Browser classes. 177 178 class Browser(ASTVisitor): 179 180 """ 181 A browsing visitor for AST nodes. 182 183 Covered: And, AssAttr, AssList, AssName, AssTuple, Assign, AugAssign, Break, 184 CallFunc, Class, Compare, Const, Continue, Dict, Discard, For, 185 Function, Getattr, If, Keyword, Lambda, List, Module, Name, Not, 186 Or, Pass, Raise, Return, Slice, Stmt, Subscript, Tuple, While. 187 188 Missing: Add, Assert, Backquote, Bitand, Bitor, Bitxor, Decorators, Div, 189 Ellipsis, Exec, FloorDiv, From, Global, Import, Invert, LeftShift, 190 ListComp, ListCompFor, ListCompIf, Mod, Mul, Not, Or, Power, Print, 191 Printnl, RightShift, Sliceobj, Sub, TryExcept, TryFinally, 192 UnaryAdd, UnarySub, Yield. 193 """ 194 195 def __init__(self, stream): 196 ASTVisitor.__init__(self) 197 self.visitor = self 198 self.stream = stream 199 200 def process(self, module): 201 self.stream.write(html_header) 202 self.dispatch(module) 203 self.stream.write(html_footer) 204 205 def dispatch(self, node): 206 try: 207 ASTVisitor.dispatch(self, node) 208 except ViewerError, exc: 209 exc.add(node) 210 raise 211 except Exception, exc: 212 raise ViewerError(exc, node) 213 214 def visitModule(self, node): 215 self.default(node) 216 217 # Statements. 218 219 def visitAssign(self, node): 220 self.stream.write("<div class='assign'>\n") 221 for lvalue in node.nodes: 222 self.dispatch(lvalue) 223 self.stream.write("=\n") 224 self.dispatch(node.expr) 225 self.stream.write("</div>\n") 226 227 def visitAugAssign(self, node): 228 self.stream.write("<div class='augassign'>\n") 229 self.dispatch(node.node) 230 self.stream.write("%s\n" % node.op) 231 self.dispatch(node.expr) 232 self.stream.write("</div>\n") 233 234 def visitBreak(self, node): 235 self.stream.write("<div class='break'>\n") 236 self._keyword("break") 237 self.stream.write("</div>\n") 238 239 def visitClass(self, node): 240 definition = node._node 241 structure = definition.expr.ref 242 self.stream.write("<div class='class' id='%s'>\n" % self._url(structure.full_name())) 243 self.stream.write("<div>\n") 244 self._keyword("class") 245 self._name_start(structure.name) 246 self._popup_start() 247 self._scopes(definition) 248 self._popup_end() 249 self._name_end() 250 bases = structure.bases 251 if bases: 252 self.stream.write("(") 253 first = 1 254 for base in bases: 255 if not first: 256 self.stream.write(",\n") 257 self._name_start(base.name) 258 self._popup_start() 259 self._types(base) 260 self._scopes(base) 261 self._popup_end() 262 self._name_end() 263 first = 0 264 self.stream.write(")") 265 self.stream.write(":\n") 266 self._comment(self._text(structure.full_name())) 267 self.stream.write("</div>\n") 268 269 self.stream.write("<div class='body'>\n") 270 self._doc(node) 271 self.dispatch(node.code) 272 self.stream.write("</div>\n") 273 self.stream.write("</div>\n") 274 275 def visitContinue(self, node): 276 self.stream.write("<div class='continue'>\n") 277 self._keyword("continue") 278 self.stream.write("</div>\n") 279 280 def visitDiscard(self, node): 281 self.stream.write("<div class='discard'>\n") 282 self.default(node) 283 self.stream.write("</div>\n") 284 285 def visitFor(self, node): 286 self.stream.write("<div class='if'>\n") 287 self.stream.write("<div>\n") 288 self._keyword("for") 289 self.dispatch(node.assign) 290 self._keyword("in") 291 self.dispatch(node.list) 292 self.stream.write(":\n") 293 self.stream.write("</div>\n") 294 self.stream.write("<div class='body'>\n") 295 self.dispatch(node.body) 296 self.stream.write("</div>\n") 297 if node.else_ is not None: 298 self.stream.write("<div>\n") 299 self._keyword("else") 300 self.stream.write(":\n") 301 self.stream.write("</div>\n") 302 self.stream.write("<div class='body'>\n") 303 self.dispatch(node.else_) 304 self.stream.write("</div>\n") 305 self.stream.write("</div>\n") 306 307 def visitFunction(self, node): 308 definition = node._node 309 subprogram = definition.expr.ref 310 self.stream.write("<div class='function' id='%s'>\n" % self._url(subprogram.full_name())) 311 self.stream.write("<div>\n") 312 self._keyword("def") 313 self._name_start(subprogram.name) 314 self._popup_start() 315 self._scopes(definition) 316 self._raises(subprogram) 317 self._popup_end() 318 self._name_end() 319 self.stream.write("(") 320 self._parameters(subprogram) 321 self.stream.write(")") 322 self.stream.write(":\n") 323 self._comment(self._text(subprogram.full_name())) 324 self.stream.write("</div>\n") 325 326 self.stream.write("<div class='body'>\n") 327 self._doc(node) 328 self.dispatch(node.code) 329 self.stream.write("</div>\n") 330 self.stream.write("</div>\n") 331 332 def visitIf(self, node): 333 self.stream.write("<div class='if'>\n") 334 first = 1 335 conditional = node._node 336 for compare, stmt in node.tests: 337 self.stream.write("<div>\n") 338 self.stream.write("<span class='conditional'>\n") 339 if first: 340 self._keyword("if") 341 else: 342 self._keyword("elif") 343 self._popup_start() 344 self._invocations(conditional.test) 345 self._popup_end() 346 self.stream.write("</span>\n") 347 self.dispatch(compare) 348 self.stream.write(":\n") 349 self.stream.write("</div>\n") 350 self.stream.write("<div class='body'>\n") 351 self.dispatch(stmt) 352 self.stream.write("</div>\n") 353 if conditional.else_: 354 conditional = conditional.else_[0] 355 else: 356 conditional = None 357 first = 0 358 if node.else_ is not None: 359 self.stream.write("<div>\n") 360 self._keyword("else") 361 self.stream.write(":\n") 362 self.stream.write("</div>\n") 363 self.stream.write("<div class='body'>\n") 364 self.dispatch(node.else_) 365 self.stream.write("</div>\n") 366 self.stream.write("</div>\n") 367 368 def visitPass(self, node): 369 self.stream.write("<div class='pass'>\n") 370 self._keyword("pass") 371 self.stream.write("</div>\n") 372 373 def visitRaise(self, node): 374 self.stream.write("<div class='raise'>\n") 375 self._keyword("raise") 376 self.dispatch(node.expr1) 377 if node.expr2 is not None: 378 self.stream.write(",\n") 379 self.dispatch(node.expr2) 380 if node.expr3 is not None: 381 self.stream.write(",\n") 382 self.dispatch(node.expr3) 383 self.stream.write("</div>\n") 384 385 def visitReturn(self, node): 386 self.stream.write("<div class='return'>\n") 387 self._keyword("return") 388 self.dispatch(node.value) 389 self.stream.write("</div>\n") 390 391 def visitStmt(self, node): 392 self.stream.write("<div class='stmt'>\n") 393 self.default(node) 394 self.stream.write("</div>\n") 395 396 def visitTryExcept(self, node): 397 self.stream.write("<div class='tryexcept'>\n") 398 self.stream.write("<div>\n") 399 self._keyword("try") 400 self.stream.write(":\n") 401 self.stream.write("</div>\n") 402 self.stream.write("<div class='body'>\n") 403 self.dispatch(node.body) 404 self.stream.write("</div>\n") 405 for spec, assign, statement in node.handlers: 406 self.stream.write("<div>\n") 407 self._keyword("except") 408 if spec is not None: 409 self.dispatch(spec) 410 if assign is not None: 411 self.stream.write(",\n") 412 self.dispatch(assign) 413 self.stream.write(":\n") 414 self.stream.write("</div>\n") 415 self.stream.write("<div class='body'>\n") 416 self.dispatch(statement) 417 self.stream.write("</div>\n") 418 if node.else_ is not None: 419 self.stream.write("<div>\n") 420 self._keyword("else") 421 self.stream.write(":\n") 422 self.stream.write("</div>\n") 423 self.stream.write("<div class='body'>\n") 424 self.dispatch(node.else_) 425 self.stream.write("</div>\n") 426 self.stream.write("</div>\n") 427 428 def visitTryFinally(self, node): 429 self.stream.write("<div class='tryfinally'>\n") 430 self.stream.write("<div>\n") 431 self._keyword("try") 432 self.stream.write(":\n") 433 self.stream.write("</div>\n") 434 self.stream.write("<div class='body'>\n") 435 self.dispatch(node.body) 436 self.stream.write("</div>\n") 437 self.stream.write("<div>\n") 438 self._keyword("finally") 439 self.stream.write(":\n") 440 self.stream.write("</div>\n") 441 self.stream.write("<div class='body'>\n") 442 self.dispatch(node.final) 443 self.stream.write("</div>\n") 444 self.stream.write("</div>\n") 445 446 def visitWhile(self, node): 447 self.stream.write("<div class='while'>\n") 448 self.stream.write("<div>\n") 449 self.stream.write("<span class='conditional'>\n") 450 self._keyword("while") 451 self._popup_start() 452 self._invocations(node.test) 453 self._popup_end() 454 self.stream.write("</span>\n") 455 self.dispatch(node.test) 456 self.stream.write(":\n") 457 self.stream.write("</div>\n") 458 self.stream.write("<div class='body'>\n") 459 self.dispatch(node.body) 460 self.stream.write("</div>\n") 461 if node.else_ is not None: 462 self.stream.write("<div>\n") 463 self._keyword("else") 464 self.stream.write(":\n") 465 self.stream.write("</div>\n") 466 self.stream.write("<div class='body'>\n") 467 self.dispatch(node.else_) 468 self.stream.write("</div>\n") 469 self.stream.write("</div>\n") 470 471 # Expressions. 472 473 def visitAnd(self, node): 474 self.stream.write("<span class='and'>\n") 475 first = 1 476 for n in node.nodes: 477 if not first: 478 self._keyword("and") 479 self.dispatch(n) 480 first = 0 481 self.stream.write("</span>") 482 483 def visitAssAttr(self, node): 484 self.stream.write("<span class='assattr'>\n") 485 self.dispatch(node.expr) 486 self.stream.write("<span class='attr'>\n") 487 self.stream.write(".%s\n" % self._text(node.attrname)) 488 if hasattr(node, "_node"): 489 self._popup_start() 490 self._types(node._node) 491 self._scopes(node._node) 492 self._popup_end() 493 else: 494 raise ValueError, node 495 self.stream.write("</span>\n") 496 self.stream.write("</span>\n") 497 498 def visitAssList(self, node): 499 self.stream.write("<span class='list'>\n") 500 self.stream.write("[") 501 self._sequence(node) 502 self.stream.write("]\n") 503 self.stream.write("</span>\n") 504 505 def visitAssName(self, node): 506 if hasattr(node, "_node"): 507 self._name_start(node._node.name) 508 self._popup_start() 509 self._types(node._node.expr) 510 self._scopes(node._node) 511 self._popup_end() 512 self._name_end() 513 else: 514 raise ValueError, node 515 self._name(node.name) 516 517 def visitAssTuple(self, node): 518 self.stream.write("<span class='tuple'>\n") 519 self.stream.write("(") 520 self._sequence(node) 521 self.stream.write(")\n") 522 self.stream.write("</span>\n") 523 524 def visitCallFunc(self, node): 525 self.stream.write("<span class='callfunc'>\n") 526 self.dispatch(node.node) 527 self.stream.write("(") 528 first = 1 529 for arg in node.args: 530 if not first: 531 self.stream.write(",\n") 532 self.dispatch(arg) 533 first = 0 534 if node.star_args is not None: 535 if not first: 536 self.stream.write(", *\n") 537 self.dispatch(node.star_args) 538 first = 0 539 if node.dstar_args is not None: 540 if not first: 541 self.stream.write(", **\n") 542 self.dispatch(node.dstar_args) 543 first = 0 544 self.stream.write(")\n") 545 self.stream.write("</span>\n") 546 547 def visitCompare(self, node): 548 self.stream.write("<span class='compare'>\n") 549 self.dispatch(node.expr) 550 for (op_name, expr), _op in map(None, node.ops, node._ops): 551 self.stream.write("<span class='op'>\n") 552 self.stream.write(op_name) 553 self._popup_start() 554 self._op(op_name, _op) 555 self._popup_end() 556 self.stream.write("</span>\n") 557 self.dispatch(expr) 558 self.stream.write("</span>\n") 559 560 def visitConst(self, node): 561 self.stream.write(repr(node.value)) 562 563 def visitGetattr(self, node): 564 self.stream.write("<span class='getattr'>\n") 565 self.dispatch(node.expr) 566 self.stream.write("<span class='attr'>\n") 567 self.stream.write(".%s\n" % self._text(node.attrname)) 568 if hasattr(node, "_node"): 569 self._popup_start() 570 self._types(node._node) 571 self._scopes(node._node) 572 self._popup_end() 573 else: 574 raise ValueError, node 575 self.stream.write("</span>\n") 576 self.stream.write("</span>\n") 577 578 def visitKeyword(self, node): 579 self.stream.write("<span class='keyword'>\n") 580 self.stream.write(node.name) 581 self.stream.write("=") 582 self.dispatch(node.expr) 583 self.stream.write("</span>\n") 584 585 def visitLambda(self, node): 586 definition = node._node 587 subprogram = definition.expr.ref 588 self.stream.write("<span class='lambda'>\n") 589 self._keyword("lambda") 590 self._parameters(subprogram) 591 self.dispatch(node.code) 592 self.stream.write("</span>\n") 593 594 visitList = visitAssList 595 596 def visitName(self, node): 597 if hasattr(node, "_node"): 598 self._name_start(node._node.name) 599 self._popup_start() 600 self._types(node._node) 601 self._scopes(node._node) 602 self._popup_end() 603 self._name_end() 604 else: 605 raise ValueError, node 606 self._name(node.name) 607 608 def visitNot(self, node): 609 self.stream.write("<span class='not'>\n") 610 self._keyword("not") 611 self.dispatch(node.expr) 612 self.stream.write("</span>") 613 614 def visitOr(self, node): 615 self.stream.write("<span class='or'>\n") 616 first = 1 617 for n in node.nodes: 618 if not first: 619 self._keyword("or") 620 self.dispatch(n) 621 first = 0 622 self.stream.write("</span>") 623 624 def visitSlice(self, node): 625 self.stream.write("<span class='slice'>\n") 626 self.dispatch(node.expr) 627 self.stream.write("[") 628 if node.lower: 629 self.dispatch(node.lower) 630 self.stream.write(":") 631 if node.upper: 632 self.dispatch(node.upper) 633 # NOTE: Step? 634 self.stream.write("]") 635 self.stream.write("</span>\n") 636 637 def visitSubscript(self, node): 638 self.stream.write("<span class='subscript'>\n") 639 self.dispatch(node.expr) 640 self.stream.write("[") 641 first = 1 642 for sub in node.subs: 643 if not first: 644 self.stream.write(", ") 645 self.dispatch(sub) 646 first = 0 647 self.stream.write("]") 648 self.stream.write("</span>\n") 649 650 visitTuple = visitAssTuple 651 652 # Output preparation methods. 653 654 def _text(self, text): 655 return text.replace("&", "&").replace("<", "<").replace(">", ">") 656 657 def _attr(self, attr): 658 return self._text(attr).replace("'", "'").replace('"', """) 659 660 def _url(self, url): 661 return self._attr(url).replace("#", "%23").replace("-", "%2d") 662 663 def _comment(self, comment): 664 self.stream.write("<span class='comment'># %s</span>\n" % comment) 665 666 def _keyword(self, kw): 667 self.stream.write("<span class='keyword'>%s</span> " % kw) 668 669 def _doc(self, node): 670 if node.doc is not None: 671 self.stream.write("<pre class='doc'>\n") 672 self.stream.write('"""') 673 output = textwrap.dedent(node.doc.replace('"""', '\\"\\"\\"')) 674 self.stream.write(self._text(output)) 675 self.stream.write('"""') 676 self.stream.write("</pre>\n") 677 678 def _sequence(self, node): 679 first = 1 680 for n in node.nodes: 681 if not first: 682 self.stream.write(",\n") 683 self.dispatch(n) 684 first = 0 685 686 def _parameters(self, subprogram): 687 first = 1 688 for param, default in subprogram.params: 689 if not first: 690 self.stream.write(",\n") 691 self._parameter(subprogram, param, default) 692 first = 0 693 if subprogram.star is not None: 694 if not first: 695 self.stream.write(", *\n") 696 param, default = subprogram.star 697 self._parameter(subprogram, param, default) 698 first = 0 699 if subprogram.dstar is not None: 700 if not first: 701 self.stream.write(", **\n") 702 param, default = subprogram.dstar 703 self._parameter(subprogram, param, default) 704 first = 0 705 706 def _parameter(self, subprogram, param, default): 707 self._name_start(param) 708 if hasattr(subprogram, "paramtypes"): 709 self._popup_start() 710 self._types_list(subprogram.paramtypes[param]) 711 self._popup_end() 712 self._name_end() 713 if default is not None and default.original is not None: 714 self.stream.write("=\n") 715 self.dispatch(default.original) 716 717 def _name(self, name): 718 self.stream.write("<span class='name'>%s</span>\n" % name) 719 720 def _name_start(self, name): 721 self.stream.write("<span class='name'>%s\n" % name) 722 723 def _name_end(self): 724 self.stream.write("</span>\n") 725 726 def _popup_start(self): 727 self.stream.write("<span class='popup'>\n") 728 729 def _popup_end(self): 730 self.stream.write("</span>\n") 731 732 def _op(self, op_name, op): 733 if op is not None: 734 self._invocations(op) 735 736 def _invocations(self, node): 737 if hasattr(node, "invocations"): 738 self._invocations_list(node.invocations) 739 740 def _invocations_list(self, invocations): 741 self.stream.write("<div class='invocations'>\n") 742 for invocation in invocations: 743 fn = invocation.full_name() 744 module = invocation.module.name 745 name = invocation.name 746 structures = [x.name for x in invocation.structures] 747 self.stream.write("<div class='invocation'>") 748 self.stream.write("<a href='%s.html#%s'>" % (self._url(module), self._url(fn))) 749 self.stream.write(self._text(".".join([module] + structures + [name]))) 750 self.stream.write("</a>") 751 self.stream.write("</div>\n") 752 self.stream.write("</div>\n") 753 754 def _types(self, node): 755 if hasattr(node, "types"): 756 if node.types: 757 self._types_list(node.types) 758 else: 759 self.stream.write("<div class='types'>\n") 760 self.stream.write("no types\n") 761 self.stream.write("</div>\n") 762 else: 763 self.stream.write("<div class='types'>\n") 764 self.stream.write("unvisited\n") 765 self.stream.write("</div>\n") 766 767 def _types_list(self, types, style_class="types"): 768 self.stream.write("<div class='%s'>\n" % style_class) 769 for type in types: 770 fn = type.type.full_name() 771 self.stream.write("<div class='type'>") 772 self.stream.write(self._text(fn)) 773 self.stream.write("</div>\n") 774 self.stream.write("</div>\n") 775 776 def _raises(self, node): 777 if hasattr(node, "namespace") and hasattr(node.namespace, "raises") and node.namespace.raises: 778 self._types_list(node.namespace.raises, style_class="raises") 779 780 def _scopes(self, node): 781 if not isinstance(node, LoadName): 782 if hasattr(node, "writes") and node.writes or hasattr(node, "accesses") and node.accesses: 783 self.stream.write("<div class='scopes'>\n") 784 for ref in getattr(node, "writes", getattr(node, "accesses", {})).keys(): 785 fn = ref.full_name() 786 self.stream.write("<div class='scope'>") 787 self.stream.write(self._text(fn)) 788 self.stream.write("</div>\n") 789 self.stream.write("</div>\n") 790 if hasattr(node, "non_accesses") and node.non_accesses: 791 self._types_list(node.non_accesses, style_class="non-accesses") 792 if hasattr(node, "non_writes") and node.non_writes: 793 self._types_list(node.non_writes, style_class="non-writes") 794 795 # Utility functions. 796 797 def flatten(lists): 798 result = [] 799 for l in lists: 800 for attr in l: 801 if attr not in result: 802 result.append(attr) 803 return result 804 805 # Convenience functions. 806 807 def view(module, stream=None): 808 viewer = Viewer(stream or sys.stdout) 809 viewer.process(module.original) 810 811 def browse(module, stream=None): 812 browser = Browser(stream or sys.stdout) 813 browser.process(module.original) 814 815 def makedoc(module, filename): 816 stream = open(filename, "wb") 817 try: 818 browser = Browser(stream) 819 browser.process(module.original) 820 finally: 821 stream.close() 822 823 # vim: tabstop=4 expandtab shiftwidth=4