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