1 #!/usr/bin/env python 2 3 """ 4 View annotated sources. 5 6 Copyright (C) 2006 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 textwrap 28 29 # Exceptions. 30 31 class ViewerError(SimplifiedError): 32 33 "An error in viewing." 34 35 pass 36 37 # Classes. 38 39 class Viewer(ASTVisitor): 40 41 """ 42 A viewing visitor for AST nodes. 43 """ 44 45 def __init__(self, stream): 46 ASTVisitor.__init__(self) 47 self.cached_files = {} 48 self.printed_lines = {} 49 self.visitor = self 50 self.stream = stream 51 52 def process(self, module): 53 self.dispatch(module) 54 55 def dispatch(self, node): 56 self.dispatch_only(node) 57 ASTVisitor.dispatch(self, node) 58 59 def dispatch_only(self, node, every_time=0): 60 self.print_line(getattr(node, "filename", None), getattr(node, "lineno", None), every_time) 61 62 def print_line(self, filename, lineno, every_time): 63 last_printed = self.printed_lines.get(filename, 0) 64 if lineno > last_printed or every_time: 65 self.stream.write(self.get_line(filename, lineno)) 66 self.printed_lines[filename] = lineno 67 68 def get_line(self, filename, lineno): 69 if filename is None or lineno is None: 70 return "" 71 72 if self.cached_files.has_key(filename): 73 lines = self.cached_files[filename] 74 else: 75 f = open(filename) 76 try: 77 self.cached_files[filename] = lines = f.readlines() 78 finally: 79 f.close() 80 81 try: 82 return lines[lineno - 1] 83 except IndexError: 84 return "" 85 86 def report(self, exc): 87 self.stream.write("Exception was:\n\n" + str(exc.exc) + "\n\n") 88 self.stream.write("Nodes:\n\n") 89 for node in exc.nodes: 90 self.stream.write(repr(node) + "\n") 91 if node is not None: 92 self.dispatch_only(node.original, every_time=1) 93 self.stream.write("\nOriginal node was:\n\n" + repr(exc.nodes[0].original) + "\n") 94 self.stream.write("\nSimplified node was:\n\n") 95 exc.nodes[0].pprint(stream=self.stream) 96 97 # HTML-related output production. 98 99 html_header = """<?xml version="1.0" encoding="iso-8859-15"?> 100 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 101 <html xmlns="http://www.w3.org/1999/xhtml"> 102 <head> 103 <title>Module</title> 104 <style type="text/css"> 105 body { 106 padding-top: 4em; padding-bottom: 4em; 107 font-size: 14pt; font-family: monospace; 108 background-color: black; color: white; 109 } 110 111 .class { margin-bottom: 1em; } 112 .function { margin-bottom: 1em; } 113 .body { padding-left: 2em; } 114 .keyword { color: yellow; } 115 .comment { color: blue; } 116 .str { color: #FF00FF; } 117 .doc { color: #FF00FF; margin-bottom: 1em; } 118 .invocation a { color: white; text-decoration: none; } 119 120 .popup { 121 display: none; z-index: 2; 122 position: absolute; top: 1em; left: 0.5em; 123 padding: 0.2em; background-color: #000000; 124 } 125 126 .invocations { 127 padding: 0.5em; background-color: #770000; 128 clear: all; 129 } 130 131 .types { 132 padding: 0.5em; background-color: #0000FF; 133 float: right; 134 } 135 136 .raises { 137 padding: 0.5em; background-color: #7700FF; 138 float: right; 139 } 140 141 .scopes { 142 padding: 0.5em; background-color: #007700; 143 float: left; 144 } 145 146 .op, 147 .name, 148 .attr, 149 .conditional 150 { 151 position: relative; 152 } 153 154 .op:hover > .popup, 155 .name:hover > .popup, 156 .attr:hover > .popup, 157 .conditional:hover > .popup 158 { 159 display: block; 160 } 161 162 </style> 163 </head> 164 <body> 165 """ 166 167 html_footer = """</body> 168 </html> 169 """ 170 171 # Browser classes. 172 173 class Browser(ASTVisitor): 174 175 """ 176 A browsing visitor for AST nodes. 177 178 Covered: AssAttr, AssList, AssName, AssTuple, Assign, AugAssign, Break, 179 CallFunc, Class, Compare, Const, Continue, Dict, Discard, For, 180 Function, Getattr, If, Keyword, Lambda, List, Module, Name, Pass, Raise, Return, Slice, 181 Stmt, Subscript, Tuple, While. 182 183 Missing: And, Add, Assert, Backquote, Bitand, Bitor, Bitxor, Decorators, Div, 184 Ellipsis, Exec, FloorDiv, From, Global, Import, Invert, LeftShift, ListComp, ListCompFor, 185 ListCompIf, Mod, Mul, Not, Or, Power, Print, Printnl, RightShift, Sliceobj, 186 Sub, TryExcept, TryFinally, UnaryAdd, UnarySub, Yield. 187 """ 188 189 def __init__(self, stream): 190 ASTVisitor.__init__(self) 191 self.visitor = self 192 self.stream = stream 193 194 def process(self, module): 195 self.stream.write(html_header) 196 self.dispatch(module) 197 self.stream.write(html_footer) 198 199 def dispatch(self, node): 200 try: 201 ASTVisitor.dispatch(self, node) 202 except ViewerError, exc: 203 exc.add(node) 204 raise 205 except Exception, exc: 206 raise ViewerError(exc, node) 207 208 def visitModule(self, node): 209 self.default(node) 210 211 # Statements. 212 213 def visitAssign(self, node): 214 self.stream.write("<div class='assign'>\n") 215 for lvalue in node.nodes: 216 self.dispatch(lvalue) 217 self.stream.write("=\n") 218 self.dispatch(node.expr) 219 self.stream.write("</div>\n") 220 221 def visitAugAssign(self, node): 222 self.stream.write("<div class='augassign'>\n") 223 self.dispatch(node.node) 224 self.stream.write("%s\n" % node.op) 225 self.dispatch(node.expr) 226 self.stream.write("</div>\n") 227 228 def visitBreak(self, node): 229 self.stream.write("<div class='break'>\n") 230 self._keyword("break") 231 self.stream.write("</div>\n") 232 233 def visitClass(self, node): 234 definition = node._node 235 structure = definition.expr.ref 236 self.stream.write("<div class='class' id='%s'>\n" % self._url(structure.full_name())) 237 self.stream.write("<div>\n") 238 self._keyword("class") 239 self._name_start(structure.name) 240 self._popup_start() 241 self._scopes(definition) 242 self._popup_end() 243 self._name_end() 244 bases = structure.bases 245 if bases: 246 self.stream.write("(") 247 first = 1 248 for base in bases: 249 if not first: 250 self.stream.write(",\n") 251 self._name_start(base.name) 252 self._popup_start() 253 self._types(base) 254 self._scopes(base) 255 self._popup_end() 256 self._name_end() 257 first = 0 258 self.stream.write(")") 259 self.stream.write(":\n") 260 self._comment(self._text(structure.full_name())) 261 self.stream.write("</div>\n") 262 263 self.stream.write("<div class='body'>\n") 264 self._doc(node) 265 self.dispatch(node.code) 266 self.stream.write("</div>\n") 267 self.stream.write("</div>\n") 268 269 def visitContinue(self, node): 270 self.stream.write("<div class='continue'>\n") 271 self._keyword("continue") 272 self.stream.write("</div>\n") 273 274 def visitDiscard(self, node): 275 self.stream.write("<div class='discard'>\n") 276 self.default(node) 277 self.stream.write("</div>\n") 278 279 def visitFor(self, node): 280 self.stream.write("<div class='if'>\n") 281 self.stream.write("<div>\n") 282 self._keyword("for") 283 self.dispatch(node.assign) 284 self._keyword("in") 285 self.dispatch(node.list) 286 self.stream.write(":\n") 287 self.stream.write("</div>\n") 288 self.stream.write("<div class='body'>\n") 289 self.dispatch(node.body) 290 self.stream.write("</div>\n") 291 if node.else_ is not None: 292 self.stream.write("<div>\n") 293 self._keyword("else") 294 self.stream.write(":\n") 295 self.stream.write("</div>\n") 296 self.stream.write("<div class='body'>\n") 297 self.dispatch(node.else_) 298 self.stream.write("</div>\n") 299 self.stream.write("</div>\n") 300 301 def visitFunction(self, node): 302 definition = node._node 303 subprogram = definition.expr.ref 304 self.stream.write("<div class='function' id='%s'>\n" % self._url(subprogram.full_name())) 305 self.stream.write("<div>\n") 306 self._keyword("def") 307 self._name_start(subprogram.name) 308 self._popup_start() 309 self._scopes(definition) 310 self._raises(subprogram) 311 self._popup_end() 312 self._name_end() 313 self.stream.write("(") 314 self._parameters(subprogram) 315 self.stream.write(")") 316 self.stream.write(":\n") 317 self._comment(self._text(subprogram.full_name())) 318 self.stream.write("</div>\n") 319 320 self.stream.write("<div class='body'>\n") 321 self._doc(node) 322 self.dispatch(node.code) 323 self.stream.write("</div>\n") 324 self.stream.write("</div>\n") 325 326 def visitIf(self, node): 327 self.stream.write("<div class='if'>\n") 328 first = 1 329 conditional = node._node 330 for compare, stmt in node.tests: 331 self.stream.write("<div>\n") 332 self.stream.write("<span class='conditional'>\n") 333 if first: 334 self._keyword("if") 335 else: 336 self._keyword("elif") 337 self._popup_start() 338 self._invocations(conditional.test) 339 self._popup_end() 340 self.stream.write("</span>\n") 341 self.dispatch(compare) 342 self.stream.write(":\n") 343 self.stream.write("</div>\n") 344 self.stream.write("<div class='body'>\n") 345 self.dispatch(stmt) 346 self.stream.write("</div>\n") 347 if conditional.else_: 348 conditional = conditional.else_[0] 349 else: 350 conditional = None 351 first = 0 352 if node.else_ is not None: 353 self.stream.write("<div>\n") 354 self._keyword("else") 355 self.stream.write(":\n") 356 self.stream.write("</div>\n") 357 self.stream.write("<div class='body'>\n") 358 self.dispatch(node.else_) 359 self.stream.write("</div>\n") 360 self.stream.write("</div>\n") 361 362 def visitPass(self, node): 363 self.stream.write("<div class='pass'>\n") 364 self._keyword("pass") 365 self.stream.write("</div>\n") 366 367 def visitRaise(self, node): 368 self.stream.write("<div class='raise'>\n") 369 self._keyword("raise") 370 self.dispatch(node.expr1) 371 if node.expr2 is not None: 372 self.stream.write(",\n") 373 self.dispatch(node.expr2) 374 if node.expr3 is not None: 375 self.stream.write(",\n") 376 self.dispatch(node.expr3) 377 self.stream.write("</div>\n") 378 379 def visitReturn(self, node): 380 self.stream.write("<div class='return'>\n") 381 self._keyword("return") 382 self.dispatch(node.value) 383 self.stream.write("</div>\n") 384 385 def visitStmt(self, node): 386 self.stream.write("<div class='stmt'>\n") 387 self.default(node) 388 self.stream.write("</div>\n") 389 390 def visitTryExcept(self, node): 391 self.stream.write("<div class='tryexcept'>\n") 392 self.stream.write("<div>\n") 393 self._keyword("try") 394 self.stream.write(":\n") 395 self.stream.write("</div>\n") 396 self.stream.write("<div class='body'>\n") 397 self.dispatch(node.body) 398 self.stream.write("</div>\n") 399 for spec, assign, statement in node.handlers: 400 self.stream.write("<div>\n") 401 self._keyword("except") 402 if spec is not None: 403 self.dispatch(spec) 404 if assign is not None: 405 self.stream.write(",\n") 406 self.dispatch(assign) 407 self.stream.write(":\n") 408 self.stream.write("</div>\n") 409 self.stream.write("<div class='body'>\n") 410 self.dispatch(statement) 411 self.stream.write("</div>\n") 412 if node.else_ is not None: 413 self.stream.write("<div>\n") 414 self._keyword("else") 415 self.stream.write(":\n") 416 self.stream.write("</div>\n") 417 self.stream.write("<div class='body'>\n") 418 self.dispatch(node.else_) 419 self.stream.write("</div>\n") 420 self.stream.write("</div>\n") 421 422 def visitTryFinally(self, node): 423 self.stream.write("<div class='tryfinally'>\n") 424 self.stream.write("<div>\n") 425 self._keyword("try") 426 self.stream.write(":\n") 427 self.stream.write("</div>\n") 428 self.stream.write("<div class='body'>\n") 429 self.dispatch(node.body) 430 self.stream.write("</div>\n") 431 self.stream.write("<div>\n") 432 self._keyword("finally") 433 self.stream.write(":\n") 434 self.stream.write("</div>\n") 435 self.stream.write("<div class='body'>\n") 436 self.dispatch(node.final) 437 self.stream.write("</div>\n") 438 self.stream.write("</div>\n") 439 440 def visitWhile(self, node): 441 self.stream.write("<div class='while'>\n") 442 self.stream.write("<div>\n") 443 self.stream.write("<span class='conditional'>\n") 444 self._keyword("while") 445 self._popup_start() 446 self._invocations(node.test) 447 self._popup_end() 448 self.stream.write("</span>\n") 449 self.dispatch(node.test) 450 self.stream.write(":\n") 451 self.stream.write("</div>\n") 452 self.stream.write("<div class='body'>\n") 453 self.dispatch(node.body) 454 self.stream.write("</div>\n") 455 if node.else_ is not None: 456 self.stream.write("<div>\n") 457 self._keyword("else") 458 self.stream.write(":\n") 459 self.stream.write("</div>\n") 460 self.stream.write("<div class='body'>\n") 461 self.dispatch(node.else_) 462 self.stream.write("</div>\n") 463 self.stream.write("</div>\n") 464 465 # Expressions. 466 467 def visitAssAttr(self, node): 468 self.stream.write("<span class='assattr'>\n") 469 self.dispatch(node.expr) 470 self.stream.write("<span class='attr'>\n") 471 self.stream.write(".%s\n" % self._text(node.attrname)) 472 if hasattr(node, "_node"): 473 self._popup_start() 474 self._types(node._node) 475 self._scopes(node._node) 476 self._popup_end() 477 else: 478 raise ValueError, node 479 self.stream.write("</span>\n") 480 self.stream.write("</span>\n") 481 482 def visitAssList(self, node): 483 self.stream.write("<span class='list'>\n") 484 self.stream.write("[") 485 self._sequence(node) 486 self.stream.write("]\n") 487 self.stream.write("</span>\n") 488 489 def visitAssName(self, node): 490 if hasattr(node, "_node"): 491 self._name_start(node._node.name) 492 self._popup_start() 493 self._types(node._node.expr) 494 self._scopes(node._node) 495 self._popup_end() 496 self._name_end() 497 else: 498 raise ValueError, node 499 self._name(node.name) 500 501 def visitAssTuple(self, node): 502 self.stream.write("<span class='tuple'>\n") 503 self.stream.write("(") 504 self._sequence(node) 505 self.stream.write(")\n") 506 self.stream.write("</span>\n") 507 508 def visitCallFunc(self, node): 509 self.stream.write("<span class='callfunc'>\n") 510 self.dispatch(node.node) 511 self.stream.write("(") 512 first = 1 513 for arg in node.args: 514 if not first: 515 self.stream.write(",\n") 516 self.dispatch(arg) 517 first = 0 518 if node.star_args is not None: 519 if not first: 520 self.stream.write(", *\n") 521 self.dispatch(node.star_args) 522 first = 0 523 if node.dstar_args is not None: 524 if not first: 525 self.stream.write(", **\n") 526 self.dispatch(node.dstar_args) 527 first = 0 528 self.stream.write(")\n") 529 self.stream.write("</span>\n") 530 531 def visitCompare(self, node): 532 self.stream.write("<span class='compare'>\n") 533 self.dispatch(node.expr) 534 for (op_name, expr), _op in map(None, node.ops, node._ops): 535 self.stream.write("<span class='op'>\n") 536 self.stream.write(op_name) 537 self._popup_start() 538 self._op(op_name, _op) 539 self._popup_end() 540 self.stream.write("</span>\n") 541 self.dispatch(expr) 542 self.stream.write("</span>\n") 543 544 def visitConst(self, node): 545 self.stream.write(repr(node.value)) 546 547 def visitGetattr(self, node): 548 self.stream.write("<span class='getattr'>\n") 549 self.dispatch(node.expr) 550 self.stream.write("<span class='attr'>\n") 551 self.stream.write(".%s\n" % self._text(node.attrname)) 552 if hasattr(node, "_node"): 553 self._popup_start() 554 self._types(node._node) 555 self._scopes(node._node) 556 self._popup_end() 557 else: 558 raise ValueError, node 559 self.stream.write("</span>\n") 560 self.stream.write("</span>\n") 561 562 def visitKeyword(self, node): 563 self.stream.write("<span class='keyword'>\n") 564 self.stream.write(node.name) 565 self.stream.write("=") 566 self.dispatch(node.expr) 567 self.stream.write("</span>\n") 568 569 def visitLambda(self, node): 570 definition = node._node 571 subprogram = definition.expr.ref 572 self.stream.write("<span class='lambda'>\n") 573 self._keyword("lambda") 574 self._parameters(subprogram) 575 self.dispatch(node.code) 576 self.stream.write("</span>\n") 577 578 visitList = visitAssList 579 580 def visitName(self, node): 581 if hasattr(node, "_node"): 582 self._name_start(node._node.name) 583 self._popup_start() 584 self._types(node._node) 585 self._scopes(node._node) 586 self._popup_end() 587 self._name_end() 588 else: 589 raise ValueError, node 590 self._name(node.name) 591 592 def visitSlice(self, node): 593 self.stream.write("<span class='slice'>\n") 594 self.dispatch(node.expr) 595 self.stream.write("[") 596 if node.lower: 597 self.dispatch(node.lower) 598 self.stream.write(":") 599 if node.upper: 600 self.dispatch(node.upper) 601 # NOTE: Step? 602 self.stream.write("]") 603 self.stream.write("</span>\n") 604 605 def visitSubscript(self, node): 606 self.stream.write("<span class='subscript'>\n") 607 self.dispatch(node.expr) 608 self.stream.write("[") 609 first = 1 610 for sub in node.subs: 611 if not first: 612 self.stream.write(", ") 613 self.dispatch(sub) 614 first = 0 615 self.stream.write("]") 616 self.stream.write("</span>\n") 617 618 visitTuple = visitAssTuple 619 620 # Output preparation methods. 621 622 def _text(self, text): 623 return text.replace("&", "&").replace("<", "<").replace(">", ">") 624 625 def _attr(self, attr): 626 return self._text(attr).replace("'", "'").replace('"', """) 627 628 def _url(self, url): 629 return self._attr(url).replace("#", "%23").replace("-", "%2d") 630 631 def _comment(self, comment): 632 self.stream.write("<span class='comment'># %s</span>\n" % comment) 633 634 def _keyword(self, kw): 635 self.stream.write("<span class='keyword'>%s</span> " % kw) 636 637 def _doc(self, node): 638 if node.doc is not None: 639 self.stream.write("<pre class='doc'>\n") 640 self.stream.write('"""') 641 output = textwrap.dedent(node.doc.replace('"""', '\\"\\"\\"')) 642 self.stream.write(self._text(output)) 643 self.stream.write('"""') 644 self.stream.write("</pre>\n") 645 646 def _sequence(self, node): 647 first = 1 648 for n in node.nodes: 649 if not first: 650 self.stream.write(",\n") 651 self.dispatch(n) 652 first = 0 653 654 def _parameters(self, subprogram): 655 first = 1 656 for param, default in subprogram.params: 657 if not first: 658 self.stream.write(",\n") 659 self._parameter(subprogram, param, default) 660 first = 0 661 if subprogram.star is not None: 662 if not first: 663 self.stream.write(", *\n") 664 param, default = subprogram.star 665 self._parameter(subprogram, param, default) 666 first = 0 667 if subprogram.dstar is not None: 668 if not first: 669 self.stream.write(", **\n") 670 param, default = subprogram.dstar 671 self._parameter(subprogram, param, default) 672 first = 0 673 674 def _parameter(self, subprogram, param, default): 675 self._name_start(param) 676 if hasattr(subprogram, "paramtypes"): 677 self._popup_start() 678 self._types_list(subprogram.paramtypes[param]) 679 self._popup_end() 680 self._name_end() 681 if default is not None and default.original is not None: 682 self.stream.write("=\n") 683 self.dispatch(default.original) 684 685 def _name(self, name): 686 self.stream.write("<span class='name'>%s</span>\n" % name) 687 688 def _name_start(self, name): 689 self.stream.write("<span class='name'>%s\n" % name) 690 691 def _name_end(self): 692 self.stream.write("</span>\n") 693 694 def _popup_start(self): 695 self.stream.write("<span class='popup'>\n") 696 697 def _popup_end(self): 698 self.stream.write("</span>\n") 699 700 def _op(self, op_name, op): 701 if op is not None: 702 self._invocations(op) 703 704 def _invocations(self, node): 705 if hasattr(node, "invocations"): 706 self._invocations_list(node.invocations) 707 708 def _invocations_list(self, invocations): 709 self.stream.write("<div class='invocations'>\n") 710 for invocation in invocations: 711 fn = invocation.full_name() 712 module = invocation.module.name 713 name = invocation.name 714 structures = [x.name for x in invocation.structures] 715 self.stream.write("<div class='invocation'>") 716 self.stream.write("<a href='%s.html#%s'>" % (self._url(module), self._url(fn))) 717 self.stream.write(self._text(".".join([module] + structures + [name]))) 718 self.stream.write("</a>") 719 self.stream.write("</div>\n") 720 self.stream.write("</div>\n") 721 722 def _types(self, node): 723 if hasattr(node, "types"): 724 self._types_list(node.types) 725 elif hasattr(node, "writes"): 726 self._types_list(flatten(node.writes.values())) 727 elif hasattr(node, "accesses"): 728 self._types_list(flatten(node.accesses.values())) 729 else: 730 self.stream.write("<div class='types'>\n") 731 self.stream.write("unvisited\n") 732 self.stream.write("</div>\n") 733 734 def _types_list(self, types, style_class="types"): 735 self.stream.write("<div class='%s'>\n" % style_class) 736 for type in types: 737 fn = type.type.full_name() 738 self.stream.write("<div class='type'>") 739 self.stream.write(self._text(fn)) 740 self.stream.write("</div>\n") 741 self.stream.write("</div>\n") 742 743 def _raises(self, node): 744 if hasattr(node, "namespace") and hasattr(node.namespace, "raises") and node.namespace.raises: 745 self._types_list(node.namespace.raises, style_class="raises") 746 747 def _scopes(self, node): 748 if not isinstance(node, LoadName): 749 if hasattr(node, "writes") or hasattr(node, "accesses"): 750 self.stream.write("<div class='scopes'>\n") 751 for ref in getattr(node, "writes", getattr(node, "accesses", {})).keys(): 752 fn = ref.full_name() 753 self.stream.write("<div class='scope'>") 754 self.stream.write(self._text(fn)) 755 self.stream.write("</div>\n") 756 self.stream.write("</div>\n") 757 758 # Utility functions. 759 760 def flatten(lists): 761 result = [] 762 for l in lists: 763 for attr in l: 764 if attr not in result: 765 result.append(attr) 766 return result 767 768 # Convenience functions. 769 770 def view(module, stream=None): 771 viewer = Viewer(stream or sys.stdout) 772 viewer.process(module.original) 773 774 def browse(module, stream=None): 775 browser = Browser(stream or sys.stdout) 776 browser.process(module.original) 777 778 def makedoc(module, filename): 779 stream = open(filename, "wb") 780 try: 781 browser = Browser(stream) 782 browser.process(module.original) 783 finally: 784 stream.close() 785 786 # vim: tabstop=4 expandtab shiftwidth=4