aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xtools/clang-format.py145
-rw-r--r--tools/clang-format/.gitattributes1
-rwxr-xr-xtools/clang-format/clang-format.sh186
3 files changed, 145 insertions, 187 deletions
diff --git a/tools/clang-format.py b/tools/clang-format.py
new file mode 100755
index 0000000..65321d2
--- /dev/null
+++ b/tools/clang-format.py
@@ -0,0 +1,145 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2020 Egor Tensin <Egor.Tensin@gmail.com>
+# This file is part of the "cmake-common" project.
+# For details, see https://github.com/egor-tensin/cmake-common.
+# Distributed under the MIT License.
+
+'''Feed C/C++ files in the repository to clang-format
+
+This script runs clang-format on every C/C++ file in the repository, either
+printing a unified diff between the original and the formatted versions, or
+formatting the files in-place.
+'''
+
+import argparse
+import difflib
+import logging
+import os.path
+import subprocess
+import sys
+
+
+def _setup_logging():
+ logging.basicConfig(
+ format='%(asctime)s | %(levelname)s | %(message)s',
+ level=logging.INFO)
+
+
+def _run_executable(cmd_line):
+ logging.debug('Running executable: %s', cmd_line)
+ try:
+ return subprocess.run(cmd_line, check=True, universal_newlines=True,
+ stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
+ except subprocess.CalledProcessError as e:
+ logging.error('Process finished with exit code %d: %s', e.returncode, cmd_line)
+ logging.error('Its output was:\n%s', e.output)
+ raise
+
+
+def _read_file(path):
+ with open(path) as file:
+ return file.read()
+
+
+class ClangFormat:
+ def __init__(self, path, style):
+ self.path = path
+ self.style = style
+
+ def _get_command_line(self, paths, in_place=False):
+ cmd_line = [self.path, f'-style={self.style}']
+ if in_place:
+ cmd_line.append('-i')
+ cmd_line.append('--')
+ cmd_line += list(paths)
+ return cmd_line
+
+ def format_in_place(self, paths):
+ _run_executable(self._get_command_line(paths, in_place=True))
+
+ @staticmethod
+ def _show_diff(path, formatted):
+ original = _read_file(path).split('\n')
+ formatted = formatted.split('\n')
+ original_lbl = f'{path} (original)'
+ formatted_lbl = f'{path} (formatted)'
+
+ diff = difflib.unified_diff(original, formatted, fromfile=original_lbl,
+ tofile=formatted_lbl, lineterm='')
+
+ clean = True
+ for line in diff:
+ clean = False
+ print(line)
+ return clean
+
+ def show_diff(self, paths):
+ clean = True
+ for path in paths:
+ formatted = _run_executable(self._get_command_line([path])).stdout
+ clean = self._show_diff(path, formatted) and clean
+ return clean
+
+
+def _list_all_files():
+ cmd_line = ['git', 'ls-tree', '-r', '-z', '--name-only', 'HEAD']
+ repo_files = _run_executable(cmd_line).stdout
+ repo_files = repo_files.split('\0')
+ return repo_files
+
+
+CPP_FILE_EXTENSIONS = {'.c', '.h', '.cc', '.hh', '.cpp', '.hpp', '.cxx', '.hxx', '.cp', '.c++'}
+
+
+def _list_cpp_files():
+ for path in _list_all_files():
+ ext = os.path.splitext(path)[1]
+ if ext in CPP_FILE_EXTENSIONS:
+ yield path
+
+
+def _process_cpp_files(args):
+ clang_format = ClangFormat(args.clang_format, args.style)
+ cpp_files = _list_cpp_files()
+ if args.in_place:
+ clang_format.format_in_place(cpp_files)
+ else:
+ if not clang_format.show_diff(cpp_files):
+ sys.exit(1)
+
+
+DEFAULT_CLANG_FORMAT = 'clang-format'
+DEFAULT_STYLE = 'file'
+
+
+def _parse_args(argv=None):
+ if argv is None:
+ argv = sys.argv[1:]
+
+ parser = argparse.ArgumentParser(
+ description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+
+ parser.add_argument('-b', '--clang-format', default=DEFAULT_CLANG_FORMAT,
+ help='clang-format executable file path')
+ parser.add_argument('-s', '--style', default=DEFAULT_STYLE,
+ help='clang-format -style parameter argument')
+ parser.add_argument('-i', '--in-place', action='store_true',
+ help='edit the files in-place')
+
+ return parser.parse_args(argv)
+
+
+def main(argv=None):
+ _setup_logging()
+ try:
+ args = _parse_args(argv)
+ _process_cpp_files(args)
+ except Exception as e:
+ logging.exception(e)
+ raise
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/clang-format/.gitattributes b/tools/clang-format/.gitattributes
deleted file mode 100644
index dfdb8b7..0000000
--- a/tools/clang-format/.gitattributes
+++ /dev/null
@@ -1 +0,0 @@
-*.sh text eol=lf
diff --git a/tools/clang-format/clang-format.sh b/tools/clang-format/clang-format.sh
deleted file mode 100755
index 00fe712..0000000
--- a/tools/clang-format/clang-format.sh
+++ /dev/null
@@ -1,186 +0,0 @@
-#!/usr/bin/env bash
-
-# Copyright (c) 2020 Egor Tensin <Egor.Tensin@gmail.com>
-# This file is part of the "cmake-common" project.
-# For details, see https://github.com/egor-tensin/cmake-common.
-# Distributed under the MIT License.
-
-set -o errexit -o nounset -o pipefail
-
-# Utility
-
-script_name="$( basename -- "${BASH_SOURCE[0]}" )"
-readonly script_name
-
-dump() {
- local prefix="${FUNCNAME[0]}"
- [ "${#FUNCNAME[@]}" -gt 1 ] && prefix="${FUNCNAME[1]}"
-
- local msg
- for msg; do
- echo "$script_name: $prefix: $msg"
- done
-}
-
-# Settings
-
-clang_format='clang-format'
-clang_format_style='file'
-clang_format_diff=
-
-update_clang_format() {
- if [ "$#" -ne 1 ]; then
- echo "usage: ${FUNCNAME[0]} CLANG_FORMAT_PATH" >&2
- return 1
- fi
-
- local new_clang_format="$1"
-
- if ! command -v "$new_clang_format" > /dev/null 2>&1; then
- dump "couldn't find clang-format at: $new_clang_format" >&2
- return 1
- fi
-
- clang_format="$new_clang_format"
-}
-
-# Command line parsing
-
-script_usage() {
- local msg
- for msg; do
- echo "$script_name: $msg"
- done
-
- echo "usage: $script_name [-h|--help] [-b|--clang-format PATH] [-s|--style STYLE] [--diff]
- -h,--help show this message and exit
- -b,--clang-format set path to clang-format executable
- -s,--style clang-format -style parameter argument
- --diff don't edit the files, just show the diff"
-}
-
-parse_script_options() {
- while [ "$#" -gt 0 ]; do
- local key="$1"
- shift
-
- case "$key" in
- -h|--help)
- script_usage
- exit 0
- ;;
- --diff)
- clang_format_diff=1
- continue
- ;;
- -b|--clang-format|-s|--style)
- ;;
- *)
- script_usage "unrecognized parameter: $key" >&2
- exit 1
- ;;
- esac
-
- if [ "$#" -eq 0 ]; then
- script_usage "missing argument for parameter: $key" >&2
- exit 1
- fi
-
- local value="$1"
- shift
-
- case "$key" in
- -b|--clang-format)
- update_clang_format "$value"
- ;;
- -s|--style)
- clang_format_style="$value"
- ;;
- *)
- script_usage "unrecognized parameter: $key" >&2
- exit 1
- ;;
- esac
- done
-}
-
-# Routines for running clang-format
-
-run_clang_format_diff() {
- local exit_code=0
- local file
-
- for file; do
- if "$clang_format" "-style=$clang_format_style" -- "$file" | diff --unified --label="$file (original)" --label="$file (clang-format)" -- "$file" -; then
- continue
- else
- exit_code="$?"
- [ "$exit_code" -eq 1 ] && continue
- break
- fi
- done
-
- return "$exit_code"
-}
-
-run_clang_format_edit() {
- "$clang_format" -i "-style=$clang_format_style" -- "$@"
-}
-
-run_clang_format() {
- if [ -z "$clang_format_diff" ]; then
- run_clang_format_edit "$@"
- else
- run_clang_format_diff "$@"
- fi
-}
-
-# File traversal
-
-list_all_files() {
- git ls-tree -r -z --name-only HEAD
-}
-
-declare -a cpp_extensions=(c h cc hh cpp hpp cxx hxx cp c++)
-
-list_cpp_files() {
- local -A cpp_extension_set
- local ext
-
- for ext in ${cpp_extensions[@]+"${cpp_extensions[@]}"}; do
- cpp_extension_set[$ext]=1
- done
-
- local -a files=()
- local file
-
- while IFS= read -d '' -r file; do
- basename="$( basename -- "$file" )"
- ext="${basename##*.}"
- [ "$ext" = "$basename" ] && continue # No .EXTension
-
- [ -n "${cpp_extension_set[$ext]+x}" ] && files+=("$file")
- done < <( list_all_files )
-
- printf -- '%s\0' ${files[@]+"${files[@]}"}
-}
-
-# Main routines
-
-process_cpp_files() {
- local -a files=()
- local file
-
- while IFS= read -d '' -r file; do
- files+=("$file")
- done < <( list_cpp_files )
-
- run_clang_format ${files[@]+"${files[@]}"}
-}
-
-main() {
- parse_script_options "$@"
- process_cpp_files
-}
-
-main "$@"