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