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 self.stream.write(repr(node.value)) 680 681 def visitDict(self, node): 682 self.stream.write("<span class='dict'>\n") 683 self.stream.write("{") 684 self._mapping(node) 685 self.stream.write("}\n") 686 self.stream.write("</span>\n") 687 688 def visitDiv(self, node): 689 self._visitBinary(node, "div", "/") 690 691 def visitFloorDiv(self, node): 692 self._visitBinary(node, "floordiv", "//") 693 694 def visitGetattr(self, node): 695 target = node._node 696 targets = target.active() 697 self.stream.write("<span class='getattr'>\n") 698 self.dispatch(node.expr) 699 self.stream.write("<span class='attr'>\n") 700 self.stream.write(".%s\n" % self._text(node.attrname)) 701 self._popup( 702 self._scopes(targets) + 703 self._types(targets) 704 ) 705 self.stream.write("</span>\n") 706 self.stream.write("</span>\n") 707 708 def visitKeyword(self, node): 709 self.stream.write("<span class='keyword-arg'>\n") 710 self.stream.write(node.name) 711 self.stream.write("=") 712 self.dispatch(node.expr) 713 self.stream.write("</span>\n") 714 715 def visitLambda(self, node): 716 definition = node._node 717 definitions = [n for n in definition.active() if not isinstance(n, Subprogram)] 718 subprogram = node._subprogram 719 subprograms = subprogram.active() 720 self.stream.write("<span class='lambda'>\n") 721 self._keyword("lambda") 722 self._parameters(subprogram, subprograms) 723 self.dispatch(node.code) 724 self.stream.write("</span>\n") 725 726 visitList = visitAssList 727 728 def visitListComp(self, node): 729 self.stream.write("<span class='listcomp'>\n") 730 self.stream.write("[") 731 self.dispatch(node.expr) 732 for qual in node.quals: 733 self.dispatch(qual) 734 self.stream.write("]\n") 735 self.stream.write("</span>\n") 736 737 def visitListCompFor(self, node): 738 self.stream.write("<span class='listcompfor'>\n") 739 self.stream.write("<span class='iterator'>\n") 740 self._keyword("for") 741 self._popup( 742 self._invocations(node._next_call.active()) 743 ) 744 self.stream.write("</span>\n") 745 self.dispatch(node.assign) 746 self.stream.write("<span class='iterator'>\n") 747 self._keyword("in") 748 self._popup( 749 self._invocations(node._iter_call.active()) 750 ) 751 self.stream.write("</span>\n") 752 self.dispatch(node.list) 753 for if_ in node.ifs: 754 self.dispatch(if_) 755 self.stream.write("</span>\n") 756 757 def visitListCompIf(self, node): 758 conditional = node._node 759 conditionals = conditional.active() 760 self.stream.write("<span class='listcompif'>\n") 761 self.stream.write("<span class='conditional'>\n") 762 self._keyword("if") 763 self._popup( 764 self._invocations([c.test for c in conditionals]) 765 ) 766 self.stream.write("</span>\n") 767 self.dispatch(node.test) 768 self.stream.write("</span>\n") 769 770 def visitMod(self, node): 771 self._visitBinary(node, "mod", "%") 772 773 def visitMul(self, node): 774 self._visitBinary(node, "mul", "*") 775 776 def visitName(self, node): 777 target = node._node 778 targets = target.active() 779 self._name_start(target.name) 780 self._popup( 781 self._scopes(targets) + 782 self._types(targets) 783 ) 784 self._name_end() 785 786 def visitNot(self, node): 787 self.stream.write("<span class='not'>\n") 788 self._keyword("not") 789 self.dispatch(node.expr) 790 self.stream.write("</span>") 791 792 def visitOr(self, node): 793 self.stream.write("<span class='or'>\n") 794 first = 1 795 for n in node.nodes: 796 if not first: 797 self._keyword("or") 798 self.dispatch(n) 799 first = 0 800 self.stream.write("</span>") 801 802 def visitPower(self, node): 803 self._visitBinary(node, "power", "**") 804 805 def visitSlice(self, node): 806 target = node._node 807 targets = target.active() 808 self.stream.write("<span class='slice'>\n") 809 self.dispatch(node.expr) 810 self.stream.write("<span class='call'>\n") 811 self.stream.write("[") 812 self._popup( 813 self._invocations(targets) 814 ) 815 self.stream.write("</span>\n") 816 if node.lower: 817 self.dispatch(node.lower) 818 self.stream.write(":") 819 if node.upper: 820 self.dispatch(node.upper) 821 # NOTE: Step? 822 self.stream.write("]") 823 self.stream.write("</span>\n") 824 825 def visitSliceobj(self, node): 826 self.stream.write("<span class='sliceobj'>\n") 827 first = 1 828 for n in node.nodes: 829 if not first: 830 self.stream.write(":") 831 self.dispatch(n) 832 self.stream.write("</span>\n") 833 834 def visitSub(self, node): 835 self._visitBinary(node, "sub", "-") 836 837 def visitSubscript(self, node): 838 target = node._node 839 targets = target.active() 840 self.stream.write("<span class='subscript'>\n") 841 self.dispatch(node.expr) 842 self.stream.write("<span class='call'>\n") 843 self.stream.write("[") 844 self._popup( 845 self._invocations(targets) 846 ) 847 self.stream.write("</span>\n") 848 first = 1 849 for sub in node.subs: 850 if not first: 851 self.stream.write(", ") 852 self.dispatch(sub) 853 first = 0 854 self.stream.write("]") 855 self.stream.write("</span>\n") 856 857 visitTuple = visitAssTuple 858 859 def visitUnaryAdd(self, node): 860 self._visitUnary(node, "add", "+") 861 862 def visitUnarySub(self, node): 863 self._visitUnary(node, "sub", "-") 864 865 # Output preparation methods. 866 867 def _text(self, text): 868 return text.replace("&", "&").replace("<", "<").replace(">", ">") 869 870 def _attr(self, attr): 871 return self._text(attr).replace("'", "'").replace('"', """) 872 873 def _url(self, url): 874 return self._attr(url).replace("#", "%23").replace("-", "%2d") 875 876 def _comment(self, comment): 877 self.stream.write("<span class='comment'># %s</span>\n" % comment) 878 879 def _keyword(self, kw): 880 self.stream.write("<span class='keyword'>%s</span> " % kw) 881 882 def _doc(self, node): 883 if node.doc is not None: 884 self.stream.write("<pre class='doc'>\n") 885 self.stream.write('"""') 886 output = textwrap.dedent(node.doc.replace('"""', '\\"\\"\\"')) 887 self.stream.write(self._text(output)) 888 self.stream.write('"""') 889 self.stream.write("</pre>\n") 890 891 def _sequence(self, node): 892 first = 1 893 for n in node.nodes: 894 if not first: 895 self.stream.write(",\n") 896 self.dispatch(n) 897 first = 0 898 899 def _mapping(self, node): 900 first = 1 901 for k, v in node.items: 902 if not first: 903 self.stream.write(",\n") 904 self.dispatch(k) 905 self.stream.write(":\n") 906 self.dispatch(v) 907 first = 0 908 909 def _parameters(self, subprogram, subprograms): 910 911 # Get all the parameter lists. 912 913 params = [] 914 nparams = 0 915 for sub in subprograms: 916 params.append(sub.params) 917 nparams = max(nparams, len(sub.params)) 918 stars = [] 919 have_star = 0 920 for sub in subprograms: 921 stars.append(sub.star) 922 if sub.star is not None: 923 have_star = 1 924 dstars = [] 925 have_dstar = 0 926 for sub in subprograms: 927 dstars.append(sub.dstar) 928 if sub.dstar is not None: 929 have_dstar = 1 930 931 # Traverse the parameter lists, choosing a "column" at a time. 932 933 first = 1 934 for n in range(0, nparams): 935 if not first: 936 self.stream.write(",\n") 937 main_param, main_default = subprogram.params[n] 938 self._name_start(main_param) 939 self._popup( 940 self._parameter(subprograms, params, n) 941 ) 942 self._name_end() 943 self._default(main_default) 944 first = 0 945 946 if have_star: 947 if not first: 948 self.stream.write(", *\n") 949 main_param, main_default = subprogram.star 950 self._name_start(main_param) 951 self._popup( 952 self._parameter(subprograms, stars) 953 ) 954 self._name_end() 955 self._default(main_default) 956 first = 0 957 958 if have_dstar: 959 if not first: 960 self.stream.write(", **\n") 961 main_param, main_default = subprogram.dstar 962 self._name_start(main_param) 963 self._popup( 964 self._parameter(subprograms, dstars) 965 ) 966 self._name_end() 967 self._default(main_default) 968 first = 0 969 970 def _parameter(self, subprograms, params, n=None): 971 types = set() 972 for i in range(0, len(subprograms)): 973 subprogram = subprograms[i] 974 if n is not None: 975 param, default = params[i][n] 976 else: 977 param, default = params[i] 978 if hasattr(subprogram, "paramtypes"): 979 types.update(subprogram.paramtypes[param]) 980 return self._types_container(types, "types") 981 982 def _default(self, default): 983 if default is not None and default.original is not None: 984 self.stream.write("=\n") 985 self.dispatch(default.original) 986 987 def _name(self, name): 988 self.stream.write("<span class='name'>%s</span>\n" % name) 989 990 def _name_start(self, name, classes=None): 991 if classes is not None: 992 classes = " " + classes 993 else: 994 classes = "" 995 self.stream.write("<span class='name%s'>%s\n" % (classes, name)) 996 997 def _name_end(self): 998 self.stream.write("</span>\n") 999 1000 def _popup(self, info): 1001 if info: 1002 self.stream.write("<span class='popup'>\n") 1003 for section, subsection, labels in info: 1004 self.stream.write("<div class='%s'>\n" % section) 1005 for label in labels: 1006 self.stream.write("<div class='%s'>\n" % subsection) 1007 self.stream.write(label) 1008 self.stream.write("</div>\n") 1009 self.stream.write("</div>\n") 1010 self.stream.write("</span>\n") 1011 1012 def _op(self, node): 1013 if hasattr(node, "_left_call") and hasattr(node, "_right_call"): 1014 return self._invocations(node._left_call.active() + node._right_call.active()) 1015 else: 1016 _node = node._node 1017 if isinstance(_node, Not): 1018 _node = _node.expr 1019 return self._invocations(_node.active()) 1020 1021 def _invocations(self, nodes): 1022 invocations = [] 1023 for node in nodes: 1024 if hasattr(node, "invocations"): 1025 invocations += node.invocations 1026 1027 # Record each link, avoiding duplicates. 1028 1029 links = {} 1030 for invocation in invocations: 1031 fn = getattr(invocation, "copy_of", invocation).full_name() 1032 module = invocation.module.name 1033 name = invocation.name 1034 structures = [x.name for x in invocation.structures] 1035 qualified_name = ".".join([module] + structures + [name]) 1036 1037 # Record the label and the link texts. 1038 1039 label = self._text(qualified_name) 1040 link = (self._url(module), self._url(fn)) 1041 links[label] = link 1042 1043 # Produce the list. 1044 1045 if links: 1046 popup_labels = [] 1047 for label, link in links.items(): 1048 popup_labels.append("<a href='%s.html#%s'>%s</a>" % (link + (label,))) 1049 else: 1050 popup_labels = [] 1051 1052 if popup_labels: 1053 return [("invocations", "invocation", popup_labels)] 1054 else: 1055 return [] 1056 1057 def _types(self, nodes): 1058 all_types = [(getattr(n, "types", []) or flatten(getattr(n, "writes", {}).values())) for n in nodes] 1059 types = flatten(all_types) 1060 return self._types_container(types, "types") 1061 1062 def _types_container(self, types, style_class): 1063 labels = {} 1064 for type in types: 1065 fn = type.type.full_name() 1066 labels[self._text(fn)] = None 1067 1068 if labels: 1069 return [(style_class, 'type', labels.keys())] 1070 else: 1071 return [] 1072 1073 def _raises(self, nodes): 1074 1075 "Output the exception information for the given simplified 'nodes'." 1076 1077 raises = set() 1078 for node in nodes: 1079 if hasattr(node, "raises") and node.raises: 1080 raises.update(node.raises) 1081 return self._types_container(raises, "raises") 1082 1083 def _scopes(self, nodes): 1084 1085 "Output the scope information for the given simplified 'nodes'." 1086 1087 labels = {} 1088 for node in nodes: 1089 1090 # Straightforward name loading/storing involves the local scope. 1091 1092 if isinstance(node, StoreName) or isinstance(node, LoadName): 1093 labels["(local)"] = None 1094 1095 # Other loading/storing involves attributes accessed on modules, classes 1096 # and objects. 1097 1098 else: 1099 1100 # Loading... 1101 1102 if hasattr(node, "accesses") and node.accesses: 1103 for ref, accesses in node.accesses.items(): 1104 fn = ref.full_name() 1105 for attr, access in accesses: 1106 access_fn = access.full_name() 1107 label = self._text(fn) 1108 if ref != access: 1109 label += " (via " + self._text(access_fn) + ")" 1110 labels[label] = None 1111 1112 # Storing... 1113 1114 if hasattr(node, "writes") and node.writes: 1115 for ref in node.writes.keys(): 1116 fn = ref.full_name() 1117 labels[self._text(fn)] = None 1118 1119 # Non-loading... 1120 1121 if hasattr(node, "non_accesses") and node.non_accesses: 1122 self._types_container(node.non_accesses, "non-accesses") 1123 1124 # Non-storing... 1125 1126 if hasattr(node, "non_writes") and node.non_writes: 1127 self._types_container(node.non_writes, "non-writes") 1128 1129 if labels: 1130 return [("scopes", "scope", labels.keys())] 1131 else: 1132 return [] 1133 1134 # Utility functions. 1135 1136 def flatten(lists): 1137 result = set() 1138 for l in lists: 1139 result.update(l) 1140 return result 1141 1142 # Convenience functions. 1143 1144 def browse(module, stream=None): 1145 browser = Browser(stream or sys.stdout) 1146 browser.process(module.original) 1147 1148 def makedoc(module, filename): 1149 stream = open(filename, "wb") 1150 try: 1151 browser = Browser(stream) 1152 browser.process(module.original) 1153 finally: 1154 stream.close() 1155 1156 def makedocs(module, modules, builtins): 1157 dirname = "%s-docs" % module.name 1158 if not os.path.exists(dirname): 1159 os.mkdir(dirname) 1160 for m in [module, builtins] + modules: 1161 makedoc(m, os.path.join(dirname, "%s%shtml" % (m.name, os.path.extsep))) 1162 1163 # vim: tabstop=4 expandtab shiftwidth=4