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