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