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