diff --git a/build.py b/build.py index 556a631bbaa4bd046c26451a88d312f36018f662..fb487d5c4e046155ad6e6d4ec8c7171194e3bbdb 100755 --- a/build.py +++ b/build.py @@ -312,8 +312,9 @@ def run_init(): for hooks_dir in hooks_directories: if not os.path.exists(hooks_dir): os.makedirs(hooks_dir) - copy_file("./extras/scripts/commit-msg", hooks_dir + "/commit-msg") - execute_script(['./extras/scripts/format.sh --install %(path)s'], + copy_file("./extras/scripts/commit-msg", + os.path.join(hooks_dir, "commit-msg")) + execute_script(['./extras/scripts/format.py --install %(path)s'], {"path": hooks_dir}) diff --git a/extras/scripts/format.py b/extras/scripts/format.py new file mode 100755 index 0000000000000000000000000000000000000000..4676c592a3f656cbee1356433d7b562d9b1dd071 --- /dev/null +++ b/extras/scripts/format.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +""" +Clang format C/C++ source files with clang-format. +Also optionally installs a pre-commit hook to run this script. + +Usage: + format.py [-a | --all] [-i | --install PATH] + + -a | --all + Format all files instead of only committed ones + + -i | --install PATH + Install a pre-commit hook to run this script in PATH +""" + +import os +import sys +import subprocess +import argparse +import shutil + +CFVERSION = "9" +CLANGFORMAT = "" + + +def command_exists(cmd): + """ Check if a command exists """ + return shutil.which(cmd) is not None + + +def clang_format_file(filename): + """ Format a file using clang-format """ + if os.path.isfile(filename): + subprocess.call([CLANGFORMAT, "-i", "-style=file", filename]) + + +def clang_format_files(files): + """ Format a list of files """ + for filename in files: + print(f"Formatting: {filename}", end='\r') + clang_format_file(filename) + + +def exit_if_no_files(): + """ Exit if no files to format """ + print("No files to format") + sys.exit(0) + + +def install_hook(hooks_path): + """ Install a pre-commit hook to run this script """ + if not os.path.isdir(hooks_path): + print(f"{hooks_path} path does not exist") + sys.exit(1) + print(f"Installing pre-commit hook in {hooks_path}") + with open(os.path.join(hooks_path, "pre-commit"), + "w", encoding="utf-8") as file: + file.write(os.path.realpath(sys.argv[0])) + os.chmod(os.path.join(hooks_path, "pre-commit"), 0o755) + + +def get_files(file_types, recursive=True, committed_only=False): + """ + Get a list of files in the src directory [and subdirectories]. + Filters by file types and whether the file is committed. + """ + file_list = [] + committed_files = [] + if committed_only: + committed_files = subprocess.check_output( + "git diff-index --cached --name-only HEAD", + shell=True).decode().strip().split('\n') + for dirpath, _, filenames in os.walk('src'): + for filename in filenames: + file_path = os.path.join(dirpath, filename) + if file_types and not any(file_path.endswith(file_type) + for file_type in file_types): + continue # Skip files that don't match any file types. + if committed_only: + if file_path not in committed_files: + continue # Skip uncommitted files. + file_list.append(file_path) + if not recursive: + break # Stop searching if not recursive. + return file_list + + +def main(): + """Check if clang-format is installed, and format files.""" + global CLANGFORMAT # pylint: disable=global-statement + parser = argparse.ArgumentParser( + description="Format source filess with a clang-format") + parser.add_argument("-a", "--all", action="store_true", + help="format all files instead of only committed ones") + parser.add_argument("-i", "--install", metavar="PATH", + help="install a pre-commit hook to run this script") + args = parser.parse_args() + + if not command_exists("clang-format-" + CFVERSION): + if not command_exists("clang-format"): + print("Required version of clang-format not found") + sys.exit(1) + else: + CLANGFORMAT = "clang-format" + else: + CLANGFORMAT = "clang-format-" + CFVERSION + + if args.install: + install_hook(args.install) + sys.exit(0) + + if args.all: + print("Formatting all files...") + # Find all files in the recursively in the current directory. + clang_format_files(get_files((".cpp", ".cxx", ".cc", ".h", ".hpp"), + committed_only=False)) + else: + files = get_files((".cpp", ".cxx", ".cc", ".h", ".hpp"), + committed_only=True) + if not files: + exit_if_no_files() + print("Formatting committed source files...") + clang_format_files(files) + + +if __name__ == "__main__": + main() diff --git a/extras/scripts/format.sh b/extras/scripts/format.sh deleted file mode 100755 index d33b506c04701f85013d94b05bc75a2a4a5d0323..0000000000000000000000000000000000000000 --- a/extras/scripts/format.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env bash -# format.sh --- set up clang-format for source files - -# Copyright (C) 2020-2023 Savoir-faire Linux Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -set -e - -command_exists () -{ - type "$1" &> /dev/null ; -} - -CFVERSION="9" -CLANGFORMAT="" -if command_exists clang-format-${CFVERSION}; then - CLANGFORMAT=clang-format-${CFVERSION} -else - if command_exists clang-format; then - CLANGFORMAT=clang-format - fi -fi - -if ! command -v $CLANGFORMAT &> /dev/null; then - echo "Required version of clang-format not found" - exit 1 -fi - -format_file() -{ - if [ -f "${1}" ]; then - $CLANGFORMAT -i -style=file "${1}" || true - fi -} - -format_files() -{ - for file in $1; do - echo -ne "Formatting: ${file}\\033[0K\\r" - format_file "${file}" - done -} - -exit_if_no_files() -{ - echo No files to format - exit 0 -} - -install_hook() -{ - hooks_path=$1 - if [ ! -d "$hooks_path" ]; then - echo "$hooks_path" path does not exist - exit 1 - fi - echo Installing pre-commit hook in "$hooks_path" - echo "$(realpath $0)" > "$hooks_path"/pre-commit - chmod +x "$hooks_path"/pre-commit -} - -display_help() -{ - echo "Usage: $0 [OPTION...] -- Clang format source files with a .clang-format file" >&2 - echo - echo " --all format all files instead of only committed ones" - echo " --install <path> install a pre-commit hook to run this script" - echo -} - -if [ "$1" == "--help" ]; then - display_help - exit 0 -fi - -case "${1}" in - --all ) - files=$(find src -regex '.*\.\(cpp\|hpp\|cc\|cxx\|h\)$') || true - echo Formatting all source files... - format_files "$files" - ;; - --install ) - install_hook "${2}" - ;; - * ) - files=$(git diff-index --cached --name-only HEAD | grep -iE '\.(cpp|cxx|cc|h|hpp)$') || exit_if_no_files - echo Formatting committed source files... - format_files "$files" - ;; -esac