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