aboutsummaryrefslogtreecommitdiff
path: root/lib/python
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python')
-rw-r--r--lib/python/qmk/cli/__init__.py1
-rw-r--r--lib/python/qmk/cli/lint.py70
-rw-r--r--lib/python/qmk/info.py43
-rw-r--r--lib/python/qmk/path.py14
4 files changed, 112 insertions, 16 deletions
diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py
index 3868f94bb..77724a244 100644
--- a/lib/python/qmk/cli/__init__.py
+++ b/lib/python/qmk/cli/__init__.py
@@ -19,6 +19,7 @@ from . import hello
19from . import info 19from . import info
20from . import json 20from . import json
21from . import json2c 21from . import json2c
22from . import lint
22from . import list 23from . import list
23from . import kle2json 24from . import kle2json
24from . import new 25from . import new
diff --git a/lib/python/qmk/cli/lint.py b/lib/python/qmk/cli/lint.py
new file mode 100644
index 000000000..74467021e
--- /dev/null
+++ b/lib/python/qmk/cli/lint.py
@@ -0,0 +1,70 @@
1"""Command to look over a keyboard/keymap and check for common mistakes.
2"""
3from milc import cli
4
5from qmk.decorators import automagic_keyboard, automagic_keymap
6from qmk.info import info_json
7from qmk.keymap import locate_keymap
8from qmk.path import is_keyboard, keyboard
9
10
11@cli.argument('--strict', action='store_true', help='Treat warnings as errors.')
12@cli.argument('-kb', '--keyboard', help='The keyboard to check.')
13@cli.argument('-km', '--keymap', help='The keymap to check.')
14@cli.subcommand('Check keyboard and keymap for common mistakes.')
15@automagic_keyboard
16@automagic_keymap
17def lint(cli):
18 """Check keyboard and keymap for common mistakes.
19 """
20 if not cli.config.lint.keyboard:
21 cli.log.error('Missing required argument: --keyboard')
22 cli.print_help()
23 return False
24
25 if not is_keyboard(cli.config.lint.keyboard):
26 cli.log.error('No such keyboard: %s', cli.config.lint.keyboard)
27 return False
28
29 # Gather data about the keyboard.
30 ok = True
31 keyboard_path = keyboard(cli.config.lint.keyboard)
32 keyboard_info = info_json(cli.config.lint.keyboard)
33 readme_path = keyboard_path / 'readme.md'
34
35 # Check for errors in the info.json
36 if keyboard_info['parse_errors']:
37 ok = False
38 cli.log.error('Errors found when generating info.json.')
39
40 if cli.config.lint.strict and keyboard_info['parse_warnings']:
41 ok = False
42 cli.log.error('Warnings found when generating info.json (Strict mode enabled.)')
43
44 # Check for a readme.md and warn if it doesn't exist
45 if not readme_path.exists():
46 ok = False
47 cli.log.error('Missing %s', readme_path)
48
49 # Keymap specific checks
50 if cli.config.lint.keymap:
51 keymap_path = locate_keymap(cli.config.lint.keyboard, cli.config.lint.keymap)
52
53 if not keymap_path:
54 ok = False
55 cli.log.error("Can't find %s keymap for %s keyboard.", cli.config.lint.keymap, cli.config.lint.keyboard)
56 else:
57 keymap_readme = keymap_path.parent / 'readme.md'
58 if not keymap_readme.exists():
59 cli.log.warning('Missing %s', keymap_readme)
60
61 if cli.config.lint.strict:
62 ok = False
63
64 # Check and report the overall status
65 if ok:
66 cli.log.info('Lint check passed!')
67 return True
68
69 cli.log.error('Lint check failed!')
70 return False
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py
index e92c3335b..d73ba8cfb 100644
--- a/lib/python/qmk/info.py
+++ b/lib/python/qmk/info.py
@@ -28,6 +28,8 @@ def info_json(keyboard):
28 'keyboard_folder': str(keyboard), 28 'keyboard_folder': str(keyboard),
29 'keymaps': {}, 29 'keymaps': {},
30 'layouts': {}, 30 'layouts': {},
31 'parse_errors': [],
32 'parse_warnings': [],
31 'maintainer': 'qmk', 33 'maintainer': 'qmk',
32 } 34 }
33 35
@@ -36,7 +38,7 @@ def info_json(keyboard):
36 info_data['keymaps'][keymap.name] = {'url': f'https://raw.githubusercontent.com/qmk/qmk_firmware/master/{keymap}/keymap.json'} 38 info_data['keymaps'][keymap.name] = {'url': f'https://raw.githubusercontent.com/qmk/qmk_firmware/master/{keymap}/keymap.json'}
37 39
38 # Populate layout data 40 # Populate layout data
39 for layout_name, layout_json in _find_all_layouts(keyboard, rules).items(): 41 for layout_name, layout_json in _find_all_layouts(info_data, keyboard, rules).items():
40 if not layout_name.startswith('LAYOUT_kc'): 42 if not layout_name.startswith('LAYOUT_kc'):
41 info_data['layouts'][layout_name] = layout_json 43 info_data['layouts'][layout_name] = layout_json
42 44
@@ -104,14 +106,16 @@ def _extract_rules_mk(info_data):
104 mcu = rules.get('MCU') 106 mcu = rules.get('MCU')
105 107
106 if mcu in CHIBIOS_PROCESSORS: 108 if mcu in CHIBIOS_PROCESSORS:
107 arm_processor_rules(info_data, rules) 109 return arm_processor_rules(info_data, rules)
110
108 elif mcu in LUFA_PROCESSORS + VUSB_PROCESSORS: 111 elif mcu in LUFA_PROCESSORS + VUSB_PROCESSORS:
109 avr_processor_rules(info_data, rules) 112 return avr_processor_rules(info_data, rules)
110 else:
111 cli.log.warning("%s: Unknown MCU: %s" % (info_data['keyboard_folder'], mcu))
112 unknown_processor_rules(info_data, rules)
113 113
114 return info_data 114 msg = "Unknown MCU: " + str(mcu)
115
116 _log_warning(info_data, msg)
117
118 return unknown_processor_rules(info_data, rules)
115 119
116 120
117def _search_keyboard_h(path): 121def _search_keyboard_h(path):
@@ -127,7 +131,7 @@ def _search_keyboard_h(path):
127 return layouts 131 return layouts
128 132
129 133
130def _find_all_layouts(keyboard, rules): 134def _find_all_layouts(info_data, keyboard, rules):
131 """Looks for layout macros associated with this keyboard. 135 """Looks for layout macros associated with this keyboard.
132 """ 136 """
133 layouts = _search_keyboard_h(Path(keyboard)) 137 layouts = _search_keyboard_h(Path(keyboard))
@@ -135,7 +139,7 @@ def _find_all_layouts(keyboard, rules):
135 if not layouts: 139 if not layouts:
136 # If we didn't find any layouts above we widen our search. This is error 140 # If we didn't find any layouts above we widen our search. This is error
137 # prone which is why we want to encourage people to follow the standard above. 141 # prone which is why we want to encourage people to follow the standard above.
138 cli.log.warning('%s: Falling back to searching for KEYMAP/LAYOUT macros.' % (keyboard)) 142 _log_warning(info_data, 'Falling back to searching for KEYMAP/LAYOUT macros.')
139 for file in glob('keyboards/%s/*.h' % keyboard): 143 for file in glob('keyboards/%s/*.h' % keyboard):
140 if file.endswith('.h'): 144 if file.endswith('.h'):
141 these_layouts = find_layouts(file) 145 these_layouts = find_layouts(file)
@@ -153,11 +157,25 @@ def _find_all_layouts(keyboard, rules):
153 supported_layouts.remove(layout_name) 157 supported_layouts.remove(layout_name)
154 158
155 if supported_layouts: 159 if supported_layouts:
156 cli.log.error('%s: Missing LAYOUT() macro for %s' % (keyboard, ', '.join(supported_layouts))) 160 _log_error(info_data, 'Missing LAYOUT() macro for %s' % (', '.join(supported_layouts)))
157 161
158 return layouts 162 return layouts
159 163
160 164
165def _log_error(info_data, message):
166 """Send an error message to both JSON and the log.
167 """
168 info_data['parse_errors'].append(message)
169 cli.log.error('%s: %s', info_data.get('keyboard_folder', 'Unknown Keyboard!'), message)
170
171
172def _log_warning(info_data, message):
173 """Send a warning message to both JSON and the log.
174 """
175 info_data['parse_warnings'].append(message)
176 cli.log.warning('%s: %s', info_data.get('keyboard_folder', 'Unknown Keyboard!'), message)
177
178
161def arm_processor_rules(info_data, rules): 179def arm_processor_rules(info_data, rules):
162 """Setup the default info for an ARM board. 180 """Setup the default info for an ARM board.
163 """ 181 """
@@ -216,7 +234,7 @@ def merge_info_jsons(keyboard, info_data):
216 new_info_data = json.load(info_fd) 234 new_info_data = json.load(info_fd)
217 235
218 if not isinstance(new_info_data, dict): 236 if not isinstance(new_info_data, dict):
219 cli.log.error("Invalid file %s, root object should be a dictionary.", str(info_file)) 237 _log_error(info_data, "Invalid file %s, root object should be a dictionary.", str(info_file))
220 continue 238 continue
221 239
222 # Copy whitelisted keys into `info_data` 240 # Copy whitelisted keys into `info_data`
@@ -230,7 +248,8 @@ def merge_info_jsons(keyboard, info_data):
230 # Only pull in layouts we have a macro for 248 # Only pull in layouts we have a macro for
231 if layout_name in info_data['layouts']: 249 if layout_name in info_data['layouts']:
232 if info_data['layouts'][layout_name]['key_count'] != len(json_layout['layout']): 250 if info_data['layouts'][layout_name]['key_count'] != len(json_layout['layout']):
233 cli.log.error('%s: %s: Number of elements in info.json does not match! info.json:%s != %s:%s', info_data['keyboard_folder'], layout_name, len(json_layout['layout']), layout_name, len(info_data['layouts'][layout_name]['layout'])) 251 msg = '%s: Number of elements in info.json does not match! info.json:%s != %s:%s'
252 _log_error(info_data, msg % (layout_name, len(json_layout['layout']), layout_name, len(info_data['layouts'][layout_name]['layout'])))
234 else: 253 else:
235 for i, key in enumerate(info_data['layouts'][layout_name]['layout']): 254 for i, key in enumerate(info_data['layouts'][layout_name]['layout']):
236 key.update(json_layout['layout'][i]) 255 key.update(json_layout['layout'][i])
diff --git a/lib/python/qmk/path.py b/lib/python/qmk/path.py
index 591fad034..54def1d5d 100644
--- a/lib/python/qmk/path.py
+++ b/lib/python/qmk/path.py
@@ -28,15 +28,21 @@ def under_qmk_firmware():
28 return None 28 return None
29 29
30 30
31def keymap(keyboard): 31def keyboard(keyboard_name):
32 """Returns the path to a keyboard's directory relative to the qmk root.
33 """
34 return Path('keyboards') / keyboard_name
35
36
37def keymap(keyboard_name):
32 """Locate the correct directory for storing a keymap. 38 """Locate the correct directory for storing a keymap.
33 39
34 Args: 40 Args:
35 41
36 keyboard 42 keyboard_name
37 The name of the keyboard. Example: clueboard/66/rev3 43 The name of the keyboard. Example: clueboard/66/rev3
38 """ 44 """
39 keyboard_folder = Path('keyboards') / keyboard 45 keyboard_folder = keyboard(keyboard_name)
40 46
41 for i in range(MAX_KEYBOARD_SUBFOLDERS): 47 for i in range(MAX_KEYBOARD_SUBFOLDERS):
42 if (keyboard_folder / 'keymaps').exists(): 48 if (keyboard_folder / 'keymaps').exists():
@@ -45,7 +51,7 @@ def keymap(keyboard):
45 keyboard_folder = keyboard_folder.parent 51 keyboard_folder = keyboard_folder.parent
46 52
47 logging.error('Could not find the keymaps directory!') 53 logging.error('Could not find the keymaps directory!')
48 raise NoSuchKeyboardError('Could not find keymaps directory for: %s' % keyboard) 54 raise NoSuchKeyboardError('Could not find keymaps directory for: %s' % keyboard_name)
49 55
50 56
51def normpath(path): 57def normpath(path):