aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQMK Bot <hello@qmk.fm>2021-03-24 16:27:28 +0000
committerQMK Bot <hello@qmk.fm>2021-03-24 16:27:28 +0000
commit0dc0516f0c38ce1eb5e7e2ec2a69b743c33e9ce6 (patch)
treed38b0682e31545e5875a7cddf4e473fd67dd0ad7
parent743d6c430bf09710be9a1055c353fbbceed939ac (diff)
parent299008be36076343edadb7a36bf2fff820425ad1 (diff)
downloadqmk_firmware-0dc0516f0c38ce1eb5e7e2ec2a69b743c33e9ce6.tar.gz
qmk_firmware-0dc0516f0c38ce1eb5e7e2ec2a69b743c33e9ce6.zip
Merge remote-tracking branch 'origin/master' into develop
-rw-r--r--data/mappings/keyboard_aliases.json443
-rw-r--r--lib/python/qmk/cli/c2json.py3
-rwxr-xr-xlib/python/qmk/cli/compile.py3
-rw-r--r--lib/python/qmk/cli/flash.py3
-rwxr-xr-xlib/python/qmk/cli/generate/api.py50
-rwxr-xr-xlib/python/qmk/cli/generate/config_h.py8
-rwxr-xr-xlib/python/qmk/cli/generate/info_json.py8
-rwxr-xr-xlib/python/qmk/cli/generate/layouts.py3
-rwxr-xr-xlib/python/qmk/cli/generate/rules_mk.py8
-rwxr-xr-xlib/python/qmk/cli/info.py4
-rw-r--r--lib/python/qmk/cli/list/keymaps.py8
-rwxr-xr-xlib/python/qmk/cli/new/keymap.py3
-rw-r--r--lib/python/qmk/commands.py10
-rw-r--r--lib/python/qmk/info.py69
-rw-r--r--lib/python/qmk/json_schema.py68
-rw-r--r--lib/python/qmk/keyboard.py24
-rw-r--r--lib/python/qmk/path.py1
-rw-r--r--lib/python/qmk/tests/test_cli_commands.py4
18 files changed, 614 insertions, 106 deletions
diff --git a/data/mappings/keyboard_aliases.json b/data/mappings/keyboard_aliases.json
new file mode 100644
index 000000000..5a2f7e3ae
--- /dev/null
+++ b/data/mappings/keyboard_aliases.json
@@ -0,0 +1,443 @@
1{
2 # Format for each entry:
3 # <alias>: {
4 # target: <keyboard_folder>,
5 # layouts: {
6 # <layout_alias>: <layout_target>
7 # }
8 # }
9 #
10 # Both target and layouts are optional.
11 '2_milk': {
12 target: 'spaceman/2_milk'
13 },
14 'aeboards/ext65': {
15 target: 'aeboards/ext65/rev1'
16 },
17 'ai03/equinox': {
18 target: 'ai03/equinox/rev1'
19 },
20 aleth42: {
21 target: 'aleth42/rev1'
22 },
23 alice: {
24 target: 'tgr/alice'
25 },
26 angel17: {
27 target: 'angel17/alpha'
28 },
29 angel64: {
30 target: 'angel64/alpha'
31 },
32 at101_blackheart: {
33 target: 'at101_bh'
34 },
35 'atom47/rev2': {
36 target: 'maartenwut/atom47/rev2'
37 },
38 'atom47/rev3': {
39 target: 'maartenwut/atom47/rev3'
40 },
41 bear_face: {
42 target: 'bear_face/v1'
43 },
44 'bpiphany/pegasushoof': {
45 target: 'bpiphany/pegasushoof/2013'
46 },
47 chavdai40: {
48 target: 'chavdai40/rev1'
49 },
50 'candybar/lefty': {
51 target: 'tkc/candybar/lefty'
52 },
53 'candybar/righty': {
54 target: 'tkc/candybar/righty'
55 },
56 canoe: {
57 target: 'percent/canoe'
58 },
59 'cmm_studio/saka68': {
60 target: 'cmm_studio/saka68/solder'
61 },
62 'crkbd/rev1': {
63 target: 'crkbd/rev1/legacy'
64 },
65 'doro67/multi': {
66 layouts: {
67 LAYOUT_ansi: 'LAYOUT_65_ansi_blocker'
68 }
69 },
70 'doro67/regular': {
71 layouts: {
72 LAYOUT: 'LAYOUT_65_ansi_blocker'
73 }
74 },
75 'doro67/rgb': {
76 layouts: {
77 LAYOUT: 'LAYOUT_65_ansi_blocker'
78 }
79 },
80 drakon: {
81 target: 'jagdpietr/drakon'
82 },
83 'dztech/dz60rgb': {
84 target: 'dztech/dz60rgb/v1'
85 },
86 'dztech/dz60rgb_ansi': {
87 target: 'dztech/dz60rgb_ansi/v1'
88 },
89 'dztech/dz60rgb_wkl': {
90 target: 'dztech/dz60rgb_wkl/v1'
91 },
92 'dztech/dz65rgb': {
93 target: 'dztech/dz65rgb/v1'
94 },
95 eek: {
96 target: 'eek/silk_down'
97 },
98 ergoinu: {
99 target: 'dm9records/ergoinu'
100 },
101 'exclusive/e85': {
102 target: 'exclusive/e85/hotswap'
103 },
104 gh60: {
105 target: 'gh60/revc'
106 },
107 'handwired/ferris': {
108 target: 'ferris/0_1'
109 },
110 'helix/pico/sc/back': {
111 target: 'helix/pico/sc'
112 },
113 'helix/pico/sc/under': {
114 target: 'helix/pico/sc'
115 },
116 'helix/rev2/back/oled': {
117 target: 'helix/rev2/back'
118 },
119 'helix/rev2/oled': {
120 target: 'helix/rev2'
121 },
122 'helix/rev2/oled/back': {
123 target: 'helix/rev2/back'
124 },
125 'helix/rev2/oled/under': {
126 target: 'helix/rev2/under'
127 },
128 'helix/rev2/sc/back': {
129 target: 'helix/rev2/sc'
130 },
131 'helix/rev2/sc/oled': {
132 target: 'helix/rev2/sc'
133 },
134 'helix/rev2/sc/oledback': {
135 target: 'helix/rev2/sc'
136 },
137 'helix/rev2/sc/oledunder': {
138 target: 'helix/rev2/sc'
139 },
140 'helix/rev2/sc/under': {
141 target: 'helix/rev2/sc'
142 },
143 'helix/rev2/under': {
144 target: 'helix/rev2/sc'
145 },
146 'helix/rev2/under/oled': {
147 target: 'helix/rev2/under'
148 },
149 id80: {
150 target: 'id80/ansi'
151 },
152 idb_60: {
153 target: 'idb/idb_60',
154 layouts: {
155 LAYOUT: 'LAYOUT_all'
156 }
157 },
158 jones: {
159 target: 'jones/v03_1'
160 },
161 katana60: {
162 target: 'rominronin/katana60/rev1'
163 },
164 'kbdfans/kbd67mkiirgb': {
165 target: 'kbdfans/kbd67/mkiirgb',
166 layouts: {
167 LAYOUT: 'LAYOUT_65_ansi_blocker'
168 }
169 },
170 'kbdfans/kbd67/mkiirgb': {
171 target: 'kbdfans/kbd67/mkiirgb/v1'
172 },
173 'keebio/dsp40': {
174 target: 'keebio/dsp40/rev1'
175 },
176 'keycapsss/plaid_pad': {
177 target: 'keycapsss/plaid_pad/rev1'
178 },
179 kudox: {
180 target: 'kudox/rev1'
181 },
182 'lfkeyboards/lfk78': {
183 target: 'lfkeyboards/lfk78/revj'
184 },
185 'lfkeyboards/smk65': {
186 target: 'lfkeyboards/smk65/revb'
187 },
188 'maartenwut/atom47/rev2': {
189 target: 'evyd13/atom47/rev2'
190 },
191 'maartenwut/atom47/rev3': {
192 target: 'evyd13/atom47/rev3'
193 },
194 'maartenwut/eon40': {
195 target: 'evyd13/eon40'
196 },
197 'maartenwut/eon65': {
198 target: 'evyd13/eon65'
199 },
200 'maartenwut/eon75': {
201 target: 'evyd13/eon75'
202 },
203 'maartenwut/eon87': {
204 target: 'evyd13/eon87'
205 },
206 'maartenwut/eon95': {
207 target: 'evyd13/eon95'
208 },
209 'maartenwut/gh80_1800': {
210 target: 'evyd13/gh80_1800'
211 },
212 'maartenwut/gh80_3700': {
213 target: 'evyd13/gh80_3700'
214 },
215 'maartenwut/minitomic': {
216 target: 'evyd13/minitomic'
217 },
218 'maartenwut/mx5160': {
219 target: 'evyd13/mx5160'
220 },
221 'maartenwut/nt660': {
222 target: 'evyd13/nt660'
223 },
224 'maartenwut/omrontkl': {
225 target: 'evyd13/omrontkl'
226 },
227 'maartenwut/plain60': {
228 target: 'evyd13/plain60'
229 },
230 'maartenwut/pockettype': {
231 target: 'evyd13/pockettype'
232 },
233 'maartenwut/quackfire': {
234 target: 'evyd13/quackfire'
235 },
236 'maartenwut/solheim68': {
237 target: 'evyd13/solheim68'
238 },
239 'maartenwut/ta65': {
240 target: 'evyd13/ta65'
241 },
242 'maartenwut/wasdat': {
243 target: 'evyd13/wasdat'
244 },
245 'maartenwut/wasdat_code': {
246 target: 'evyd13/wasdat_code'
247 },
248 'maartenwut/wonderland': {
249 target: 'evyd13/wonderland'
250 },
251 'mechlovin/hannah910': {
252 target: 'mechlovin/hannah910/rev1'
253 },
254 'mechlovin/adelais/rgb_led': {
255 target: 'mechlovin/adelais/rgb_led/rev1'
256 },
257 'mechlovin/adelais/standard_led': {
258 target: 'mechlovin/adelais/standard_led/rev2'
259 },
260 'mechlovin/delphine': {
261 target: 'mechlovin/delphine/mono_led'
262 },
263 'mechlovin/hannah60rgb': {
264 target: 'mechlovin/hannah60rgb/rev1'
265 },
266 'melgeek/z70ultra': {
267 target: 'melgeek/z70ultra/rev1'
268 },
269 'mechlovin/hannah65': {
270 target: 'mechlovin/hannah65/rev1'
271 },
272 model01: {
273 target: 'keyboardio/model01'
274 },
275 m0lly: {
276 target: 'tkc/m0lly'
277 },
278 'montsinger/rebound': {
279 target: 'montsinger/rebound/rev1'
280 },
281 nomu30: {
282 target: 'nomu30/rev1'
283 },
284 'noxary/268_2': {
285 layouts: {
286 LAYOUT: 'LAYOUT_65_ansi_blocker'
287 }
288 },
289 oddball: {
290 target: 'oddball/v1'
291 },
292 omnikey_blackheart: {
293 target: 'omnikey_bh'
294 },
295 'pabile/p20': {
296 target: 'pabile/p20/ver1'
297 },
298 'pancake/feather': {
299 target: 'spaceman/pancake/feather'
300 },
301 'pancake/promicro': {
302 target: 'spaceman/pancake/promicro'
303 },
304 'percent/canoe': {
305 layouts: {
306 LAYOUT_iso: 'LAYOUT_65_iso_blocker'
307 }
308 },
309 plaid: {
310 target: 'dm9records/plaid'
311 },
312 plain60: {
313 target: 'maartenwut/plain60'
314 },
315 'ploopyco/trackball': {
316 target: 'ploopyco/trackball/rev1_005'
317 },
318 polilla: {
319 target: 'polilla/rev1'
320 },
321 'preonic/rev1': {
322 layouts: {
323 LAYOUT_preonic_grid: 'LAYOUT_ortho_5x12'
324 }
325 },
326 'preonic/rev2': {
327 layouts: {
328 LAYOUT_preonic_grid: 'LAYOUT_ortho_5x12'
329 }
330 },
331 'preonic/rev3': {
332 layouts: {
333 LAYOUT_preonic_grid: 'LAYOUT_ortho_5x12'
334 }
335 },
336 'primekb/prime_l': {
337 target: 'primekb/prime_l/v1'
338 },
339 'primekb/prime_l_v2': {
340 target: 'primekb/prime_l/v2'
341 },
342 'projectkb/alice': {
343 target: 'projectkb/alice/rev1'
344 },
345 'rama/koyu': {
346 target: 'wilba_tech/rama_works_koyu'
347 },
348 'rama/m6_a': {
349 target: 'wilba_tech/rama_works_m6_a'
350 },
351 'rama/m6_b': {
352 target: 'wilba_tech/rama_works_m6_b'
353 },
354 'rama/m10_b': {
355 target: 'wilba_tech/rama_works_m10_b'
356 },
357 'rama/m60_a': {
358 target: 'wilba_tech/rama_works_m60_a'
359 },
360 'rama/u80_a': {
361 target: 'wilba_tech/rama_works_u80_a'
362 },
363 'ramonimbao/herringbone': {
364 target: 'ramonimbao/herringbone/v1'
365 },
366 'rgbkb/pan': {
367 target: 'rgbkb/pan/rev1/32a'
368 },
369 'rgbkb/pan/rev1': {
370 target: 'rgbkb/pan/rev1/32a'
371 },
372 romac: {
373 target: 'kingly_keys/romac'
374 },
375 ropro: {
376 target: 'kingly_keys/ropro'
377 },
378 satan: {
379 target: 'gh60/satan'
380 },
381 skog: {
382 target: 'percent/skog'
383 },
384 speedo: {
385 target: 'cozykeys/speedo/v2'
386 },
387 stoutgat: {
388 target: 'tkw/stoutgat/v1'
389 },
390 suihankey: {
391 target: 'suihankey/split/alpha'
392 },
393 ta65: {
394 target: 'maartenwut/ta65'
395 },
396 tartan: {
397 target: 'dm9records/tartan'
398 },
399 tkc1800: {
400 target: 'tkc/tkc1800'
401 },
402 'tkw/stoutgat/v2': {
403 target: 'tkw/stoutgat/v2/f411'
404 },
405 underscore33: {
406 target: 'underscore33/rev1'
407 },
408 vinta: {
409 layouts: {
410 LAYOUT_67_ansi: 'LAYOUT_65_ansi_blocker'
411 }
412 },
413 wasdat: {
414 target: 'maartenwut/wasdat'
415 },
416 'westfoxtrot/cypher': {
417 target: 'westfoxtrot/cypher/rev1'
418 },
419 'whale/sk': {
420 target: 'whale/sk/v3'
421 },
422 'xelus/dawn60': {
423 target: 'xelus/dawn60/rev1'
424 },
425 'xelus/valor': {
426 target: 'xelus/valor/rev1'
427 },
428 yd60mq: {
429 target: 'yd60mq/12led'
430 },
431 ymd75: {
432 target: 'ymd75/rev1'
433 },
434 z150_blackheart: {
435 target: 'z150_bh'
436 },
437 zeal60: {
438 target: 'wilba_tech/zeal60'
439 },
440 zeal65: {
441 target: 'wilba_tech/zeal65'
442 }
443}
diff --git a/lib/python/qmk/cli/c2json.py b/lib/python/qmk/cli/c2json.py
index b9d55ebdb..a97e21222 100644
--- a/lib/python/qmk/cli/c2json.py
+++ b/lib/python/qmk/cli/c2json.py
@@ -7,12 +7,13 @@ from milc import cli
7import qmk.keymap 7import qmk.keymap
8import qmk.path 8import qmk.path
9from qmk.info_json_encoder import InfoJSONEncoder 9from qmk.info_json_encoder import InfoJSONEncoder
10from qmk.keyboard import keyboard_folder
10 11
11 12
12@cli.argument('--no-cpp', arg_only=True, action='store_false', help='Do not use \'cpp\' on keymap.c') 13@cli.argument('--no-cpp', arg_only=True, action='store_false', help='Do not use \'cpp\' on keymap.c')
13@cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') 14@cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to')
14@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") 15@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
15@cli.argument('-kb', '--keyboard', arg_only=True, required=True, help='The keyboard\'s name') 16@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, required=True, help='The keyboard\'s name')
16@cli.argument('-km', '--keymap', arg_only=True, required=True, help='The keymap\'s name') 17@cli.argument('-km', '--keymap', arg_only=True, required=True, help='The keymap\'s name')
17@cli.argument('filename', arg_only=True, help='keymap.c file') 18@cli.argument('filename', arg_only=True, help='keymap.c file')
18@cli.subcommand('Creates a keymap.json from a keymap.c file.') 19@cli.subcommand('Creates a keymap.json from a keymap.c file.')
diff --git a/lib/python/qmk/cli/compile.py b/lib/python/qmk/cli/compile.py
index db195f78a..5793e9892 100755
--- a/lib/python/qmk/cli/compile.py
+++ b/lib/python/qmk/cli/compile.py
@@ -7,10 +7,11 @@ from milc import cli
7import qmk.path 7import qmk.path
8from qmk.decorators import automagic_keyboard, automagic_keymap 8from qmk.decorators import automagic_keyboard, automagic_keymap
9from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json 9from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json
10from qmk.keyboard import keyboard_folder
10 11
11 12
12@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), help='The configurator export to compile') 13@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), help='The configurator export to compile')
13@cli.argument('-kb', '--keyboard', help='The keyboard to build a firmware for. Ignored when a configurator export is supplied.') 14@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='The keyboard to build a firmware for. Ignored when a configurator export is supplied.')
14@cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Ignored when a configurator export is supplied.') 15@cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Ignored when a configurator export is supplied.')
15@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.") 16@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.")
16@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.") 17@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.")
diff --git a/lib/python/qmk/cli/flash.py b/lib/python/qmk/cli/flash.py
index 173dee3df..c9273c3f9 100644
--- a/lib/python/qmk/cli/flash.py
+++ b/lib/python/qmk/cli/flash.py
@@ -9,6 +9,7 @@ from milc import cli
9import qmk.path 9import qmk.path
10from qmk.decorators import automagic_keyboard, automagic_keymap 10from qmk.decorators import automagic_keyboard, automagic_keymap
11from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json 11from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json
12from qmk.keyboard import keyboard_folder
12 13
13 14
14def print_bootloader_help(): 15def print_bootloader_help():
@@ -33,7 +34,7 @@ def print_bootloader_help():
33@cli.argument('-b', '--bootloaders', action='store_true', help='List the available bootloaders.') 34@cli.argument('-b', '--bootloaders', action='store_true', help='List the available bootloaders.')
34@cli.argument('-bl', '--bootloader', default='flash', help='The flash command, corresponding to qmk\'s make options of bootloaders.') 35@cli.argument('-bl', '--bootloader', default='flash', help='The flash command, corresponding to qmk\'s make options of bootloaders.')
35@cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.') 36@cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.')
36@cli.argument('-kb', '--keyboard', help='The keyboard to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.') 37@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='The keyboard to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.')
37@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.") 38@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.")
38@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.") 39@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.")
39@cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.") 40@cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.")
diff --git a/lib/python/qmk/cli/generate/api.py b/lib/python/qmk/cli/generate/api.py
index 6d111f244..9870f7201 100755
--- a/lib/python/qmk/cli/generate/api.py
+++ b/lib/python/qmk/cli/generate/api.py
@@ -9,6 +9,7 @@ from milc import cli
9from qmk.datetime import current_datetime 9from qmk.datetime import current_datetime
10from qmk.info import info_json 10from qmk.info import info_json
11from qmk.info_json_encoder import InfoJSONEncoder 11from qmk.info_json_encoder import InfoJSONEncoder
12from qmk.json_schema import json_load
12from qmk.keyboard import list_keyboards 13from qmk.keyboard import list_keyboards
13 14
14 15
@@ -18,43 +19,58 @@ def generate_api(cli):
18 """ 19 """
19 api_data_dir = Path('api_data') 20 api_data_dir = Path('api_data')
20 v1_dir = api_data_dir / 'v1' 21 v1_dir = api_data_dir / 'v1'
21 keyboard_list = v1_dir / 'keyboard_list.json' 22 keyboard_all_file = v1_dir / 'keyboards.json' # A massive JSON containing everything
22 keyboard_all = v1_dir / 'keyboards.json' 23 keyboard_list_file = v1_dir / 'keyboard_list.json' # A simple list of keyboard targets
23 usb_file = v1_dir / 'usb.json' 24 keyboard_aliases_file = v1_dir / 'keyboard_aliases.json' # A list of historical keyboard names and their new name
25 keyboard_metadata_file = v1_dir / 'keyboard_metadata.json' # All the data configurator/via needs for initialization
26 usb_file = v1_dir / 'usb.json' # A mapping of USB VID/PID -> keyboard target
24 27
25 if not api_data_dir.exists(): 28 if not api_data_dir.exists():
26 api_data_dir.mkdir() 29 api_data_dir.mkdir()
27 30
28 kb_all = {'last_updated': current_datetime(), 'keyboards': {}} 31 kb_all = {}
29 usb_list = {'last_updated': current_datetime(), 'devices': {}} 32 usb_list = {}
30 33
31 # Generate and write keyboard specific JSON files 34 # Generate and write keyboard specific JSON files
32 for keyboard_name in list_keyboards(): 35 for keyboard_name in list_keyboards():
33 kb_all['keyboards'][keyboard_name] = info_json(keyboard_name) 36 kb_all[keyboard_name] = info_json(keyboard_name)
34 keyboard_dir = v1_dir / 'keyboards' / keyboard_name 37 keyboard_dir = v1_dir / 'keyboards' / keyboard_name
35 keyboard_info = keyboard_dir / 'info.json' 38 keyboard_info = keyboard_dir / 'info.json'
36 keyboard_readme = keyboard_dir / 'readme.md' 39 keyboard_readme = keyboard_dir / 'readme.md'
37 keyboard_readme_src = Path('keyboards') / keyboard_name / 'readme.md' 40 keyboard_readme_src = Path('keyboards') / keyboard_name / 'readme.md'
38 41
39 keyboard_dir.mkdir(parents=True, exist_ok=True) 42 keyboard_dir.mkdir(parents=True, exist_ok=True)
40 keyboard_info.write_text(json.dumps({'last_updated': current_datetime(), 'keyboards': {keyboard_name: kb_all['keyboards'][keyboard_name]}})) 43 keyboard_info.write_text(json.dumps({'last_updated': current_datetime(), 'keyboards': {keyboard_name: kb_all[keyboard_name]}}))
41 44
42 if keyboard_readme_src.exists(): 45 if keyboard_readme_src.exists():
43 copyfile(keyboard_readme_src, keyboard_readme) 46 copyfile(keyboard_readme_src, keyboard_readme)
44 47
45 if 'usb' in kb_all['keyboards'][keyboard_name]: 48 if 'usb' in kb_all[keyboard_name]:
46 usb = kb_all['keyboards'][keyboard_name]['usb'] 49 usb = kb_all[keyboard_name]['usb']
47 50
48 if 'vid' in usb and usb['vid'] not in usb_list['devices']: 51 if 'vid' in usb and usb['vid'] not in usb_list:
49 usb_list['devices'][usb['vid']] = {} 52 usb_list[usb['vid']] = {}
50 53
51 if 'pid' in usb and usb['pid'] not in usb_list['devices'][usb['vid']]: 54 if 'pid' in usb and usb['pid'] not in usb_list[usb['vid']]:
52 usb_list['devices'][usb['vid']][usb['pid']] = {} 55 usb_list[usb['vid']][usb['pid']] = {}
53 56
54 if 'vid' in usb and 'pid' in usb: 57 if 'vid' in usb and 'pid' in usb:
55 usb_list['devices'][usb['vid']][usb['pid']][keyboard_name] = usb 58 usb_list[usb['vid']][usb['pid']][keyboard_name] = usb
56 59
57 # Write the global JSON files 60 # Write the global JSON files
58 keyboard_list.write_text(json.dumps({'last_updated': current_datetime(), 'keyboards': sorted(kb_all['keyboards'])}, cls=InfoJSONEncoder)) 61 keyboard_all_file.write_text(json.dumps({'last_updated': current_datetime(), 'keyboards': kb_all}, cls=InfoJSONEncoder))
59 keyboard_all.write_text(json.dumps(kb_all, cls=InfoJSONEncoder)) 62 usb_file.write_text(json.dumps({'last_updated': current_datetime(), 'usb': usb_list}, cls=InfoJSONEncoder))
60 usb_file.write_text(json.dumps(usb_list, cls=InfoJSONEncoder)) 63
64 keyboard_list = sorted(kb_all)
65 keyboard_list_file.write_text(json.dumps({'last_updated': current_datetime(), 'keyboards': keyboard_list}, cls=InfoJSONEncoder))
66
67 keyboard_aliases = json_load(Path('data/mappings/keyboard_aliases.json'))
68 keyboard_aliases_file.write_text(json.dumps({'last_updated': current_datetime(), 'keyboard_aliases': keyboard_aliases}, cls=InfoJSONEncoder))
69
70 keyboard_metadata = {
71 'last_updated': current_datetime(),
72 'keyboards': keyboard_list,
73 'keyboard_aliases': keyboard_aliases,
74 'usb': usb_list
75 }
76 keyboard_metadata_file.write_text(json.dumps(keyboard_metadata, cls=InfoJSONEncoder))
diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py
index e6d49ea4d..ccea6d7a0 100755
--- a/lib/python/qmk/cli/generate/config_h.py
+++ b/lib/python/qmk/cli/generate/config_h.py
@@ -6,7 +6,9 @@ from dotty_dict import dotty
6from milc import cli 6from milc import cli
7 7
8from qmk.decorators import automagic_keyboard, automagic_keymap 8from qmk.decorators import automagic_keyboard, automagic_keymap
9from qmk.info import _json_load, info_json 9from qmk.info import info_json
10from qmk.json_schema import json_load
11from qmk.keyboard import keyboard_folder
10from qmk.path import is_keyboard, normpath 12from qmk.path import is_keyboard, normpath
11 13
12 14
@@ -73,7 +75,7 @@ def matrix_pins(matrix_pins):
73 75
74@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') 76@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
75@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") 77@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
76@cli.argument('-kb', '--keyboard', help='Keyboard to generate config.h for.') 78@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to generate config.h for.')
77@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True) 79@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True)
78@automagic_keyboard 80@automagic_keyboard
79@automagic_keymap 81@automagic_keymap
@@ -92,7 +94,7 @@ def generate_config_h(cli):
92 94
93 # Build the info_config.h file. 95 # Build the info_config.h file.
94 kb_info_json = dotty(info_json(cli.config.generate_config_h.keyboard)) 96 kb_info_json = dotty(info_json(cli.config.generate_config_h.keyboard))
95 info_config_map = _json_load(Path('data/mappings/info_config.json')) 97 info_config_map = json_load(Path('data/mappings/info_config.json'))
96 98
97 config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once'] 99 config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once']
98 100
diff --git a/lib/python/qmk/cli/generate/info_json.py b/lib/python/qmk/cli/generate/info_json.py
index f3fc54ddc..6c00ba7d8 100755
--- a/lib/python/qmk/cli/generate/info_json.py
+++ b/lib/python/qmk/cli/generate/info_json.py
@@ -8,8 +8,10 @@ from jsonschema import Draft7Validator, validators
8from milc import cli 8from milc import cli
9 9
10from qmk.decorators import automagic_keyboard, automagic_keymap 10from qmk.decorators import automagic_keyboard, automagic_keymap
11from qmk.info import info_json, _jsonschema 11from qmk.info import info_json
12from qmk.info_json_encoder import InfoJSONEncoder 12from qmk.info_json_encoder import InfoJSONEncoder
13from qmk.json_schema import load_jsonschema
14from qmk.keyboard import keyboard_folder
13from qmk.path import is_keyboard 15from qmk.path import is_keyboard
14 16
15 17
@@ -33,13 +35,13 @@ def strip_info_json(kb_info_json):
33 """Remove the API-only properties from the info.json. 35 """Remove the API-only properties from the info.json.
34 """ 36 """
35 pruning_draft_7_validator = pruning_validator(Draft7Validator) 37 pruning_draft_7_validator = pruning_validator(Draft7Validator)
36 schema = _jsonschema('keyboard') 38 schema = load_jsonschema('keyboard')
37 validator = pruning_draft_7_validator(schema).validate 39 validator = pruning_draft_7_validator(schema).validate
38 40
39 return validator(kb_info_json) 41 return validator(kb_info_json)
40 42
41 43
42@cli.argument('-kb', '--keyboard', help='Keyboard to show info for.') 44@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to show info for.')
43@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.') 45@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.')
44@cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True) 46@cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True)
45@automagic_keyboard 47@automagic_keyboard
diff --git a/lib/python/qmk/cli/generate/layouts.py b/lib/python/qmk/cli/generate/layouts.py
index a738edfe6..7b4394291 100755
--- a/lib/python/qmk/cli/generate/layouts.py
+++ b/lib/python/qmk/cli/generate/layouts.py
@@ -5,6 +5,7 @@ from milc import cli
5from qmk.constants import COL_LETTERS, ROW_LETTERS 5from qmk.constants import COL_LETTERS, ROW_LETTERS
6from qmk.decorators import automagic_keyboard, automagic_keymap 6from qmk.decorators import automagic_keyboard, automagic_keymap
7from qmk.info import info_json 7from qmk.info import info_json
8from qmk.keyboard import keyboard_folder
8from qmk.path import is_keyboard, normpath 9from qmk.path import is_keyboard, normpath
9 10
10usb_properties = { 11usb_properties = {
@@ -16,7 +17,7 @@ usb_properties = {
16 17
17@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') 18@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
18@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") 19@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
19@cli.argument('-kb', '--keyboard', help='Keyboard to generate config.h for.') 20@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to generate config.h for.')
20@cli.subcommand('Used by the make system to generate layouts.h from info.json', hidden=True) 21@cli.subcommand('Used by the make system to generate layouts.h from info.json', hidden=True)
21@automagic_keyboard 22@automagic_keyboard
22@automagic_keymap 23@automagic_keymap
diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py
index 15917987b..91759d26c 100755
--- a/lib/python/qmk/cli/generate/rules_mk.py
+++ b/lib/python/qmk/cli/generate/rules_mk.py
@@ -6,7 +6,9 @@ from dotty_dict import dotty
6from milc import cli 6from milc import cli
7 7
8from qmk.decorators import automagic_keyboard, automagic_keymap 8from qmk.decorators import automagic_keyboard, automagic_keymap
9from qmk.info import _json_load, info_json 9from qmk.info import info_json
10from qmk.json_schema import json_load
11from qmk.keyboard import keyboard_folder
10from qmk.path import is_keyboard, normpath 12from qmk.path import is_keyboard, normpath
11 13
12 14
@@ -37,7 +39,7 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict):
37@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') 39@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
38@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") 40@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
39@cli.argument('-e', '--escape', arg_only=True, action='store_true', help="Escape spaces in quiet mode") 41@cli.argument('-e', '--escape', arg_only=True, action='store_true', help="Escape spaces in quiet mode")
40@cli.argument('-kb', '--keyboard', help='Keyboard to generate config.h for.') 42@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to generate config.h for.')
41@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True) 43@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True)
42@automagic_keyboard 44@automagic_keyboard
43@automagic_keymap 45@automagic_keymap
@@ -54,7 +56,7 @@ def generate_rules_mk(cli):
54 return False 56 return False
55 57
56 kb_info_json = dotty(info_json(cli.config.generate_rules_mk.keyboard)) 58 kb_info_json = dotty(info_json(cli.config.generate_rules_mk.keyboard))
57 info_rules_map = _json_load(Path('data/mappings/info_rules.json')) 59 info_rules_map = json_load(Path('data/mappings/info_rules.json'))
58 rules_mk_lines = ['# This file was generated by `qmk generate-rules-mk`. Do not edit or copy.', ''] 60 rules_mk_lines = ['# This file was generated by `qmk generate-rules-mk`. Do not edit or copy.', '']
59 61
60 # Iterate through the info_rules map to generate basic rules 62 # Iterate through the info_rules map to generate basic rules
diff --git a/lib/python/qmk/cli/info.py b/lib/python/qmk/cli/info.py
index a7ce8abf0..88b65686f 100755
--- a/lib/python/qmk/cli/info.py
+++ b/lib/python/qmk/cli/info.py
@@ -10,7 +10,7 @@ from milc import cli
10from qmk.info_json_encoder import InfoJSONEncoder 10from qmk.info_json_encoder import InfoJSONEncoder
11from qmk.constants import COL_LETTERS, ROW_LETTERS 11from qmk.constants import COL_LETTERS, ROW_LETTERS
12from qmk.decorators import automagic_keyboard, automagic_keymap 12from qmk.decorators import automagic_keyboard, automagic_keymap
13from qmk.keyboard import render_layouts, render_layout 13from qmk.keyboard import keyboard_folder, render_layouts, render_layout
14from qmk.keymap import locate_keymap 14from qmk.keymap import locate_keymap
15from qmk.info import info_json 15from qmk.info import info_json
16from qmk.path import is_keyboard 16from qmk.path import is_keyboard
@@ -124,7 +124,7 @@ def print_text_output(kb_info_json):
124 show_keymap(kb_info_json, False) 124 show_keymap(kb_info_json, False)
125 125
126 126
127@cli.argument('-kb', '--keyboard', help='Keyboard to show info for.') 127@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to show info for.')
128@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.') 128@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.')
129@cli.argument('-l', '--layouts', action='store_true', help='Render the layouts.') 129@cli.argument('-l', '--layouts', action='store_true', help='Render the layouts.')
130@cli.argument('-m', '--matrix', action='store_true', help='Render the layouts with matrix information.') 130@cli.argument('-m', '--matrix', action='store_true', help='Render the layouts with matrix information.')
diff --git a/lib/python/qmk/cli/list/keymaps.py b/lib/python/qmk/cli/list/keymaps.py
index 49bc84b2c..7c0ad4399 100644
--- a/lib/python/qmk/cli/list/keymaps.py
+++ b/lib/python/qmk/cli/list/keymaps.py
@@ -4,18 +4,14 @@ from milc import cli
4 4
5import qmk.keymap 5import qmk.keymap
6from qmk.decorators import automagic_keyboard 6from qmk.decorators import automagic_keyboard
7from qmk.path import is_keyboard 7from qmk.keyboard import keyboard_folder
8 8
9 9
10@cli.argument("-kb", "--keyboard", help="Specify keyboard name. Example: 1upkeyboards/1up60hse") 10@cli.argument("-kb", "--keyboard", type=keyboard_folder, help="Specify keyboard name. Example: 1upkeyboards/1up60hse")
11@cli.subcommand("List the keymaps for a specific keyboard") 11@cli.subcommand("List the keymaps for a specific keyboard")
12@automagic_keyboard 12@automagic_keyboard
13def list_keymaps(cli): 13def list_keymaps(cli):
14 """List the keymaps for a specific keyboard 14 """List the keymaps for a specific keyboard
15 """ 15 """
16 if not is_keyboard(cli.config.list_keymaps.keyboard):
17 cli.log.error('Keyboard %s does not exist!', cli.config.list_keymaps.keyboard)
18 return False
19
20 for name in qmk.keymap.list_keymaps(cli.config.list_keymaps.keyboard): 16 for name in qmk.keymap.list_keymaps(cli.config.list_keymaps.keyboard):
21 print(name) 17 print(name)
diff --git a/lib/python/qmk/cli/new/keymap.py b/lib/python/qmk/cli/new/keymap.py
index 52c564997..ea98a287c 100755
--- a/lib/python/qmk/cli/new/keymap.py
+++ b/lib/python/qmk/cli/new/keymap.py
@@ -5,10 +5,11 @@ from pathlib import Path
5 5
6import qmk.path 6import qmk.path
7from qmk.decorators import automagic_keyboard, automagic_keymap 7from qmk.decorators import automagic_keyboard, automagic_keymap
8from qmk.keyboard import keyboard_folder
8from milc import cli 9from milc import cli
9 10
10 11
11@cli.argument('-kb', '--keyboard', help='Specify keyboard name. Example: 1upkeyboards/1up60hse') 12@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Specify keyboard name. Example: 1upkeyboards/1up60hse')
12@cli.argument('-km', '--keymap', help='Specify the name for the new keymap directory') 13@cli.argument('-km', '--keymap', help='Specify the name for the new keymap directory')
13@cli.subcommand('Creates a new keymap for the keyboard of your choosing') 14@cli.subcommand('Creates a new keymap for the keyboard of your choosing')
14@automagic_keyboard 15@automagic_keyboard
diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py
index 4809365af..510abffdc 100644
--- a/lib/python/qmk/commands.py
+++ b/lib/python/qmk/commands.py
@@ -13,6 +13,7 @@ from milc import cli
13 13
14import qmk.keymap 14import qmk.keymap
15from qmk.constants import KEYBOARD_OUTPUT_PREFIX 15from qmk.constants import KEYBOARD_OUTPUT_PREFIX
16from qmk.json_schema import json_load
16 17
17time_fmt = '%Y-%m-%d-%H:%M:%S' 18time_fmt = '%Y-%m-%d-%H:%M:%S'
18 19
@@ -191,6 +192,15 @@ def parse_configurator_json(configurator_file):
191 """ 192 """
192 # FIXME(skullydazed/anyone): Add validation here 193 # FIXME(skullydazed/anyone): Add validation here
193 user_keymap = json.load(configurator_file) 194 user_keymap = json.load(configurator_file)
195 orig_keyboard = user_keymap['keyboard']
196 aliases = json_load(Path('data/mappings/keyboard_aliases.json'))
197
198 if orig_keyboard in aliases:
199 if 'target' in aliases[orig_keyboard]:
200 user_keymap['keyboard'] = aliases[orig_keyboard]['target']
201
202 if 'layouts' in aliases[orig_keyboard] and user_keymap['layout'] in aliases[orig_keyboard]['layouts']:
203 user_keymap['layout'] = aliases[orig_keyboard]['layouts'][user_keymap['layout']]
194 204
195 return user_keymap 205 return user_keymap
196 206
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py
index 60d3a0132..e2350b7f7 100644
--- a/lib/python/qmk/info.py
+++ b/lib/python/qmk/info.py
@@ -1,17 +1,15 @@
1"""Functions that help us generate and use info.json files. 1"""Functions that help us generate and use info.json files.
2""" 2"""
3import json
4from collections.abc import Mapping
5from glob import glob 3from glob import glob
6from pathlib import Path 4from pathlib import Path
7 5
8import hjson
9import jsonschema 6import jsonschema
10from dotty_dict import dotty 7from dotty_dict import dotty
11from milc import cli 8from milc import cli
12 9
13from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS 10from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS
14from qmk.c_parse import find_layouts 11from qmk.c_parse import find_layouts
12from qmk.json_schema import deep_update, json_load, keyboard_validate, keyboard_api_validate
15from qmk.keyboard import config_h, rules_mk 13from qmk.keyboard import config_h, rules_mk
16from qmk.keymap import list_keymaps 14from qmk.keymap import list_keymaps
17from qmk.makefile import parse_rules_mk_file 15from qmk.makefile import parse_rules_mk_file
@@ -82,52 +80,6 @@ def info_json(keyboard):
82 return info_data 80 return info_data
83 81
84 82
85def _json_load(json_file):
86 """Load a json file from disk.
87
88 Note: file must be a Path object.
89 """
90 try:
91 return hjson.load(json_file.open(encoding='utf-8'))
92
93 except json.decoder.JSONDecodeError as e:
94 cli.log.error('Invalid JSON encountered attempting to load {fg_cyan}%s{fg_reset}:\n\t{fg_red}%s', json_file, e)
95 exit(1)
96
97
98def _jsonschema(schema_name):
99 """Read a jsonschema file from disk.
100
101 FIXME(skullydazed/anyone): Refactor to make this a public function.
102 """
103 schema_path = Path(f'data/schemas/{schema_name}.jsonschema')
104
105 if not schema_path.exists():
106 schema_path = Path('data/schemas/false.jsonschema')
107
108 return _json_load(schema_path)
109
110
111def keyboard_validate(data):
112 """Validates data against the keyboard jsonschema.
113 """
114 schema = _jsonschema('keyboard')
115 validator = jsonschema.Draft7Validator(schema).validate
116
117 return validator(data)
118
119
120def keyboard_api_validate(data):
121 """Validates data against the api_keyboard jsonschema.
122 """
123 base = _jsonschema('keyboard')
124 relative = _jsonschema('api_keyboard')
125 resolver = jsonschema.RefResolver.from_schema(base)
126 validator = jsonschema.Draft7Validator(relative, resolver=resolver).validate
127
128 return validator(data)
129
130
131def _extract_features(info_data, rules): 83def _extract_features(info_data, rules):
132 """Find all the features enabled in rules.mk. 84 """Find all the features enabled in rules.mk.
133 """ 85 """
@@ -258,7 +210,7 @@ def _extract_config_h(info_data):
258 210
259 # Pull in data from the json map 211 # Pull in data from the json map
260 dotty_info = dotty(info_data) 212 dotty_info = dotty(info_data)
261 info_config_map = _json_load(Path('data/mappings/info_config.json')) 213 info_config_map = json_load(Path('data/mappings/info_config.json'))
262 214
263 for config_key, info_dict in info_config_map.items(): 215 for config_key, info_dict in info_config_map.items():
264 info_key = info_dict['info_key'] 216 info_key = info_dict['info_key']
@@ -326,7 +278,7 @@ def _extract_rules_mk(info_data):
326 278
327 # Pull in data from the json map 279 # Pull in data from the json map
328 dotty_info = dotty(info_data) 280 dotty_info = dotty(info_data)
329 info_rules_map = _json_load(Path('data/mappings/info_rules.json')) 281 info_rules_map = json_load(Path('data/mappings/info_rules.json'))
330 282
331 for rules_key, info_dict in info_rules_map.items(): 283 for rules_key, info_dict in info_rules_map.items():
332 info_key = info_dict['info_key'] 284 info_key = info_dict['info_key']
@@ -516,25 +468,12 @@ def unknown_processor_rules(info_data, rules):
516 return info_data 468 return info_data
517 469
518 470
519def deep_update(origdict, newdict):
520 """Update a dictionary in place, recursing to do a deep copy.
521 """
522 for key, value in newdict.items():
523 if isinstance(value, Mapping):
524 origdict[key] = deep_update(origdict.get(key, {}), value)
525
526 else:
527 origdict[key] = value
528
529 return origdict
530
531
532def merge_info_jsons(keyboard, info_data): 471def merge_info_jsons(keyboard, info_data):
533 """Return a merged copy of all the info.json files for a keyboard. 472 """Return a merged copy of all the info.json files for a keyboard.
534 """ 473 """
535 for info_file in find_info_json(keyboard): 474 for info_file in find_info_json(keyboard):
536 # Load and validate the JSON data 475 # Load and validate the JSON data
537 new_info_data = _json_load(info_file) 476 new_info_data = json_load(info_file)
538 477
539 if not isinstance(new_info_data, dict): 478 if not isinstance(new_info_data, dict):
540 _log_error(info_data, "Invalid file %s, root object should be a dictionary." % (str(info_file),)) 479 _log_error(info_data, "Invalid file %s, root object should be a dictionary." % (str(info_file),))
diff --git a/lib/python/qmk/json_schema.py b/lib/python/qmk/json_schema.py
new file mode 100644
index 000000000..b4cd1776b
--- /dev/null
+++ b/lib/python/qmk/json_schema.py
@@ -0,0 +1,68 @@
1"""Functions that help us generate and use info.json files.
2"""
3import json
4from collections.abc import Mapping
5from pathlib import Path
6
7import hjson
8import jsonschema
9from milc import cli
10
11
12def json_load(json_file):
13 """Load a json file from disk.
14
15 Note: file must be a Path object.
16 """
17 try:
18 return hjson.load(json_file.open())
19
20 except json.decoder.JSONDecodeError as e:
21 cli.log.error('Invalid JSON encountered attempting to load {fg_cyan}%s{fg_reset}:\n\t{fg_red}%s', json_file, e)
22 exit(1)
23
24
25def load_jsonschema(schema_name):
26 """Read a jsonschema file from disk.
27
28 FIXME(skullydazed/anyone): Refactor to make this a public function.
29 """
30 schema_path = Path(f'data/schemas/{schema_name}.jsonschema')
31
32 if not schema_path.exists():
33 schema_path = Path('data/schemas/false.jsonschema')
34
35 return json_load(schema_path)
36
37
38def keyboard_validate(data):
39 """Validates data against the keyboard jsonschema.
40 """
41 schema = load_jsonschema('keyboard')
42 validator = jsonschema.Draft7Validator(schema).validate
43
44 return validator(data)
45
46
47def keyboard_api_validate(data):
48 """Validates data against the api_keyboard jsonschema.
49 """
50 base = load_jsonschema('keyboard')
51 relative = load_jsonschema('api_keyboard')
52 resolver = jsonschema.RefResolver.from_schema(base)
53 validator = jsonschema.Draft7Validator(relative, resolver=resolver).validate
54
55 return validator(data)
56
57
58def deep_update(origdict, newdict):
59 """Update a dictionary in place, recursing to do a deep copy.
60 """
61 for key, value in newdict.items():
62 if isinstance(value, Mapping):
63 origdict[key] = deep_update(origdict.get(key, {}), value)
64
65 else:
66 origdict[key] = value
67
68 return origdict
diff --git a/lib/python/qmk/keyboard.py b/lib/python/qmk/keyboard.py
index a4c287375..89f9346c4 100644
--- a/lib/python/qmk/keyboard.py
+++ b/lib/python/qmk/keyboard.py
@@ -7,7 +7,9 @@ import os
7from glob import glob 7from glob import glob
8 8
9from qmk.c_parse import parse_config_h_file 9from qmk.c_parse import parse_config_h_file
10from qmk.json_schema import json_load
10from qmk.makefile import parse_rules_mk_file 11from qmk.makefile import parse_rules_mk_file
12from qmk.path import is_keyboard
11 13
12BOX_DRAWING_CHARACTERS = { 14BOX_DRAWING_CHARACTERS = {
13 "unicode": { 15 "unicode": {
@@ -31,6 +33,28 @@ BOX_DRAWING_CHARACTERS = {
31base_path = os.path.join(os.getcwd(), "keyboards") + os.path.sep 33base_path = os.path.join(os.getcwd(), "keyboards") + os.path.sep
32 34
33 35
36def keyboard_folder(keyboard):
37 """Returns the actual keyboard folder.
38
39 This checks aliases and DEFAULT_FOLDER to resolve the actual path for a keyboard.
40 """
41 aliases = json_load(Path('data/mappings/keyboard_aliases.json'))
42
43 if keyboard in aliases:
44 keyboard = aliases[keyboard].get('target', keyboard)
45
46 rules_mk_file = Path(base_path, keyboard, 'rules.mk')
47
48 if rules_mk_file.exists():
49 rules_mk = parse_rules_mk_file(rules_mk_file)
50 keyboard = rules_mk.get('DEFAULT_FOLDER', keyboard)
51
52 if not is_keyboard(keyboard):
53 raise ValueError(f'Invalid keyboard: {keyboard}')
54
55 return keyboard
56
57
34def _find_name(path): 58def _find_name(path):
35 """Determine the keyboard name by stripping off the base_path and rules.mk. 59 """Determine the keyboard name by stripping off the base_path and rules.mk.
36 """ 60 """
diff --git a/lib/python/qmk/path.py b/lib/python/qmk/path.py
index 2aa1916f5..72bae5927 100644
--- a/lib/python/qmk/path.py
+++ b/lib/python/qmk/path.py
@@ -15,6 +15,7 @@ def is_keyboard(keyboard_name):
15 if keyboard_name: 15 if keyboard_name:
16 keyboard_path = QMK_FIRMWARE / 'keyboards' / keyboard_name 16 keyboard_path = QMK_FIRMWARE / 'keyboards' / keyboard_name
17 rules_mk = keyboard_path / 'rules.mk' 17 rules_mk = keyboard_path / 'rules.mk'
18
18 return rules_mk.exists() 19 return rules_mk.exists()
19 20
20 21
diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py
index bfecebdd7..b16777e54 100644
--- a/lib/python/qmk/tests/test_cli_commands.py
+++ b/lib/python/qmk/tests/test_cli_commands.py
@@ -134,8 +134,8 @@ def test_list_keymaps_vendor_kb_rev():
134 134
135def test_list_keymaps_no_keyboard_found(): 135def test_list_keymaps_no_keyboard_found():
136 result = check_subcommand('list-keymaps', '-kb', 'asdfghjkl') 136 result = check_subcommand('list-keymaps', '-kb', 'asdfghjkl')
137 check_returncode(result, [1]) 137 check_returncode(result, [2])
138 assert 'does not exist' in result.stdout 138 assert 'invalid keyboard_folder value' in result.stdout
139 139
140 140
141def test_json2c(): 141def test_json2c():