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 252 # Suppress the "object" class appearing alone. 253 254 if bases and not (len(bases) == 1 and bases[0].name == "object"): 255 self.stream.write("(") 256 first = 1 257 for base in bases: 258 if not first: 259 self.stream.write(",\n") 260 self._name_start(base.name) 261 self._popup_start() 262 self._types(base) 263 self._scopes(base) 264 self._popup_end() 265 self._name_end() 266 first = 0 267 self.stream.write(")") 268 269 self.stream.write(":\n") 270 self._comment(self._text(structure.full_name())) 271 self.stream.write("</div>\n") 272 273 self.stream.write("<div class='body'>\n") 274 self._doc(node) 275 self.dispatch(node.code) 276 self.stream.write("</div>\n") 277 self.stream.write("</div>\n") 278 279 def visitContinue(self, node): 280 self.stream.write("<div class='continue'>\n") 281 self._keyword("continue") 282 self.stream.write("</div>\n") 283 284 def visitDiscard(self, node): 285 self.stream.write("<div class='discard'>\n") 286 self.default(node) 287 self.stream.write("</div>\n") 288 289 def visitFor(self, node): 290 self.stream.write("<div class='if'>\n") 291 self.stream.write("<div>\n") 292 self._keyword("for") 293 self.dispatch(node.assign) 294 self._keyword("in") 295 self.dispatch(node.list) 296 self.stream.write(":\n") 297 self.stream.write("</div>\n") 298 self.stream.write("<div class='body'>\n") 299 self.dispatch(node.body) 300 self.stream.write("</div>\n") 301 if node.else_ is not None: 302 self.stream.write("<div>\n") 303 self._keyword("else") 304 self.stream.write(":\n") 305 self.stream.write("</div>\n") 306 self.stream.write("<div class='body'>\n") 307 self.dispatch(node.else_) 308 self.stream.write("</div>\n") 309 self.stream.write("</div>\n") 310 311 def visitFunction(self, node): 312 definition = node._node 313 subprogram = definition.expr.ref 314 self.stream.write("<div class='function' id='%s'>\n" % self._url(subprogram.full_name())) 315 self.stream.write("<div>\n") 316 self._keyword("def") 317 self._name_start(subprogram.name) 318 self._popup_start() 319 self._scopes(definition) 320 self._raises(subprogram) 321 self._popup_end() 322 self._name_end() 323 self.stream.write("(") 324 self._parameters(subprogram) 325 self.stream.write(")") 326 self.stream.write(":\n") 327 self._comment(self._text(subprogram.full_name())) 328 self.stream.write("</div>\n") 329 330 self.stream.write("<div class='body'>\n") 331 self._doc(node) 332 self.dispatch(node.code) 333 self.stream.write("</div>\n") 334 self.stream.write("</div>\n") 335 336 def visitIf(self, node): 337 self.stream.write("<div class='if'>\n") 338 first = 1 339 conditional = node._node 340 for compare, stmt in node.tests: 341 self.stream.write("<div>\n") 342 self.stream.write("<span class='conditional'>\n") 343 if first: 344 self._keyword("if") 345 else: 346 self._keyword("elif") 347 self._popup_start() 348 self._invocations(conditional.test) 349 self._popup_end() 350 self.stream.write("</span>\n") 351 self.dispatch(compare) 352 self.stream.write(":\n") 353 self.stream.write("</div>\n") 354 self.stream.write("<div class='body'>\n") 355 self.dispatch(stmt) 356 self.stream.write("</div>\n") 357 if conditional.else_: 358 conditional = conditional.else_[0] 359 else: 360 conditional = None 361 first = 0 362 if node.else_ is not None: 363 self.stream.write("<div>\n") 364 self._keyword("else") 365 self.stream.write(":\n") 366 self.stream.write("</div>\n") 367 self.stream.write("<div class='body'>\n") 368 self.dispatch(node.else_) 369 self.stream.write("</div>\n") 370 self.stream.write("</div>\n") 371 372 def visitPass(self, node): 373 self.stream.write("<div class='pass'>\n") 374 self._keyword("pass") 375 self.stream.write("</div>\n") 376 377 def visitRaise(self, node): 378 self.stream.write("<div class='raise'>\n") 379 self._keyword("raise") 380 self.dispatch(node.expr1) 381 if node.expr2 is not None: 382 self.stream.write(",\n") 383 self.dispatch(node.expr2) 384 if node.expr3 is not None: 385 self.stream.write(",\n") 386 self.dispatch(node.expr3) 387 self.stream.write("</div>\n") 388 389 def visitReturn(self, node): 390 self.stream.write("<div class='return'>\n") 391 self._keyword("return") 392 self.dispatch(node.value) 393 self.stream.write("</div>\n") 394 395 def visitStmt(self, node): 396 self.stream.write("<div class='stmt'>\n") 397 self.default(node) 398 self.stream.write("</div>\n") 399 400 def visitTryExcept(self, node): 401 self.stream.write("<div class='tryexcept'>\n") 402 self.stream.write("<div>\n") 403 self._keyword("try") 404 self.stream.write(":\n") 405 self.stream.write("</div>\n") 406 self.stream.write("<div class='body'>\n") 407 self.dispatch(node.body) 408 self.stream.write("</div>\n") 409 for spec, assign, statement in node.handlers: 410 self.stream.write("<div>\n") 411 self._keyword("except") 412 if spec is not None: 413 self.dispatch(spec) 414 if assign is not None: 415 self.stream.write(",\n") 416 self.dispatch(assign) 417 self.stream.write(":\n") 418 self.stream.write("</div>\n") 419 self.stream.write("<div class='body'>\n") 420 self.dispatch(statement) 421 self.stream.write("</div>\n") 422 if node.else_ is not None: 423 self.stream.write("<div>\n") 424 self._keyword("else") 425 self.stream.write(":\n") 426 self.stream.write("</div>\n") 427 self.stream.write("<div class='body'>\n") 428 self.dispatch(node.else_) 429 self.stream.write("</div>\n") 430 self.stream.write("</div>\n") 431 432 def visitTryFinally(self, node): 433 self.stream.write("<div class='tryfinally'>\n") 434 self.stream.write("<div>\n") 435 self._keyword("try") 436 self.stream.write(":\n") 437 self.stream.write("</div>\n") 438 self.stream.write("<div class='body'>\n") 439 self.dispatch(node.body) 440 self.stream.write("</div>\n") 441 self.stream.write("<div>\n") 442 self._keyword("finally") 443 self.stream.write(":\n") 444 self.stream.write("</div>\n") 445 self.stream.write("<div class='body'>\n") 446 self.dispatch(node.final) 447 self.stream.write("</div>\n") 448 self.stream.write("</div>\n") 449 450 def visitWhile(self, node): 451 self.stream.write("<div class='while'>\n") 452 self.stream.write("<div>\n") 453 self.stream.write("<span class='conditional'>\n") 454 self._keyword("while") 455 self._popup_start() 456 self._invocations(node.test) 457 self._popup_end() 458 self.stream.write("</span>\n") 459 self.dispatch(node.test) 460 self.stream.write(":\n") 461 self.stream.write("</div>\n") 462 self.stream.write("<div class='body'>\n") 463 self.dispatch(node.body) 464 self.stream.write("</div>\n") 465 if node.else_ is not None: 466 self.stream.write("<div>\n") 467 self._keyword("else") 468 self.stream.write(":\n") 469 self.stream.write("</div>\n") 470 self.stream.write("<div class='body'>\n") 471 self.dispatch(node.else_) 472 self.stream.write("</div>\n") 473 self.stream.write("</div>\n") 474 475 # Expressions. 476 477 def visitAnd(self, node): 478 self.stream.write("<span class='and'>\n") 479 first = 1 480 for n in node.nodes: 481 if not first: 482 self._keyword("and") 483 self.dispatch(n) 484 first = 0 485 self.stream.write("</span>") 486 487 def visitAssAttr(self, node): 488 self.stream.write("<span class='assattr'>\n") 489 self.dispatch(node.expr) 490 self.stream.write("<span class='attr'>\n") 491 self.stream.write(".%s\n" % self._text(node.attrname)) 492 if hasattr(node, "_node"): 493 self._popup_start() 494 self._types(node._node) 495 self._scopes(node._node) 496 self._popup_end() 497 else: 498 raise ValueError, node 499 self.stream.write("</span>\n") 500 self.stream.write("</span>\n") 501 502 def visitAssList(self, node): 503 self.stream.write("<span class='list'>\n") 504 self.stream.write("[") 505 self._sequence(node) 506 self.stream.write("]\n") 507 self.stream.write("</span>\n") 508 509 def visitAssName(self, node): 510 if hasattr(node, "_node"): 511 self._name_start(node._node.name) 512 self._popup_start() 513 self._types(node._node.expr) 514 self._scopes(node._node) 515 self._popup_end() 516 self._name_end() 517 else: 518 raise ValueError, node 519 self._name(node.name) 520 521 def visitAssTuple(self, node): 522 self.stream.write("<span class='tuple'>\n") 523 self.stream.write("(") 524 self._sequence(node) 525 self.stream.write(")\n") 526 self.stream.write("</span>\n") 527 528 def visitCallFunc(self, node): 529 self.stream.write("<span class='callfunc'>\n") 530 self.dispatch(node.node) 531 self.stream.write("(") 532 first = 1 533 for arg in node.args: 534 if not first: 535 self.stream.write(",\n") 536 self.dispatch(arg) 537 first = 0 538 if node.star_args is not None: 539 if not first: 540 self.stream.write(", *\n") 541 self.dispatch(node.star_args) 542 first = 0 543 if node.dstar_args is not None: 544 if not first: 545 self.stream.write(", **\n") 546 self.dispatch(node.dstar_args) 547 first = 0 548 self.stream.write(")\n") 549 self.stream.write("</span>\n") 550 551 def visitCompare(self, node): 552 self.stream.write("<span class='compare'>\n") 553 self.dispatch(node.expr) 554 for (op_name, expr), _op in map(None, node.ops, node._ops): 555 self.stream.write("<span class='op'>\n") 556 self.stream.write(op_name) 557 self._popup_start() 558 self._op(op_name, _op) 559 self._popup_end() 560 self.stream.write("</span>\n") 561 self.dispatch(expr) 562 self.stream.write("</span>\n") 563 564 def visitConst(self, node): 565 self.stream.write(repr(node.value)) 566 567 def visitGetattr(self, node): 568 self.stream.write("<span class='getattr'>\n") 569 self.dispatch(node.expr) 570 self.stream.write("<span class='attr'>\n") 571 self.stream.write(".%s\n" % self._text(node.attrname)) 572 if hasattr(node, "_node"): 573 self._popup_start() 574 self._types(node._node) 575 self._scopes(node._node) 576 self._popup_end() 577 else: 578 raise ValueError, node 579 self.stream.write("</span>\n") 580 self.stream.write("</span>\n") 581 582 def visitKeyword(self, node): 583 self.stream.write("<span class='keyword'>\n") 584 self.stream.write(node.name) 585 self.stream.write("=") 586 self.dispatch(node.expr) 587 self.stream.write("</span>\n") 588 589 def visitLambda(self, node): 590 definition = node._node 591 subprogram = definition.expr.ref 592 self.stream.write("<span class='lambda'>\n") 593 self._keyword("lambda") 594 self._parameters(subprogram) 595 self.dispatch(node.code) 596 self.stream.write("</span>\n") 597 598 visitList = visitAssList 599 600 def visitName(self, node): 601 if hasattr(node, "_node"): 602 self._name_start(node._node.name) 603 self._popup_start() 604 self._types(node._node) 605 self._scopes(node._node) 606 self._popup_end() 607 self._name_end() 608 else: 609 raise ValueError, node 610 self._name(node.name) 611 612 def visitNot(self, node): 613 self.stream.write("<span class='not'>\n") 614 self._keyword("not") 615 self.dispatch(node.expr) 616 self.stream.write("</span>") 617 618 def visitOr(self, node): 619 self.stream.write("<span class='or'>\n") 620 first = 1 621 for n in node.nodes: 622 if not first: 623 self._keyword("or") 624 self.dispatch(n) 625 first = 0 626 self.stream.write("</span>") 627 628 def visitSlice(self, node): 629 self.stream.write("<span class='slice'>\n") 630 self.dispatch(node.expr) 631 self.stream.write("[") 632 if node.lower: 633 self.dispatch(node.lower) 634 self.stream.write(":") 635 if node.upper: 636 self.dispatch(node.upper) 637 # NOTE: Step? 638 self.stream.write("]") 639 self.stream.write("</span>\n") 640 641 def visitSubscript(self, node): 642 self.stream.write("<span class='subscript'>\n") 643 self.dispatch(node.expr) 644 self.stream.write("[") 645 first = 1 646 for sub in node.subs: 647 if not first: 648 self.stream.write(", ") 649 self.dispatch(sub) 650 first = 0 651 self.stream.write("]") 652 self.stream.write("</span>\n") 653 654 visitTuple = visitAssTuple 655 656 # Output preparation methods. 657 658 def _text(self, text): 659 return text.replace("&", "&").replace("<", "<").replace(">", ">") 660 661 def _attr(self, attr): 662 return self._text(attr).replace("'", "'").replace('"', """) 663 664 def _url(self, url): 665 return self._attr(url).replace("#", "%23").replace("-", "%2d") 666 667 def _comment(self, comment): 668 self.stream.write("<span class='comment'># %s</span>\n" % comment) 669 670 def _keyword(self, kw): 671 self.stream.write("<span class='keyword'>%s</span> " % kw) 672 673 def _doc(self, node): 674 if node.doc is not None: 675 self.stream.write("<pre class='doc'>\n") 676 self.stream.write('"""') 677 output = textwrap.dedent(node.doc.replace('"""', '\\"\\"\\"')) 678 self.stream.write(self._text(output)) 679 self.stream.write('"""') 680 self.stream.write("</pre>\n") 681 682 def _sequence(self, node): 683 first = 1 684 for n in node.nodes: 685 if not first: 686 self.stream.write(",\n") 687 self.dispatch(n) 688 first = 0 689 690 def _parameters(self, subprogram): 691 first = 1 692 for param, default in subprogram.params: 693 if not first: 694 self.stream.write(",\n") 695 self._parameter(subprogram, param, default) 696 first = 0 697 if subprogram.star is not None: 698 if not first: 699 self.stream.write(", *\n") 700 param, default = subprogram.star 701 self._parameter(subprogram, param, default) 702 first = 0 703 if subprogram.dstar is not None: 704 if not first: 705 self.stream.write(", **\n") 706 param, default = subprogram.dstar 707 self._parameter(subprogram, param, default) 708 first = 0 709 710 def _parameter(self, subprogram, param, default): 711 self._name_start(param) 712 if hasattr(subprogram, "paramtypes"): 713 self._popup_start() 714 self._types_list(subprogram.paramtypes[param]) 715 self._popup_end() 716 self._name_end() 717 if default is not None and default.original is not None: 718 self.stream.write("=\n") 719 self.dispatch(default.original) 720 721 def _name(self, name): 722 self.stream.write("<span class='name'>%s</span>\n" % name) 723 724 def _name_start(self, name): 725 self.stream.write("<span class='name'>%s\n" % name) 726 727 def _name_end(self): 728 self.stream.write("</span>\n") 729 730 def _popup_start(self): 731 self.stream.write("<span class='popup'>\n") 732 733 def _popup_end(self): 734 self.stream.write("</span>\n") 735 736 def _op(self, op_name, op): 737 if op is not None: 738 self._invocations(op) 739 740 def _invocations(self, node): 741 if hasattr(node, "invocations"): 742 self._invocations_list(node.invocations) 743 744 def _invocations_list(self, invocations): 745 self.stream.write("<div class='invocations'>\n") 746 for invocation in invocations: 747 fn = invocation.full_name() 748 module = invocation.module.name 749 name = invocation.name 750 structures = [x.name for x in invocation.structures] 751 self.stream.write("<div class='invocation'>") 752 self.stream.write("<a href='%s.html#%s'>" % (self._url(module), self._url(fn))) 753 self.stream.write(self._text(".".join([module] + structures + [name]))) 754 self.stream.write("</a>") 755 self.stream.write("</div>\n") 756 self.stream.write("</div>\n") 757 758 def _types(self, node): 759 if hasattr(node, "types"): 760 if node.types: 761 self._types_list(node.types) 762 else: 763 self.stream.write("<div class='types'>\n") 764 self.stream.write("no types\n") 765 self.stream.write("</div>\n") 766 else: 767 self.stream.write("<div class='types'>\n") 768 self.stream.write("unvisited\n") 769 self.stream.write("</div>\n") 770 771 def _types_list(self, types, style_class="types"): 772 self.stream.write("<div class='%s'>\n" % style_class) 773 for type in types: 774 fn = type.type.full_name() 775 self.stream.write("<div class='type'>") 776 self.stream.write(self._text(fn)) 777 self.stream.write("</div>\n") 778 self.stream.write("</div>\n") 779 780 def _raises(self, node): 781 if hasattr(node, "namespace") and hasattr(node.namespace, "raises") and node.namespace.raises: 782 self._types_list(node.namespace.raises, style_class="raises") 783 784 def _scopes(self, node): 785 if not isinstance(node, LoadName): 786 if hasattr(node, "writes") and node.writes or hasattr(node, "accesses") and node.accesses: 787 self.stream.write("<div class='scopes'>\n") 788 for ref in getattr(node, "writes", getattr(node, "accesses", {})).keys(): 789 fn = ref.full_name() 790 self.stream.write("<div class='scope'>") 791 self.stream.write(self._text(fn)) 792 self.stream.write("</div>\n") 793 self.stream.write("</div>\n") 794 if hasattr(node, "non_accesses") and node.non_accesses: 795 self._types_list(node.non_accesses, style_class="non-accesses") 796 if hasattr(node, "non_writes") and node.non_writes: 797 self._types_list(node.non_writes, style_class="non-writes") 798 799 # Utility functions. 800 801 def flatten(lists): 802 result = [] 803 for l in lists: 804 for attr in l: 805 if attr not in result: 806 result.append(attr) 807 return result 808 809 # Convenience functions. 810 811 def view(module, stream=None): 812 viewer = Viewer(stream or sys.stdout) 813 viewer.process(module.original) 814 815 def browse(module, stream=None): 816 browser = Browser(stream or sys.stdout) 817 browser.process(module.original) 818 819 def makedoc(module, filename): 820 stream = open(filename, "wb") 821 try: 822 browser = Browser(stream) 823 browser.process(module.original) 824 finally: 825 stream.close() 826 827 # vim: tabstop=4 expandtab shiftwidth=4