init
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright 2021-2023, Arm Limited and Contributors
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 the "License";
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import datetime
|
||||
import io
|
||||
from shutil import which
|
||||
|
||||
from subprocess import check_output
|
||||
|
||||
# Patterns to search for in the files - Order is important!
|
||||
COPYRIGHT_PATTERNS = [
|
||||
re.compile(r"\bCopyright \(c\)[^a-zA-Z0-9]*\b\d{4}-\d{4}\b", re.IGNORECASE),
|
||||
re.compile(r"\bCopyright[^a-zA-Z0-9]*\b\d{4}-\d{4}\b", re.IGNORECASE),
|
||||
re.compile(r"\bCopyright \(c\)[^a-zA-Z0-9]*\b\d{4}\b", re.IGNORECASE),
|
||||
re.compile(r"\bCopyright[^a-zA-Z0-9]*\b\d{4}\b", re.IGNORECASE),
|
||||
]
|
||||
|
||||
YEAR_PATTERN = re.compile(r"\b\d{4}\b")
|
||||
YEAR_RANGE_PATTERN = re.compile(r"\b\d{4}-\d{4}\b")
|
||||
|
||||
EXCEPTION_FILE = ".copyrightignore"
|
||||
|
||||
class terminal_colors:
|
||||
SUCCESS = "\033[92m"
|
||||
INFO = "\033[94m"
|
||||
WARNING = "\033[33m"
|
||||
ERROR = "\033[91m"
|
||||
END = "\033[0m"
|
||||
|
||||
# Get the file extension
|
||||
def get_ext(file_path):
|
||||
file_name = os.path.basename(file_path)
|
||||
file_name, file_ext = os.path.splitext(file_name)
|
||||
return file_ext
|
||||
|
||||
# Query files for the presence of a valid year in the header
|
||||
def query_files(files):
|
||||
failed_queries = {}
|
||||
queries = {}
|
||||
|
||||
for filename in files:
|
||||
with io.open(filename, "r+", encoding="utf-8") as f:
|
||||
queries[filename] = None
|
||||
file_contents = f.read()
|
||||
for query in COPYRIGHT_PATTERNS:
|
||||
try:
|
||||
matches = re.findall(query, file_contents)
|
||||
if len(matches) > 0:
|
||||
queries[filename] = matches
|
||||
break # Stop searching after the first match
|
||||
except re.error as error:
|
||||
failed_queries[filename] = error
|
||||
|
||||
return queries, failed_queries
|
||||
|
||||
# Check the files for the presence of a valid year in the header
|
||||
def check_files(check_files):
|
||||
queries, failures = query_files(check_files)
|
||||
|
||||
current_year = datetime.datetime.now().year
|
||||
|
||||
missing = []
|
||||
outdated = {}
|
||||
|
||||
for filename, tokens in queries.items():
|
||||
if not tokens:
|
||||
missing.append(filename)
|
||||
continue
|
||||
|
||||
outdated[filename] = None
|
||||
|
||||
for token in tokens:
|
||||
copyright_years = re.findall(YEAR_PATTERN, token)
|
||||
|
||||
most_recent_year = int(copyright_years[-1]) if copyright_years else 0
|
||||
|
||||
if most_recent_year != current_year:
|
||||
if not outdated[filename]:
|
||||
outdated[filename] = []
|
||||
outdated[filename].append(token)
|
||||
|
||||
outdated = {k: v for k, v in outdated.items() if v is not None}
|
||||
return missing, outdated, failures
|
||||
|
||||
def fix(file):
|
||||
queries, failures = query_files([file])
|
||||
|
||||
current_year = datetime.datetime.now().year
|
||||
|
||||
|
||||
for filename, tokens in queries.items():
|
||||
if not tokens:
|
||||
continue
|
||||
|
||||
for token in tokens:
|
||||
year_range = YEAR_RANGE_PATTERN.search(token)
|
||||
if year_range:
|
||||
year_range = year_range.group(0)
|
||||
start_year, end_year = year_range.split("-")
|
||||
if int(end_year) != current_year:
|
||||
fixed_token = token.replace(year_range, start_year + "-" + str(current_year))
|
||||
with io.open(filename, "r+", encoding="utf-8") as f:
|
||||
file_contents = f.read()
|
||||
file_contents = file_contents.replace(token, fixed_token)
|
||||
f.seek(0)
|
||||
f.write(file_contents)
|
||||
f.truncate(
|
||||
f.tell()
|
||||
)
|
||||
continue
|
||||
|
||||
year = YEAR_PATTERN.search(token)
|
||||
if year:
|
||||
year = year.group(0)
|
||||
if int(year) != current_year:
|
||||
fixed_token = token + "-" + str(current_year)
|
||||
with io.open(filename, "r+", encoding="utf-8") as f:
|
||||
file_contents = f.read()
|
||||
file_contents = file_contents.replace(token, fixed_token)
|
||||
f.seek(0)
|
||||
f.write(file_contents)
|
||||
f.truncate(
|
||||
f.tell()
|
||||
) # Truncate the file to the current position of the file pointer
|
||||
|
||||
if __name__ == "__main__":
|
||||
argument_parser = argparse.ArgumentParser(
|
||||
description="Check that modified files include copyright headers with current year."
|
||||
)
|
||||
argument_parser.add_argument(
|
||||
"branch",
|
||||
type=str,
|
||||
default="main",
|
||||
nargs="?",
|
||||
help="Branch from which to compute the diff",
|
||||
)
|
||||
argument_parser.add_argument(
|
||||
"--fix",
|
||||
action="store_true",
|
||||
help="Fix the files that are missing the header",
|
||||
default=False,
|
||||
)
|
||||
args = argument_parser.parse_args()
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
argument_parser.print_help(sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
files = None
|
||||
|
||||
if not which("git"):
|
||||
print(terminal_colors.ERROR + "Missing git" + terminal_colors.END)
|
||||
sys.exit(1)
|
||||
|
||||
file_exceptions = [EXCEPTION_FILE]
|
||||
|
||||
try:
|
||||
ignored = open(EXCEPTION_FILE).readlines()
|
||||
for file in ignored:
|
||||
file_exceptions.append(file.strip())
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
out = check_output(["git", "diff", args.branch, "--name-only"])
|
||||
|
||||
files = out.decode("utf-8").split("\n")
|
||||
|
||||
files_to_check = list(
|
||||
filter(
|
||||
lambda x: os.path.isfile(x)
|
||||
and os.path.basename(x) not in file_exceptions
|
||||
and get_ext(x) not in file_exceptions
|
||||
and len(x) > 0,
|
||||
files,
|
||||
)
|
||||
)
|
||||
|
||||
if files_to_check and len(files_to_check) > 0:
|
||||
missing, outdated, failures = check_files(files_to_check)
|
||||
|
||||
if len(failures) > 0:
|
||||
print(terminal_colors.ERROR + "Failed to search:" + terminal_colors.END)
|
||||
|
||||
for filename, error in failures.items():
|
||||
print(filename)
|
||||
|
||||
print()
|
||||
|
||||
if len(missing) > 0:
|
||||
print(terminal_colors.ERROR + "Missing copyright:" + terminal_colors.END)
|
||||
|
||||
for filename in missing:
|
||||
print(filename)
|
||||
|
||||
print()
|
||||
|
||||
if len(outdated) > 0:
|
||||
print(terminal_colors.ERROR + "Outdated copyright:" + terminal_colors.END)
|
||||
|
||||
for filename, tokens in outdated.items():
|
||||
if args.fix:
|
||||
fix(filename)
|
||||
print(terminal_colors.SUCCESS + "Fixed " + filename + terminal_colors.END)
|
||||
else:
|
||||
print(filename)
|
||||
for token in tokens:
|
||||
print("\t", terminal_colors.WARNING + token + terminal_colors.END)
|
||||
|
||||
print()
|
||||
|
||||
print("\n=== Files Checked ===")
|
||||
for filename in files_to_check:
|
||||
print(terminal_colors.INFO + filename + terminal_colors.END)
|
||||
|
||||
if len(outdated) > 0 or len(missing) > 0:
|
||||
sys.exit(-1)
|
||||
else:
|
||||
sys.exit(0)
|
||||
else:
|
||||
print(terminal_colors.INFO + "No files found" + terminal_colors.END)
|
||||
Reference in New Issue
Block a user