+++ /dev/null
-#! /usr/bin/env python\r
-\r
-# Released to the public domain, by Tim Peters, 03 October 2000.\r
-\r
-"""reindent [-d][-r][-v] [ path ... ]\r
-\r
--d (--dryrun) Dry run. Analyze, but don't make any changes to, files.\r
--r (--recurse) Recurse. Search for all .py files in subdirectories too.\r
--n (--nobackup) No backup. Does not make a ".bak" file before reindenting.\r
--v (--verbose) Verbose. Print informative msgs; else no output.\r
--h (--help) Help. Print this usage information and exit.\r
-\r
-Change Python (.py) files to use 4-space indents and no hard tab characters.\r
-Also trim excess spaces and tabs from ends of lines, and remove empty lines\r
-at the end of files. Also ensure the last line ends with a newline.\r
-\r
-If no paths are given on the command line, reindent operates as a filter,\r
-reading a single source file from standard input and writing the transformed\r
-source to standard output. In this case, the -d, -r and -v flags are\r
-ignored.\r
-\r
-You can pass one or more file and/or directory paths. When a directory\r
-path, all .py files within the directory will be examined, and, if the -r\r
-option is given, likewise recursively for subdirectories.\r
-\r
-If output is not to standard output, reindent overwrites files in place,\r
-renaming the originals with a .bak extension. If it finds nothing to\r
-change, the file is left alone. If reindent does change a file, the changed\r
-file is a fixed-point for future runs (i.e., running reindent on the\r
-resulting .py file won't change it again).\r
-\r
-The hard part of reindenting is figuring out what to do with comment\r
-lines. So long as the input files get a clean bill of health from\r
-tabnanny.py, reindent should do a good job.\r
-\r
-The backup file is a copy of the one that is being reindented. The ".bak"\r
-file is generated with shutil.copy(), but some corner cases regarding\r
-user/group and permissions could leave the backup file more readable that\r
-you'd prefer. You can always use the --nobackup option to prevent this.\r
-"""\r
-\r
-__version__ = "1"\r
-\r
-import tokenize\r
-import os, shutil\r
-import sys\r
-\r
-verbose = 0\r
-recurse = 0\r
-dryrun = 0\r
-makebackup = True\r
-\r
-def usage(msg=None):\r
- if msg is not None:\r
- print >> sys.stderr, msg\r
- print >> sys.stderr, __doc__\r
-\r
-def errprint(*args):\r
- sep = ""\r
- for arg in args:\r
- sys.stderr.write(sep + str(arg))\r
- sep = " "\r
- sys.stderr.write("\n")\r
-\r
-def main():\r
- import getopt\r
- global verbose, recurse, dryrun, makebackup\r
- try:\r
- opts, args = getopt.getopt(sys.argv[1:], "drnvh",\r
- ["dryrun", "recurse", "nobackup", "verbose", "help"])\r
- except getopt.error, msg:\r
- usage(msg)\r
- return\r
- for o, a in opts:\r
- if o in ('-d', '--dryrun'):\r
- dryrun += 1\r
- elif o in ('-r', '--recurse'):\r
- recurse += 1\r
- elif o in ('-n', '--nobackup'):\r
- makebackup = False\r
- elif o in ('-v', '--verbose'):\r
- verbose += 1\r
- elif o in ('-h', '--help'):\r
- usage()\r
- return\r
- if not args:\r
- r = Reindenter(sys.stdin)\r
- r.run()\r
- r.write(sys.stdout)\r
- return\r
- for arg in args:\r
- check(arg)\r
-\r
-def check(file):\r
- if os.path.isdir(file) and not os.path.islink(file):\r
- if verbose:\r
- print "listing directory", file\r
- names = os.listdir(file)\r
- for name in names:\r
- fullname = os.path.join(file, name)\r
- if ((recurse and os.path.isdir(fullname) and\r
- not os.path.islink(fullname) and\r
- not os.path.split(fullname)[1].startswith("."))\r
- or name.lower().endswith(".py")):\r
- check(fullname)\r
- return\r
-\r
- if verbose:\r
- print "checking", file, "...",\r
- try:\r
- f = open(file)\r
- except IOError, msg:\r
- errprint("%s: I/O Error: %s" % (file, str(msg)))\r
- return\r
-\r
- r = Reindenter(f)\r
- f.close()\r
- if r.run():\r
- if verbose:\r
- print "changed."\r
- if dryrun:\r
- print "But this is a dry run, so leaving it alone."\r
- if not dryrun:\r
- bak = file + ".bak"\r
- if makebackup:\r
- shutil.copyfile(file, bak)\r
- if verbose:\r
- print "backed up", file, "to", bak\r
- f = open(file, "w")\r
- r.write(f)\r
- f.close()\r
- if verbose:\r
- print "wrote new", file\r
- return True\r
- else:\r
- if verbose:\r
- print "unchanged."\r
- return False\r
-\r
-def _rstrip(line, JUNK='\n \t'):\r
- """Return line stripped of trailing spaces, tabs, newlines.\r
-\r
- Note that line.rstrip() instead also strips sundry control characters,\r
- but at least one known Emacs user expects to keep junk like that, not\r
- mentioning Barry by name or anything <wink>.\r
- """\r
-\r
- i = len(line)\r
- while i > 0 and line[i-1] in JUNK:\r
- i -= 1\r
- return line[:i]\r
-\r
-class Reindenter:\r
-\r
- def __init__(self, f):\r
- self.find_stmt = 1 # next token begins a fresh stmt?\r
- self.level = 0 # current indent level\r
-\r
- # Raw file lines.\r
- self.raw = f.readlines()\r
-\r
- # File lines, rstripped & tab-expanded. Dummy at start is so\r
- # that we can use tokenize's 1-based line numbering easily.\r
- # Note that a line is all-blank iff it's "\n".\r
- self.lines = [_rstrip(line).expandtabs() + "\n"\r
- for line in self.raw]\r
- self.lines.insert(0, None)\r
- self.index = 1 # index into self.lines of next line\r
-\r
- # List of (lineno, indentlevel) pairs, one for each stmt and\r
- # comment line. indentlevel is -1 for comment lines, as a\r
- # signal that tokenize doesn't know what to do about them;\r
- # indeed, they're our headache!\r
- self.stats = []\r
-\r
- def run(self):\r
- tokenize.tokenize(self.getline, self.tokeneater)\r
- # Remove trailing empty lines.\r
- lines = self.lines\r
- while lines and lines[-1] == "\n":\r
- lines.pop()\r
- # Sentinel.\r
- stats = self.stats\r
- stats.append((len(lines), 0))\r
- # Map count of leading spaces to # we want.\r
- have2want = {}\r
- # Program after transformation.\r
- after = self.after = []\r
- # Copy over initial empty lines -- there's nothing to do until\r
- # we see a line with *something* on it.\r
- i = stats[0][0]\r
- after.extend(lines[1:i])\r
- for i in range(len(stats)-1):\r
- thisstmt, thislevel = stats[i]\r
- nextstmt = stats[i+1][0]\r
- have = getlspace(lines[thisstmt])\r
- want = thislevel * 4\r
- if want < 0:\r
- # A comment line.\r
- if have:\r
- # An indented comment line. If we saw the same\r
- # indentation before, reuse what it most recently\r
- # mapped to.\r
- want = have2want.get(have, -1)\r
- if want < 0:\r
- # Then it probably belongs to the next real stmt.\r
- for j in xrange(i+1, len(stats)-1):\r
- jline, jlevel = stats[j]\r
- if jlevel >= 0:\r
- if have == getlspace(lines[jline]):\r
- want = jlevel * 4\r
- break\r
- if want < 0: # Maybe it's a hanging\r
- # comment like this one,\r
- # in which case we should shift it like its base\r
- # line got shifted.\r
- for j in xrange(i-1, -1, -1):\r
- jline, jlevel = stats[j]\r
- if jlevel >= 0:\r
- want = have + getlspace(after[jline-1]) - \\r
- getlspace(lines[jline])\r
- break\r
- if want < 0:\r
- # Still no luck -- leave it alone.\r
- want = have\r
- else:\r
- want = 0\r
- assert want >= 0\r
- have2want[have] = want\r
- diff = want - have\r
- if diff == 0 or have == 0:\r
- after.extend(lines[thisstmt:nextstmt])\r
- else:\r
- for line in lines[thisstmt:nextstmt]:\r
- if diff > 0:\r
- if line == "\n":\r
- after.append(line)\r
- else:\r
- after.append(" " * diff + line)\r
- else:\r
- remove = min(getlspace(line), -diff)\r
- after.append(line[remove:])\r
- return self.raw != self.after\r
-\r
- def write(self, f):\r
- f.writelines(self.after)\r
-\r
- # Line-getter for tokenize.\r
- def getline(self):\r
- if self.index >= len(self.lines):\r
- line = ""\r
- else:\r
- line = self.lines[self.index]\r
- self.index += 1\r
- return line\r
-\r
- # Line-eater for tokenize.\r
- def tokeneater(self, type, token, (sline, scol), end, line,\r
- INDENT=tokenize.INDENT,\r
- DEDENT=tokenize.DEDENT,\r
- NEWLINE=tokenize.NEWLINE,\r
- COMMENT=tokenize.COMMENT,\r
- NL=tokenize.NL):\r
-\r
- if type == NEWLINE:\r
- # A program statement, or ENDMARKER, will eventually follow,\r
- # after some (possibly empty) run of tokens of the form\r
- # (NL | COMMENT)* (INDENT | DEDENT+)?\r
- self.find_stmt = 1\r
-\r
- elif type == INDENT:\r
- self.find_stmt = 1\r
- self.level += 1\r
-\r
- elif type == DEDENT:\r
- self.find_stmt = 1\r
- self.level -= 1\r
-\r
- elif type == COMMENT:\r
- if self.find_stmt:\r
- self.stats.append((sline, -1))\r
- # but we're still looking for a new stmt, so leave\r
- # find_stmt alone\r
-\r
- elif type == NL:\r
- pass\r
-\r
- elif self.find_stmt:\r
- # This is the first "real token" following a NEWLINE, so it\r
- # must be the first token of the next program statement, or an\r
- # ENDMARKER.\r
- self.find_stmt = 0\r
- if line: # not endmarker\r
- self.stats.append((sline, self.level))\r
-\r
-# Count number of leading blanks.\r
-def getlspace(line):\r
- i, n = 0, len(line)\r
- while i < n and line[i] == " ":\r
- i += 1\r
- return i\r
-\r
-if __name__ == '__main__':\r
- main()\r