Some time ago I wrote a piece of code to fix one of my biggest pet peeves in our code at work – the fact that a large number of #if(def) foo statements have #else or #endif statements attached which are blank. In other words, the #endif doesn’t have /*foo*/ after it to tell you which #ifdef you’re coming from. This drives me nuts, so I fixed it. The code is in python, and looks like this:
import walk, re from os.path import split from os import getcwd from sys import argv from optparse import OptionParser #basic setup patternmatch ="*.c;*.h" ifdeflist =  #define the re's we need ifpattern = re.compile("^[ t]*" #find any number of whitespace chars before the #if "#if" #find a #if at least "(n?def)?" #allow only one def (or ndef) to complete the previous #if (group 0) " +" #find at least one space "(.*)" #find everything else (group 1) "$" #find the end of the line ,re.VERBOSE) elseendpattern = re.compile("^[ t]*" #find any number of whitespace chars before the #else "#" #find a # "(else|" #find an else or... "endif)" #find an endif "[t ]*" #find any amount of whitespace "(.*)$" #find everything else, that runs to the end of the line (group 0) ,re.VERBOSE) def writeListToFile(filename,list,head=""): '''Writes a list of lines to a supplied file name, preceded by a supplied header. Returns true if no errors, false otherwise.''' try: file = open(filename,"a") #check if appending or writing a new file if file.tell() >= 1: #appending, so add a delimiter from previous file contents file.write("n------------------------------------------------------------n") if head: file.write(head) file.write("n".join(list)) except: file.close() return False file.close() return True def processFiles(dir, pattern, errorfile, goodfile, usecomment=False,recurse=False): '''Generates a list of files matching the pattern supplied. Calls the decorateFile function on each file found, then records details of success or failure and the number of files processed and actually edited.''' errorFiles =  goodFiles =  numFiles = 0 #find and process each file that matches for file in walk.find_files_in_tree(dir,pattern,not recurse): code = decorateFile(file,usecomment) if not code: errorFiles.append(file) elif code == 2: goodFiles.append(file) numFiles += 1 #Print the results print "n%d file(s) in total were processed, while %d had #if(def)'s that needed decorating. %s file(s) reported errors." % (numFiles,len(goodFiles),len(errorFiles)) if goodFiles: if writeListToFile(goodfile, goodFiles,"These files had their if(def)'s decorated:nn"): print "A list of successfully decorated files is available in %s" % (split(goodfile),) else: print "Unfortunately, an error occurred during the writing of the list of decorated files to %s" % (split(goodfile),) if errorFiles: if writeListToFile(errorfile, errorFiles,"These files had errors:nn"): print "A list of files with errors is available in %s" % (split(errorfile),) else: print "Unfortunately, an error occurred during the writing of the list of error-causing files to %s" % (split(errorfile),) def decorateFile(file,usecomment=False): '''Opens a passed in file and parses it for undecorated #else and #endif statements. These are decorated with the text attached to the associated #if(def). The changes are written back to the passed in file. Returns False if some error occured. Returns 1 if no changes were made, 2 if some changes were made.''' returnCode = 1 #try to open the file and get into a n-less list. 2 try's ensure we handle the exception, but also always close the file try: try: fileobj = open(file,"r") linelist = fileobj.readlines() linelist = [line.strip("n") for line in linelist] #close the fileobj if we hit a failure and return false finally: fileobj.close() except: return False #try to open the file again, to write into. 2 try's ensure we handle the exception, but also always close the file try: try: fileobj = open(file,"w") for line in linelist: #check for #else or #endif matches matchline = elseendpattern.search(line) #check that we have a match to #else or #endif with only whitespace after it if matchline and not matchline.groups(): extraText = ifdeflist[-1] #see if we have a #endif - need to pop from the ifdeflist if matchline.groups() == "endif": ifdeflist.pop() #print the #else or #endif along with the ifdef text commented after it fileobj.write("%s %s%s%sn" % (line.rstrip(), usecomment and "/*" or "", extraText, usecomment and "*/" or "")) returnCode = 2 continue #check for #ifdef matches - grabbing only char's before a comment starts matchline = ifpattern.search(line.split("/*").strip("t ")) if matchline: #Found a #if(def), so grab its text ifdeflist.append(matchline.groups()) #intentional fall-through #write the line back to the file fileobj.write("%sn" % (line,)) #close the fileobj if we hit a failure and return false finally: fileobj.close() except: return False #all was well, so return True return returnCode def main(argv): usage='''Searches (possibly recursively) through a directory of files which match a given pattern to decorate the #if(def)'s. This is done by replacing all occurances of #endif and #else which do not have accompanying text with the text of the associated #if(def). ''' parser = OptionParser(usage=usage) parser.add_option("-p", "--pattern", action="store", type="string", dest="patternmatch", default="*.c;*.h", help="Define a pattern of files to look into. Default pattern: %default") parser.add_option("-r", "--recurse", default=False, action="store_true", dest="recurse", help="Recursively look into subdirectories as well. Default: %default") parser.add_option("-c", "--comment", default=False, action="store_true", dest="usecomment", help="Put comment chars around the inserted text. Default: %default") parser.add_option("-e","--errorfile", default="errors.txt", action="store", type="string", dest="errorfile", help="Define the file to write the list of files which caused errors into.nA file is only created if any errors actually ocurred.n Default: %default in the current dir") parser.add_option("-d","--basedir", default=getcwd(), action="store", type="string", dest="basedir", help="The base directory to start in. Default: The current dir") parser.add_option("-f","--outfile", default="files.txt", action="store", type="string", dest="goodfile", help="Define the file to write the list of edited files into. Default: %default in the current dir") (options, args) = parser.parse_args() processFiles(options.basedir, options.patternmatch, options.errorfile, options.goodfile, options.usecomment, options.recurse) if __name__ == "__main__": main(argv[1:])
Not the finest ever written, but it does the trick.