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