/Tools/scripts/methfix.py

http://unladen-swallow.googlecode.com/ · Python · 171 lines · 120 code · 14 blank · 37 comment · 44 complexity · bae5308a46832c4ecc68fa37ee2afe27 MD5 · raw file

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