IAP GITLAB

Skip to content
Snippets Groups Projects
do-copyright.py 9.32 KiB
Newer Older
ralfulrich's avatar
ralfulrich committed
#!/usr/bin/env python3
ralfulrich's avatar
ralfulrich committed
#
# (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu
#
# This software is distributed under the terms of the GNU General Public
# Licence version 3 (GPL Version 3). See file LICENSE for a full version of
# the license.
#

"""
Script to crawl all files (hpp,inl,cpp) in a directory structure and
check if there is an initial comment block within each file that
resembles the CORSIKA 8 copyright notice.

Exceptions can be specified in `excludeDirs` and `excludedFiles`.
"""


ralfulrich's avatar
ralfulrich committed
import os
import sys, getopt
import re
"""
 Note: this is technically used as a mutliline regexp
"""
text = """/*
ralfulrich's avatar
ralfulrich committed
 * (c) Copyright YEAR CORSIKA Project, corsika-project@lists.kit.edu
ralfulrich's avatar
ralfulrich committed
 *
 * This software is distributed under the terms of the GNU General Public
 * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
 * the license.
ralfulrich's avatar
ralfulrich committed
 */\n
"""
Debug settings are 0: nothing, 1: checking, 2: filesystem
"""
Debug = 0 
ralfulrich's avatar
ralfulrich committed
excludeDirs = ["./modules", "./externals", "build", "install", "git", "framework/units", "Random123"]
ralfulrich's avatar
ralfulrich committed
excludeFiles = ['PhysicalConstants.h','CorsikaFenvOSX.cc', 'sgn.h', 'quartic.h']
extensions = [".cpp", ".inl", ".hpp"]
"""
justCheck: T: only checking, F: also changing files 
"""
justCheck = True
"""
foundMissing: global variable set to True in case of any needed action 
"""
ralfulrich's avatar
ralfulrich committed
foundMissing = False
ralfulrich's avatar
ralfulrich committed
"""
updateMessage: T: update message, put preserve the YEAR, F: no special action
"""
updateMessage = False
"""
forYear: replace this with year for copyright notice via command line
"""
forYear="YEAR" 
ralfulrich's avatar
ralfulrich committed


ralfulrich's avatar
ralfulrich committed
###############################################
#
ralfulrich's avatar
ralfulrich committed
def checkNote(filename, justCheck, forYear, updateMessage):
    """
    function to check, if the file 'filename' contains an exact copy
    of the copyright notice defined above. 
    The function also checks for eventual multiple (maybe conflicting) 
    copyright notices in the same file. 
    
    If 'justCheck' is True, the file will never be changed, otherwise
    the function will attempt to put the correct notice exactly once 
    at the top of the file. The copyright YEAR will be replace with 
    'forYear', e.g. forYear="2019" or forYear="2018-2020", etc.
ralfulrich's avatar
ralfulrich committed

    If 'updateMessage' is True, then the value of Year will be determined 
    from the previous copyright message, if possible. Thus, the copyright year
    is preserved. 
    
    The global variable 'foundMissing' is set to True in the event 
    where any changes are identified, BUT not implemented. 
    """
    global foundMissing
ralfulrich's avatar
ralfulrich committed
    if Debug>0:
        print ("***********************************************")
        print ("file: " + filename )
    
ralfulrich's avatar
ralfulrich committed
    startNote = []
    endNote = []
    
    """ read input file into lines """
ralfulrich's avatar
ralfulrich committed
    lines = []
ralfulrich's avatar
ralfulrich committed
    with open(filename, "r", encoding="utf-8") as file:
        for line in file.readlines():
            lines.append(line)            
        file.close()
    
    """ 0:before comment block, #1 in comment block, #2 found copyright """
    searchStatus = 0 
ralfulrich's avatar
ralfulrich committed
    blockStart = 0
    for iLine in range(len(lines)):
        line = lines[iLine]
ralfulrich's avatar
ralfulrich committed
        if "/*" in line and searchStatus==0:
            searchStatus = 1
            blockStart = iLine
        if "copyright" in line.lower() and searchStatus>0:
            searchStatus = 2
        if "*/" in line:
            if searchStatus>=2:
                startNote.append(blockStart)
                endNote.append(iLine)
ralfulrich's avatar
ralfulrich committed
            searchStatus = 0
ralfulrich's avatar
ralfulrich committed
    if Debug>0:
        txt = "states: n=" + str(len(startNote))
ralfulrich's avatar
ralfulrich committed
        for i in range(len(startNote)):
ralfulrich's avatar
ralfulrich committed
            txt += ",  [" + str(startNote[i]) + "-" + str(endNote[i]) + "]"         
