File indexing completed on 2024-05-12 16:28:26
0001 #!/usr/bin/python -Qwarnall 0002 0003 # 0004 # This script fixes trivial errors in the sources: 0005 # - Add a newline to files that don't end in one. (only apply to source files!) 0006 # - Normalize SIGNAL and SLOT signatures 0007 # - Improve #include status: remove duplicates 0008 # - ...more later 0009 # 0010 0011 import sys 0012 import os 0013 import string 0014 import getopt 0015 import re 0016 import fnmatch 0017 0018 0019 # Global variables 0020 dryrun = False # Maybe the default should be True... 0021 pattern = "*" 0022 recursive = False 0023 verbose = False 0024 0025 0026 # ---------------------------------------------------------------- 0027 # Individual actions 0028 0029 0030 def doIncludes(contents): 0031 global verbose 0032 global dryrun 0033 0034 # Compile a regex that matches an include statement. 0035 # We do no syntax check so #include "foo> is found by this. 0036 includePattern = '^(\\#include\\s*)(["<])([^">]+)([">])(.*)' 0037 includeRegex = re.compile(includePattern) 0038 0039 lineNo = 1 0040 newContents = [] 0041 foundIncludes = [] 0042 for line in contents: 0043 #print "input: ", line 0044 0045 # See if this line is an include line. If not, just append it 0046 # to the output and continue. 0047 includes = includeRegex.findall(line) 0048 #print "includes: ", includes 0049 if len(includes) == 0: 0050 newContents.append(line) 0051 lineNo = lineNo + 1 0052 continue 0053 0054 include = includes[0] 0055 # Here we know it's an include statement. We should only have found one hit. 0056 # include[0] = "#include " 0057 # include[1] = "\"" or "<" 0058 # include[2] = the filename in the include statement 0059 # include[3] = "\"" or ">" 0060 # include[4] = the rest of the line 0061 includeName = include[2] 0062 #print "include name: ", includeName 0063 0064 if includeName in foundIncludes: 0065 if verbose: 0066 sys.stderr.write(" Duplicate include on line " + str(lineNo) + " (removing)\n") 0067 else: 0068 foundIncludes.append(includeName) 0069 newContents.append(line) 0070 0071 lineNo = lineNo + 1 0072 0073 return newContents 0074 0075 0076 def doNormalize(contents): 0077 global verbose 0078 0079 # Compile a regex that matches SIGNAL or SLOT followed by two 0080 # parenthesis levels (signal/slot + function) 0081 # 0082 # Warning: This is a very simple parser that will fail on multi-line 0083 # cases or cases with more than 2 parenthesis. But it should 0084 # cover the majority of cases. 0085 topPattern = "(SIGNAL|SLOT)(\\s*\\([^\\(]*\\([^\\)]*\\)\s*\\))" 0086 topRegex = re.compile(topPattern) 0087 0088 # regex to identify the parameter list 0089 # [0]: Start parenthesis 0090 # [1]: the identifier (function name of the signal or slot) 0091 # [2]: the parameter list 0092 # [3]: end parenthesis 0093 # Note: this won't work on function parameters with their own parameter list. 0094 paramPattern = "(\s*\\(\s*)([a-zA-z][a-zA-z0-9]*)(\s*\\(\s*[^\\)]*\s*\\))(\s*\\)\s*)" 0095 paramRegex = re.compile(paramPattern) 0096 0097 lineNo = 1 0098 newContents = [] 0099 for line in contents: 0100 #print "input: ", line 0101 0102 # Replace signal/slot signatures with the normalized equivalent. 0103 signalSlotMacros = topRegex.findall(line) 0104 for macro in signalSlotMacros: 0105 # Parameters to SIGNAL or SLOT 0106 signalSlotParam = macro[1] #macro[0] = "SIGNAL" or "SLOT" 0107 0108 params = paramRegex.findall(signalSlotParam) 0109 if not params: 0110 continue 0111 params = params[0] # There should only be one hit 0112 #print "params:", params, len(params) 0113 0114 functionName = params[1] 0115 paramList = params[2].strip()[1:-1] # remove the parenthesis 0116 #print repr(functionName), repr(paramList) 0117 0118 # Now we have dug out the parameter list to the function 0119 # This sequence does the actual normalizing. 0120 functionParams = paramList.split(",") 0121 #print functionParams 0122 outParamList = [] 0123 for functionParam in functionParams: 0124 s = functionParam.strip() 0125 if s[:5] == "const" and s[-1:] == "&": 0126 # Remove const-& 0127 outParamList.append(s[5:-1].replace(" ", "")) 0128 elif s[:5] == "const" and s[-1:] == "*": 0129 # Do NOT remove const for pointers. Just remove spaces. 0130 outParamList.append("const " + s[6:].replace(" ", "")) 0131 elif s[:5] == "const": 0132 # Remove const without & or * 0133 outParamList.append(s[5:].replace(" ", "")) 0134 elif s[-1:] == "&": 0135 # Keep & without const but remove spaces 0136 outParamList.append(s.replace(" ", "")) 0137 else: 0138 # Keep parameter without any const and & but remove spaces 0139 # (This and the last line could be combined.) 0140 outParamList.append(s.replace(" ", "")) 0141 outParams = string.join(outParamList, ",") 0142 signalSlotParam2 = "(" + functionName + "(" + outParams + "))" 0143 0144 if verbose and signalSlotParam != signalSlotParam2: 0145 sys.stderr.write(" Normalizing " + macro[0] + " statement on line " 0146 + str(lineNo) + "\n") 0147 0148 line = line.replace(signalSlotParam, signalSlotParam2, 1) 0149 #print "result: ", line 0150 0151 # When we exit the loop all the substitutions inside the line are done. 0152 newContents.append(line) 0153 lineNo = lineNo + 1 0154 0155 return newContents 0156 0157 0158 # ---------------------------------------------------------------- 0159 0160 0161 def handleFile(name, actions): 0162 global dryrun, verbose 0163 0164 if verbose: 0165 sys.stderr.write(name + ":\n") 0166 0167 # Read the contents of the file into a list, one item per line. 0168 infile = open(name) 0169 contents = infile.readlines() 0170 infile.close() 0171 0172 if "endswitheol" in actions: 0173 if contents != [] and contents[-1] != "" and contents[-1][-1] != "\n": 0174 if verbose: 0175 sys.stderr.write(" Adding EOL to end of file\n") 0176 contents[-1] = contents[-1] + "\n" 0177 if "normalize" in actions: 0178 contents = doNormalize(contents) 0179 if "includes" in actions: 0180 contents = doIncludes(contents) 0181 0182 if not dryrun: 0183 outname = name + "--temp" #FIXME: Generate real tempname 0184 outfile = open(outname, "w") 0185 outfile.writelines(contents) 0186 outfile.close() 0187 os.rename(outname, name) 0188 0189 0190 def traverseTree(dir, actions, names): 0191 global recursive, verbose, pattern 0192 0193 # We could also use os.walk() 0194 for name in names: 0195 if dir == "": 0196 fullname = name 0197 else: 0198 fullname = dir + "/" + name 0199 0200 if not os.path.exists(fullname): 0201 sys.stderr.write(fullname + ": unknown file or directory\n") 0202 continue 0203 0204 if os.path.isdir(fullname): 0205 if recursive: 0206 traverseTree(fullname, actions, os.listdir(fullname)) 0207 # Ignore all directories if not in recursive mode 0208 else: 0209 if fnmatch.fnmatch(name, pattern): 0210 handleFile(fullname, actions) 0211 0212 0213 # ================================================================ 0214 0215 0216 def usage(errormsg=""): 0217 if errormsg: 0218 print "Error:", sys.argv[0] + ":", errormsg, "\n" 0219 else: 0220 print "Fix trivial errors in the source tree.\n" 0221 0222 print """usage: fixsrc.py [options] [files] 0223 options: 0224 -a --actions a comma separated list of the following possible actions: 0225 endswitheol: adds newline at the end to files that don't end in newline 0226 normalize: normalizes SIGNAL and SLOT signatures 0227 includes: improves #include status (currently: removes duplicates) 0228 all: all of the above 0229 -d --dryrun don't actually perform the actions (combine with --verbose) 0230 This is recommended before doing the full run. 0231 -h --help print this help and exit immediately 0232 -p --pattern apply to files whose name matches a certain glob pattern 0233 -r --recursive recursive: all files that are directories are traversed recursively 0234 -v --verbose print extra verbose output 0235 0236 files: 0237 source files to be fixed and/or directories if --recursive is given 0238 0239 examples: 0240 fixsrc.py -rv --actions endswitheol libs 0241 fixsrc.py -rv --pattern '*.cpp' --actions normalize . 0242 """ 0243 sys.exit(0) 0244 0245 0246 def main(): 0247 global dryrun, recursive, pattern, verbose 0248 allActions = ["endswitheol", "normalize", "includes", "all"] 0249 0250 try : 0251 opts, params = getopt.getopt(sys.argv[1:], "a:dhp:rv" , 0252 ["actions=", "dryrun", "help", "pattern=", "recursive", "verbose"]) 0253 except getopt.GetoptError: 0254 usage("unknown options") 0255 #print "opts:", opts 0256 #print "params:", params 0257 0258 actions = [] 0259 for opt, param in opts: 0260 #print opt, param 0261 if opt in ("-a" , "--actions"): 0262 actions = string.split(param, ",") 0263 #print "actions: ", actions 0264 for a in actions: 0265 if not a in allActions: 0266 usage("unknown action: " + a + "\n\n") 0267 if "all" in actions: 0268 actions = allActions[:-1] # Remove "all", which is a meta action. 0269 0270 elif opt in ("-d" , "--dryrun"): 0271 dryrun = True 0272 elif opt in ("-h" , "--help"): 0273 usage() 0274 elif opt in ("-p" , "--pattern"): 0275 pattern = param 0276 elif opt in ("-r" , "--recursive"): 0277 recursive = True 0278 elif opt in ("-v" , "--verbose"): 0279 verbose = True 0280 0281 if actions == []: 0282 usage("no actions defined") 0283 0284 # Do the actual work 0285 traverseTree("", actions, params) 0286 0287 return 0 0288 0289 if __name__ == '__main__': 0290 main()