/Tools/scripts/fixcid.py

http://unladen-swallow.googlecode.com/ · Python · 314 lines · 232 code · 23 blank · 59 comment · 69 complexity · 76599154c605ed40790f6fb08c43b35a MD5 · raw file

  1. #! /usr/bin/env python
  2. # Perform massive identifier substitution on C source files.
  3. # This actually tokenizes the files (to some extent) so it can
  4. # avoid making substitutions inside strings or comments.
  5. # Inside strings, substitutions are never made; inside comments,
  6. # it is a user option (off by default).
  7. #
  8. # The substitutions are read from one or more files whose lines,
  9. # when not empty, after stripping comments starting with #,
  10. # must contain exactly two words separated by whitespace: the
  11. # old identifier and its replacement.
  12. #
  13. # The option -r reverses the sense of the substitutions (this may be
  14. # useful to undo a particular substitution).
  15. #
  16. # If the old identifier is prefixed with a '*' (with no intervening
  17. # whitespace), then it will not be substituted inside comments.
  18. #
  19. # Command line arguments are files or directories to be processed.
  20. # Directories are searched recursively for files whose name looks
  21. # like a C file (ends in .h or .c). The special filename '-' means
  22. # operate in filter mode: read stdin, write stdout.
  23. #
  24. # Symbolic links are always ignored (except as explicit directory
  25. # arguments).
  26. #
  27. # The original files are kept as back-up with a "~" suffix.
  28. #
  29. # Changes made are reported to stdout in a diff-like format.
  30. #
  31. # NB: by changing only the function fixline() you can turn this
  32. # into a program for different changes to C source files; by
  33. # changing the function wanted() you can make a different selection of
  34. # files.
  35. import sys
  36. import re
  37. import os
  38. from stat import *
  39. import getopt
  40. err = sys.stderr.write
  41. dbg = err
  42. rep = sys.stdout.write
  43. def usage():
  44. progname = sys.argv[0]
  45. err('Usage: ' + progname +
  46. ' [-c] [-r] [-s file] ... file-or-directory ...\n')
  47. err('\n')
  48. err('-c : substitute inside comments\n')
  49. err('-r : reverse direction for following -s options\n')
  50. err('-s substfile : add a file of substitutions\n')
  51. err('\n')
  52. err('Each non-empty non-comment line in a substitution file must\n')
  53. err('contain exactly two words: an identifier and its replacement.\n')
  54. err('Comments start with a # character and end at end of line.\n')
  55. err('If an identifier is preceded with a *, it is not substituted\n')
  56. err('inside a comment even when -c is specified.\n')
  57. def main():
  58. try:
  59. opts, args = getopt.getopt(sys.argv[1:], 'crs:')
  60. except getopt.error, msg:
  61. err('Options error: ' + str(msg) + '\n')
  62. usage()
  63. sys.exit(2)
  64. bad = 0
  65. if not args: # No arguments
  66. usage()
  67. sys.exit(2)
  68. for opt, arg in opts:
  69. if opt == '-c':
  70. setdocomments()
  71. if opt == '-r':
  72. setreverse()
  73. if opt == '-s':
  74. addsubst(arg)
  75. for arg in args:
  76. if os.path.isdir(arg):
  77. if recursedown(arg): bad = 1
  78. elif os.path.islink(arg):
  79. err(arg + ': will not process symbolic links\n')
  80. bad = 1
  81. else:
  82. if fix(arg): bad = 1
  83. sys.exit(bad)
  84. # Change this regular expression to select a different set of files
  85. Wanted = '^[a-zA-Z0-9_]+\.[ch]$'
  86. def wanted(name):
  87. return re.match(Wanted, name) >= 0
  88. def recursedown(dirname):
  89. dbg('recursedown(%r)\n' % (dirname,))
  90. bad = 0
  91. try:
  92. names = os.listdir(dirname)
  93. except os.error, msg:
  94. err(dirname + ': cannot list directory: ' + str(msg) + '\n')
  95. return 1
  96. names.sort()
  97. subdirs = []
  98. for name in names:
  99. if name in (os.curdir, os.pardir): continue
  100. fullname = os.path.join(dirname, name)
  101. if os.path.islink(fullname): pass
  102. elif os.path.isdir(fullname):
  103. subdirs.append(fullname)
  104. elif wanted(name):
  105. if fix(fullname): bad = 1
  106. for fullname in subdirs:
  107. if recursedown(fullname): bad = 1
  108. return bad
  109. def fix(filename):
  110. ## dbg('fix(%r)\n' % (filename,))
  111. if filename == '-':
  112. # Filter mode
  113. f = sys.stdin
  114. g = sys.stdout
  115. else:
  116. # File replacement mode
  117. try:
  118. f = open(filename, 'r')
  119. except IOError, msg:
  120. err(filename + ': cannot open: ' + str(msg) + '\n')
  121. return 1
  122. head, tail = os.path.split(filename)
  123. tempname = os.path.join(head, '@' + tail)
  124. g = None
  125. # If we find a match, we rewind the file and start over but
  126. # now copy everything to a temp file.
  127. lineno = 0
  128. initfixline()
  129. while 1:
  130. line = f.readline()
  131. if not line: break
  132. lineno = lineno + 1
  133. while line[-2:] == '\\\n':
  134. nextline = f.readline()
  135. if not nextline: break
  136. line = line + nextline
  137. lineno = lineno + 1
  138. newline = fixline(line)
  139. if newline != line:
  140. if g is None:
  141. try:
  142. g = open(tempname, 'w')
  143. except IOError, msg:
  144. f.close()
  145. err(tempname+': cannot create: '+
  146. str(msg)+'\n')
  147. return 1
  148. f.seek(0)
  149. lineno = 0
  150. initfixline()
  151. rep(filename + ':\n')
  152. continue # restart from the beginning
  153. rep(repr(lineno) + '\n')
  154. rep('< ' + line)
  155. rep('> ' + newline)
  156. if g is not None:
  157. g.write(newline)
  158. # End of file
  159. if filename == '-': return 0 # Done in filter mode
  160. f.close()
  161. if not g: return 0 # No changes
  162. # Finishing touch -- move files
  163. # First copy the file's mode to the temp file
  164. try:
  165. statbuf = os.stat(filename)
  166. os.chmod(tempname, statbuf[ST_MODE] & 07777)
  167. except os.error, msg:
  168. err(tempname + ': warning: chmod failed (' + str(msg) + ')\n')
  169. # Then make a backup of the original file as filename~
  170. try:
  171. os.rename(filename, filename + '~')
  172. except os.error, msg:
  173. err(filename + ': warning: backup failed (' + str(msg) + ')\n')
  174. # Now move the temp file to the original file
  175. try:
  176. os.rename(tempname, filename)
  177. except os.error, msg:
  178. err(filename + ': rename failed (' + str(msg) + ')\n')
  179. return 1
  180. # Return succes
  181. return 0
  182. # Tokenizing ANSI C (partly)
  183. Identifier = '\(struct \)?[a-zA-Z_][a-zA-Z0-9_]+'
  184. String = '"\([^\n\\"]\|\\\\.\)*"'
  185. Char = '\'\([^\n\\\']\|\\\\.\)*\''
  186. CommentStart = '/\*'
  187. CommentEnd = '\*/'
  188. Hexnumber = '0[xX][0-9a-fA-F]*[uUlL]*'
  189. Octnumber = '0[0-7]*[uUlL]*'
  190. Decnumber = '[1-9][0-9]*[uUlL]*'
  191. Intnumber = Hexnumber + '\|' + Octnumber + '\|' + Decnumber
  192. Exponent = '[eE][-+]?[0-9]+'
  193. Pointfloat = '\([0-9]+\.[0-9]*\|\.[0-9]+\)\(' + Exponent + '\)?'
  194. Expfloat = '[0-9]+' + Exponent
  195. Floatnumber = Pointfloat + '\|' + Expfloat
  196. Number = Floatnumber + '\|' + Intnumber
  197. # Anything else is an operator -- don't list this explicitly because of '/*'
  198. OutsideComment = (Identifier, Number, String, Char, CommentStart)
  199. OutsideCommentPattern = '(' + '|'.join(OutsideComment) + ')'
  200. OutsideCommentProgram = re.compile(OutsideCommentPattern)
  201. InsideComment = (Identifier, Number, CommentEnd)
  202. InsideCommentPattern = '(' + '|'.join(InsideComment) + ')'
  203. InsideCommentProgram = re.compile(InsideCommentPattern)
  204. def initfixline():
  205. global Program
  206. Program = OutsideCommentProgram
  207. def fixline(line):
  208. global Program
  209. ## print '-->', repr(line)
  210. i = 0
  211. while i < len(line):
  212. i = Program.search(line, i)
  213. if i < 0: break
  214. found = Program.group(0)
  215. ## if Program is InsideCommentProgram: print '...',
  216. ## else: print ' ',
  217. ## print found
  218. if len(found) == 2:
  219. if found == '/*':
  220. Program = InsideCommentProgram
  221. elif found == '*/':
  222. Program = OutsideCommentProgram
  223. n = len(found)
  224. if Dict.has_key(found):
  225. subst = Dict[found]
  226. if Program is InsideCommentProgram:
  227. if not Docomments:
  228. print 'Found in comment:', found
  229. i = i + n
  230. continue
  231. if NotInComment.has_key(found):
  232. ## print 'Ignored in comment:',
  233. ## print found, '-->', subst
  234. ## print 'Line:', line,
  235. subst = found
  236. ## else:
  237. ## print 'Substituting in comment:',
  238. ## print found, '-->', subst
  239. ## print 'Line:', line,
  240. line = line[:i] + subst + line[i+n:]
  241. n = len(subst)
  242. i = i + n
  243. return line
  244. Docomments = 0
  245. def setdocomments():
  246. global Docomments
  247. Docomments = 1
  248. Reverse = 0
  249. def setreverse():
  250. global Reverse
  251. Reverse = (not Reverse)
  252. Dict = {}
  253. NotInComment = {}
  254. def addsubst(substfile):
  255. try:
  256. fp = open(substfile, 'r')
  257. except IOError, msg:
  258. err(substfile + ': cannot read substfile: ' + str(msg) + '\n')
  259. sys.exit(1)
  260. lineno = 0
  261. while 1:
  262. line = fp.readline()
  263. if not line: break
  264. lineno = lineno + 1
  265. try:
  266. i = line.index('#')
  267. except ValueError:
  268. i = -1 # Happens to delete trailing \n
  269. words = line[:i].split()
  270. if not words: continue
  271. if len(words) == 3 and words[0] == 'struct':
  272. words[:2] = [words[0] + ' ' + words[1]]
  273. elif len(words) <> 2:
  274. err(substfile + '%s:%r: warning: bad line: %r' % (substfile, lineno, line))
  275. continue
  276. if Reverse:
  277. [value, key] = words
  278. else:
  279. [key, value] = words
  280. if value[0] == '*':
  281. value = value[1:]
  282. if key[0] == '*':
  283. key = key[1:]
  284. NotInComment[key] = value
  285. if Dict.has_key(key):
  286. err('%s:%r: warning: overriding: %r %r\n' % (substfile, lineno, key, value))
  287. err('%s:%r: warning: previous: %r\n' % (substfile, lineno, Dict[key]))
  288. Dict[key] = value
  289. fp.close()
  290. if __name__ == '__main__':
  291. main()