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