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