BASIS  r3148
updatefile.py
Go to the documentation of this file.
00001 #! /usr/bin/env python
00002 
00003 # ATTENTION: DO NOT use the tokens used by the file update anywhere within
00004 #            this file. Write < basis-custom > instead, for example.
00005 
00006 ##
00007 # @file  updatefile.py
00008 # @brief Update file from template file while preserving custom sections (deprecated).
00009 #
00010 # This script is used by the BasisUpdate.cmake module. This module is used to
00011 # update files of a project instantiated from a particular revision of the
00012 # BASIS project template during the configure step of CMake. This way,
00013 # projects pull the changes of the compatible template automatically.
00014 # Sections in the original file which are enclosed by the tokens
00015 # <basis-custom> and </basis-custom> or <basis-license> and
00016 # </basis-license> (without trailing spaces) are preserved while all other
00017 # content is replaced by the template file. The customized sections are
00018 # inserted into the template in the order they appear in the original file
00019 # and the template file. If more custom sections are present in the original
00020 # file than in the template file, these custom sections are appended at the
00021 # end of the resulting file.
00022 #
00023 # See the documentation of BasisUpdate.cmake for further details.
00024 #
00025 # Copyright (c) 2011 University of Pennsylvania. All rights reserved.<br />
00026 # See https://www.cbica.upenn.edu/sbia/software/license.html or COPYING file.
00027 #
00028 # Contact: SBIA Group <sbia-software at uphs.upenn.edu>
00029 #
00030 # @ingroup CMakeHelpers
00031 
00032 # modules
00033 import os
00034 import sys
00035 import getopt
00036 
00037 # constants
00038 customTag  = "basis-custom"
00039 licenseTag = "basis-license"
00040 
00041 tokenCustomStart  = "<"  + customTag  + ">"
00042 tokenCustomEnd    = "</" + customTag  + ">"
00043 tokenLicenseStart = "<"  + licenseTag + ">"
00044 tokenLicenseEnd   = "</" + licenseTag + ">"
00045 tokenKeepTemplate = "REMOVE_THIS_STRING_IF_YOU_WANT_TO_KEEP_YOUR_CHANGES"
00046 
00047 # ****************************************************************************
00048 def version (progName):
00049     """Print version information."""
00050     print progName + "1.0.0"
00051 
00052 # ****************************************************************************
00053 def usage (progName):
00054     print "Usage:"
00055     print "  " + progName + " [options]"
00056     print
00057     print "Required options:"
00058     print "  [-i --in]       : Filename of original file"
00059     print "  [-t --template] : Filename of template file"
00060     print
00061     print "Options:"
00062     print "  [-o --out]     Filename of output file. If this option is not given,"
00063     print "                 changes are not applied and the exit code can be used"
00064     print "                 to check whether changes would have been applied."
00065     print "  [-f --force]   Force overwrite of output file. Otherwise ask user."
00066     print
00067     print "Return value:"
00068     print "  0   Merged output differs from input file and output file was"
00069     print "      written successfully if option -o or --out was given"
00070     print "  1   Failed to read or write file"
00071     print "  2   Nothing changed, input file not overwritten"
00072     print "  3   Merged output differs from input file but user chose not"
00073     print "      to overwrite input file"
00074     print "Example:"
00075     print "  " + progName + " -i CMakeLists.txt -t CMakeLists.txt.template -o CMakeLists.txt"
00076     print
00077     print "Contact:"
00078     print "  SBIA Group <sbia-software at uphs.upenn.edu>"
00079 
00080 # ****************************************************************************
00081 def help (progName):
00082     usage (progName)
00083 
00084 # ****************************************************************************
00085 def extractCustomizedSections (input):
00086     custom = []
00087     start  = 0
00088     while True:
00089         start = input.find (tokenCustomStart, start)
00090         if start == -1:
00091             break
00092         start += len (tokenCustomStart)
00093         next = input.find (tokenCustomStart, start)
00094         end  = input.find (tokenCustomEnd,   start)
00095         if end == -1 or (next != -1 and end > next):
00096             print "WARNING: Found begin of customized section without end token '" + tokenCustomEnd + "'"
00097             print "WARNING: Will keep template section instead"
00098             custom.append (tokenKeepTemplate)
00099         else:
00100             custom.append (input [start:end])
00101         start = next
00102     return custom
00103 
00104 # ****************************************************************************
00105 def replaceCustomizedSections (input, custom):
00106     start  = 0
00107     end    = -1
00108     result = input
00109     for section in custom:
00110         if start != -1:
00111             start = result.find (tokenCustomStart, start)
00112             end   = -1
00113         if start != -1:
00114             start += len (tokenCustomStart)
00115             end = result.find (tokenCustomEnd, start)
00116         if start == -1 or end == -1:
00117             if section != tokenKeepTemplate:
00118                 result = result + '\n'
00119                 result = result + '# ' + tokenCustomStart
00120                 result = result + section
00121                 result = result + tokenCustomEnd
00122         else:
00123             if section != tokenKeepTemplate:
00124                 result = result [:start] + section + result [end:]
00125                 end    = start + len (section)
00126         if end != -1:
00127             start = end + len (tokenCustomEnd)
00128     return result
00129 
00130 # ****************************************************************************
00131 def extractLicenseSections (input):
00132     license = []
00133     start   = 0
00134     while True:
00135         start = input.find (tokenLicenseStart, start)
00136         if start == -1:
00137             break
00138         start += len (tokenLicenseStart)
00139         next = input.find (tokenLicenseStart, start)
00140         end  = input.find (tokenLicenseEnd,   start)
00141         if end == -1 or (next != -1 and end > next):
00142             print "WARNING: Found begin of license section without end token '" + tokenLicenseEnd + "'"
00143             print "WARNING: Will keep template section instead"
00144             license.append (tokenKeepTemplate)
00145         else:
00146             license.append (input [start:end])
00147         start = next
00148     return license
00149 
00150 # ****************************************************************************
00151 def replaceLicenseSections (input, license):
00152     start  = 0
00153     end    = -1
00154     result = input
00155     for section in license:
00156         if start != -1:
00157             start = result.find (tokenLicenseStart, start)
00158         if start != -1:
00159             start += len (tokenLicenseStart)
00160             end = result.find (tokenLicenseEnd, start)
00161         if start != -1 and end != -1:
00162             if section != tokenKeepTemplate:
00163                 result = result [:start] + section + result [end:]
00164             start = end + len (tokenLicenseEnd)
00165     return result
00166 
00167 
00168 # ****************************************************************************
00169 def run (inputFile, templateFile, outputFile, force):
00170     # open input and output files
00171     try:
00172         fIn = open (inputFile, 'r')
00173     except IOError:
00174         sys.stderr.write ("Failed to open file '" + inputFile + "'\n")
00175         return 1
00176     try:
00177         fTem = open (templateFile, 'r')
00178     except IOError:
00179         sys.stderr.write ("Failed to open file '" + templateFile + "'\n")
00180         fIn.close ()
00181         return 1
00182     # read files
00183     input = fIn.read ()
00184     fIn.close ()
00185     result = fTem.read ()
00186     fTem.close ()
00187     # extract custom sections from input and substitute them in template
00188     sections = extractCustomizedSections (input)
00189     result   = replaceCustomizedSections (result, sections)
00190     # extract license sections from input and substitute them in template
00191     sections = extractLicenseSections (input)
00192     result   = replaceLicenseSections (result, sections)
00193     # check whether input file equals output file
00194     if inputFile == outputFile:
00195         # check if content changed
00196         if result == input:
00197             return 2
00198         # query user if file should be overwritten
00199         if not force:
00200             try:
00201                 sys.stdout.write ("Template of file '" + inputFile + "' has been modified.\n")
00202                 sys.stdout.write ("Do you want to apply the changes (basis-custom sections remain unchanged)? ")
00203                 sys.stdout.flush ()
00204                 apply = raw_input ()
00205             if apply != "y" and apply != "yes":
00206                     return 3
00207             except EOFError:
00208                 return 1
00209     # write result to output file if -o option given
00210     if outputFile != "":
00211         try:
00212             fOut = open (outputFile, 'w')
00213         except IOError:
00214             sys.stderr.write ("Failed to open file '" + outputFile + "'\n")
00215             return 1
00216         fOut.write (result)
00217         fOut.close ()
00218     # done
00219     if result == input:
00220         return 2
00221     return 0
00222 
00223 # ****************************************************************************
00224 if __name__ == "__main__":
00225     progName = os.path.basename (sys.argv [0])
00226     # options
00227     verbosity    = 0
00228     inputFile    = ""
00229     templateFile = ""
00230     outputFile   = ""
00231     force        = False
00232     # get options
00233     try:
00234         opts, files = getopt.gnu_getopt (sys.argv [1:], "uhvVfi:t:o:",
00235           ["usage","help","version","verbose","force","in=","template=","out="])
00236     except getopt.GetoptError, err:
00237         usage (progName)
00238         print str(err)
00239         sys.exit(1)
00240     # parse command line options
00241     for o, a in opts:
00242         if o in ["-V", "--verbose"]:
00243             verbosity += 1
00244         elif o in ["-h", "--help","-u","--usage"]:
00245             help (progName)
00246             sys.exit(0)
00247         elif o in ["-v", "--version", "--Version"]:
00248             version (progName)
00249             sys.exit(0)
00250         elif o in ["-f", "--force"]:
00251             force = True
00252         elif o in ["-i", "--in"]:
00253             inputFile = os.path.realpath(a)
00254         elif o in ["-t", "--template"]:
00255             templateFile = os.path.realpath(a)
00256         elif o in ["-o", "--out"]:
00257             outputFile = os.path.realpath(a)
00258         else:
00259             assert False, "unhandled option " + o
00260     # all required inputs specified ?
00261     if inputFile == "" or templateFile == "":
00262         usage (progName)
00263         sys.exit (1)
00264     # run
00265     sys.exit (run (inputFile, templateFile, outputFile, force))
00266