#!/usr/bin/python ################################################################ # # Author : ajack@apache.org # Version : 0.1 # ################################################################ # CHANGE HISTORY: # # Hack away folks... # ################################################################ # This code traverses a source tree replacing the top /* ... */ # (over multiple lines) with the contents of a text file, i.e. # removing an old license and inserting a new one. # # Usage python relicense.py [source root] [license file] # # NOTE #1: Currently this does not preserver copyright dates, # and such. This flaw (until resolved) might make this script # useless for all practical purposes. # # NOTE #2: # # THIS CODE MAY NOT WORK, DON'T RUN IT UNLESS YOU CAN # HANDLE THAT. # # DO NOT UNDER ANY CIRCUMSTANCES RUN THIS WITHOUT A FULL # BACKUP OF YOUR SOURCE TREE. # # DO A DIRECTORY COMPARISON, AFTER AN APPARENT RUN, TO # ENSURE NOTHING UNEXPECTED OCCURED. # ################################################################ #http://article.gmane.org/gmane.comp.jakarta.ant.devel/26477/match=copyright+years #This is from my "Taligent Guide to Designing Programs" # #"If you significantly modify a file, list the year of modification. #The years correspond to publication, not creation dates. Separate #consecutive years with a dash, but off-years with a comma" # #This was published in 1994, for C++ programs. How much of the above #is required and how much is merely convention, I'm not sure. Regardless, #the dates not listed do not imply there is no copyright asserted for #those years. # #Conor ################################################################ # # * Added support for alternative copyright owner passed in on # command line # * Improved exception reporting # # Robert ################################################################ import os.path import os import sys import re import string import traceback from time import gmtime, strftime class FileType: def __init__(self, startcomment, continuecomment, endcomment, endspec): self.start = startcomment self.cont = continuecomment self.end = endcomment self.endspec = endspec def printcopyright(licenseFile, copyrightOwner, copyrightyearsbefore, separator, lastcopyrightyear, currentyear, tmpStream, mytype, javatype, openHeader, closeHeader): #prints the copyright statement """licenseFile : path of the file containing the license file copyrightOwner : name of the new owner of the copyright copyrightyearsbefore : a string representing all the copyright years before the last separator : the character before the last original copyright year lastcopyrightyear : int the last copyright year from the original currentyear : int the current year tmpStream : the stream to write to mytype : information about the type of file javatype : information about java files (because the template is for java) openHeader : should we open the current header? closeHeader : should we close the current header? """ ###################################################################### # last copyright year is # < currentyear - 1 # add ,current # = currentyear - 1 # and is introduced by a comma or nothing # add -current # and is introduced by a dash # replace with current # = currentyear # do nothing ################################################################### licenseStream = None templateregex = re.compile('Copyright (\[yyyy\]) (\[name of copyright owner\])') try: try: # Transfer license header licenseStream = open(licenseFile, 'r') # Attach license at top of new file... line = licenseStream.readline() seenLicenseEnd = False while line: # Did we have some preamble lines? if line.startswith(javatype.start) and not openHeader and mytype.endspec: line = string.replace(line, javatype.start, '', 1) # Check to see if we've reached the end of the license block. if line.strip().startswith(javatype.end): seenLicenseEnd = True # first adjust the line to the conventions of the particular language if mytype.start != javatype.start and line.startswith(javatype.start): line = string.replace(line, javatype.start, mytype.start, 1) elif mytype.end != javatype.end and line.strip().startswith(javatype.end): line = string.replace(line, javatype.end, mytype.end, 1) elif mytype.cont != javatype.cont: line = string.replace(line, javatype.cont, mytype.cont, 1) match = templateregex.search(line) if match: if lastcopyrightyear < (int(currentyear) - 1): line = line.replace('[yyyy]', copyrightyearsbefore + separator + str(lastcopyrightyear) + "," + str(currentyear)) elif lastcopyrightyear == (int(currentyear) - 1): if separator == "-": line = line.replace('[yyyy]', copyrightyearsbefore + separator + str(currentyear)) else: line = line.replace('[yyyy]', copyrightyearsbefore + separator + str(lastcopyrightyear) + "-" + str(currentyear)) else: line = line.replace('[yyyy]', copyrightyearsbefore + separator + str(lastcopyrightyear)) line = line.replace( '[name of copyright owner]', copyrightOwner) # We may not want to close the header, so check for that first! if seenLicenseEnd and not closeHeader: line = None seenLicenseEnd = False else: tmpStream.write(line) line = licenseStream.readline() except Exception, details: print "Failed on: " + str(details) traceback.print_exc() finally: if licenseStream: licenseStream.close() return # Default values sourceTreeRoot='.' licenseFile = './license.txt' copyrightOwner = 'The Apache Software Foundation' mymap = {'xsl' : FileType("", True), 'xml' : FileType("", True), 'sh' : FileType("#", "#", "#", False), 'in' : FileType("#", "#", "#", False), 'cmd' : FileType("REM", "REM", "REM", False), 'bat' : FileType("REM", "REM", "REM", False), 'c' : FileType("/*"," *", "*/", True), 'h' : FileType("/*"," *", "*/", True), 'java' : FileType("/*"," *", "*/", True)} javatype = mymap['java'] usage="python relicense.py [source root] [license file] [copyright owner]" # if len(sys.argv) > 1: sourceTreeRoot=sys.argv[1] if len(sys.argv) > 2: licenseFile=sys.argv[2] if len(sys.argv) > 3: copyrightOwner=sys.argv[3] if len(sys.argv) > 4: print usage sys.exit(1) copyrightregex = re.compile('Copyright (?P[-,0-9]*)(?P[ ,-])?(?P\d\d\d\d)?.+' + copyrightOwner) copyrightend = re.compile(' limitations under the License.') extregexp=re.compile('.*\.([a-zA-Z]*)') if not os.path.exists(licenseFile): print 'License file [' + licenseFile + '] not found.' sys.exit(1) # # Traverse a tree # print 'Traverse [' + sourceTreeRoot + '] attaching license [' + licenseFile + ']' currentyear=strftime("%Y", gmtime()) for root, dirs, files in os.walk(sourceTreeRoot): for file in files: result = extregexp.match(file) if result and mymap.get(result.group(1)): # # get the file type information from the map # mytype = mymap[result.group(1)] sourceFile=os.path.join(root,file) tmpFile=os.path.join(root,file+'.tmp') print 'Convert [' + sourceFile + '] via [' + tmpFile + ']' # The java stream is the source file # The tmp stream is where things get written to (during transer) # The license stream is the new header fileStream=None tmpStream=None try: try: # Open tmp for writing... tmpStream=open(tmpFile,'w') # Open Java source file for reading... fileStream=open(sourceFile, 'r') # Transfer source, skipping first comment. line = fileStream.readline() inHeader = False pastHeader = False seenCopyright = False seenPreamble = False # default for first copyright year lastcopyrightyear = currentyear copyrightyearsbefore = "" separator = " " while line: if not inHeader and line.strip().startswith(mytype.start): inHeader = True seenPreamble = False if inHeader: while line: mymatch = copyrightregex.search(line) if mymatch: copyrightyearsbefore = mymatch.group('copyrightyearsbefore') separator = mymatch.group('separator') lastcopyrightyear = mymatch.group('lastcopyrightyear') if lastcopyrightyear == None: lastcopyrightyear = currentyear else: lastcopyrightyear = int(lastcopyrightyear) seenCopyright = True elif not seenCopyright: # this line doesn't concern us yet. skip it. # It could be a necessary preamble. tmpStream.write(line) seenPreamble = True if mytype.endspec and \ line.strip().endswith(mytype.end.strip()) or \ not mytype.endspec and not \ line.strip().startswith(mytype.cont.strip()): line = fileStream.readline() inHeader = False break if seenCopyright and copyrightend.search(line): # Skip to the next line break # Read the next line line = fileStream.readline() if seenCopyright: printcopyright(licenseFile, copyrightOwner, copyrightyearsbefore, separator, lastcopyrightyear, currentyear, tmpStream, mytype, javatype, not seenPreamble, not inHeader) seenCopyright = False # do not go past EOF if not line: break elif not inHeader: tmpStream.write(line) #else: # print 'Skip ' + line line = fileStream.readline() finally: if fileStream: fileStream.close() if tmpStream: tmpStream.close() # Remove the original os.remove(sourceFile) # Replace with temporary (enhanced) os.rename(tmpFile,sourceFile) except Exception, details: print "Failed on: " + sourceFile + ':' + str(details) traceback.print_exc() # Cleanup if failed.. if os.path.exists(tmpFile): os.remove(tmpFile)