1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
|
#!/usr/bin/env python3
# Copyright 2021-2023 The Mumble Developers. All rights reserved.
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file at the root of the
# Mumble source tree or at <https://www.mumble.info/LICENSE>.
import argparse
import os
import platform
import subprocess
import datetime
from string import Template
import re
def cmd(args):
shell = platform.system() == 'Windows'
p = subprocess.Popen(args, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
if p.returncode != 0:
raise Exception('cmd(): {0} failed with status {1}: {2}'.format(args, p.returncode, stderr))
return stdout.decode('utf-8')
licenseHeaderTemplate = Template(
"""${commentChar} Copyright ${copyright} The Mumble Developers. All rights reserved.
${commentChar} Use of this source code is governed by a BSD-style license
${commentChar} that can be found in the LICENSE file at the root of the
${commentChar} Mumble source tree or at <https://www.mumble.info/LICENSE>.""")
blackListFiles = []
class InvalidFileError(Exception):
pass
class LicenseNotFoundError(Exception):
pass
def generateHeaderForFile(filePath):
if filePath in blackListFiles:
raise InvalidFileError("File is blacklisted: \"" + filePath + "\"")
fileName = os.path.basename(filePath)
extension = os.path.splitext(fileName)[-1]
commentChar = None
if extension in [".cpp", ".c", ".h", ".hpp", ".cs", ".ice", ".proto", ".mm", ".vs", ".m", ".ps", ".hlsl", ".fx"]:
commentChar = "//"
elif extension in [".bash", ".sh", ".py", ".yaml", ".yml", ".ps1", ".pl", ".cmake", ".rb", ".init"] or fileName == "CMakeLists.txt":
commentChar = "#"
elif extension in [".bat", ".cmd"]:
commentChar = "::"
if commentChar is None:
raise InvalidFileError("Can't handle extension: \"" + extension + "\"")
# Get file's creation date
creationDateStr = cmd(["git", "log", "--diff-filter=A", "--format=%ci", "-1", "--", filePath]).strip()
if not creationDateStr:
raise InvalidFileError("File not in git index: \"" + filePath + "\"")
# We only need the day, month and year
creationDate = datetime.datetime.fromisoformat(creationDateStr.split()[0])
copyright = None
if creationDate.year == datetime.datetime.today().year:
copyright = str(creationDate.year)
else:
copyright = str(creationDate.year) + "-" + str(datetime.datetime.today().year)
return licenseHeaderTemplate.substitute(commentChar=commentChar, copyright=copyright)
def findCopyright(content, pattern):
match = re.search(pattern, content)
if match:
return (match.start(), match.end())
else:
raise LicenseNotFoundError()
def exclude(path, excludes):
for current in excludes:
if path.startswith(current):
return True
return False
def main():
parser = argparse.ArgumentParser(description="Updates (and adds) license headers of the Mumble source files")
parser.add_argument("-a", "--add-new", action="store_true", default=False, help="Also add license headers to files that did not contain one before")
args = parser.parse_args()
scriptDir = os.path.dirname(os.path.realpath(__file__))
rootDir = os.path.realpath(os.path.join(scriptDir, ".."))
excludePaths = ["build", "docs", "3rdparty", "3rdPartylLicenses", "themes", ".git", "man"]
for i in range(len(excludePaths)):
excludePaths[i] = os.path.join(rootDir, excludePaths[i])
for i in range(len(blackListFiles)):
blackListFiles[i] = os.path.join(rootDir, blackListFiles[i])
# Convert the license template into a pattern that matches the license declaration inside the source files
licensePattern = re.compile(licenseHeaderTemplate.substitute(copyright="\d+(-\d+)?", commentChar=".+?").strip().replace("\n", "\\r?\\n").replace(" ",
"\\s*"), re.IGNORECASE)
modifiedFiles = 0
for root, dirs, files in os.walk(rootDir, topdown = True):
for currentFile in files:
fullPath = os.path.join(root, currentFile)
if exclude(fullPath, excludePaths):
continue
try:
generatedCopyright = generateHeaderForFile(fullPath)
fileHandle = open(fullPath, "r")
fileContent = fileHandle.read()
try:
start,end = findCopyright(fileContent, licensePattern)
except LicenseNotFoundError:
# no license yet
if not args.add_new:
print("No license header found for", fullPath)
continue
else:
print("Adding license header to", fullPath)
if not fileContent.startswith("#!"):
# File does not start with shebang -> write license to the top
start = 0
end = 0
generatedCopyright += "\n\n"
else:
# File starts with shebang -> append license after the first line (and add
# an extra blank line)
start = fileContent.find("\n") + 1
assert start > 0
end = start
generatedCopyright = "\n" + generatedCopyright + "\n\n"
modifiedContent = fileContent[ : start] + generatedCopyright + fileContent[end : ]
if modifiedContent != fileContent:
fileHandle.close()
# Open for writing
fileHandle = open(fullPath, "w")
modifiedFiles += 1
fileHandle.write(modifiedContent)
except InvalidFileError:
continue
print("Modified {} files".format(modifiedFiles))
if __name__ == "__main__":
main()
|