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