+++ /dev/null
-#! /usr/bin/env python\r
-\r
-"""cleanfuture [-d][-r][-v] path ...\r
-\r
--d Dry run. Analyze, but don't make any changes to, files.\r
--r Recurse. Search for all .py files in subdirectories too.\r
--v Verbose. Print informative msgs.\r
-\r
-Search Python (.py) files for future statements, and remove the features\r
-from such statements that are already mandatory in the version of Python\r
-you're using.\r
-\r
-Pass one or more file and/or directory paths. When a directory path, all\r
-.py files within the directory will be examined, and, if the -r option is\r
-given, likewise recursively for subdirectories.\r
-\r
-Overwrites files in place, renaming the originals with a .bak extension. If\r
-cleanfuture finds nothing to change, the file is left alone. If cleanfuture\r
-does change a file, the changed file is a fixed-point (i.e., running\r
-cleanfuture on the resulting .py file won't change it again, at least not\r
-until you try it again with a later Python release).\r
-\r
-Limitations: You can do these things, but this tool won't help you then:\r
-\r
-+ A future statement cannot be mixed with any other statement on the same\r
- physical line (separated by semicolon).\r
-\r
-+ A future statement cannot contain an "as" clause.\r
-\r
-Example: Assuming you're using Python 2.2, if a file containing\r
-\r
-from __future__ import nested_scopes, generators\r
-\r
-is analyzed by cleanfuture, the line is rewritten to\r
-\r
-from __future__ import generators\r
-\r
-because nested_scopes is no longer optional in 2.2 but generators is.\r
-"""\r
-\r
-import __future__\r
-import tokenize\r
-import os\r
-import sys\r
-\r
-dryrun = 0\r
-recurse = 0\r
-verbose = 0\r
-\r
-def errprint(*args):\r
- strings = map(str, args)\r
- msg = ' '.join(strings)\r
- if msg[-1:] != '\n':\r
- msg += '\n'\r
- sys.stderr.write(msg)\r
-\r
-def main():\r
- import getopt\r
- global verbose, recurse, dryrun\r
- try:\r
- opts, args = getopt.getopt(sys.argv[1:], "drv")\r
- except getopt.error, msg:\r
- errprint(msg)\r
- return\r
- for o, a in opts:\r
- if o == '-d':\r
- dryrun += 1\r
- elif o == '-r':\r
- recurse += 1\r
- elif o == '-v':\r
- verbose += 1\r
- if not args:\r
- errprint("Usage:", __doc__)\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))\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("%r: I/O Error: %s" % (file, str(msg)))\r
- return\r
-\r
- ff = FutureFinder(f, file)\r
- changed = ff.run()\r
- if changed:\r
- ff.gettherest()\r
- f.close()\r
- if changed:\r
- if verbose:\r
- print "changed."\r
- if dryrun:\r
- print "But this is a dry run, so leaving it alone."\r
- for s, e, line in changed:\r
- print "%r lines %d-%d" % (file, s+1, e+1)\r
- for i in range(s, e+1):\r
- print ff.lines[i],\r
- if line is None:\r
- print "-- deleted"\r
- else:\r
- print "-- change to:"\r
- print line,\r
- if not dryrun:\r
- bak = file + ".bak"\r
- if os.path.exists(bak):\r
- os.remove(bak)\r
- os.rename(file, bak)\r
- if verbose:\r
- print "renamed", file, "to", bak\r
- g = open(file, "w")\r
- ff.write(g)\r
- g.close()\r
- if verbose:\r
- print "wrote new", file\r
- else:\r
- if verbose:\r
- print "unchanged."\r
-\r
-class FutureFinder:\r
-\r
- def __init__(self, f, fname):\r
- self.f = f\r
- self.fname = fname\r
- self.ateof = 0\r
- self.lines = [] # raw file lines\r
-\r
- # List of (start_index, end_index, new_line) triples.\r
- self.changed = []\r
-\r
- # Line-getter for tokenize.\r
- def getline(self):\r
- if self.ateof:\r
- return ""\r
- line = self.f.readline()\r
- if line == "":\r
- self.ateof = 1\r
- else:\r
- self.lines.append(line)\r
- return line\r
-\r
- def run(self):\r
- STRING = tokenize.STRING\r
- NL = tokenize.NL\r
- NEWLINE = tokenize.NEWLINE\r
- COMMENT = tokenize.COMMENT\r
- NAME = tokenize.NAME\r
- OP = tokenize.OP\r
-\r
- changed = self.changed\r
- get = tokenize.generate_tokens(self.getline).next\r
- type, token, (srow, scol), (erow, ecol), line = get()\r
-\r
- # Chew up initial comments and blank lines (if any).\r
- while type in (COMMENT, NL, NEWLINE):\r
- type, token, (srow, scol), (erow, ecol), line = get()\r
-\r
- # Chew up docstring (if any -- and it may be implicitly catenated!).\r
- while type is STRING:\r
- type, token, (srow, scol), (erow, ecol), line = get()\r
-\r
- # Analyze the future stmts.\r
- while 1:\r
- # Chew up comments and blank lines (if any).\r
- while type in (COMMENT, NL, NEWLINE):\r
- type, token, (srow, scol), (erow, ecol), line = get()\r
-\r
- if not (type is NAME and token == "from"):\r
- break\r
- startline = srow - 1 # tokenize is one-based\r
- type, token, (srow, scol), (erow, ecol), line = get()\r
-\r
- if not (type is NAME and token == "__future__"):\r
- break\r
- type, token, (srow, scol), (erow, ecol), line = get()\r
-\r
- if not (type is NAME and token == "import"):\r
- break\r
- type, token, (srow, scol), (erow, ecol), line = get()\r
-\r
- # Get the list of features.\r
- features = []\r
- while type is NAME:\r
- features.append(token)\r
- type, token, (srow, scol), (erow, ecol), line = get()\r
-\r
- if not (type is OP and token == ','):\r
- break\r
- type, token, (srow, scol), (erow, ecol), line = get()\r
-\r
- # A trailing comment?\r
- comment = None\r
- if type is COMMENT:\r
- comment = token\r
- type, token, (srow, scol), (erow, ecol), line = get()\r
-\r
- if type is not NEWLINE:\r
- errprint("Skipping file %r; can't parse line %d:\n%s" %\r
- (self.fname, srow, line))\r
- return []\r
-\r
- endline = srow - 1\r
-\r
- # Check for obsolete features.\r
- okfeatures = []\r
- for f in features:\r
- object = getattr(__future__, f, None)\r
- if object is None:\r
- # A feature we don't know about yet -- leave it in.\r
- # They'll get a compile-time error when they compile\r
- # this program, but that's not our job to sort out.\r
- okfeatures.append(f)\r
- else:\r
- released = object.getMandatoryRelease()\r
- if released is None or released <= sys.version_info:\r
- # Withdrawn or obsolete.\r
- pass\r
- else:\r
- okfeatures.append(f)\r
-\r
- # Rewrite the line if at least one future-feature is obsolete.\r
- if len(okfeatures) < len(features):\r
- if len(okfeatures) == 0:\r
- line = None\r
- else:\r
- line = "from __future__ import "\r
- line += ', '.join(okfeatures)\r
- if comment is not None:\r
- line += ' ' + comment\r
- line += '\n'\r
- changed.append((startline, endline, line))\r
-\r
- # Loop back for more future statements.\r
-\r
- return changed\r
-\r
- def gettherest(self):\r
- if self.ateof:\r
- self.therest = ''\r
- else:\r
- self.therest = self.f.read()\r
-\r
- def write(self, f):\r
- changed = self.changed\r
- assert changed\r
- # Prevent calling this again.\r
- self.changed = []\r
- # Apply changes in reverse order.\r
- changed.reverse()\r
- for s, e, line in changed:\r
- if line is None:\r
- # pure deletion\r
- del self.lines[s:e+1]\r
- else:\r
- self.lines[s:e+1] = [line]\r
- f.writelines(self.lines)\r
- # Copy over the remainder of the file.\r
- if self.therest:\r
- f.write(self.therest)\r
-\r
-if __name__ == '__main__':\r
- main()\r