forked from qmk/qmk_firmware
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move all our CLI file formatters to the format dir (qmk#13296)
* move all our file formatters to the format dir * Apply suggestions from code review Co-authored-by: Erovia <[email protected]> Co-authored-by: Erovia <[email protected]>
- Loading branch information
1 parent
c4db9f7
commit 4ab8734
Showing
7 changed files
with
240 additions
and
148 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,137 +1,28 @@ | ||
"""Format C code according to QMK's style. | ||
"""Point people to the new command name. | ||
""" | ||
from os import path | ||
from shutil import which | ||
from subprocess import CalledProcessError, DEVNULL, Popen, PIPE | ||
import sys | ||
from pathlib import Path | ||
|
||
from argcomplete.completers import FilesCompleter | ||
from milc import cli | ||
|
||
from qmk.path import normpath | ||
from qmk.c_parse import c_source_files | ||
|
||
c_file_suffixes = ('c', 'h', 'cpp') | ||
core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms') | ||
ignored = ('tmk_core/protocol/usb_hid', 'quantum/template', 'platforms/chibios') | ||
|
||
|
||
def find_clang_format(): | ||
"""Returns the path to clang-format. | ||
""" | ||
for clang_version in range(20, 6, -1): | ||
binary = f'clang-format-{clang_version}' | ||
|
||
if which(binary): | ||
return binary | ||
|
||
return 'clang-format' | ||
|
||
|
||
def find_diffs(files): | ||
"""Run clang-format and diff it against a file. | ||
""" | ||
found_diffs = False | ||
|
||
for file in files: | ||
cli.log.debug('Checking for changes in %s', file) | ||
clang_format = Popen([find_clang_format(), file], stdout=PIPE, stderr=PIPE, universal_newlines=True) | ||
diff = cli.run(['diff', '-u', f'--label=a/{file}', f'--label=b/{file}', str(file), '-'], stdin=clang_format.stdout, capture_output=True) | ||
|
||
if diff.returncode != 0: | ||
print(diff.stdout) | ||
found_diffs = True | ||
|
||
return found_diffs | ||
|
||
|
||
def cformat_run(files): | ||
"""Spawn clang-format subprocess with proper arguments | ||
""" | ||
# Determine which version of clang-format to use | ||
clang_format = [find_clang_format(), '-i'] | ||
|
||
try: | ||
cli.run([*clang_format, *map(str, files)], check=True, capture_output=False, stdin=DEVNULL) | ||
cli.log.info('Successfully formatted the C code.') | ||
return True | ||
|
||
except CalledProcessError as e: | ||
cli.log.error('Error formatting C code!') | ||
cli.log.debug('%s exited with returncode %s', e.cmd, e.returncode) | ||
cli.log.debug('STDOUT:') | ||
cli.log.debug(e.stdout) | ||
cli.log.debug('STDERR:') | ||
cli.log.debug(e.stderr) | ||
return False | ||
|
||
|
||
def filter_files(files, core_only=False): | ||
"""Yield only files to be formatted and skip the rest | ||
""" | ||
if core_only: | ||
# Filter non-core files | ||
for index, file in enumerate(files): | ||
# The following statement checks each file to see if the file path is | ||
# - in the core directories | ||
# - not in the ignored directories | ||
if not any(i in str(file) for i in core_dirs) or any(i in str(file) for i in ignored): | ||
files[index] = None | ||
cli.log.debug("Skipping non-core file %s, as '--core-only' is used.", file) | ||
|
||
for file in files: | ||
if file and file.name.split('.')[-1] in c_file_suffixes: | ||
yield file | ||
else: | ||
cli.log.debug('Skipping file %s', file) | ||
|
||
|
||
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Flag only, don't automatically format.") | ||
@cli.argument('-b', '--base-branch', default='origin/master', help='Branch to compare to diffs to.') | ||
@cli.argument('-a', '--all-files', arg_only=True, action='store_true', help='Format all core files.') | ||
@cli.argument('--core-only', arg_only=True, action='store_true', help='Format core files only.') | ||
@cli.argument('files', nargs='*', arg_only=True, type=normpath, completer=FilesCompleter('.c'), help='Filename(s) to format.') | ||
@cli.subcommand("Format C code according to QMK's style.", hidden=False if cli.config.user.developer else True) | ||
@cli.argument('files', nargs='*', arg_only=True, help='Filename(s) to format.') | ||
@cli.subcommand('Pointer to the new command name: qmk format-c.', hidden=True) | ||
def cformat(cli): | ||
"""Format C code according to QMK's style. | ||
"""Pointer to the new command name: qmk format-c. | ||
""" | ||
# Find the list of files to format | ||
if cli.args.files: | ||
files = list(filter_files(cli.args.files, cli.args.core_only)) | ||
|
||
if not files: | ||
cli.log.error('No C files in filelist: %s', ', '.join(map(str, cli.args.files))) | ||
exit(0) | ||
|
||
if cli.args.all_files: | ||
cli.log.warning('Filenames passed with -a, only formatting: %s', ','.join(map(str, files))) | ||
|
||
elif cli.args.all_files: | ||
all_files = c_source_files(core_dirs) | ||
files = list(filter_files(all_files, True)) | ||
|
||
else: | ||
git_diff_cmd = ['git', 'diff', '--name-only', cli.args.base_branch, *core_dirs] | ||
git_diff = cli.run(git_diff_cmd, stdin=DEVNULL) | ||
|
||
if git_diff.returncode != 0: | ||
cli.log.error("Error running %s", git_diff_cmd) | ||
print(git_diff.stderr) | ||
return git_diff.returncode | ||
|
||
files = [] | ||
|
||
for file in git_diff.stdout.strip().split('\n'): | ||
if not any([file.startswith(ignore) for ignore in ignored]): | ||
if path.exists(file) and file.split('.')[-1] in c_file_suffixes: | ||
files.append(file) | ||
cli.log.warning('"qmk cformat" has been renamed to "qmk format-c". Please use the new command in the future.') | ||
argv = [sys.executable, *sys.argv] | ||
argv[argv.index('cformat')] = 'format-c' | ||
script_path = Path(argv[1]) | ||
script_path_exe = Path(f'{argv[1]}.exe') | ||
|
||
# Sanity check | ||
if not files: | ||
cli.log.error('No changed files detected. Use "qmk cformat -a" to format all core files') | ||
return False | ||
if not script_path.exists() and script_path_exe.exists(): | ||
# For reasons I don't understand ".exe" is stripped from the script name on windows. | ||
argv[1] = str(script_path_exe) | ||
|
||
# Run clang-format on the files we've found | ||
if cli.args.dry_run: | ||
return not find_diffs(files) | ||
else: | ||
return cformat_run(files) | ||
return cli.run(argv, capture_output=False).returncode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,23 @@ | ||
"""Format files according to QMK's style. | ||
"""Point people to the new command name. | ||
""" | ||
from milc import cli | ||
import sys | ||
from pathlib import Path | ||
|
||
import subprocess | ||
from milc import cli | ||
|
||
|
||
@cli.subcommand("Format files according to QMK's style.", hidden=True) | ||
@cli.subcommand('Pointer to the new command name: qmk format-text.', hidden=True) | ||
def fileformat(cli): | ||
"""Run several general formatting commands. | ||
"""Pointer to the new command name: qmk format-text. | ||
""" | ||
dos2unix = subprocess.run(['bash', '-c', 'git ls-files -z | xargs -0 dos2unix'], stdout=subprocess.DEVNULL) | ||
return dos2unix.returncode | ||
cli.log.warning('"qmk fileformat" has been renamed to "qmk format-text". Please use the new command in the future.') | ||
argv = [sys.executable, *sys.argv] | ||
argv[argv.index('fileformat')] = 'format-text' | ||
script_path = Path(argv[1]) | ||
script_path_exe = Path(f'{argv[1]}.exe') | ||
|
||
if not script_path.exists() and script_path_exe.exists(): | ||
# For reasons I don't understand ".exe" is stripped from the script name on windows. | ||
argv[1] = str(script_path_exe) | ||
|
||
return cli.run(argv, capture_output=False).returncode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
"""Format C code according to QMK's style. | ||
""" | ||
from os import path | ||
from shutil import which | ||
from subprocess import CalledProcessError, DEVNULL, Popen, PIPE | ||
|
||
from argcomplete.completers import FilesCompleter | ||
from milc import cli | ||
|
||
from qmk.path import normpath | ||
from qmk.c_parse import c_source_files | ||
|
||
c_file_suffixes = ('c', 'h', 'cpp') | ||
core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms') | ||
ignored = ('tmk_core/protocol/usb_hid', 'quantum/template', 'platforms/chibios') | ||
|
||
|
||
def find_clang_format(): | ||
"""Returns the path to clang-format. | ||
""" | ||
for clang_version in range(20, 6, -1): | ||
binary = f'clang-format-{clang_version}' | ||
|
||
if which(binary): | ||
return binary | ||
|
||
return 'clang-format' | ||
|
||
|
||
def find_diffs(files): | ||
"""Run clang-format and diff it against a file. | ||
""" | ||
found_diffs = False | ||
|
||
for file in files: | ||
cli.log.debug('Checking for changes in %s', file) | ||
clang_format = Popen([find_clang_format(), file], stdout=PIPE, stderr=PIPE, universal_newlines=True) | ||
diff = cli.run(['diff', '-u', f'--label=a/{file}', f'--label=b/{file}', str(file), '-'], stdin=clang_format.stdout, capture_output=True) | ||
|
||
if diff.returncode != 0: | ||
print(diff.stdout) | ||
found_diffs = True | ||
|
||
return found_diffs | ||
|
||
|
||
def cformat_run(files): | ||
"""Spawn clang-format subprocess with proper arguments | ||
""" | ||
# Determine which version of clang-format to use | ||
clang_format = [find_clang_format(), '-i'] | ||
|
||
try: | ||
cli.run([*clang_format, *map(str, files)], check=True, capture_output=False, stdin=DEVNULL) | ||
cli.log.info('Successfully formatted the C code.') | ||
return True | ||
|
||
except CalledProcessError as e: | ||
cli.log.error('Error formatting C code!') | ||
cli.log.debug('%s exited with returncode %s', e.cmd, e.returncode) | ||
cli.log.debug('STDOUT:') | ||
cli.log.debug(e.stdout) | ||
cli.log.debug('STDERR:') | ||
cli.log.debug(e.stderr) | ||
return False | ||
|
||
|
||
def filter_files(files, core_only=False): | ||
"""Yield only files to be formatted and skip the rest | ||
""" | ||
if core_only: | ||
# Filter non-core files | ||
for index, file in enumerate(files): | ||
# The following statement checks each file to see if the file path is | ||
# - in the core directories | ||
# - not in the ignored directories | ||
if not any(i in str(file) for i in core_dirs) or any(i in str(file) for i in ignored): | ||
files[index] = None | ||
cli.log.debug("Skipping non-core file %s, as '--core-only' is used.", file) | ||
|
||
for file in files: | ||
if file and file.name.split('.')[-1] in c_file_suffixes: | ||
yield file | ||
else: | ||
cli.log.debug('Skipping file %s', file) | ||
|
||
|
||
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Flag only, don't automatically format.") | ||
@cli.argument('-b', '--base-branch', default='origin/master', help='Branch to compare to diffs to.') | ||
@cli.argument('-a', '--all-files', arg_only=True, action='store_true', help='Format all core files.') | ||
@cli.argument('--core-only', arg_only=True, action='store_true', help='Format core files only.') | ||
@cli.argument('files', nargs='*', arg_only=True, type=normpath, completer=FilesCompleter('.c'), help='Filename(s) to format.') | ||
@cli.subcommand("Format C code according to QMK's style.", hidden=False if cli.config.user.developer else True) | ||
def format_c(cli): | ||
"""Format C code according to QMK's style. | ||
""" | ||
# Find the list of files to format | ||
if cli.args.files: | ||
files = list(filter_files(cli.args.files, cli.args.core_only)) | ||
|
||
if not files: | ||
cli.log.error('No C files in filelist: %s', ', '.join(map(str, cli.args.files))) | ||
exit(0) | ||
|
||
if cli.args.all_files: | ||
cli.log.warning('Filenames passed with -a, only formatting: %s', ','.join(map(str, files))) | ||
|
||
elif cli.args.all_files: | ||
all_files = c_source_files(core_dirs) | ||
files = list(filter_files(all_files, True)) | ||
|
||
else: | ||
git_diff_cmd = ['git', 'diff', '--name-only', cli.args.base_branch, *core_dirs] | ||
git_diff = cli.run(git_diff_cmd, stdin=DEVNULL) | ||
|
||
if git_diff.returncode != 0: | ||
cli.log.error("Error running %s", git_diff_cmd) | ||
print(git_diff.stderr) | ||
return git_diff.returncode | ||
|
||
files = [] | ||
|
||
for file in git_diff.stdout.strip().split('\n'): | ||
if not any([file.startswith(ignore) for ignore in ignored]): | ||
if path.exists(file) and file.split('.')[-1] in c_file_suffixes: | ||
files.append(file) | ||
|
||
# Sanity check | ||
if not files: | ||
cli.log.error('No changed files detected. Use "qmk cformat -a" to format all core files') | ||
return False | ||
|
||
# Run clang-format on the files we've found | ||
if cli.args.dry_run: | ||
return not find_diffs(files) | ||
else: | ||
return cformat_run(files) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
"""Format python code according to QMK's style. | ||
""" | ||
from subprocess import CalledProcessError, DEVNULL | ||
|
||
from milc import cli | ||
|
||
|
||
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually format.") | ||
@cli.subcommand("Format python code according to QMK's style.", hidden=False if cli.config.user.developer else True) | ||
def format_python(cli): | ||
"""Format python code according to QMK's style. | ||
""" | ||
edit = '--diff' if cli.args.dry_run else '--in-place' | ||
yapf_cmd = ['yapf', '-vv', '--recursive', edit, 'bin/qmk', 'lib/python'] | ||
try: | ||
cli.run(yapf_cmd, check=True, capture_output=False, stdin=DEVNULL) | ||
cli.log.info('Python code in `bin/qmk` and `lib/python` is correctly formatted.') | ||
return True | ||
|
||
except CalledProcessError: | ||
if cli.args.dry_run: | ||
cli.log.error('Python code in `bin/qmk` and `lib/python` incorrectly formatted!') | ||
else: | ||
cli.log.error('Error formatting python code!') | ||
|
||
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
"""Ensure text files have the proper line endings. | ||
""" | ||
from subprocess import CalledProcessError | ||
|
||
from milc import cli | ||
|
||
|
||
@cli.subcommand("Ensure text files have the proper line endings.", hidden=True) | ||
def format_text(cli): | ||
"""Ensure text files have the proper line endings. | ||
""" | ||
try: | ||
file_list_cmd = cli.run(['git', 'ls-files', '-z'], check=True) | ||
except CalledProcessError as e: | ||
cli.log.error('Could not get file list: %s', e) | ||
exit(1) | ||
except Exception as e: | ||
cli.log.error('Unhandled exception: %s: %s', e.__class__.__name__, e) | ||
cli.log.exception(e) | ||
exit(1) | ||
|
||
dos2unix = cli.run(['xargs', '-0', 'dos2unix'], stdin=None, input=file_list_cmd.stdout) | ||
|
||
if dos2unix.returncode != 0: | ||
print(dos2unix.stderr) | ||
|
||
return dos2unix.returncode |
Oops, something went wrong.