/Demo/scripts/eqfix.py

http://unladen-swallow.googlecode.com/ · Python · 198 lines · 141 code · 16 blank · 41 comment · 54 complexity · 66be99e64f8a02441f14f512ec29c178 MD5 · raw file

  1. #! /usr/bin/env python
  2. # Fix Python source files to use the new equality test operator, i.e.,
  3. # if x = y: ...
  4. # is changed to
  5. # if x == y: ...
  6. # The script correctly tokenizes the Python program to reliably
  7. # distinguish between assignments and equality tests.
  8. #
  9. # Command line arguments are files or directories to be processed.
  10. # Directories are searched recursively for files whose name looks
  11. # like a python module.
  12. # Symbolic links are always ignored (except as explicit directory
  13. # arguments). Of course, the original file is kept as a back-up
  14. # (with a "~" attached to its name).
  15. # It complains about binaries (files containing null bytes)
  16. # and about files that are ostensibly not Python files: if the first
  17. # line starts with '#!' and does not contain the string 'python'.
  18. #
  19. # Changes made are reported to stdout in a diff-like format.
  20. #
  21. # Undoubtedly you can do this using find and sed or perl, but this is
  22. # a nice example of Python code that recurses down a directory tree
  23. # and uses regular expressions. Also note several subtleties like
  24. # preserving the file's mode and avoiding to even write a temp file
  25. # when no changes are needed for a file.
  26. #
  27. # NB: by changing only the function fixline() you can turn this
  28. # into a program for a different change to Python programs...
  29. import sys
  30. import re
  31. import os
  32. from stat import *
  33. import string
  34. err = sys.stderr.write
  35. dbg = err
  36. rep = sys.stdout.write
  37. def main():
  38. bad = 0
  39. if not sys.argv[1:]: # No arguments
  40. err('usage: ' + sys.argv[0] + ' file-or-directory ...\n')
  41. sys.exit(2)
  42. for arg in sys.argv[1:]:
  43. if os.path.isdir(arg):
  44. if recursedown(arg): bad = 1
  45. elif os.path.islink(arg):
  46. err(arg + ': will not process symbolic links\n')
  47. bad = 1
  48. else:
  49. if fix(arg): bad = 1
  50. sys.exit(bad)
  51. ispythonprog = re.compile('^[a-zA-Z0-9_]+\.py$')
  52. def ispython(name):
  53. return ispythonprog.match(name) >= 0
  54. def recursedown(dirname):
  55. dbg('recursedown(%r)\n' % (dirname,))
  56. bad = 0
  57. try:
  58. names = os.listdir(dirname)
  59. except os.error, msg:
  60. err('%s: cannot list directory: %r\n' % (dirname, msg))
  61. return 1
  62. names.sort()
  63. subdirs = []
  64. for name in names:
  65. if name in (os.curdir, os.pardir): continue
  66. fullname = os.path.join(dirname, name)
  67. if os.path.islink(fullname): pass
  68. elif os.path.isdir(fullname):
  69. subdirs.append(fullname)
  70. elif ispython(name):
  71. if fix(fullname): bad = 1
  72. for fullname in subdirs:
  73. if recursedown(fullname): bad = 1
  74. return bad
  75. def fix(filename):
  76. ## dbg('fix(%r)\n' % (dirname,))
  77. try:
  78. f = open(filename, 'r')
  79. except IOError, msg:
  80. err('%s: cannot open: %r\n' % (filename, msg))
  81. return 1
  82. head, tail = os.path.split(filename)
  83. tempname = os.path.join(head, '@' + tail)
  84. g = None
  85. # If we find a match, we rewind the file and start over but
  86. # now copy everything to a temp file.
  87. lineno = 0
  88. while 1:
  89. line = f.readline()
  90. if not line: break
  91. lineno = lineno + 1
  92. if g is None and '\0' in line:
  93. # Check for binary files
  94. err(filename + ': contains null bytes; not fixed\n')
  95. f.close()
  96. return 1
  97. if lineno == 1 and g is None and line[:2] == '#!':
  98. # Check for non-Python scripts
  99. words = string.split(line[2:])
  100. if words and re.search('[pP]ython', words[0]) < 0:
  101. msg = filename + ': ' + words[0]
  102. msg = msg + ' script; not fixed\n'
  103. err(msg)
  104. f.close()
  105. return 1
  106. while line[-2:] == '\\\n':
  107. nextline = f.readline()
  108. if not nextline: break
  109. line = line + nextline
  110. lineno = lineno + 1
  111. newline = fixline(line)
  112. if newline != line:
  113. if g is None:
  114. try:
  115. g = open(tempname, 'w')
  116. except IOError, msg:
  117. f.close()
  118. err('%s: cannot create: %r\n' % (tempname, msg))
  119. return 1
  120. f.seek(0)
  121. lineno = 0
  122. rep(filename + ':\n')
  123. continue # restart from the beginning
  124. rep(repr(lineno) + '\n')
  125. rep('< ' + line)
  126. rep('> ' + newline)
  127. if g is not None:
  128. g.write(newline)
  129. # End of file
  130. f.close()
  131. if not g: return 0 # No changes
  132. # Finishing touch -- move files
  133. # First copy the file's mode to the temp file
  134. try:
  135. statbuf = os.stat(filename)
  136. os.chmod(tempname, statbuf[ST_MODE] & 07777)
  137. except os.error, msg:
  138. err('%s: warning: chmod failed (%r)\n' % (tempname, msg))
  139. # Then make a backup of the original file as filename~
  140. try:
  141. os.rename(filename, filename + '~')
  142. except os.error, msg:
  143. err('%s: warning: backup failed (%r)\n' % (filename, msg))
  144. # Now move the temp file to the original file
  145. try:
  146. os.rename(tempname, filename)
  147. except os.error, msg:
  148. err('%s: rename failed (%r)\n' % (filename, msg))
  149. return 1
  150. # Return succes
  151. return 0
  152. from tokenize import tokenprog
  153. match = {'if':':', 'elif':':', 'while':':', 'return':'\n', \
  154. '(':')', '[':']', '{':'}', '`':'`'}
  155. def fixline(line):
  156. # Quick check for easy case
  157. if '=' not in line: return line
  158. i, n = 0, len(line)
  159. stack = []
  160. while i < n:
  161. j = tokenprog.match(line, i)
  162. if j < 0:
  163. # A bad token; forget about the rest of this line
  164. print '(Syntax error:)'
  165. print line,
  166. return line
  167. a, b = tokenprog.regs[3] # Location of the token proper
  168. token = line[a:b]
  169. i = i+j
  170. if stack and token == stack[-1]:
  171. del stack[-1]
  172. elif match.has_key(token):
  173. stack.append(match[token])
  174. elif token == '=' and stack:
  175. line = line[:a] + '==' + line[b:]
  176. i, n = a + len('=='), len(line)
  177. elif token == '==' and not stack:
  178. print '(Warning: \'==\' at top level:)'
  179. print line,
  180. return line
  181. if __name__ == "__main__":
  182. main()