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