ralfulrich's avatar
ralfulrich committed
        print ("stats: " + txt)
    
    """ now check if first copyright notices is already identical... """
    isSame = False
ralfulrich's avatar
ralfulrich committed
    prevYear = ""
ralfulrich's avatar
ralfulrich committed
    if len(startNote)>0: 
        isSame = True
ralfulrich's avatar
ralfulrich committed
        noteLines = text.split('\n')
        for iLine in range(len(noteLines)-2):
ralfulrich's avatar
ralfulrich committed
            if startNote[0]+iLine >= len(lines):
ralfulrich's avatar
ralfulrich committed
                break
ralfulrich's avatar
ralfulrich committed

            # only if updateMessage is True:
            # read the YEAR from old message to use it for new message (->update message)
            if updateMessage:
ralfulrich's avatar
ralfulrich committed
                regexYear = re.compile(r'.*Copyright\s+(\d\d\S*)\s.*')
ralfulrich's avatar
ralfulrich committed
                matchYear = re.match(regexYear, lines[startNote[0]+iLine].strip(" \n"))
ralfulrich's avatar
ralfulrich committed
                if matchYear and len(matchYear.groups())>0:
                    prevYear = str(matchYear.groups()[0])
ralfulrich's avatar
ralfulrich committed
            # compare old with new message here:
ralfulrich's avatar
ralfulrich committed
            regex = re.compile(re.escape(noteLines[iLine].strip(" \n")).replace('YEAR','(..+)'))
            match = re.match(regex, lines[startNote[0]+iLine].strip(" \n"))
            if not match:
ralfulrich's avatar
ralfulrich committed
                if isSame:
                    print ("needs update: " + filename + "\n   [diff] new=\'" + noteLines[iLine] +
                           "\' vs old=\'" + lines[startNote[0]+iLine].rstrip('\n') + "\'")
                isSame = False
ralfulrich's avatar
ralfulrich committed
                foundMissing = True
ralfulrich's avatar
ralfulrich committed
    if Debug>0:
ralfulrich's avatar
ralfulrich committed
        print ("isSame=" + str(isSame) + " " + str(len(startNote)))
ralfulrich's avatar
ralfulrich committed
    
    """ check if notice is the same, or we need to remove multiple notices... """
ralfulrich's avatar
ralfulrich committed
    if isSame and len(startNote)<=1:
ralfulrich's avatar
ralfulrich committed
    if (len(startNote)==0):
        print ("No copyright note in file: " + filename)
    
    """ either we found a file with no copyright, or with wrong copyright notice here """
ralfulrich's avatar
ralfulrich committed
    if justCheck:
        foundMissing = True
        return
    """ add (new) copyright notice here: """
    print ("   [file] " + filename + " --> backup to " + filename+".bak")
    os.rename(filename, filename+".bak")
    with open(filename, "w") as file:
ralfulrich's avatar
ralfulrich committed

        if len(prevYear)>0:
            textReplace = re.sub(r"Copyright YEAR ", "Copyright " + prevYear + " ", text)
        else:
            textReplace = re.sub(r"Copyright YEAR ", "Copyright " + forYear + " ", text)
ralfulrich's avatar
ralfulrich committed
        file.write(textReplace)
        
ralfulrich's avatar
ralfulrich committed
        skip = False
ralfulrich's avatar
ralfulrich committed
        for iLine in range(len(lines)):
ralfulrich's avatar
ralfulrich committed
            inBlock = False
ralfulrich's avatar
ralfulrich committed
            for iBlock in range(len(startNote)):
ralfulrich's avatar
ralfulrich committed
                if iLine>=startNote[iBlock] and iLine<=endNote[iBlock]:
ralfulrich's avatar
ralfulrich committed
                    print ("   [remove " + str(iBlock) + "] " + (lines[iLine]).strip())
ralfulrich's avatar
ralfulrich committed
                    inBlock = True
ralfulrich's avatar
ralfulrich committed
                    skip = True
ralfulrich's avatar
ralfulrich committed
            if inBlock:
                continue

            """ if line after comment is empty, also remove it """
            if lines[iLine].strip() != "":
ralfulrich's avatar
ralfulrich committed
                skip = False
                    
ralfulrich's avatar
ralfulrich committed
            if not skip:
                file.write(lines[iLine])
ralfulrich's avatar
ralfulrich committed
###############################################
#
ralfulrich's avatar
ralfulrich committed
def next_file(dir_name, files, justCheck, forYear, updateMessage):
    """
    check files: loops over list of files,
    excludes if wished, process otherwise
    """
    for check in excludeDirs :
        if check in dir_name:
