File indexing completed on 2024-05-12 16:39:34

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()