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