1b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters#! /usr/bin/env python
2b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
3b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters"""cleanfuture [-d][-r][-v] path ...
4b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
5b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters-d  Dry run.  Analyze, but don't make any changes to, files.
6b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters-r  Recurse.  Search for all .py files in subdirectories too.
7b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters-v  Verbose.  Print informative msgs.
8b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
9b704238a6c4ee0e2fd907d46a1e867661af5d315Tim PetersSearch Python (.py) files for future statements, and remove the features
10b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersfrom such statements that are already mandatory in the version of Python
11b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersyou're using.
12b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
13b704238a6c4ee0e2fd907d46a1e867661af5d315Tim PetersPass one or more file and/or directory paths.  When a directory path, all
14b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters.py files within the directory will be examined, and, if the -r option is
15b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersgiven, likewise recursively for subdirectories.
16b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
17b704238a6c4ee0e2fd907d46a1e867661af5d315Tim PetersOverwrites files in place, renaming the originals with a .bak extension. If
18b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peterscleanfuture finds nothing to change, the file is left alone.  If cleanfuture
19b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersdoes change a file, the changed file is a fixed-point (i.e., running
20b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peterscleanfuture on the resulting .py file won't change it again, at least not
21ebb7133f4f10755a24797513c508c0f09b29d20cTim Petersuntil you try it again with a later Python release).
22b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
23b704238a6c4ee0e2fd907d46a1e867661af5d315Tim PetersLimitations:  You can do these things, but this tool won't help you then:
24b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
25b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters+ A future statement cannot be mixed with any other statement on the same
26b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters  physical line (separated by semicolon).
27b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
28b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters+ A future statement cannot contain an "as" clause.
29b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
30b704238a6c4ee0e2fd907d46a1e867661af5d315Tim PetersExample:  Assuming you're using Python 2.2, if a file containing
31b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
32b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersfrom __future__ import nested_scopes, generators
33b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
34b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersis analyzed by cleanfuture, the line is rewritten to
35b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
36b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersfrom __future__ import generators
37b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
38b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersbecause nested_scopes is no longer optional in 2.2 but generators is.
39b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters"""
40b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
41b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersimport __future__
42b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersimport tokenize
43b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersimport os
44b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersimport sys
45b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
46b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersdryrun  = 0
47b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersrecurse = 0
48b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersverbose = 0
49b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
50b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersdef errprint(*args):
51b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    strings = map(str, args)
523055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters    msg = ' '.join(strings)
533055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters    if msg[-1:] != '\n':
543055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        msg += '\n'
553055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters    sys.stderr.write(msg)
56b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
57b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersdef main():
58b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    import getopt
59b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    global verbose, recurse, dryrun
60b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    try:
61b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        opts, args = getopt.getopt(sys.argv[1:], "drv")
62b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    except getopt.error, msg:
63b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        errprint(msg)
64b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        return
65b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    for o, a in opts:
66b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        if o == '-d':
67b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            dryrun += 1
68b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        elif o == '-r':
69b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            recurse += 1
70b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        elif o == '-v':
71b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            verbose += 1
72b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    if not args:
73b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        errprint("Usage:", __doc__)
74b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        return
75b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    for arg in args:
76b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        check(arg)
77b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
78b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersdef check(file):
79b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    if os.path.isdir(file) and not os.path.islink(file):
80b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        if verbose:
81b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            print "listing directory", file
82b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        names = os.listdir(file)
83b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        for name in names:
84b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            fullname = os.path.join(file, name)
85b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            if ((recurse and os.path.isdir(fullname) and
86b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                 not os.path.islink(fullname))
87b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                or name.lower().endswith(".py")):
88b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                check(fullname)
89b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        return
90b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
91b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    if verbose:
92b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        print "checking", file, "...",
93b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    try:
94b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        f = open(file)
95b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    except IOError, msg:
96b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        errprint("%r: I/O Error: %s" % (file, str(msg)))
97b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        return
98b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
993055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters    ff = FutureFinder(f, file)
100b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    changed = ff.run()
101b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    if changed:
1023055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        ff.gettherest()
1033055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters    f.close()
1043055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters    if changed:
105b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        if verbose:
106b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            print "changed."
107b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            if dryrun:
108b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                print "But this is a dry run, so leaving it alone."
109b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        for s, e, line in changed:
110b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            print "%r lines %d-%d" % (file, s+1, e+1)
111b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            for i in range(s, e+1):
112b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                print ff.lines[i],
113b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            if line is None:
114b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                print "-- deleted"
115b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            else:
116b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                print "-- change to:"
117b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                print line,
118b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        if not dryrun:
119b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            bak = file + ".bak"
120b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            if os.path.exists(bak):
121b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                os.remove(bak)
122b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            os.rename(file, bak)
123b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            if verbose:
124b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                print "renamed", file, "to", bak
1253055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters            g = open(file, "w")
1263055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters            ff.write(g)
1273055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters            g.close()
128b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            if verbose:
129b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                print "wrote new", file
130b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    else:
131b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        if verbose:
132b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            print "unchanged."
133b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
134b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersclass FutureFinder:
135b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
1363055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters    def __init__(self, f, fname):
1373055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        self.f = f
1383055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        self.fname = fname
1393055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        self.ateof = 0
1403055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        self.lines = [] # raw file lines
141b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
142b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        # List of (start_index, end_index, new_line) triples.
143b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        self.changed = []
144b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
145b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    # Line-getter for tokenize.
146b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    def getline(self):
1473055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        if self.ateof:
1483055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters            return ""
1493055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        line = self.f.readline()
1503055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        if line == "":
1513055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters            self.ateof = 1
152b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        else:
1533055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters            self.lines.append(line)
154b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        return line
155b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
156b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    def run(self):
157b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        STRING = tokenize.STRING
158b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        NL = tokenize.NL
159b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        NEWLINE = tokenize.NEWLINE
160b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        COMMENT = tokenize.COMMENT
161b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        NAME = tokenize.NAME
162b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        OP = tokenize.OP
163b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
164b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        changed = self.changed
165b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        get = tokenize.generate_tokens(self.getline).next
166b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        type, token, (srow, scol), (erow, ecol), line = get()
167b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
1683055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        # Chew up initial comments and blank lines (if any).
1693055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        while type in (COMMENT, NL, NEWLINE):
1703055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters            type, token, (srow, scol), (erow, ecol), line = get()
1713055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters
1723055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        # Chew up docstring (if any -- and it may be implicitly catenated!).
1733055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        while type is STRING:
174b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            type, token, (srow, scol), (erow, ecol), line = get()
175b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
176b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        # Analyze the future stmts.
1773055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        while 1:
1783055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters            # Chew up comments and blank lines (if any).
1793055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters            while type in (COMMENT, NL, NEWLINE):
1803055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters                type, token, (srow, scol), (erow, ecol), line = get()
1813055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters
1823055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters            if not (type is NAME and token == "from"):
1833055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters                break
184b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            startline = srow - 1    # tokenize is one-based
185b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            type, token, (srow, scol), (erow, ecol), line = get()
186b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
187b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            if not (type is NAME and token == "__future__"):
188b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                break
189b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            type, token, (srow, scol), (erow, ecol), line = get()
190b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
191b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            if not (type is NAME and token == "import"):
192b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                break
193b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            type, token, (srow, scol), (erow, ecol), line = get()
194b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
195b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            # Get the list of features.
196b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            features = []
197b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            while type is NAME:
198b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                features.append(token)
199b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                type, token, (srow, scol), (erow, ecol), line = get()
200b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
201b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                if not (type is OP and token == ','):
202b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                    break
203b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                type, token, (srow, scol), (erow, ecol), line = get()
204b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
205b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            # A trailing comment?
206b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            comment = None
207b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            if type is COMMENT:
208b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                comment = token
209b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                type, token, (srow, scol), (erow, ecol), line = get()
210b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
211b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            if type is not NEWLINE:
2123055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters                errprint("Skipping file %r; can't parse line %d:\n%s" %
2133055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters                         (self.fname, srow, line))
214b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                return []
215b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
216b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            endline = srow - 1
217b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
218b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            # Check for obsolete features.
219b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            okfeatures = []
220b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            for f in features:
221b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                object = getattr(__future__, f, None)
222b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                if object is None:
223b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                    # A feature we don't know about yet -- leave it in.
224b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                    # They'll get a compile-time error when they compile
225b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                    # this program, but that's not our job to sort out.
226b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                    okfeatures.append(f)
227b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                else:
228b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                    released = object.getMandatoryRelease()
229b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                    if released is None or released <= sys.version_info:
230b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                        # Withdrawn or obsolete.
231b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                        pass
232b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                    else:
233b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                        okfeatures.append(f)
234b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
2353055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters            # Rewrite the line if at least one future-feature is obsolete.
236b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            if len(okfeatures) < len(features):
237b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                if len(okfeatures) == 0:
238b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                    line = None
239b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                else:
240b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                    line = "from __future__ import "
241b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                    line += ', '.join(okfeatures)
242b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                    if comment is not None:
243b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                        line += ' ' + comment
244b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                    line += '\n'
245b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                changed.append((startline, endline, line))
246b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
2473055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters            # Loop back for more future statements.
248b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
249b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        return changed
250b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
2513055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters    def gettherest(self):
2523055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        if self.ateof:
2533055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters            self.therest = ''
2543055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        else:
2553055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters            self.therest = self.f.read()
2563055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters
257b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    def write(self, f):
258b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        changed = self.changed
259b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        assert changed
260b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        # Prevent calling this again.
261b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        self.changed = []
262b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        # Apply changes in reverse order.
263b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        changed.reverse()
264b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        for s, e, line in changed:
265b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            if line is None:
266b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                # pure deletion
267b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                del self.lines[s:e+1]
268b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters            else:
269b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters                self.lines[s:e+1] = [line]
270b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters        f.writelines(self.lines)
2713055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        # Copy over the remainder of the file.
2723055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters        if self.therest:
2733055ad234aeaf768f58bdbc45ab25c47770b6f81Tim Peters            f.write(self.therest)
274b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters
275b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Petersif __name__ == '__main__':
276b704238a6c4ee0e2fd907d46a1e867661af5d315Tim Peters    main()
277