Skip to content

Commit 4ab8734

Browse files
skullydazedErovia
andauthored
Move all our CLI file formatters to the format dir (#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]>
1 parent c4db9f7 commit 4ab8734

File tree

7 files changed

+240
-148
lines changed

7 files changed

+240
-148
lines changed

lib/python/qmk/cli/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@
4040
'qmk.cli.doctor',
4141
'qmk.cli.fileformat',
4242
'qmk.cli.flash',
43+
'qmk.cli.format.c',
4344
'qmk.cli.format.json',
45+
'qmk.cli.format.python',
46+
'qmk.cli.format.text',
4447
'qmk.cli.generate.api',
4548
'qmk.cli.generate.config_h',
4649
'qmk.cli.generate.dfu_header',

lib/python/qmk/cli/cformat.py

100644100755
Lines changed: 15 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,137 +1,28 @@
1-
"""Format C code according to QMK's style.
1+
"""Point people to the new command name.
22
"""
3-
from os import path
4-
from shutil import which
5-
from subprocess import CalledProcessError, DEVNULL, Popen, PIPE
3+
import sys
4+
from pathlib import Path
65

7-
from argcomplete.completers import FilesCompleter
86
from milc import cli
97

10-
from qmk.path import normpath
11-
from qmk.c_parse import c_source_files
12-
13-
c_file_suffixes = ('c', 'h', 'cpp')
14-
core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms')
15-
ignored = ('tmk_core/protocol/usb_hid', 'quantum/template', 'platforms/chibios')
16-
17-
18-
def find_clang_format():
19-
"""Returns the path to clang-format.
20-
"""
21-
for clang_version in range(20, 6, -1):
22-
binary = f'clang-format-{clang_version}'
23-
24-
if which(binary):
25-
return binary
26-
27-
return 'clang-format'
28-
29-
30-
def find_diffs(files):
31-
"""Run clang-format and diff it against a file.
32-
"""
33-
found_diffs = False
34-
35-
for file in files:
36-
cli.log.debug('Checking for changes in %s', file)
37-
clang_format = Popen([find_clang_format(), file], stdout=PIPE, stderr=PIPE, universal_newlines=True)
38-
diff = cli.run(['diff', '-u', f'--label=a/{file}', f'--label=b/{file}', str(file), '-'], stdin=clang_format.stdout, capture_output=True)
39-
40-
if diff.returncode != 0:
41-
print(diff.stdout)
42-
found_diffs = True
43-
44-
return found_diffs
45-
46-
47-
def cformat_run(files):
48-
"""Spawn clang-format subprocess with proper arguments
49-
"""
50-
# Determine which version of clang-format to use
51-
clang_format = [find_clang_format(), '-i']
52-
53-
try:
54-
cli.run([*clang_format, *map(str, files)], check=True, capture_output=False, stdin=DEVNULL)
55-
cli.log.info('Successfully formatted the C code.')
56-
return True
57-
58-
except CalledProcessError as e:
59-
cli.log.error('Error formatting C code!')
60-
cli.log.debug('%s exited with returncode %s', e.cmd, e.returncode)
61-
cli.log.debug('STDOUT:')
62-
cli.log.debug(e.stdout)
63-
cli.log.debug('STDERR:')
64-
cli.log.debug(e.stderr)
65-
return False
66-
67-
68-
def filter_files(files, core_only=False):
69-
"""Yield only files to be formatted and skip the rest
70-
"""
71-
if core_only:
72-
# Filter non-core files
73-
for index, file in enumerate(files):
74-
# The following statement checks each file to see if the file path is
75-
# - in the core directories
76-
# - not in the ignored directories
77-
if not any(i in str(file) for i in core_dirs) or any(i in str(file) for i in ignored):
78-
files[index] = None
79-
cli.log.debug("Skipping non-core file %s, as '--core-only' is used.", file)
80-
81-
for file in files:
82-
if file and file.name.split('.')[-1] in c_file_suffixes:
83-
yield file
84-
else:
85-
cli.log.debug('Skipping file %s', file)
86-
878

889
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Flag only, don't automatically format.")
8910
@cli.argument('-b', '--base-branch', default='origin/master', help='Branch to compare to diffs to.')
9011
@cli.argument('-a', '--all-files', arg_only=True, action='store_true', help='Format all core files.')
9112
@cli.argument('--core-only', arg_only=True, action='store_true', help='Format core files only.')
92-
@cli.argument('files', nargs='*', arg_only=True, type=normpath, completer=FilesCompleter('.c'), help='Filename(s) to format.')
93-
@cli.subcommand("Format C code according to QMK's style.", hidden=False if cli.config.user.developer else True)
13+
@cli.argument('files', nargs='*', arg_only=True, help='Filename(s) to format.')
14+
@cli.subcommand('Pointer to the new command name: qmk format-c.', hidden=True)
9415
def cformat(cli):
95-
"""Format C code according to QMK's style.
16+
"""Pointer to the new command name: qmk format-c.
9617
"""
97-
# Find the list of files to format
98-
if cli.args.files:
99-
files = list(filter_files(cli.args.files, cli.args.core_only))
100-
101-
if not files:
102-
cli.log.error('No C files in filelist: %s', ', '.join(map(str, cli.args.files)))
103-
exit(0)
104-
105-
if cli.args.all_files:
106-
cli.log.warning('Filenames passed with -a, only formatting: %s', ','.join(map(str, files)))
107-
108-
elif cli.args.all_files:
109-
all_files = c_source_files(core_dirs)
110-
files = list(filter_files(all_files, True))
111-
112-
else:
113-
git_diff_cmd = ['git', 'diff', '--name-only', cli.args.base_branch, *core_dirs]
114-
git_diff = cli.run(git_diff_cmd, stdin=DEVNULL)
115-
116-
if git_diff.returncode != 0:
117-
cli.log.error("Error running %s", git_diff_cmd)
118-
print(git_diff.stderr)
119-
return git_diff.returncode
120-
121-
files = []
122-
123-
for file in git_diff.stdout.strip().split('\n'):
124-
if not any([file.startswith(ignore) for ignore in ignored]):
125-
if path.exists(file) and file.split('.')[-1] in c_file_suffixes:
126-
files.append(file)
18+
cli.log.warning('"qmk cformat" has been renamed to "qmk format-c". Please use the new command in the future.')
19+
argv = [sys.executable, *sys.argv]
20+
argv[argv.index('cformat')] = 'format-c'
21+
script_path = Path(argv[1])
22+
script_path_exe = Path(f'{argv[1]}.exe')
12723

128-
# Sanity check
129-
if not files:
130-
cli.log.error('No changed files detected. Use "qmk cformat -a" to format all core files')
131-
return False
24+
if not script_path.exists() and script_path_exe.exists():
25+
# For reasons I don't understand ".exe" is stripped from the script name on windows.
26+
argv[1] = str(script_path_exe)
13227

133-
# Run clang-format on the files we've found
134-
if cli.args.dry_run:
135-
return not find_diffs(files)
136-
else:
137-
return cformat_run(files)
28+
return cli.run(argv, capture_output=False).returncode

lib/python/qmk/cli/fileformat.py

100644100755
Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
1-
"""Format files according to QMK's style.
1+
"""Point people to the new command name.
22
"""
3-
from milc import cli
3+
import sys
4+
from pathlib import Path
45

5-
import subprocess
6+
from milc import cli
67

78

8-
@cli.subcommand("Format files according to QMK's style.", hidden=True)
9+
@cli.subcommand('Pointer to the new command name: qmk format-text.', hidden=True)
910
def fileformat(cli):
10-
"""Run several general formatting commands.
11+
"""Pointer to the new command name: qmk format-text.
1112
"""
12-
dos2unix = subprocess.run(['bash', '-c', 'git ls-files -z | xargs -0 dos2unix'], stdout=subprocess.DEVNULL)
13-
return dos2unix.returncode
13+
cli.log.warning('"qmk fileformat" has been renamed to "qmk format-text". Please use the new command in the future.')
14+
argv = [sys.executable, *sys.argv]
15+
argv[argv.index('fileformat')] = 'format-text'
16+
script_path = Path(argv[1])
17+
script_path_exe = Path(f'{argv[1]}.exe')
18+
19+
if not script_path.exists() and script_path_exe.exists():
20+
# For reasons I don't understand ".exe" is stripped from the script name on windows.
21+
argv[1] = str(script_path_exe)
22+
23+
return cli.run(argv, capture_output=False).returncode

lib/python/qmk/cli/format/c.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""Format C code according to QMK's style.
2+
"""
3+
from os import path
4+
from shutil import which
5+
from subprocess import CalledProcessError, DEVNULL, Popen, PIPE
6+
7+
from argcomplete.completers import FilesCompleter
8+
from milc import cli
9+
10+
from qmk.path import normpath
11+
from qmk.c_parse import c_source_files
12+
13+
c_file_suffixes = ('c', 'h', 'cpp')
14+
core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms')
15+
ignored = ('tmk_core/protocol/usb_hid', 'quantum/template', 'platforms/chibios')
16+
17+
18+
def find_clang_format():
19+
"""Returns the path to clang-format.
20+
"""
21+
for clang_version in range(20, 6, -1):
22+
binary = f'clang-format-{clang_version}'
23+
24+
if which(binary):
25+
return binary
26+
27+
return 'clang-format'
28+
29+
30+
def find_diffs(files):
31+
"""Run clang-format and diff it against a file.
32+
"""
33+
found_diffs = False
34+
35+
for file in files:
36+
cli.log.debug('Checking for changes in %s', file)
37+
clang_format = Popen([find_clang_format(), file], stdout=PIPE, stderr=PIPE, universal_newlines=True)
38+
diff = cli.run(['diff', '-u', f'--label=a/{file}', f'--label=b/{file}', str(file), '-'], stdin=clang_format.stdout, capture_output=True)
39+
40+
if diff.returncode != 0:
41+
print(diff.stdout)
42+
found_diffs = True
43+
44+
return found_diffs
45+
46+
47+
def cformat_run(files):
48+
"""Spawn clang-format subprocess with proper arguments
49+
"""
50+
# Determine which version of clang-format to use
51+
clang_format = [find_clang_format(), '-i']
52+
53+
try:
54+
cli.run([*clang_format, *map(str, files)], check=True, capture_output=False, stdin=DEVNULL)
55+
cli.log.info('Successfully formatted the C code.')
56+
return True
57+
58+
except CalledProcessError as e:
59+
cli.log.error('Error formatting C code!')
60+
cli.log.debug('%s exited with returncode %s', e.cmd, e.returncode)
61+
cli.log.debug('STDOUT:')
62+
cli.log.debug(e.stdout)
63+
cli.log.debug('STDERR:')
64+
cli.log.debug(e.stderr)
65+
return False
66+
67+
68+
def filter_files(files, core_only=False):
69+
"""Yield only files to be formatted and skip the rest
70+
"""
71+
if core_only:
72+
# Filter non-core files
73+
for index, file in enumerate(files):
74+
# The following statement checks each file to see if the file path is
75+
# - in the core directories
76+
# - not in the ignored directories
77+
if not any(i in str(file) for i in core_dirs) or any(i in str(file) for i in ignored):
78+
files[index] = None
79+
cli.log.debug("Skipping non-core file %s, as '--core-only' is used.", file)
80+
81+
for file in files:
82+
if file and file.name.split('.')[-1] in c_file_suffixes:
83+
yield file
84+
else:
85+
cli.log.debug('Skipping file %s', file)
86+
87+
88+
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Flag only, don't automatically format.")
89+
@cli.argument('-b', '--base-branch', default='origin/master', help='Branch to compare to diffs to.')
90+
@cli.argument('-a', '--all-files', arg_only=True, action='store_true', help='Format all core files.')
91+
@cli.argument('--core-only', arg_only=True, action='store_true', help='Format core files only.')
92+
@cli.argument('files', nargs='*', arg_only=True, type=normpath, completer=FilesCompleter('.c'), help='Filename(s) to format.')
93+
@cli.subcommand("Format C code according to QMK's style.", hidden=False if cli.config.user.developer else True)
94+
def format_c(cli):
95+
"""Format C code according to QMK's style.
96+
"""
97+
# Find the list of files to format
98+
if cli.args.files:
99+
files = list(filter_files(cli.args.files, cli.args.core_only))
100+
101+
if not files:
102+
cli.log.error('No C files in filelist: %s', ', '.join(map(str, cli.args.files)))
103+
exit(0)
104+
105+
if cli.args.all_files:
106+
cli.log.warning('Filenames passed with -a, only formatting: %s', ','.join(map(str, files)))
107+
108+
elif cli.args.all_files:
109+
all_files = c_source_files(core_dirs)
110+
files = list(filter_files(all_files, True))
111+
112+
else:
113+
git_diff_cmd = ['git', 'diff', '--name-only', cli.args.base_branch, *core_dirs]
114+
git_diff = cli.run(git_diff_cmd, stdin=DEVNULL)
115+
116+
if git_diff.returncode != 0:
117+
cli.log.error("Error running %s", git_diff_cmd)
118+
print(git_diff.stderr)
119+
return git_diff.returncode
120+
121+
files = []
122+
123+
for file in git_diff.stdout.strip().split('\n'):
124+
if not any([file.startswith(ignore) for ignore in ignored]):
125+
if path.exists(file) and file.split('.')[-1] in c_file_suffixes:
126+
files.append(file)
127+
128+
# Sanity check
129+
if not files:
130+
cli.log.error('No changed files detected. Use "qmk cformat -a" to format all core files')
131+
return False
132+
133+
# Run clang-format on the files we've found
134+
if cli.args.dry_run:
135+
return not find_diffs(files)
136+
else:
137+
return cformat_run(files)

lib/python/qmk/cli/format/python.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""Format python code according to QMK's style.
2+
"""
3+
from subprocess import CalledProcessError, DEVNULL
4+
5+
from milc import cli
6+
7+
8+
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually format.")
9+
@cli.subcommand("Format python code according to QMK's style.", hidden=False if cli.config.user.developer else True)
10+
def format_python(cli):
11+
"""Format python code according to QMK's style.
12+
"""
13+
edit = '--diff' if cli.args.dry_run else '--in-place'
14+
yapf_cmd = ['yapf', '-vv', '--recursive', edit, 'bin/qmk', 'lib/python']
15+
try:
16+
cli.run(yapf_cmd, check=True, capture_output=False, stdin=DEVNULL)
17+
cli.log.info('Python code in `bin/qmk` and `lib/python` is correctly formatted.')
18+
return True
19+
20+
except CalledProcessError:
21+
if cli.args.dry_run:
22+
cli.log.error('Python code in `bin/qmk` and `lib/python` incorrectly formatted!')
23+
else:
24+
cli.log.error('Error formatting python code!')
25+
26+
return False

lib/python/qmk/cli/format/text.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""Ensure text files have the proper line endings.
2+
"""
3+
from subprocess import CalledProcessError
4+
5+
from milc import cli
6+
7+
8+
@cli.subcommand("Ensure text files have the proper line endings.", hidden=True)
9+
def format_text(cli):
10+
"""Ensure text files have the proper line endings.
11+
"""
12+
try:
13+
file_list_cmd = cli.run(['git', 'ls-files', '-z'], check=True)
14+
except CalledProcessError as e:
15+
cli.log.error('Could not get file list: %s', e)
16+
exit(1)
17+
except Exception as e:
18+
cli.log.error('Unhandled exception: %s: %s', e.__class__.__name__, e)
19+
cli.log.exception(e)
20+
exit(1)
21+
22+
dos2unix = cli.run(['xargs', '-0', 'dos2unix'], stdin=None, input=file_list_cmd.stdout)
23+
24+
if dos2unix.returncode != 0:
25+
print(dos2unix.stderr)
26+
27+
return dos2unix.returncode

0 commit comments

Comments
 (0)