(root)/
Python-3.11.7/
Tools/
scripts/
cleanfuture.py
       1  #! /usr/bin/env python3
       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 as 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, "...", end=' ')
      93      try:
      94          f = open(file)
      95      except IOError as msg:
      96          errprint("%r: I/O Error: %s" % (file, str(msg)))
      97          return
      98  
      99      with f:
     100          ff = FutureFinder(f, file)
     101          changed = ff.run()
     102          if changed:
     103              ff.gettherest()
     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], end=' ')
     113              if line is None:
     114                  print("-- deleted")
     115              else:
     116                  print("-- change to:")
     117                  print(line, end=' ')
     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              with open(file, "w") as g:
     126                  ff.write(g)
     127              if verbose:
     128                  print("wrote new", file)
     129      else:
     130          if verbose:
     131              print("unchanged.")
     132  
     133  class ESC[4;38;5;81mFutureFinder:
     134  
     135      def __init__(self, f, fname):
     136          self.f = f
     137          self.fname = fname
     138          self.ateof = 0
     139          self.lines = [] # raw file lines
     140  
     141          # List of (start_index, end_index, new_line) triples.
     142          self.changed = []
     143  
     144      # Line-getter for tokenize.
     145      def getline(self):
     146          if self.ateof:
     147              return ""
     148          line = self.f.readline()
     149          if line == "":
     150              self.ateof = 1
     151          else:
     152              self.lines.append(line)
     153          return line
     154  
     155      def run(self):
     156          STRING = tokenize.STRING
     157          NL = tokenize.NL
     158          NEWLINE = tokenize.NEWLINE
     159          COMMENT = tokenize.COMMENT
     160          NAME = tokenize.NAME
     161          OP = tokenize.OP
     162  
     163          changed = self.changed
     164          get = tokenize.generate_tokens(self.getline).__next__
     165          type, token, (srow, scol), (erow, ecol), line = get()
     166  
     167          # Chew up initial comments and blank lines (if any).
     168          while type in (COMMENT, NL, NEWLINE):
     169              type, token, (srow, scol), (erow, ecol), line = get()
     170  
     171          # Chew up docstring (if any -- and it may be implicitly catenated!).
     172          while type is STRING:
     173              type, token, (srow, scol), (erow, ecol), line = get()
     174  
     175          # Analyze the future stmts.
     176          while 1:
     177              # Chew up comments and blank lines (if any).
     178              while type in (COMMENT, NL, NEWLINE):
     179                  type, token, (srow, scol), (erow, ecol), line = get()
     180  
     181              if not (type is NAME and token == "from"):
     182                  break
     183              startline = srow - 1    # tokenize is one-based
     184              type, token, (srow, scol), (erow, ecol), line = get()
     185  
     186              if not (type is NAME and token == "__future__"):
     187                  break
     188              type, token, (srow, scol), (erow, ecol), line = get()
     189  
     190              if not (type is NAME and token == "import"):
     191                  break
     192              type, token, (srow, scol), (erow, ecol), line = get()
     193  
     194              # Get the list of features.
     195              features = []
     196              while type is NAME:
     197                  features.append(token)
     198                  type, token, (srow, scol), (erow, ecol), line = get()
     199  
     200                  if not (type is OP and token == ','):
     201                      break
     202                  type, token, (srow, scol), (erow, ecol), line = get()
     203  
     204              # A trailing comment?
     205              comment = None
     206              if type is COMMENT:
     207                  comment = token
     208                  type, token, (srow, scol), (erow, ecol), line = get()
     209  
     210              if type is not NEWLINE:
     211                  errprint("Skipping file %r; can't parse line %d:\n%s" %
     212                           (self.fname, srow, line))
     213                  return []
     214  
     215              endline = srow - 1
     216  
     217              # Check for obsolete features.
     218              okfeatures = []
     219              for f in features:
     220                  object = getattr(__future__, f, None)
     221                  if object is None:
     222                      # A feature we don't know about yet -- leave it in.
     223                      # They'll get a compile-time error when they compile
     224                      # this program, but that's not our job to sort out.
     225                      okfeatures.append(f)
     226                  else:
     227                      released = object.getMandatoryRelease()
     228                      if released is None or released <= sys.version_info:
     229                          # Withdrawn or obsolete.
     230                          pass
     231                      else:
     232                          okfeatures.append(f)
     233  
     234              # Rewrite the line if at least one future-feature is obsolete.
     235              if len(okfeatures) < len(features):
     236                  if len(okfeatures) == 0:
     237                      line = None
     238                  else:
     239                      line = "from __future__ import "
     240                      line += ', '.join(okfeatures)
     241                      if comment is not None:
     242                          line += ' ' + comment
     243                      line += '\n'
     244                  changed.append((startline, endline, line))
     245  
     246              # Loop back for more future statements.
     247  
     248          return changed
     249  
     250      def gettherest(self):
     251          if self.ateof:
     252              self.therest = ''
     253          else:
     254              self.therest = self.f.read()
     255  
     256      def write(self, f):
     257          changed = self.changed
     258          assert changed
     259          # Prevent calling this again.
     260          self.changed = []
     261          # Apply changes in reverse order.
     262          changed.reverse()
     263          for s, e, line in changed:
     264              if line is None:
     265                  # pure deletion
     266                  del self.lines[s:e+1]
     267              else:
     268                  self.lines[s:e+1] = [line]
     269          f.writelines(self.lines)
     270          # Copy over the remainder of the file.
     271          if self.therest:
     272              f.write(self.therest)
     273  
     274  if __name__ == '__main__':
     275      main()