from git import Repo import datetime import os import re from typing import Tuple import sys REDCOLOR = "\033[1;31m" GREENCOLOR = "\033[1;32m" YELLOWCOLOR = "\033[1;33m" BLUECOLOR = "\033[1;34m" RESETCOLOR = "\033[0m" HEADER_PATTERN = re.compile(r"^.*\*{71}.*$(?:\n.*)*?^.*\*{71}.*\n", re.MULTILINE) def get_source_dir(envVar): sd = os.environ.get(envVar, None) if sd is None: print(f"{REDCOLOR}ERROR! Enviromental variable {envVar} is not set. Perhapse you forgot to set it in your shell profile?{RESETCOLOR}") exit(1) return sd def get_git_log(repoPath, filePath): """ Retrieves the git commit history for a specific file. :param repoPath: Path to the Git repository :param filePath: Path to the file relative to the repo root :return: List of commit messages with commit hashes """ repo = Repo(repoPath) commits = list(repo.iter_commits(paths=filePath)) # Get commits affecting the file logEntries = [] for commit in commits: logEntries.append(f"{commit.hexsha[:7]} - {commit.author.name} - {commit.committed_date}") return logEntries def get_all_file_authors(logEntries): """ Retrieves the authors of the commits in a list of log entries. :param logEntries: List of log entries :return: List of authors """ authors = [] for entry in logEntries: authors.append(entry.split(" - ")[1]) return set(authors) def get_last_modification_date(logEntries): """ Retrieves the last modification date of the file. :param logEntries: List of log entries :return: Last modification date """ return datetime.datetime.fromtimestamp(int(logEntries[0].split(" - ")[2])) def select_comment_chars(filename) -> Tuple[str, str, str]: fileExtension = filename.split(".")[-1] #switch statement based on file extension match fileExtension: case "py": return ("\"\"\"", "\"\"\"", "#") case "c" | "cpp" | "h" | "hpp": return ("/*", "*/", "//") case "f90" | "f": return ("!", "!", "!") case "sh": return ("#", "#", "#") case "cmake": return ("#", "#", "#") case "build": return ("#", "#", "#") case _: return ("#", "", "#") def make_header(authors, date, filename, projectName): authorSuffix = "s" if len(authors) > 1 else "" authorsString = ', '.join(authors) bs, be, c = select_comment_chars(filename) defaultHeader = f"""{bs} *********************************************************************** {c} {c} Copyright (C) {date.year} -- The 4D-STAR Collaboration {c} File Author{authorSuffix}: {authorsString} {c} Last Modified: {date.date().strftime("%B %d, %Y")} {c} {c} {projectName} is free software; you can use it and/or modify {c} it under the terms and restrictions the GNU General Library Public {c} License version 3 (GPLv3) as published by the Free Software Foundation. {c} {c} {projectName} is distributed in the hope that it will be useful, {c} but WITHOUT ANY WARRANTY; without even the implied warranty of {c} MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. {c} See the GNU Library General Public License for more details. {c} {c} You should have received a copy of the GNU Library General Public License {c} along with this software; if not, write to the Free Software {c} Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA {c} {c} *********************************************************************** {be}\n""" return defaultHeader if __name__ == "__main__": # Ensure python version at least 3.10 if sys.version_info < (3, 10): print(f"{REDCOLOR}ERROR! Python version 3.10 or higher is required{RESETCOLOR}") import argparse parser = argparse.ArgumentParser(description="Format the header of a file") parser.add_argument("file", type=str, help="The file to format") parser.add_argument("--root", type=str, default="_4DSSE_ROOT", help="The name of the environmental variable that points to the root of the project") parser.add_argument("--project", type=str, default="4DSSE", help="The name of the project") args = parser.parse_args() absPath = os.path.abspath(args.file) fileName = os.path.basename(args.file) if fileName == "formatHeader.py": print(f"{BLUECOLOR}NOTE! Cannot format the formatHeader script{RESETCOLOR}") exit(0) assert os.path.exists(absPath), f"{REDCOLOR}ERROR! The file {absPath} does not exist{RESETCOLOR}" sd = get_source_dir(args.root) if not absPath.startswith(sd): print(f"{REDCOLOR}ERROR! The file {absPath} is not in the source directory {sd}{RESETCOLOR}") exit(1) logEntry = get_git_log(sd, absPath) authors = get_all_file_authors(logEntry) lastModificationDate = get_last_modification_date(logEntry) header = make_header(authors, lastModificationDate, args.file, args.project) headerNumLines = header.count("\n") # check if the first lines of the file are the header with open(args.file, "r") as f: # Add the header to the file with open(args.file, "r") as f: fileContent = f.read() # remove any old version of the header fileContent = HEADER_PATTERN.sub("", fileContent) with open(args.file, "w") as f: f.write(header) f.write(fileContent)