| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  | #! /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 | 
					
						
							| 
									
										
										
										
											2001-08-15 06:07:42 +00:00
										 |  |  | until you try it again with a later Python release). | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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) | 
					
						
							| 
									
										
										
										
											2001-08-13 05:33:53 +00:00
										 |  |  |     msg = ' '.join(strings) | 
					
						
							|  |  |  |     if msg[-1:] != '\n': | 
					
						
							|  |  |  |         msg += '\n' | 
					
						
							|  |  |  |     sys.stderr.write(msg) | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | def main(): | 
					
						
							|  |  |  |     import getopt | 
					
						
							|  |  |  |     global verbose, recurse, dryrun | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         opts, args = getopt.getopt(sys.argv[1:], "drv") | 
					
						
							| 
									
										
										
										
											2007-01-10 16:19:56 +00:00
										 |  |  |     except getopt.error as msg: | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |         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: | 
					
						
							| 
									
										
										
										
											2007-08-03 17:06:41 +00:00
										 |  |  |             print("listing directory", file) | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |         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: | 
					
						
							| 
									
										
										
										
											2007-08-03 17:06:41 +00:00
										 |  |  |         print("checking", file, "...", end=' ') | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |     try: | 
					
						
							|  |  |  |         f = open(file) | 
					
						
							| 
									
										
										
										
											2007-01-10 16:19:56 +00:00
										 |  |  |     except IOError as msg: | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |         errprint("%r: I/O Error: %s" % (file, str(msg))) | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-08-13 05:33:53 +00:00
										 |  |  |     ff = FutureFinder(f, file) | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |     changed = ff.run() | 
					
						
							| 
									
										
										
										
											2001-08-13 05:33:53 +00:00
										 |  |  |     if changed: | 
					
						
							|  |  |  |         ff.gettherest() | 
					
						
							|  |  |  |     f.close() | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |     if changed: | 
					
						
							|  |  |  |         if verbose: | 
					
						
							| 
									
										
										
										
											2007-08-03 17:06:41 +00:00
										 |  |  |             print("changed.") | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |             if dryrun: | 
					
						
							| 
									
										
										
										
											2007-08-03 17:06:41 +00:00
										 |  |  |                 print("But this is a dry run, so leaving it alone.") | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |         for s, e, line in changed: | 
					
						
							| 
									
										
										
										
											2007-08-03 17:06:41 +00:00
										 |  |  |             print("%r lines %d-%d" % (file, s+1, e+1)) | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |             for i in range(s, e+1): | 
					
						
							| 
									
										
										
										
											2007-08-03 17:06:41 +00:00
										 |  |  |                 print(ff.lines[i], end=' ') | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |             if line is None: | 
					
						
							| 
									
										
										
										
											2007-08-03 17:06:41 +00:00
										 |  |  |                 print("-- deleted") | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |             else: | 
					
						
							| 
									
										
										
										
											2007-08-03 17:06:41 +00:00
										 |  |  |                 print("-- change to:") | 
					
						
							|  |  |  |                 print(line, end=' ') | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |         if not dryrun: | 
					
						
							|  |  |  |             bak = file + ".bak" | 
					
						
							|  |  |  |             if os.path.exists(bak): | 
					
						
							|  |  |  |                 os.remove(bak) | 
					
						
							|  |  |  |             os.rename(file, bak) | 
					
						
							|  |  |  |             if verbose: | 
					
						
							| 
									
										
										
										
											2007-08-03 17:06:41 +00:00
										 |  |  |                 print("renamed", file, "to", bak) | 
					
						
							| 
									
										
										
										
											2001-08-13 05:33:53 +00:00
										 |  |  |             g = open(file, "w") | 
					
						
							|  |  |  |             ff.write(g) | 
					
						
							|  |  |  |             g.close() | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |             if verbose: | 
					
						
							| 
									
										
										
										
											2007-08-03 17:06:41 +00:00
										 |  |  |                 print("wrote new", file) | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |     else: | 
					
						
							|  |  |  |         if verbose: | 
					
						
							| 
									
										
										
										
											2007-08-03 17:06:41 +00:00
										 |  |  |             print("unchanged.") | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | class FutureFinder: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-08-13 05:33:53 +00:00
										 |  |  |     def __init__(self, f, fname): | 
					
						
							|  |  |  |         self.f = f | 
					
						
							|  |  |  |         self.fname = fname | 
					
						
							|  |  |  |         self.ateof = 0 | 
					
						
							|  |  |  |         self.lines = [] # raw file lines | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # List of (start_index, end_index, new_line) triples. | 
					
						
							|  |  |  |         self.changed = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Line-getter for tokenize. | 
					
						
							|  |  |  |     def getline(self): | 
					
						
							| 
									
										
										
										
											2001-08-13 05:33:53 +00:00
										 |  |  |         if self.ateof: | 
					
						
							|  |  |  |             return "" | 
					
						
							|  |  |  |         line = self.f.readline() | 
					
						
							|  |  |  |         if line == "": | 
					
						
							|  |  |  |             self.ateof = 1 | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2001-08-13 05:33:53 +00:00
										 |  |  |             self.lines.append(line) | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |         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 | 
					
						
							| 
									
										
										
										
											2007-04-21 15:47:16 +00:00
										 |  |  |         get = tokenize.generate_tokens(self.getline).__next__ | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |         type, token, (srow, scol), (erow, ecol), line = get() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-08-13 05:33:53 +00:00
										 |  |  |         # 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: | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |             type, token, (srow, scol), (erow, ecol), line = get() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Analyze the future stmts. | 
					
						
							| 
									
										
										
										
											2001-08-13 05:33:53 +00:00
										 |  |  |         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 | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |             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: | 
					
						
							| 
									
										
										
										
											2001-08-13 05:33:53 +00:00
										 |  |  |                 errprint("Skipping file %r; can't parse line %d:\n%s" % | 
					
						
							|  |  |  |                          (self.fname, srow, line)) | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |                 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) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-08-13 05:33:53 +00:00
										 |  |  |             # Rewrite the line if at least one future-feature is obsolete. | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |             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)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-08-13 05:33:53 +00:00
										 |  |  |             # Loop back for more future statements. | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return changed | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-08-13 05:33:53 +00:00
										 |  |  |     def gettherest(self): | 
					
						
							|  |  |  |         if self.ateof: | 
					
						
							|  |  |  |             self.therest = '' | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.therest = self.f.read() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  |     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) | 
					
						
							| 
									
										
										
										
											2001-08-13 05:33:53 +00:00
										 |  |  |         # Copy over the remainder of the file. | 
					
						
							|  |  |  |         if self.therest: | 
					
						
							|  |  |  |             f.write(self.therest) | 
					
						
							| 
									
										
										
										
											2001-08-12 08:41:13 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | if __name__ == '__main__': | 
					
						
							|  |  |  |     main() |