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