~malept/ubuntu/lucid/python2.6/dev-dependency-fix

« back to all changes in this revision

Viewing changes to Tools/scripts/cleanfuture.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2009-02-13 12:51:00 UTC
  • Revision ID: james.westby@ubuntu.com-20090213125100-uufgcb9yeqzujpqw
Tags: upstream-2.6.1
ImportĀ upstreamĀ versionĀ 2.6.1

Show diffs side-by-side

added added

removed removed

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