aboutsummaryrefslogtreecommitdiff
path: root/lib/python
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python')
-rw-r--r--lib/python/qmk/cli/__init__.py3
-rwxr-xr-x[-rw-r--r--]lib/python/qmk/cli/cformat.py139
-rwxr-xr-x[-rw-r--r--]lib/python/qmk/cli/fileformat.py24
-rw-r--r--lib/python/qmk/cli/format/c.py137
-rwxr-xr-xlib/python/qmk/cli/format/python.py26
-rw-r--r--lib/python/qmk/cli/format/text.py27
-rwxr-xr-xlib/python/qmk/cli/pyformat.py32
7 files changed, 240 insertions, 148 deletions
diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py
index 91d42bb3a..dea0eaeaf 100644
--- a/lib/python/qmk/cli/__init__.py
+++ b/lib/python/qmk/cli/__init__.py
@@ -40,7 +40,10 @@ subcommands = [
40 'qmk.cli.doctor', 40 'qmk.cli.doctor',
41 'qmk.cli.fileformat', 41 'qmk.cli.fileformat',
42 'qmk.cli.flash', 42 'qmk.cli.flash',
43 'qmk.cli.format.c',
43 'qmk.cli.format.json', 44 'qmk.cli.format.json',
45 'qmk.cli.format.python',
46 'qmk.cli.format.text',
44 'qmk.cli.generate.api', 47 'qmk.cli.generate.api',
45 'qmk.cli.generate.config_h', 48 'qmk.cli.generate.config_h',
46 'qmk.cli.generate.dfu_header', 49 'qmk.cli.generate.dfu_header',
diff --git a/lib/python/qmk/cli/cformat.py b/lib/python/qmk/cli/cformat.py
index efeb45967..9d0ecaeba 100644..100755
--- a/lib/python/qmk/cli/cformat.py
+++ b/lib/python/qmk/cli/cformat.py
@@ -1,137 +1,28 @@
1"""Format C code according to QMK's style. 1"""Point people to the new command name.
2""" 2"""
3from os import path 3import sys
4from shutil import which 4from pathlib import Path
5from subprocess import CalledProcessError, DEVNULL, Popen, PIPE
6 5
7from argcomplete.completers import FilesCompleter
8from milc import cli 6from milc import cli
9 7
10from qmk.path import normpath
11from qmk.c_parse import c_source_files
12
13c_file_suffixes = ('c', 'h', 'cpp')
14core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms')
15ignored = ('tmk_core/protocol/usb_hid', 'quantum/template', 'platforms/chibios')
16
17
18def 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
30def 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
47def 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
68def 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 8
88@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Flag only, don't automatically format.") 9@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.') 10@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.') 11@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.') 12@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.') 13@cli.argument('files', nargs='*', arg_only=True, 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) 14@cli.subcommand('Pointer to the new command name: qmk format-c.', hidden=True)
94def cformat(cli): 15def cformat(cli):
95 """Format C code according to QMK's style. 16 """Pointer to the new command name: qmk format-c.
96 """ 17 """
97 # Find the list of files to format 18 cli.log.warning('"qmk cformat" has been renamed to "qmk format-c". Please use the new command in the future.')
98 if cli.args.files: 19 argv = [sys.executable, *sys.argv]
99 files = list(filter_files(cli.args.files, cli.args.core_only)) 20 argv[argv.index('cformat')] = 'format-c'
100 21 script_path = Path(argv[1])
101 if not files: 22 script_path_exe = Path(f'{argv[1]}.exe')
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 23
128 # Sanity check 24 if not script_path.exists() and script_path_exe.exists():
129 if not files: 25 # For reasons I don't understand ".exe" is stripped from the script name on windows.
130 cli.log.error('No changed files detected. Use "qmk cformat -a" to format all core files') 26 argv[1] = str(script_path_exe)
131 return False
132 27
133 # Run clang-format on the files we've found 28 return cli.run(argv, capture_output=False).returncode
134 if cli.args.dry_run:
135 return not find_diffs(files)
136 else:
137 return cformat_run(files)
diff --git a/lib/python/qmk/cli/fileformat.py b/lib/python/qmk/cli/fileformat.py
index 112d8d59d..cee4ba1ac 100644..100755
--- a/lib/python/qmk/cli/fileformat.py
+++ b/lib/python/qmk/cli/fileformat.py
@@ -1,13 +1,23 @@
1"""Format files according to QMK's style. 1"""Point people to the new command name.
2""" 2"""
3from milc import cli 3import sys
4from pathlib import Path
4 5
5import subprocess 6from milc import cli
6 7
7 8
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)
9def fileformat(cli): 10def fileformat(cli):
10 """Run several general formatting commands. 11 """Pointer to the new command name: qmk format-text.
11 """ 12 """
12 dos2unix = subprocess.run(['bash', '-c', 'git ls-files -z | xargs -0 dos2unix'], stdout=subprocess.DEVNULL) 13 cli.log.warning('"qmk fileformat" has been renamed to "qmk format-text". Please use the new command in the future.')
13 return dos2unix.returncode 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
diff --git a/lib/python/qmk/cli/format/c.py b/lib/python/qmk/cli/format/c.py
new file mode 100644
index 000000000..699de8d49
--- /dev/null
+++ b/lib/python/qmk/cli/format/c.py
@@ -0,0 +1,137 @@
1"""Format C code according to QMK's style.
2"""
3from os import path
4from shutil import which
5from subprocess import CalledProcessError, DEVNULL, Popen, PIPE
6
7from argcomplete.completers import FilesCompleter
8from milc import cli
9
10from qmk.path import normpath
11from qmk.c_parse import c_source_files
12
13c_file_suffixes = ('c', 'h', 'cpp')
14core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms')
15ignored = ('tmk_core/protocol/usb_hid', 'quantum/template', 'platforms/chibios')
16
17
18def 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
30def 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
47def 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
68def 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)
94def 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)
diff --git a/lib/python/qmk/cli/format/python.py b/lib/python/qmk/cli/format/python.py
new file mode 100755
index 000000000..00612f97e
--- /dev/null
+++ b/lib/python/qmk/cli/format/python.py
@@ -0,0 +1,26 @@
1"""Format python code according to QMK's style.
2"""
3from subprocess import CalledProcessError, DEVNULL
4
5from 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)
10def 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
diff --git a/lib/python/qmk/cli/format/text.py b/lib/python/qmk/cli/format/text.py
new file mode 100644
index 000000000..e7e07b729
--- /dev/null
+++ b/lib/python/qmk/cli/format/text.py
@@ -0,0 +1,27 @@
1"""Ensure text files have the proper line endings.
2"""
3from subprocess import CalledProcessError
4
5from milc import cli
6
7
8@cli.subcommand("Ensure text files have the proper line endings.", hidden=True)
9def 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
diff --git a/lib/python/qmk/cli/pyformat.py b/lib/python/qmk/cli/pyformat.py
index abe5f6de1..c624f74ae 100755
--- a/lib/python/qmk/cli/pyformat.py
+++ b/lib/python/qmk/cli/pyformat.py
@@ -1,26 +1,24 @@
1"""Format python code according to QMK's style. 1"""Point people to the new command name.
2""" 2"""
3from subprocess import CalledProcessError, DEVNULL 3import sys
4from pathlib import Path
4 5
5from milc import cli 6from milc import cli
6 7
7 8
8@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Flag only, don't automatically format.") 9@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@cli.subcommand('Pointer to the new command name: qmk format-python.', hidden=False if cli.config.user.developer else True)
10def pyformat(cli): 11def pyformat(cli):
11 """Format python code according to QMK's style. 12 """Pointer to the new command name: qmk format-python.
12 """ 13 """
13 edit = '--diff' if cli.args.dry_run else '--in-place' 14 cli.log.warning('"qmk pyformat" has been renamed to "qmk format-python". Please use the new command in the future.')
14 yapf_cmd = ['yapf', '-vv', '--recursive', edit, 'bin/qmk', 'lib/python'] 15 argv = [sys.executable, *sys.argv]
15 try: 16 argv[argv.index('pyformat')] = 'format-python'
16 cli.run(yapf_cmd, check=True, capture_output=False, stdin=DEVNULL) 17 script_path = Path(argv[1])
17 cli.log.info('Python code in `bin/qmk` and `lib/python` is correctly formatted.') 18 script_path_exe = Path(f'{argv[1]}.exe')
18 return True
19 19
20 except CalledProcessError: 20 if not script_path.exists() and script_path_exe.exists():
21 if cli.args.dry_run: 21 # For reasons I don't understand ".exe" is stripped from the script name on windows.
22 cli.log.error('Python code in `bin/qmk` and `lib/python` incorrectly formatted!') 22 argv[1] = str(script_path_exe)
23 else:
24 cli.log.error('Error formatting python code!')
25 23
26 return False 24 return cli.run(argv, capture_output=False).returncode