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