From b612776787cb1b841df8feb4c746e80b2e1f0fe0 Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Tue, 14 Jan 2020 13:19:56 +0300 Subject: tools: replace clang-format.sh with clang-format.py --- .travis.yml | 2 +- tools/clang-format.py | 145 +++++++++++++++++++++++++++++ tools/clang-format/.gitattributes | 1 - tools/clang-format/clang-format.sh | 186 ------------------------------------- 4 files changed, 146 insertions(+), 188 deletions(-) create mode 100755 tools/clang-format.py delete mode 100644 tools/clang-format/.gitattributes delete mode 100755 tools/clang-format/clang-format.sh diff --git a/.travis.yml b/.travis.yml index 77a0669..3aaf42a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ jobs: include: - name: Run clang-format script: - - ./tools/clang-format/clang-format.sh --clang-format clang-format-9 --diff + - ./tools/clang-format.py --clang-format clang-format-9 - name: Simple CMake projects script: 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 +# 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 -# 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 "$@" -- cgit v1.2.3