Files
patch/scripts/clang_format.py
Shannon Booth 640ab9af01 meta: honor --no-write for explicit files
Pass the parsed flag through when formatting positional paths. This
keeps check mode from modifying files when an explicit file list is
used.
2026-07-24 22:05:07 +02:00

130 lines
4.6 KiB
Python
Executable File

#!/usr/bin/python3
# SPDX-License-Identifier: BSD-3-Clause
# Copyright 2022-2026 Shannon Booth <shannon.ml.booth@gmail.com>
'''
clang_format.py
Wrapper script around clang-format intended to be used for reformatting an
entire git repository of code, or for use in CI, for checking that changes meet
the formatting outlined by .clang-format
There is also support for an ignore file which can be used for not formatting
or checking formatting for files that are not owned by this repository, or should
not have formatting done on them.
Color diff support for clang-format is also implemented through these scripts.
'''
import sys
import argparse
from difflib import unified_diff
from subprocess import check_output
import pathspec
RED = '\033[0;31m'
GREEN = '\033[92m'
BLUE = '\033[0;34m'
BOLD = '\033[1m'
ENDC = '\033[0m'
def list_from_lines(lines):
''' Returns a list from each line '''
return [line.rstrip() for line in lines.splitlines()]
def tracked_files(branch='HEAD'):
''' Returns a list of all tracked files for a branch '''
return list_from_lines(run_cmd(['git', 'ls-tree', '-r', branch, '--name-only']))
def run_cmd(cmd):
''' Run a shell command and return the output as a string '''
return check_output(cmd).decode('utf-8')
def color_diff(diff):
''' Colors a diff using ANSI color control characters '''
for line in diff:
if line.startswith('+'):
yield GREEN + line + ENDC
elif line.startswith('-'):
yield RED + line + ENDC
elif line.startswith('^'):
yield BLUE + line + ENDC
else:
yield line
def format_file(path_to_file, quiet=False, no_write=False):
''' Format a specific file using clang-format '''
with open(path_to_file, 'r') as original_text:
original_file = original_text.read()
cmd = ['clang-format', '--style=file', path_to_file]
if not no_write:
cmd += ['-i']
# We get the formatted file from stdout if it is not being written to.
formatted_file = run_cmd(cmd)
if not no_write:
with open(path_to_file, 'r') as formated_text:
formatted_file = formated_text.read()
if original_file == formatted_file:
return 0
print('{}NOTE{}: Found diff for {}'.format(BOLD, ENDC, path_to_file))
if not quiet:
diff = color_diff(unified_diff(original_file.splitlines(),
formatted_file.splitlines(),
fromfile=path_to_file,
tofile=path_to_file,
lineterm=''))
for line in diff:
print(line.rstrip())
return 1
def format_all(quiet=False, no_write=False):
''' Format all files in a git repository using clang-format '''
extensions_to_format = ('.cpp', '.cc', '.h', '.hpp')
files_to_format = [file for file in tracked_files() if file.endswith(extensions_to_format)]
try:
with open('.clang-format.ignore', 'r') as ignore_file:
files_to_ignore = ignore_file.readlines()
except FileNotFoundError:
files_to_ignore = []
ignore = pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, files_to_ignore)
ret = 0
for i, file in enumerate(files_to_format):
# Nicely align the count of files that we have formatted
print('{0:>{align}}/{1:<{align}}: '.format(i + 1, len(files_to_format),
align=len(str(len(files_to_format)))), end='')
if ignore.match_file(file):
print('Skipping ' + file)
else:
print('Formating ' + file)
ret |= format_file(file, quiet=quiet, no_write=no_write)
return ret
def main():
''' Wraps all of this '''
parser = argparse.ArgumentParser(description='run clang format over a selection of files')
parser.add_argument('files', nargs='*', help='path to files to format. if none given, '
' defaults to all files in the repository.')
parser.add_argument('--quiet', action='store_true',
help='do not output the diff of formatted files')
parser.add_argument('--no-write', action='store_true',
help='do not write the formatted files to disk')
args = parser.parse_args()
if not args.files:
ret = format_all(quiet=args.quiet, no_write=args.no_write)
sys.exit(ret)
ret = 0
for file_to_format in args.files:
ret |= format_file(file_to_format, quiet=args.quiet, no_write=args.no_write)
sys.exit(ret)
if __name__ == '__main__':
main()