ralfulrich's avatar
ralfulrich committed
            if Debug>1:
ralfulrich's avatar
ralfulrich committed
                print ("exclude-dir: " + check, dir_name)
ralfulrich's avatar
ralfulrich committed
            return True
ralfulrich's avatar
ralfulrich committed
        if (os.path.isdir(check)):
            continue
        filename, file_extension = os.path.splitext(check)
        if '#' in check or '~' in check:
ralfulrich's avatar
ralfulrich committed
            continue
ralfulrich's avatar
ralfulrich committed
        excludeThisFile=False
        for check2 in excludeFiles :
            if check2 in check:
ralfulrich's avatar
ralfulrich committed
                if Debug>1:
                    print ("exclude: " + check2)
ralfulrich's avatar
ralfulrich committed
                excludeThisFile=True
        if excludeThisFile:
            continue
        if file_extension in extensions:
ralfulrich's avatar
ralfulrich committed
            checkNote(os.path.join(dir_name, check), justCheck, forYear, updateMessage)
ralfulrich's avatar
ralfulrich committed
        else:
            if Debug>1:
ralfulrich's avatar
ralfulrich committed
                print ("exclude-extension: " + os.path.join(dir_name, check))

                
###############################################
ralfulrich's avatar
ralfulrich committed
def main(argv):
   """
    the main program
   """
ralfulrich's avatar
ralfulrich committed
   global justCheck, foundMissing, updateMessage, Debug, forYear
ralfulrich's avatar
ralfulrich committed
   justCheck = True
ralfulrich's avatar
ralfulrich committed
   updateMessage = False
ralfulrich's avatar
ralfulrich committed
   Debug = 0
   try:
ralfulrich's avatar
ralfulrich committed
      opts, args = getopt.getopt(argv, "cnuA:hd:", ["check", "no-check", "update-message", "add=", "help", "debug="])
ralfulrich's avatar
ralfulrich committed
   except getopt.GetoptError:
ralfulrich's avatar
ralfulrich committed
      print ('do-copyright.py [--check/--no-check] [--add=YEAR] [--update-message] [--debug=0]')
ralfulrich's avatar
ralfulrich committed
      sys.exit(2)
   for opt, arg in opts:
ralfulrich's avatar
ralfulrich committed
      if opt in ("-h", "--help"):
ralfulrich's avatar
ralfulrich committed
         print ('do-copyright.py [--check/--no-check] [--add=YEAR] [--update-message] [--debug=0]')
ralfulrich's avatar
ralfulrich committed
         sys.exit() 
ralfulrich's avatar
ralfulrich committed
      elif opt in ("-c", "--check"):
ralfulrich's avatar
ralfulrich committed
         justCheck = True
      elif opt in ("-n", "--no-check"):
         justCheck = False         
ralfulrich's avatar
ralfulrich committed
      elif opt in ("-u", "--update-message"):
         updateMessage = True
         print ('Preserve YEAR of existing message, where possible.')
ralfulrich's avatar
ralfulrich committed
      elif opt in ("-A", "--add"):
ralfulrich's avatar
ralfulrich committed
         justCheck = False
ralfulrich's avatar
ralfulrich committed
         forYear = str(arg)
ralfulrich's avatar
ralfulrich committed
         print ('Adding \'Copyright ' + forYear + '\' notice, where needed. ')
ralfulrich's avatar
ralfulrich committed
      elif opt in ("-d", "--debug"):
         Debug = int(arg)

   if justCheck:
ralfulrich's avatar
ralfulrich committed
       print ('Only checking. No changes. See \'do-copyright.py -h\' for options.')
ralfulrich's avatar
ralfulrich committed
   else:
ralfulrich's avatar
ralfulrich committed
       print ('** WARN: THIS WILL MODIFY YOUR FILES! ** ')
ralfulrich's avatar
ralfulrich committed
   for root, dirs, files in os.walk('./'):
ralfulrich's avatar
ralfulrich committed
       next_file(root, files, justCheck, forYear, updateMessage)
ralfulrich's avatar
ralfulrich committed
    
###############################################
ralfulrich's avatar
ralfulrich committed
if __name__ == "__main__":
   """
   main python entry point 
   """
ralfulrich's avatar
ralfulrich committed
   main(sys.argv[1:])

   if justCheck and foundMissing:
       """ found any need for action """
       sys.exit(-1)
ralfulrich's avatar
ralfulrich committed
   print ("Finished")
ralfulrich's avatar
ralfulrich committed
   sys.exit(0)