1 #!/usr/bin/env python 2 3 """ 4 View annotated sources. 5 6 Copyright (C) 2006, 2007 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 os 28 import textwrap 29 30 # Classes. 31 32 # HTML-related output production. 33 34 html_header = """<?xml version="1.0" encoding="iso-8859-15"?> 35 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 36 <html xmlns="http://www.w3.org/1999/xhtml"> 37 <head> 38 <title>Module</title> 39 <style type="text/css"> 40 body { 41 padding-bottom: 4em; 42 font-size: 14pt; font-family: monospace; 43 background-color: black; color: white; 44 } 45 46 .class { margin-top: 1em; margin-bottom: 1em; } 47 .function { margin-top: 1em; margin-bottom: 1em; } 48 .body { padding-left: 2em; } 49 .keyword { color: yellow; } 50 .comment { color: blue; } 51 .class-name { color: cyan; } 52 .function-name { color: cyan; } 53 .str { color: #FF00FF; } 54 .doc { color: #FF00FF; margin-top: 1em; margin-bottom: 1em; } 55 .invocation a { color: white; text-decoration: none; } 56 57 .popup { 58 display: none; z-index: 2; 59 position: absolute; top: 1em; left: 0.5em; 60 padding: 0.2em; background-color: #000000; color: white; 61 border: 2px solid #dddddd; 62 } 63 64 .invocations { 65 padding: 0.5em; background-color: #770000; 66 clear: all; 67 } 68 69 .types { 70 padding: 0.5em; background-color: #0000FF; 71 float: right; 72 } 73 74 .raises { 75 padding: 0.5em; background-color: #7700FF; 76 float: right; 77 } 78 79 .scopes { 80 padding: 0.5em; background-color: #007700; 81 float: left; 82 } 83 84 .non-writes, .non-accesses { 85 padding: 0.5em; background-color: #FF0000; 86 float: right; 87 } 88 89 .op, 90 .name, 91 .attr, 92 .conditional, 93 .operator, 94 .iterator, 95 .call, 96 .returns, 97 .failure 98 { 99 position: relative; 100 } 101 102 .op:hover > .popup, 103 .name:hover > .popup, 104 .attr:hover > .popup, 105 .conditional:hover > .popup, 106 .operator:hover > .popup, 107 .iterator:hover > .popup, 108 .call:hover > .popup, 109 .returns:hover > .popup, 110 .failure:hover > .popup 111 { 112 display: block; 113 } 114 115 </style> 116 </head> 117 <body> 118 """ 119 120 html_footer = """</body> 121 </html> 122 """ 123 124 # Browser classes. 125 126 class Browser(ASTVisitor): 127 128 """ 129 A browsing visitor for AST nodes. 130 131 Covered: Add, And, Assert, AssAttr, AssList, AssName, AssTuple, Assign, 132 AugAssign, Bitand, Break, CallFunc, Class, Compare, Const, 133 Continue, Dict, Discard, Div, FloorDiv, For, From, Function, 134 Getattr, Global, If, Import, Keyword, Lambda, List, ListComp, 135 ListCompFor, ListCompIf, Mod, Module, Mul, Name, Not, Or, Pass, 136 Power, Print, Printnl, Raise, Return, Slice, Sliceobj, Stmt, Sub, 137 Subscript, TryExcept, TryFinally, Tuple, UnaryAdd, UnarySub, While. 138 139 Missing: Backquote, Bitor, Bitxor, Decorators, Ellipsis, 140 Exec, Invert, LeftShift, RightShift, Yield. 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 visitAssert(self, node): 159 self.stream.write("<div class='assert'>\n") 160 self.stream.write("<span class='failure'>\n") 161 self._keyword("assert") 162 self._popup( 163 self._types(node._raises.active()) 164 ) 165 self.stream.write("</span>\n") 166 self.dispatch(node.test) 167 if node.fail: 168 self.stream.write(", ") 169 self.dispatch(node.fail) 170 self.stream.write("</div>\n") 171 172 def visitAssign(self, node): 173 self.stream.write("<div class='assign'>\n") 174 for lvalue in node.nodes: 175 self.dispatch(lvalue) 176 self.stream.write("=\n") 177 self.dispatch(node.expr) 178 self.stream.write("</div>\n") 179 180 def visitAugAssign(self, node): 181 self.stream.write("<div class='augassign'>\n") 182 self.dispatch(node.node) 183 self.stream.write("<span class='operator'>\n") 184 self.stream.write("%s\n" % node.op) 185 self._popup( 186 self._invocations(node._op_call.active()) 187 ) 188 self.stream.write("</span>\n") 189 self.dispatch(node.expr) 190 self.stream.write("</div>\n") 191 192 def visitBreak(self, node): 193 self.stream.write("<div class='break'>\n") 194 self._keyword("break") 195 self.stream.write("</div>\n") 196 197 def visitClass(self, node): 198 definition = node._node 199 definitions = definition.active() 200 structure = definition.expr.ref 201 self.stream.write("<div class='class' id='%s'>\n" % self._url(structure.full_name())) 202 self.stream.write("<div>\n") 203 self._keyword("class") 204 self._name_start(structure.name, "class-name") 205 self._popup( 206 self._scopes(definitions) 207 ) 208 self._name_end() 209 bases = structure.bases 210 211 # Suppress the "object" class appearing alone. 212 213 if bases and not (len(bases) == 1 and bases[0].name == "object"): 214 self.stream.write("(") 215 first = 1 216 for base in bases: 217 if not first: 218 self.stream.write(",\n") 219 self._name_start(base.name) 220 self._popup( 221 self._scopes([base]) + 222 self._types([base]) 223 ) 224 self._name_end() 225 first = 0 226 self.stream.write(")") 227 228 self.stream.write(":\n") 229 self._comment(self._text(structure.full_name())) 230 self.stream.write("</div>\n") 231 232 self.stream.write("<div class='body'>\n") 233 self._doc(node) 234 self.dispatch(node.code) 235 self.stream.write("</div>\n") 236 self.stream.write("</div>\n") 237 238 def visitContinue(self, node): 239 self.stream.write("<div class='continue'>\n") 240 self._keyword("continue") 241 self.stream.write("</div>\n") 242 243 def visitDiscard(self, node): 244 self.stream.write("<div class='discard'>\n") 245 self.default(node) 246 self.stream.write("</div>\n") 247 248 def visitFor(self, node): 249 self.stream.write("<div class='if'>\n") 250 self.stream.write("<div>\n") 251 self.stream.write("<span class='iterator'>\n") 252 self._keyword("for") 253 self._popup( 254 self._invocations(node._next_call.active()) 255 ) 256 self.stream.write("</span>\n") 257 self.dispatch(node.assign) 258 self.stream.write("<span class='iterator'>\n") 259 self._keyword("in") 260 self._popup( 261 self._invocations(node._iter_call.active()) 262 ) 263 self.stream.write("</span>\n") 264 self.dispatch(node.list) 265 self.stream.write(":\n") 266 self.stream.write("</div>\n") 267 self.stream.write("<div class='body'>\n") 268 self.dispatch(node.body) 269 self.stream.write("</div>\n") 270 if node.else_ is not None: 271 self.stream.write("<div>\n") 272 self._keyword("else") 273 self.stream.write(":\n") 274 self.stream.write("</div>\n") 275 self.stream.write("<div class='body'>\n") 276 self.dispatch(node.else_) 277 self.stream.write("</div>\n") 278 self.stream.write("</div>\n") 279 280 def visitFrom(self, node): 281 self.stream.write("<div class='from'>\n") 282 self._keyword("from") 283 self.stream.write("<span class='name'>\n") 284 self.stream.write(node.modname) 285 self._popup( 286 self._types(node._modname.active()) 287 ) 288 self.stream.write("</span>\n") 289 self._keyword("import") 290 first = 1 291 for (name, alias), _name in map(None, node.names, node._names): 292 if not first: 293 self.stream.write(",\n") 294 if alias: 295 self.stream.write(name + " ") 296 self._keyword("as") 297 self.stream.write("<span class='name'>\n") 298 self.stream.write(alias or name) 299 self._popup( 300 self._types([_name]) 301 ) 302 self.stream.write("</span>\n") 303 first = 0 304 self.stream.write("</div>\n") 305 306 def visitFunction(self, node): 307 definition = node._node 308 definitions = [n for n in definition.active() if not isinstance(n, Subprogram)] 309 subprogram = node._subprogram 310 subprograms = subprogram.active() 311 self.stream.write("<div class='function' id='%s'>\n" % self._url(subprogram.full_name())) 312 self.stream.write("<div>\n") 313 self._keyword("def") 314 self._name_start(subprogram.name, "function-name") 315 self._popup( 316 self._scopes([definition]) + # not dependent on subprograms 317 self._raises(subprograms) 318 ) 319 self._name_end() 320 self.stream.write("(") 321 self._parameters(subprogram, subprograms) 322 self.stream.write(")") 323 self.stream.write(":\n") 324 self._comment(self._text(subprogram.full_name())) 325 self.stream.write("</div>\n") 326 327 self.stream.write("<div class='body'>\n") 328 self._doc(node) 329 self.dispatch(node.code) 330 self.stream.write("</div>\n") 331 self.stream.write("</div>\n") 332 333 def visitGlobal(self, node): 334 self.stream.write("<div class='global'>\n") 335 self._keyword("global") 336 first = 1 337 for name in node.names: 338 if not first: 339 self.stream.write(",\n") 340 self.stream.write(name) 341 first = 0 342 self.stream.write("</div>\n") 343 344 def visitIf(self, node): 345 self.stream.write("<div class='if'>\n") 346 first = 1 347 conditional = node._node 348 conditionals = conditional.active() 349 for compare, stmt in node.tests: 350 self.stream.write("<div>\n") 351 self.stream.write("<span class='conditional'>\n") 352 if first: 353 self._keyword("if") 354 else: 355 self._keyword("elif") 356 self._popup( 357 self._invocations([c.test for c in conditionals]) 358 ) 359 self.stream.write("</span>\n") 360 self.dispatch(compare) 361 self.stream.write(":\n") 362 self.stream.write("</div>\n") 363 self.stream.write("<div class='body'>\n") 364 self.dispatch(stmt) 365 self.stream.write("</div>\n") 366 if conditional.else_: 367 conditional = conditional.else_[0] 368 conditionals = conditional.active() 369 else: 370 conditional = None 371 conditionals = [] 372 first = 0 373 if node.else_ is not None: 374 self.stream.write("<div>\n") 375 self._keyword("else") 376 self.stream.write(":\n") 377 self.stream.write("</div>\n") 378 self.stream.write("<div class='body'>\n") 379 self.dispatch(node.else_) 380 self.stream.write("</div>\n") 381 self.stream.write("</div>\n") 382 383 def visitImport(self, node): 384 self.stream.write("<div class='import'>\n") 385 self._keyword("import") 386 first = 1 387 for (name, alias), _name in map(None, node.names, node._names): 388 if not first: 389 self.stream.write(",\n") 390 if alias: 391 self.stream.write(name + " ") 392 self._keyword("as") 393 self.stream.write("<span class='name'>\n") 394 self.stream.write(alias or name) 395 self._popup( 396 self._types([_name]) 397 ) 398 self.stream.write("</span>\n") 399 first = 0 400 self.stream.write("</div>\n") 401 402 def visitPass(self, node): 403 self.stream.write("<div class='pass'>\n") 404 self._keyword("pass") 405 self.stream.write("</div>\n") 406 407 def visitPrint(self, node): 408 self.stream.write("<div class='print'>\n") 409 self._keyword("print") 410 if node.dest is not None: 411 self.stream.write(">>\n") 412 self.dispatch(node.dest) 413 for n in node.nodes: 414 self.dispatch(n) 415 self.stream.write(",\n") 416 self.stream.write("</div>\n") 417 418 def visitPrintnl(self, node): 419 self.stream.write("<div class='printnl'>\n") 420 self._keyword("print") 421 if node.dest is not None: 422 self.stream.write(">>\n") 423 self.dispatch(node.dest) 424 first = 1 425 for n in node.nodes: 426 if not first: 427 self.stream.write(",\n") 428 self.dispatch(n) 429 first = 0 430 self.stream.write("</div>\n") 431 432 def visitRaise(self, node): 433 target = node._node.expr 434 targets = target.active() 435 self.stream.write("<div class='raise'>\n") 436 self.stream.write("<span class='call'>\n") 437 self._keyword("raise") 438 self._popup( 439 self._invocations(targets) 440 ) 441 self.stream.write("</span>\n") 442 self.dispatch(node.expr1) 443 if node.expr2 is not None: 444 self.stream.write(",\n") 445 self.dispatch(node.expr2) 446 if node.expr3 is not None: 447 self.stream.write(",\n") 448 self.dispatch(node.expr3) 449 self.stream.write("</div>\n") 450 451 def visitReturn(self, node): 452 value = node._node 453 values = value.active() 454 self.stream.write("<div class='return'>\n") 455 self.stream.write("<span class='returns'>\n") 456 self._keyword("return") 457 self._popup( 458 self._types(values) 459 ) 460 self.stream.write("</span>\n") 461 self.dispatch(node.value) 462 self.stream.write("</div>\n") 463 464 def visitStmt(self, node): 465 self.stream.write("<div class='stmt'>\n") 466 self.default(node) 467 self.stream.write("</div>\n") 468 469 def visitTryExcept(self, node): 470 self.stream.write("<div class='tryexcept'>\n") 471 self.stream.write("<div>\n") 472 self._keyword("try") 473 self.stream.write(":\n") 474 self.stream.write("</div>\n") 475 self.stream.write("<div class='body'>\n") 476 self.dispatch(node.body) 477 self.stream.write("</div>\n") 478 for spec, assign, statement in node.handlers: 479 self.stream.write("<div>\n") 480 self._keyword("except") 481 if spec is not None: 482 self.dispatch(spec) 483 if assign is not None: 484 self.stream.write(",\n") 485 self.dispatch(assign) 486 self.stream.write(":\n") 487 self.stream.write("</div>\n") 488 self.stream.write("<div class='body'>\n") 489 self.dispatch(statement) 490 self.stream.write("</div>\n") 491 if node.else_ is not None: 492 self.stream.write("<div>\n") 493 self._keyword("else") 494 self.stream.write(":\n") 495 self.stream.write("</div>\n") 496 self.stream.write("<div class='body'>\n") 497 self.dispatch(node.else_) 498 self.stream.write("</div>\n") 499 self.stream.write("</div>\n") 500 501 def visitTryFinally(self, node): 502 self.stream.write("<div class='tryfinally'>\n") 503 self.stream.write("<div>\n") 504 self._keyword("try") 505 self.stream.write(":\n") 506 self.stream.write("</div>\n") 507 self.stream.write("<div class='body'>\n") 508 self.dispatch(node.body) 509 self.stream.write("</div>\n") 510 self.stream.write("<div>\n") 511 self._keyword("finally") 512 self.stream.write(":\n") 513 self.stream.write("</div>\n") 514 self.stream.write("<div class='body'>\n") 515 self.dispatch(node.final) 516 self.stream.write("</div>\n") 517 self.stream.write("</div>\n") 518 519 def visitWhile(self, node): 520 self.stream.write("<div class='while'>\n") 521 self.stream.write("<div>\n") 522 self.stream.write("<span class='conditional'>\n") 523 self._keyword("while") 524 self._popup( 525 self._invocations(node._test_call.active()) 526 ) 527 self.stream.write("</span>\n") 528 self.dispatch(node.test) 529 self.stream.write(":\n") 530 self.stream.write("</div>\n") 531 self.stream.write("<div class='body'>\n") 532 self.dispatch(node.body) 533 self.stream.write("</div>\n") 534 if node.else_ is not None: 535 self.stream.write("<div>\n") 536 self._keyword("else") 537 self.stream.write(":\n") 538 self.stream.write("</div>\n") 539 self.stream.write("<div class='body'>\n") 540 self.dispatch(node.else_) 541 self.stream.write("</div>\n") 542 self.stream.write("</div>\n") 543 544 # Expression-related helper methods. 545 546 def _visitBinary(self, node, name, symbol): 547 self.stream.write("<span class='%s'>\n" % name) 548 self.dispatch(node.left) 549 self.stream.write("<span class='operator'>\n") 550 self.stream.write(self._text(symbol)) 551 self._popup( 552 self._invocations(node._left_call.active() + node._right_call.active()) 553 ) 554 self.stream.write("</span>\n") 555 self.dispatch(node.right) 556 self.stream.write("</span>") 557 558 def _visitUnary(self, node, name, symbol): 559 self.stream.write("<span class='%s'>\n" % name) 560 self.stream.write("<span class='operator'>\n") 561 self.stream.write(symbol) 562 self._popup( 563 self._invocations(node._unary_call.active()) 564 ) 565 self.stream.write("</span>\n") 566 self.dispatch(node.expr) 567 self.stream.write("</span>") 568 569 # Expressions. 570 571 def visitAdd(self, node): 572 self._visitBinary(node, "add", "+") 573 574 def visitAnd(self, node): 575 self.stream.write("<span class='and'>\n") 576 first = 1 577 for n in node.nodes: 578 if not first: 579 self._keyword("and") 580 self.dispatch(n) 581 first = 0 582 self.stream.write("</span>") 583 584 def visitAssAttr(self, node): 585 target = node._node 586 targets = target.active() 587 self.stream.write("<span class='assattr'>\n") 588 self.dispatch(node.expr) 589 self.stream.write("<span class='attr'>\n") 590 self.stream.write(".%s\n" % self._text(node.attrname)) 591 self._popup( 592 self._scopes(targets) + 593 self._types(targets) 594 ) 595 self.stream.write("</span>\n") 596 self.stream.write("</span>\n") 597 598 def visitAssList(self, node): 599 self.stream.write("<span class='list'>\n") 600 self.stream.write("[") 601 self._sequence(node) 602 self.stream.write("]\n") 603 self.stream.write("</span>\n") 604 605 def visitAssName(self, node): 606 target = node._node 607 targets = target.active() 608 self._name_start(target.name) 609 self._popup( 610 self._scopes(targets) + 611 self._types(targets) 612 ) 613 self._name_end() 614 615 def visitAssTuple(self, node): 616 self.stream.write("<span class='tuple'>\n") 617 self.stream.write("(") 618 self._sequence(node) 619 self.stream.write(")\n") 620 self.stream.write("</span>\n") 621 622 def visitBitand(self, node): 623 self.stream.write("<span class='bitand'>\n") 624 self.dispatch(node.nodes[0]) 625 for op in node._ops: 626 self.stream.write("<span class='op'>\n") 627 self.stream.write(self._text(op.name)) 628 self._popup( 629 self._op(op) 630 ) 631 self.stream.write("</span>\n") 632 self.dispatch(op.expr) 633 self.stream.write("</span>") 634 635 def visitCallFunc(self, node): 636 target = node._node 637 targets = target.active() 638 self.stream.write("<span class='callfunc'>\n") 639 self.dispatch(node.node) 640 self.stream.write("<span class='call'>\n") 641 self.stream.write("(") 642 self._popup( 643 self._invocations(targets) 644 ) 645 self.stream.write("</span>\n") 646 first = 1 647 for arg in node.args: 648 if not first: 649 self.stream.write(",\n") 650 self.dispatch(arg) 651 first = 0 652 if node.star_args is not None: 653 if not first: 654 self.stream.write(", *\n") 655 self.dispatch(node.star_args) 656 first = 0 657 if node.dstar_args is not None: 658 if not first: 659 self.stream.write(", **\n") 660 self.dispatch(node.dstar_args) 661 first = 0 662 self.stream.write(")\n") 663 self.stream.write("</span>\n") 664 665 def visitCompare(self, node): 666 self.stream.write("<span class='compare'>\n") 667 self.dispatch(node.expr) 668 for op in node._ops: 669 self.stream.write("<span class='op'>\n") 670 self.stream.write(self._text(op.name)) 671 self._popup( 672 self._op(op) 673 ) 674 self.stream.write("</span>\n") 675 self.dispatch(op.expr) 676 self.stream.write("</span>\n") 677 678 def visitConst(self, node): 679 if isinstance(node.value, (str, unicode)): 680 self.stream.write("<span class='str'>\n") 681 self.stream.write(repr(node.value)) 682 if isinstance(node.value, (str, unicode)): 683 self.stream.write("</span>\n") 684 685 def visitDict(self, node): 686 self.stream.write("<span class='dict'>\n") 687 self.stream.write("{") 688 self._mapping(node) 689 self.stream.write("}\n") 690 self.stream.write("</span>\n") 691 692 def visitDiv(self, node): 693 self._visitBinary(node, "div", "/") 694 695 def visitFloorDiv(self, node): 696 self._visitBinary(node, "floordiv", "//") 697 698 def visitGetattr(self, node): 699 target = node._node 700 targets = target.active() 701 self.stream.write("<span class='getattr'>\n") 702 self.dispatch(node.expr) 703 self.stream.write("<span class='attr'>\n") 704 self.stream.write(".%s\n" % self._text(node.attrname)) 705 self._popup( 706 self._scopes(targets) + 707 self._types(targets) 708 ) 709 self.stream.write("</span>\n") 710 self.stream.write("</span>\n") 711 712 def visitKeyword(self, node): 713 self.stream.write("<span class='keyword-arg'>\n") 714 self.stream.write(node.name) 715 self.stream.write("=") 716 self.dispatch(node.expr) 717 self.stream.write("</span>\n") 718 719 def visitLambda(self, node): 720 definition = node._node 721 definitions = [n for n in definition.active() if not isinstance(n, Subprogram)] 722 subprogram = node._subprogram 723 subprograms = subprogram.active() 724 self.stream.write("<span class='lambda'>\n") 725 self._keyword("lambda") 726 self._parameters(subprogram, subprograms) 727 self.dispatch(node.code) 728 self.stream.write("</span>\n") 729 730 visitList = visitAssList 731 732 def visitListComp(self, node): 733 self.stream.write("<span class='listcomp'>\n") 734 self.stream.write("[") 735 self.dispatch(node.expr) 736 for qual in node.quals: 737 self.dispatch(qual) 738 self.stream.write("]\n") 739 self.stream.write("</span>\n") 740 741 def visitListCompFor(self, node): 742 self.stream.write("<span class='listcompfor'>\n") 743 self.stream.write("<span class='iterator'>\n") 744 self._keyword("for") 745 self._popup( 746 self._invocations(node._next_call.active()) 747 ) 748 self.stream.write("</span>\n") 749 self.dispatch(node.assign) 750 self.stream.write("<span class='iterator'>\n") 751 self._keyword("in") 752 self._popup( 753 self._invocations(node._iter_call.active()) 754 ) 755 self.stream.write("</span>\n") 756 self.dispatch(node.list) 757 for if_ in node.ifs: 758 self.dispatch(if_) 759 self.stream.write("</span>\n") 760 761 def visitListCompIf(self, node): 762 conditional = node._node 763 conditionals = conditional.active() 764 self.stream.write("<span class='listcompif'>\n") 765 self.stream.write("<span class='conditional'>\n") 766 self._keyword("if") 767 self._popup( 768 self._invocations([c.test for c in conditionals]) 769 ) 770 self.stream.write("</span>\n") 771 self.dispatch(node.test) 772 self.stream.write("</span>\n") 773 774 def visitMod(self, node): 775 self._visitBinary(node, "mod", "%") 776 777 def visitMul(self, node): 778 self._visitBinary(node, "mul", "*") 779 780 def visitName(self, node): 781 target = node._node 782 targets = target.active() 783 self._name_start(target.name) 784 self._popup( 785 self._scopes(targets) + 786 self._types(targets) 787 ) 788 self._name_end() 789 790 def visitNot(self, node): 791 self.stream.write("<span class='not'>\n") 792 self._keyword("not") 793 self.dispatch(node.expr) 794 self.stream.write("</span>") 795 796 def visitOr(self, node): 797 self.stream.write("<span class='or'>\n") 798 first = 1 799 for n in node.nodes: 800 if not first: 801 self._keyword("or") 802 self.dispatch(n) 803 first = 0 804 self.stream.write("</span>") 805 806 def visitPower(self, node): 807 self._visitBinary(node, "power", "**") 808 809 def visitSlice(self, node): 810 target = node._node 811 targets = target.active() 812 self.stream.write("<span class='slice'>\n") 813 self.dispatch(node.expr) 814 self.stream.write("<span class='call'>\n") 815 self.stream.write("[") 816 self._popup( 817 self._invocations(targets) 818 ) 819 self.stream.write("</span>\n") 820 if node.lower: 821 self.dispatch(node.lower) 822 self.stream.write(":") 823 if node.upper: 824 self.dispatch(node.upper) 825 # NOTE: Step? 826 self.stream.write("]") 827 self.stream.write("</span>\n") 828 829 def visitSliceobj(self, node): 830 self.stream.write("<span class='sliceobj'>\n") 831 first = 1 832 for n in node.nodes: 833 if not first: 834 self.stream.write(":") 835 self.dispatch(n) 836 self.stream.write("</span>\n") 837 838 def visitSub(self, node): 839 self._visitBinary(node, "sub", "-") 840 841 def visitSubscript(self, node): 842 target = node._node 843 targets = target.active() 844 self.stream.write("<span class='subscript'>\n") 845 self.dispatch(node.expr) 846 self.stream.write("<span class='call'>\n") 847 self.stream.write("[") 848 self._popup( 849 self._invocations(targets) 850 ) 851 self.stream.write("</span>\n") 852 first = 1 853 for sub in node.subs: 854 if not first: 855 self.stream.write(", ") 856 self.dispatch(sub) 857 first = 0 858 self.stream.write("]") 859 self.stream.write("</span>\n") 860 861 visitTuple = visitAssTuple 862 863 def visitUnaryAdd(self, node): 864 self._visitUnary(node, "add", "+") 865 866 def visitUnarySub(self, node): 867 self._visitUnary(node, "sub", "-") 868 869 # Output preparation methods. 870 871 def _text(self, text): 872 return text.replace("&", "&").replace("<", "<").replace(">", ">") 873 874 def _attr(self, attr): 875 return self._text(attr).replace("'", "'").replace('"', """) 876 877 def _url(self, url): 878 return self._attr(url).replace("#", "%23").replace("-", "%2d") 879 880 def _comment(self, comment): 881 self.stream.write("<span class='comment'># %s</span>\n" % comment) 882 883 def _keyword(self, kw): 884 self.stream.write("<span class='keyword'>%s</span> " % kw) 885 886 def _doc(self, node): 887 if node.doc is not None: 888 self.stream.write("<pre class='doc'>\n") 889 self.stream.write('"""') 890 output = textwrap.dedent(node.doc.replace('"""', '\\"\\"\\"')) 891 self.stream.write(self._text(output)) 892 self.stream.write('"""') 893 self.stream.write("</pre>\n") 894 895 def _sequence(self, node): 896 first = 1 897 for n in node.nodes: 898 if not first: 899 self.stream.write(",\n") 900 self.dispatch(n) 901 first = 0 902 903 def _mapping(self, node): 904 first = 1 905 for k, v in node.items: 906 if not first: 907 self.stream.write(",\n") 908 self.dispatch(k) 909 self.stream.write(":\n") 910 self.dispatch(v) 911 first = 0 912 913 def _parameters(self, subprogram, subprograms): 914 915 # Get all the parameter lists. 916 917 params = [] 918 nparams = 0 919 for sub in subprograms: 920 params.append(sub.params) 921 nparams = max(nparams, len(sub.params)) 922 stars = [] 923 have_star = 0 924 for sub in subprograms: 925 stars.append(sub.star) 926 if sub.star is not None: 927 have_star = 1 928 dstars = [] 929 have_dstar = 0 930 for sub in subprograms: 931 dstars.append(sub.dstar) 932 if sub.dstar is not None: 933 have_dstar = 1 934 935 # Traverse the parameter lists, choosing a "column" at a time. 936 937 first = 1 938 for n in range(0, nparams): 939 if not first: 940 self.stream.write(",\n") 941 main_param, main_default = subprogram.params[n] 942 self._name_start(main_param) 943 self._popup( 944 self._parameter(subprograms, params, n) 945 ) 946 self._name_end() 947 self._default(main_default) 948 first = 0 949 950 if have_star: 951 if not first: 952 self.stream.write(", *\n") 953 main_param, main_default = subprogram.star 954 self._name_start(main_param) 955 self._popup( 956 self._parameter(subprograms, stars) 957 ) 958 self._name_end() 959 self._default(main_default) 960 first = 0 961 962 if have_dstar: 963 if not first: 964 self.stream.write(", **\n") 965 main_param, main_default = subprogram.dstar 966 self._name_start(main_param) 967 self._popup( 968 self._parameter(subprograms, dstars) 969 ) 970 self._name_end() 971 self._default(main_default) 972 first = 0 973 974 def _parameter(self, subprograms, params, n=None): 975 types = set() 976 for i in range(0, len(subprograms)): 977 subprogram = subprograms[i] 978 if n is not None: 979 param, default = params[i][n] 980 else: 981 param, default = params[i] 982 if hasattr(subprogram, "paramtypes"): 983 types.update(subprogram.paramtypes[param]) 984 return self._types_container(types, "types") 985 986 def _default(self, default): 987 if default is not None and default.original is not None: 988 self.stream.write("=\n") 989 self.dispatch(default.original) 990 991 def _name(self, name): 992 self.stream.write("<span class='name'>%s</span>\n" % name) 993 994 def _name_start(self, name, classes=None): 995 if classes is not None: 996 classes = " " + classes 997 else: 998 classes = "" 999 self.stream.write("<span class='name%s'>%s\n" % (classes, name)) 1000 1001 def _name_end(self): 1002 self.stream.write("</span>\n") 1003 1004 def _popup(self, info): 1005 if info: 1006 self.stream.write("<span class='popup'>\n") 1007 for section, subsection, labels in info: 1008 self.stream.write("<div class='%s'>\n" % section) 1009 for label in labels: 1010 self.stream.write("<div class='%s'>\n" % subsection) 1011 self.stream.write(label) 1012 self.stream.write("</div>\n") 1013 self.stream.write("</div>\n") 1014 self.stream.write("</span>\n") 1015 1016 def _op(self, node): 1017 if hasattr(node, "_left_call") and hasattr(node, "_right_call"): 1018 return self._invocations(node._left_call.active() + node._right_call.active()) 1019 else: 1020 _node = node._node 1021 if isinstance(_node, Not): 1022 _node = _node.expr 1023 return self._invocations(_node.active()) 1024 1025 def _invocations(self, nodes): 1026 invocations = [] 1027 for node in nodes: 1028 if hasattr(node, "invocations"): 1029 invocations += node.invocations 1030 1031 # Record each link, avoiding duplicates. 1032 1033 links = {} 1034 for invocation in invocations: 1035 fn = getattr(invocation, "copy_of", invocation).full_name() 1036 module = invocation.module.name 1037 name = invocation.name 1038 structures = [x.name for x in invocation.structures] 1039 qualified_name = ".".join([module] + structures + [name]) 1040 1041 # Record the label and the link texts. 1042 1043 label = self._text(qualified_name) 1044 link = (self._url(module), self._url(fn)) 1045 links[label] = link 1046 1047 # Produce the list. 1048 1049 if links: 1050 popup_labels = [] 1051 for label, link in links.items(): 1052 popup_labels.append("<a href='%s.html#%s'>%s</a>" % (link + (label,))) 1053 else: 1054 popup_labels = [] 1055 1056 if popup_labels: 1057 return [("invocations", "invocation", popup_labels)] 1058 else: 1059 return [] 1060 1061 def _types(self, nodes): 1062 all_types = [(getattr(n, "types", []) or flatten(getattr(n, "writes", {}).values())) for n in nodes] 1063 types = flatten(all_types) 1064 return self._types_container(types, "types") 1065 1066 def _types_container(self, types, style_class): 1067 labels = {} 1068 for type in types: 1069 fn = type.type.full_name() 1070 labels[self._text(fn)] = None 1071 1072 if labels: 1073 return [(style_class, 'type', labels.keys())] 1074 else: 1075 return [] 1076 1077 def _raises(self, nodes): 1078 1079 "Output the exception information for the given simplified 'nodes'." 1080 1081 raises = set() 1082 for node in nodes: 1083 if hasattr(node, "raises") and node.raises: 1084 raises.update(node.raises) 1085 return self._types_container(raises, "raises") 1086 1087 def _scopes(self, nodes): 1088 1089 "Output the scope information for the given simplified 'nodes'." 1090 1091 labels = {} 1092 for node in nodes: 1093 1094 # Straightforward name loading/storing involves the local scope. 1095 1096 if isinstance(node, StoreName) or isinstance(node, LoadName): 1097 labels["(local)"] = None 1098 1099 # Other loading/storing involves attributes accessed on modules, classes 1100 # and objects. 1101 1102 else: 1103 1104 # Loading... 1105 1106 if hasattr(node, "accesses") and node.accesses: 1107 for ref, accesses in node.accesses.items(): 1108 fn = ref.full_name() 1109 for attr, access in accesses: 1110 access_fn = access.full_name() 1111 label = self._text(fn) 1112 if ref != access: 1113 label += " (via " + self._text(access_fn) + ")" 1114 labels[label] = None 1115 1116 # Storing... 1117 1118 if hasattr(node, "writes") and node.writes: 1119 for ref in node.writes.keys(): 1120 fn = ref.full_name() 1121 labels[self._text(fn)] = None 1122 1123 # Non-loading... 1124 1125 if hasattr(node, "non_accesses") and node.non_accesses: 1126 self._types_container(node.non_accesses, "non-accesses") 1127 1128 # Non-storing... 1129 1130 if hasattr(node, "non_writes") and node.non_writes: 1131 self._types_container(node.non_writes, "non-writes") 1132 1133 if labels: 1134 return [("scopes", "scope", labels.keys())] 1135 else: 1136 return [] 1137 1138 # Utility functions. 1139 1140 def flatten(lists): 1141 result = set() 1142 for l in lists: 1143 result.update(l) 1144 return result 1145 1146 # Convenience functions. 1147 1148 def browse(module, stream=None): 1149 browser = Browser(stream or sys.stdout) 1150 browser.process(module.original) 1151 1152 def makedoc(module, filename): 1153 stream = open(filename, "wb") 1154 try: 1155 browser = Browser(stream) 1156 browser.process(module.original) 1157 finally: 1158 stream.close() 1159 1160 def makedocs(module, modules, builtins): 1161 dirname = "%s-docs" % module.name 1162 if not os.path.exists(dirname): 1163 os.mkdir(dirname) 1164 for m in [module, builtins] + modules: 1165 makedoc(m, os.path.join(dirname, "%s%shtml" % (m.name, os.path.extsep))) 1166 1167 # vim: tabstop=4 expandtab shiftwidth=4