diff --git a/docs/cli.md b/docs/cli.md index 8ee8ab0e8..7c3effcc0 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -71,14 +71,36 @@ There are some limitations to the local CLI compared to the global CLI: ## `qmk cformat` -This command formats C code using clang-format. Run it with no arguments to format all core code, or pass filenames on the command line to run it on specific files. +This command formats C code using clang-format. -**Usage**: +Run it with no arguments to format all core code that has been changed. Default checks `origin/master` with `git diff`, branch can be changed using `-b ` + +Run it with `-a` to format all core code, or pass filenames on the command line to run it on specific files. + +**Usage for specified files**: ``` qmk cformat [file1] [file2] [...] [fileN] ``` +**Usage for all core files**: + +``` +qmk cformat -a +``` + +**Usage for only changed files against origin/master**: + +``` +qmk cformat +``` + +**Usage for only changed files against branch_name**: + +``` +qmk cformat -b branch_name +``` + ## `qmk compile` This command allows you to compile firmware from any directory. You can compile JSON exports from , compile keymaps in the repo, or compile the keyboard in the current working directory. diff --git a/lib/python/qmk/cli/cformat.py b/lib/python/qmk/cli/cformat.py index de55218ae..7e3a91dcf 100644 --- a/lib/python/qmk/cli/cformat.py +++ b/lib/python/qmk/cli/cformat.py @@ -1,16 +1,14 @@ """Format C code according to QMK's style. """ -import os import subprocess from shutil import which from milc import cli +import qmk.path -@cli.argument('files', nargs='*', arg_only=True, help='Filename(s) to format.') -@cli.subcommand("Format C code according to QMK's style.") -def cformat(cli): - """Format C code according to QMK's style. +def cformat_run(files, all_files): + """Spawn clang-format subprocess with proper arguments """ # Determine which version of clang-format to use clang_format = ['clang-format', '-i'] @@ -19,27 +17,48 @@ def cformat(cli): if which(binary): clang_format[0] = binary break - - # Find the list of files to format - if cli.args.files: - cli.args.files = [os.path.join(os.environ['ORIG_CWD'], file) for file in cli.args.files] - else: - ignores = ['tmk_core/protocol/usb_hid', 'quantum/template'] - for dir in ['drivers', 'quantum', 'tests', 'tmk_core']: - for dirpath, dirnames, filenames in os.walk(dir): - if any(i in dirpath for i in ignores): - dirnames.clear() - continue - - for name in filenames: - if name.endswith(('.c', '.h', '.cpp')): - cli.args.files.append(os.path.join(dirpath, name)) - - # Run clang-format on the files we've found try: - subprocess.run(clang_format + cli.args.files, check=True) + if not files: + cli.log.warn('No changes detected. Use "qmk cformat -a" to format all files') + return False + if files and all_files: + cli.log.warning('Filenames passed with -a, only formatting: %s', ','.join(cli.args.files)) + # 3.6+: Can remove the str casting, python will cast implicitly + subprocess.run(clang_format + [str(file) for file in files], check=True) cli.log.info('Successfully formatted the C code.') except subprocess.CalledProcessError: cli.log.error('Error formatting C code!') return False + + +@cli.argument('-a', '--all-files', arg_only=True, action='store_true', help='Format all core files.') +@cli.argument('-b', '--base-branch', default='origin/master', help='Branch to compare to diffs to.') +@cli.argument('files', nargs='*', arg_only=True, help='Filename(s) to format.') +@cli.subcommand("Format C code according to QMK's style.") +def cformat(cli): + """Format C code according to QMK's style. + """ + # Empty array for files + files = [] + # Core directories for formatting + core_dirs = ['drivers', 'quantum', 'tests', 'tmk_core'] + ignores = ['tmk_core/protocol/usb_hid', 'quantum/template'] + # Find the list of files to format + if cli.args.files: + files.extend(qmk.path.normpath(file) for file in cli.args.files) + # If -a is specified + elif cli.args.all_files: + all_files = qmk.path.c_source_files(core_dirs) + # The following statement checks each file to see if the file path is in the ignored directories. + files.extend(file for file in all_files if not any(i in str(file) for i in ignores)) + # No files specified & no -a flag + else: + base_args = ['git', 'diff', '--name-only', cli.args.base_branch] + out = subprocess.run(base_args + core_dirs, check=True, stdout=subprocess.PIPE) + changed_files = filter(None, out.stdout.decode('UTF-8').split('\n')) + filtered_files = [qmk.path.normpath(file) for file in changed_files if not any(i in file for i in ignores)] + files.extend(file for file in filtered_files if file.exists() and file.suffix in ['.c', '.h', '.cpp']) + + # Run clang-format on the files we've found + cformat_run(files, cli.args.all_files) diff --git a/lib/python/qmk/path.py b/lib/python/qmk/path.py index d16928afb..bfaa43924 100644 --- a/lib/python/qmk/path.py +++ b/lib/python/qmk/path.py @@ -68,3 +68,17 @@ def normpath(path): return Path(path) return Path(os.environ['ORIG_CWD']) / path + + +def c_source_files(dir_names): + """Returns a list of all *.c, *.h, and *.cpp files for a given list of directories + + Args: + + dir_names + List of directories, relative pathing starts at qmk's cwd + """ + files = [] + for dir in dir_names: + files.extend(file for file in Path(dir).glob('**/*') if file.suffix in ['.c', '.h', '.cpp']) + return files diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index bb77952fa..a2595eb78 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py @@ -7,7 +7,8 @@ def check_subcommand(command, *args): def test_cformat(): - assert check_subcommand('cformat', 'tmk_core/common/keyboard.c').returncode == 0 + result = check_subcommand('cformat', 'quantum/matrix.c') + assert result.returncode == 0 def test_compile():