home *** CD-ROM | disk | FTP | other *** search
/ Enter 2003: The Beautiful Scenery / enter-parhaat-2003.iso / files / Python-2.2.1.exe / CLEANFUTURE.PY < prev    next >
Encoding:
Python Source  |  2001-08-15  |  8.6 KB  |  277 lines

  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()
  277.