ci(hooks/formatHeader): script to auto format file headers added
This commit is contained in:
42
commit-config/hooks/READEME.md
Normal file
42
commit-config/hooks/READEME.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Hook for adding and maintaing file headers automatically
|
||||
All 4DSSE files should have a header specifying authors, last edits, copyright, and liscence information.
|
||||
|
||||
Hook should be setup automatically to run on each commit.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- GitPython
|
||||
- Python (>=3.10)
|
||||
|
||||
## Usage
|
||||
This script assumes that an enviromental variable has been set which points to the root of the 4DSSE project. This is used to retreive the git log so that author information and last update date information may be read. By default this uses the enviromental variable `_4DSSE_ROOT`; however, this may be changed with the `--root` command line flag. Whatever is set as `--root` will be the name of the enviromental variable this script will search for.
|
||||
|
||||
General usage might look like
|
||||
```bash
|
||||
python formatHeader.py ../../src/meshIO/private/meshIO.cpp
|
||||
```
|
||||
|
||||
This will automatically add the following (or similar...) header to the file
|
||||
|
||||
```
|
||||
/* ***********************************************************************
|
||||
//
|
||||
// Copyright (C) 2025 -- The 4D-STAR Collaboration
|
||||
// File Author: Emily Boudreaux
|
||||
// Last Modified: February 16, 2025
|
||||
//
|
||||
// 4DSSE is free software; you can use it and/or modify
|
||||
// it under the terms and restrictions the GNU General Library Public
|
||||
// License version 3 (GPLv3) as published by the Free Software Foundation.
|
||||
//
|
||||
// 4DSSE is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// See the GNU Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public License
|
||||
// along with this software; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
// *********************************************************************** */
|
||||
```
|
||||
149
commit-config/hooks/formatHeader.py
Executable file
149
commit-config/hooks/formatHeader.py
Executable file
@@ -0,0 +1,149 @@
|
||||
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)
|
||||
|
||||
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)
|
||||
Reference in New Issue
Block a user