mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			276 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			276 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#! /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 as 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, "...", end=' ')
 | 
						|
    try:
 | 
						|
        f = open(file)
 | 
						|
    except IOError as 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], end=' ')
 | 
						|
            if line is None:
 | 
						|
                print("-- deleted")
 | 
						|
            else:
 | 
						|
                print("-- change to:")
 | 
						|
                print(line, end=' ')
 | 
						|
        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()
 |