diff options
author | Zach White <skullydazed@gmail.com> | 2021-08-16 15:33:30 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-16 23:33:30 +0100 |
commit | 8d9bfdc25437bb401985ba93b47edae2126e7fac (patch) | |
tree | 2439e7adde0bafd6af9a403c92c1a89384c3f6ea /lib/python/qmk | |
parent | fac717c11cfa27780f2f9098383673784174141a (diff) | |
download | qmk_firmware-8d9bfdc25437bb401985ba93b47edae2126e7fac.tar.gz qmk_firmware-8d9bfdc25437bb401985ba93b47edae2126e7fac.zip |
Add a lot more data to info.json (#13366)
* add some split data to info.json
* add tags
* add half of config_options.md to info.json
* add support for designating master split
* sort out split transport and primary
* fix bad data in UNUSED_PINS
* fixup custom transport
* wip
* allow for setting split right half keyboard matrix
* add SPLIT_USB_DETECT
* minor cleanup
* fix an erroneous message
* rework split.usb_detect
* adding missing rgblight vars to info.json
* add mouse_key to info.json
* add all remaining options from docs/config_options.md
* fix audio voices
* qmk info: Change text output to use dotted notation
* tweak layout output
* resolve alias names
* break out some functions to make flake8 happy
* add a field for bootloader instructions
* qmk generate-info-json: add a write-to-file argument
Adds an argument that instructs qmk generate-info-json to write the output to a file instead of just to the terminal.
* -arg_only, +action
Because it was never my intention that one would have to specify a value for the argument that enables writing the file.
* Bring qmk generate-info-json inline with other generate commands
* pytest fixup
* fix esca/getawayvan
* fix data driven errors for bpiphany converters
* features.force_nkro -> usb.force_nkro
* split.primary->split.main
* fix esca/getawayvan_f042
* fix the bpiphany converters for real
* fix bpiphany/tiger_lily
* Apply suggestions from code review
Co-authored-by: Nick Brassel <nick@tzarc.org>
* fix generate-api errors
* fix matrix pin extraction for split boards
* fix ploopyco/trackball_nano/rev1_001
Co-authored-by: James Young <18669334+noroadsleft@users.noreply.github.com>
Co-authored-by: Nick Brassel <nick@tzarc.org>
Diffstat (limited to 'lib/python/qmk')
-rwxr-xr-x | lib/python/qmk/cli/generate/config_h.py | 140 | ||||
-rwxr-xr-x | lib/python/qmk/cli/generate/info_json.py | 40 | ||||
-rwxr-xr-x | lib/python/qmk/cli/generate/rules_mk.py | 11 | ||||
-rwxr-xr-x | lib/python/qmk/cli/info.py | 53 | ||||
-rw-r--r-- | lib/python/qmk/info.py | 184 | ||||
-rw-r--r-- | lib/python/qmk/json_schema.py | 15 |
6 files changed, 360 insertions, 83 deletions
diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index 54cd5b96a..c0c148f1c 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py | |||
@@ -12,7 +12,7 @@ from qmk.keyboard import keyboard_completer, keyboard_folder | |||
12 | from qmk.path import is_keyboard, normpath | 12 | from qmk.path import is_keyboard, normpath |
13 | 13 | ||
14 | 14 | ||
15 | def direct_pins(direct_pins): | 15 | def direct_pins(direct_pins, postfix): |
16 | """Return the config.h lines that set the direct pins. | 16 | """Return the config.h lines that set the direct pins. |
17 | """ | 17 | """ |
18 | rows = [] | 18 | rows = [] |
@@ -24,81 +24,60 @@ def direct_pins(direct_pins): | |||
24 | col_count = len(direct_pins[0]) | 24 | col_count = len(direct_pins[0]) |
25 | row_count = len(direct_pins) | 25 | row_count = len(direct_pins) |
26 | 26 | ||
27 | return """ | 27 | return f""" |
28 | #ifndef MATRIX_COLS | 28 | #ifndef MATRIX_COLS{postfix} |
29 | # define MATRIX_COLS %s | 29 | # define MATRIX_COLS{postfix} {col_count} |
30 | #endif // MATRIX_COLS | 30 | #endif // MATRIX_COLS{postfix} |
31 | 31 | ||
32 | #ifndef MATRIX_ROWS | 32 | #ifndef MATRIX_ROWS{postfix} |
33 | # define MATRIX_ROWS %s | 33 | # define MATRIX_ROWS{postfix} {row_count} |
34 | #endif // MATRIX_ROWS | 34 | #endif // MATRIX_ROWS{postfix} |
35 | 35 | ||
36 | #ifndef DIRECT_PINS | 36 | #ifndef DIRECT_PINS{postfix} |
37 | # define DIRECT_PINS {%s} | 37 | # define DIRECT_PINS{postfix} {{ {", ".join(rows)} }} |
38 | #endif // DIRECT_PINS | 38 | #endif // DIRECT_PINS{postfix} |
39 | """ % (col_count, row_count, ','.join(rows)) | 39 | """ |
40 | 40 | ||
41 | 41 | ||
42 | def pin_array(define, pins): | 42 | def pin_array(define, pins, postfix): |
43 | """Return the config.h lines that set a pin array. | 43 | """Return the config.h lines that set a pin array. |
44 | """ | 44 | """ |
45 | pin_num = len(pins) | 45 | pin_num = len(pins) |
46 | pin_array = ', '.join(map(str, [pin or 'NO_PIN' for pin in pins])) | 46 | pin_array = ', '.join(map(str, [pin or 'NO_PIN' for pin in pins])) |
47 | 47 | ||
48 | return f""" | 48 | return f""" |
49 | #ifndef {define}S | 49 | #ifndef {define}S{postfix} |
50 | # define {define}S {pin_num} | 50 | # define {define}S{postfix} {pin_num} |
51 | #endif // {define}S | 51 | #endif // {define}S{postfix} |
52 | 52 | ||
53 | #ifndef {define}_PINS | 53 | #ifndef {define}_PINS{postfix} |
54 | # define {define}_PINS {{ {pin_array} }} | 54 | # define {define}_PINS{postfix} {{ {pin_array} }} |
55 | #endif // {define}_PINS | 55 | #endif // {define}_PINS{postfix} |
56 | """ | 56 | """ |
57 | 57 | ||
58 | 58 | ||
59 | def matrix_pins(matrix_pins): | 59 | def matrix_pins(matrix_pins, postfix=''): |
60 | """Add the matrix config to the config.h. | 60 | """Add the matrix config to the config.h. |
61 | """ | 61 | """ |
62 | pins = [] | 62 | pins = [] |
63 | 63 | ||
64 | if 'direct' in matrix_pins: | 64 | if 'direct' in matrix_pins: |
65 | pins.append(direct_pins(matrix_pins['direct'])) | 65 | pins.append(direct_pins(matrix_pins['direct'], postfix)) |
66 | 66 | ||
67 | if 'cols' in matrix_pins: | 67 | if 'cols' in matrix_pins: |
68 | pins.append(pin_array('MATRIX_COL', matrix_pins['cols'])) | 68 | pins.append(pin_array('MATRIX_COL', matrix_pins['cols'], postfix)) |
69 | 69 | ||
70 | if 'rows' in matrix_pins: | 70 | if 'rows' in matrix_pins: |
71 | pins.append(pin_array('MATRIX_ROW', matrix_pins['rows'])) | 71 | pins.append(pin_array('MATRIX_ROW', matrix_pins['rows'], postfix)) |
72 | 72 | ||
73 | return '\n'.join(pins) | 73 | return '\n'.join(pins) |
74 | 74 | ||
75 | 75 | ||
76 | @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') | 76 | def generate_config_items(kb_info_json, config_h_lines): |
77 | @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") | 77 | """Iterate through the info_config map to generate basic config values. |
78 | @cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.') | ||
79 | @cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True) | ||
80 | @automagic_keyboard | ||
81 | @automagic_keymap | ||
82 | def generate_config_h(cli): | ||
83 | """Generates the info_config.h file. | ||
84 | """ | 78 | """ |
85 | # Determine our keyboard(s) | ||
86 | if not cli.config.generate_config_h.keyboard: | ||
87 | cli.log.error('Missing parameter: --keyboard') | ||
88 | cli.subcommands['info'].print_help() | ||
89 | return False | ||
90 | |||
91 | if not is_keyboard(cli.config.generate_config_h.keyboard): | ||
92 | cli.log.error('Invalid keyboard: "%s"', cli.config.generate_config_h.keyboard) | ||
93 | return False | ||
94 | |||
95 | # Build the info_config.h file. | ||
96 | kb_info_json = dotty(info_json(cli.config.generate_config_h.keyboard)) | ||
97 | info_config_map = json_load(Path('data/mappings/info_config.json')) | 79 | info_config_map = json_load(Path('data/mappings/info_config.json')) |
98 | 80 | ||
99 | config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once'] | ||
100 | |||
101 | # Iterate through the info_config map to generate basic things | ||
102 | for config_key, info_dict in info_config_map.items(): | 81 | for config_key, info_dict in info_config_map.items(): |
103 | info_key = info_dict['info_key'] | 82 | info_key = info_dict['info_key'] |
104 | key_type = info_dict.get('value_type', 'str') | 83 | key_type = info_dict.get('value_type', 'str') |
@@ -135,9 +114,78 @@ def generate_config_h(cli): | |||
135 | config_h_lines.append(f'# define {config_key} {config_value}') | 114 | config_h_lines.append(f'# define {config_key} {config_value}') |
136 | config_h_lines.append(f'#endif // {config_key}') | 115 | config_h_lines.append(f'#endif // {config_key}') |
137 | 116 | ||
117 | |||
118 | def generate_split_config(kb_info_json, config_h_lines): | ||
119 | """Generate the config.h lines for split boards.""" | ||
120 | if 'primary' in kb_info_json['split']: | ||
121 | if kb_info_json['split']['primary'] in ('left', 'right'): | ||
122 | config_h_lines.append('') | ||
123 | config_h_lines.append('#ifndef MASTER_LEFT') | ||
124 | config_h_lines.append('# ifndef MASTER_RIGHT') | ||
125 | if kb_info_json['split']['primary'] == 'left': | ||
126 | config_h_lines.append('# define MASTER_LEFT') | ||
127 | elif kb_info_json['split']['primary'] == 'right': | ||
128 | config_h_lines.append('# define MASTER_RIGHT') | ||
129 | config_h_lines.append('# endif // MASTER_RIGHT') | ||
130 | config_h_lines.append('#endif // MASTER_LEFT') | ||
131 | elif kb_info_json['split']['primary'] == 'pin': | ||
132 | config_h_lines.append('') | ||
133 | config_h_lines.append('#ifndef SPLIT_HAND_PIN') | ||
134 | config_h_lines.append('# define SPLIT_HAND_PIN') | ||
135 | config_h_lines.append('#endif // SPLIT_HAND_PIN') | ||
136 | elif kb_info_json['split']['primary'] == 'matrix_grid': | ||
137 | config_h_lines.append('') | ||
138 | config_h_lines.append('#ifndef SPLIT_HAND_MATRIX_GRID') | ||
139 | config_h_lines.append('# define SPLIT_HAND_MATRIX_GRID {%s}' % (','.join(kb_info_json["split"]["matrix_grid"],))) | ||
140 | config_h_lines.append('#endif // SPLIT_HAND_MATRIX_GRID') | ||
141 | elif kb_info_json['split']['primary'] == 'eeprom': | ||
142 | config_h_lines.append('') | ||
143 | config_h_lines.append('#ifndef EE_HANDS') | ||
144 | config_h_lines.append('# define EE_HANDS') | ||
145 | config_h_lines.append('#endif // EE_HANDS') | ||
146 | |||
147 | if 'protocol' in kb_info_json['split'].get('transport', {}): | ||
148 | if kb_info_json['split']['transport']['protocol'] == 'i2c': | ||
149 | config_h_lines.append('') | ||
150 | config_h_lines.append('#ifndef USE_I2C') | ||
151 | config_h_lines.append('# define USE_I2C') | ||
152 | config_h_lines.append('#endif // USE_I2C') | ||
153 | |||
154 | if 'right' in kb_info_json['split'].get('matrix_pins', {}): | ||
155 | config_h_lines.append(matrix_pins(kb_info_json['split']['matrix_pins']['right'], '_RIGHT')) | ||
156 | |||
157 | |||
158 | @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') | ||
159 | @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") | ||
160 | @cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.') | ||
161 | @cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True) | ||
162 | @automagic_keyboard | ||
163 | @automagic_keymap | ||
164 | def generate_config_h(cli): | ||
165 | """Generates the info_config.h file. | ||
166 | """ | ||
167 | # Determine our keyboard(s) | ||
168 | if not cli.config.generate_config_h.keyboard: | ||
169 | cli.log.error('Missing parameter: --keyboard') | ||
170 | cli.subcommands['info'].print_help() | ||
171 | return False | ||
172 | |||
173 | if not is_keyboard(cli.config.generate_config_h.keyboard): | ||
174 | cli.log.error('Invalid keyboard: "%s"', cli.config.generate_config_h.keyboard) | ||
175 | return False | ||
176 | |||
177 | # Build the info_config.h file. | ||
178 | kb_info_json = dotty(info_json(cli.config.generate_config_h.keyboard)) | ||
179 | config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once'] | ||
180 | |||
181 | generate_config_items(kb_info_json, config_h_lines) | ||
182 | |||
138 | if 'matrix_pins' in kb_info_json: | 183 | if 'matrix_pins' in kb_info_json: |
139 | config_h_lines.append(matrix_pins(kb_info_json['matrix_pins'])) | 184 | config_h_lines.append(matrix_pins(kb_info_json['matrix_pins'])) |
140 | 185 | ||
186 | if 'split' in kb_info_json: | ||
187 | generate_split_config(kb_info_json, config_h_lines) | ||
188 | |||
141 | # Show the results | 189 | # Show the results |
142 | config_h = '\n'.join(config_h_lines) | 190 | config_h = '\n'.join(config_h_lines) |
143 | 191 | ||
diff --git a/lib/python/qmk/cli/generate/info_json.py b/lib/python/qmk/cli/generate/info_json.py index 8931b68b6..284d1a851 100755 --- a/lib/python/qmk/cli/generate/info_json.py +++ b/lib/python/qmk/cli/generate/info_json.py | |||
@@ -4,15 +4,17 @@ Compile an info.json for a particular keyboard and pretty-print it. | |||
4 | """ | 4 | """ |
5 | import json | 5 | import json |
6 | 6 | ||
7 | from jsonschema import Draft7Validator, validators | 7 | from argcomplete.completers import FilesCompleter |
8 | from jsonschema import Draft7Validator, RefResolver, validators | ||
8 | from milc import cli | 9 | from milc import cli |
10 | from pathlib import Path | ||
9 | 11 | ||
10 | from qmk.decorators import automagic_keyboard, automagic_keymap | 12 | from qmk.decorators import automagic_keyboard, automagic_keymap |
11 | from qmk.info import info_json | 13 | from qmk.info import info_json |
12 | from qmk.json_encoders import InfoJSONEncoder | 14 | from qmk.json_encoders import InfoJSONEncoder |
13 | from qmk.json_schema import load_jsonschema | 15 | from qmk.json_schema import compile_schema_store |
14 | from qmk.keyboard import keyboard_completer, keyboard_folder | 16 | from qmk.keyboard import keyboard_completer, keyboard_folder |
15 | from qmk.path import is_keyboard | 17 | from qmk.path import is_keyboard, normpath |
16 | 18 | ||
17 | 19 | ||
18 | def pruning_validator(validator_class): | 20 | def pruning_validator(validator_class): |
@@ -34,15 +36,19 @@ def pruning_validator(validator_class): | |||
34 | def strip_info_json(kb_info_json): | 36 | def strip_info_json(kb_info_json): |
35 | """Remove the API-only properties from the info.json. | 37 | """Remove the API-only properties from the info.json. |
36 | """ | 38 | """ |
39 | schema_store = compile_schema_store() | ||
37 | pruning_draft_7_validator = pruning_validator(Draft7Validator) | 40 | pruning_draft_7_validator = pruning_validator(Draft7Validator) |
38 | schema = load_jsonschema('keyboard') | 41 | schema = schema_store['qmk.keyboard.v1'] |
39 | validator = pruning_draft_7_validator(schema).validate | 42 | resolver = RefResolver.from_schema(schema_store['qmk.keyboard.v1'], store=schema_store) |
43 | validator = pruning_draft_7_validator(schema, resolver=resolver).validate | ||
40 | 44 | ||
41 | return validator(kb_info_json) | 45 | return validator(kb_info_json) |
42 | 46 | ||
43 | 47 | ||
44 | @cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to show info for.') | 48 | @cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to show info for.') |
45 | @cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.') | 49 | @cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.') |
50 | @cli.argument('-o', '--output', arg_only=True, completer=FilesCompleter, help='Write the output the specified file, overwriting if necessary.') | ||
51 | @cli.argument('-ow', '--overwrite', arg_only=True, action='store_true', help='Overwrite the existing info.json. (Overrides the location of --output)') | ||
46 | @cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True) | 52 | @cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True) |
47 | @automagic_keyboard | 53 | @automagic_keyboard |
48 | @automagic_keymap | 54 | @automagic_keymap |
@@ -59,9 +65,29 @@ def generate_info_json(cli): | |||
59 | cli.log.error('Invalid keyboard: "%s"', cli.config.generate_info_json.keyboard) | 65 | cli.log.error('Invalid keyboard: "%s"', cli.config.generate_info_json.keyboard) |
60 | return False | 66 | return False |
61 | 67 | ||
68 | if cli.args.overwrite: | ||
69 | output_path = (Path('keyboards') / cli.config.generate_info_json.keyboard / 'info.json').resolve() | ||
70 | |||
71 | if cli.args.output: | ||
72 | cli.log.warning('Overwriting user supplied --output with %s', output_path) | ||
73 | |||
74 | cli.args.output = output_path | ||
75 | |||
62 | # Build the info.json file | 76 | # Build the info.json file |
63 | kb_info_json = info_json(cli.config.generate_info_json.keyboard) | 77 | kb_info_json = info_json(cli.config.generate_info_json.keyboard) |
64 | strip_info_json(kb_info_json) | 78 | strip_info_json(kb_info_json) |
79 | info_json_text = json.dumps(kb_info_json, indent=4, cls=InfoJSONEncoder) | ||
80 | |||
81 | if cli.args.output: | ||
82 | # Write to a file | ||
83 | output_path = normpath(cli.args.output) | ||
84 | |||
85 | if output_path.exists(): | ||
86 | cli.log.warning('Overwriting output file %s', output_path) | ||
87 | |||
88 | output_path.write_text(info_json_text + '\n') | ||
89 | cli.log.info('Wrote info.json to %s.', output_path) | ||
65 | 90 | ||
66 | # Display the results | 91 | else: |
67 | print(json.dumps(kb_info_json, indent=2, cls=InfoJSONEncoder)) | 92 | # Display the results |
93 | print(info_json_text) | ||
diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py index 41c94e16b..2712b81cb 100755 --- a/lib/python/qmk/cli/generate/rules_mk.py +++ b/lib/python/qmk/cli/generate/rules_mk.py | |||
@@ -76,6 +76,17 @@ def generate_rules_mk(cli): | |||
76 | enabled = 'yes' if enabled else 'no' | 76 | enabled = 'yes' if enabled else 'no' |
77 | rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}') | 77 | rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}') |
78 | 78 | ||
79 | # Set SPLIT_TRANSPORT, if needed | ||
80 | if kb_info_json.get('split', {}).get('transport', {}).get('protocol') == 'custom': | ||
81 | rules_mk_lines.append('SPLIT_TRANSPORT ?= custom') | ||
82 | |||
83 | # Set CUSTOM_MATRIX, if needed | ||
84 | if kb_info_json.get('matrix_pins', {}).get('custom'): | ||
85 | if kb_info_json.get('matrix_pins', {}).get('custom_lite'): | ||
86 | rules_mk_lines.append('CUSTOM_MATRIX ?= lite') | ||
87 | else: | ||
88 | rules_mk_lines.append('CUSTOM_MATRIX ?= yes') | ||
89 | |||
79 | # Show the results | 90 | # Show the results |
80 | rules_mk = '\n'.join(rules_mk_lines) + '\n' | 91 | rules_mk = '\n'.join(rules_mk_lines) + '\n' |
81 | 92 | ||
diff --git a/lib/python/qmk/cli/info.py b/lib/python/qmk/cli/info.py index 337b494a9..3131d4b53 100755 --- a/lib/python/qmk/cli/info.py +++ b/lib/python/qmk/cli/info.py | |||
@@ -24,19 +24,15 @@ def show_keymap(kb_info_json, title_caps=True): | |||
24 | keymap_path = locate_keymap(cli.config.info.keyboard, cli.config.info.keymap) | 24 | keymap_path = locate_keymap(cli.config.info.keyboard, cli.config.info.keymap) |
25 | 25 | ||
26 | if keymap_path and keymap_path.suffix == '.json': | 26 | if keymap_path and keymap_path.suffix == '.json': |
27 | if title_caps: | ||
28 | cli.echo('{fg_blue}Keymap "%s"{fg_reset}:', cli.config.info.keymap) | ||
29 | else: | ||
30 | cli.echo('{fg_blue}keymap_%s{fg_reset}:', cli.config.info.keymap) | ||
31 | |||
32 | keymap_data = json.load(keymap_path.open(encoding='utf-8')) | 27 | keymap_data = json.load(keymap_path.open(encoding='utf-8')) |
33 | layout_name = keymap_data['layout'] | 28 | layout_name = keymap_data['layout'] |
29 | layout_name = kb_info_json.get('layout_aliases', {}).get(layout_name, layout_name) # Resolve alias names | ||
34 | 30 | ||
35 | for layer_num, layer in enumerate(keymap_data['layers']): | 31 | for layer_num, layer in enumerate(keymap_data['layers']): |
36 | if title_caps: | 32 | if title_caps: |
37 | cli.echo('{fg_cyan}Layer %s{fg_reset}:', layer_num) | 33 | cli.echo('{fg_cyan}Keymap %s Layer %s{fg_reset}:', cli.config.info.keymap, layer_num) |
38 | else: | 34 | else: |
39 | cli.echo('{fg_cyan}layer_%s{fg_reset}:', layer_num) | 35 | cli.echo('{fg_cyan}keymap.%s.layer.%s{fg_reset}:', cli.config.info.keymap, layer_num) |
40 | 36 | ||
41 | print(render_layout(kb_info_json['layouts'][layout_name]['layout'], cli.config.info.ascii, layer)) | 37 | print(render_layout(kb_info_json['layouts'][layout_name]['layout'], cli.config.info.ascii, layer)) |
42 | 38 | ||
@@ -45,7 +41,7 @@ def show_layouts(kb_info_json, title_caps=True): | |||
45 | """Render the layouts with info.json labels. | 41 | """Render the layouts with info.json labels. |
46 | """ | 42 | """ |
47 | for layout_name, layout_art in render_layouts(kb_info_json, cli.config.info.ascii).items(): | 43 | for layout_name, layout_art in render_layouts(kb_info_json, cli.config.info.ascii).items(): |
48 | title = layout_name.title() if title_caps else layout_name | 44 | title = f'Layout {layout_name.title()}' if title_caps else f'layouts.{layout_name}' |
49 | cli.echo('{fg_cyan}%s{fg_reset}:', title) | 45 | cli.echo('{fg_cyan}%s{fg_reset}:', title) |
50 | print(layout_art) # Avoid passing dirty data to cli.echo() | 46 | print(layout_art) # Avoid passing dirty data to cli.echo() |
51 | 47 | ||
@@ -93,15 +89,6 @@ def print_friendly_output(kb_info_json): | |||
93 | aliases = [f'{key}={value}' for key, value in kb_info_json['layout_aliases'].items()] | 89 | aliases = [f'{key}={value}' for key, value in kb_info_json['layout_aliases'].items()] |
94 | cli.echo('{fg_blue}Layout aliases:{fg_reset} %s' % (', '.join(aliases),)) | 90 | cli.echo('{fg_blue}Layout aliases:{fg_reset} %s' % (', '.join(aliases),)) |
95 | 91 | ||
96 | if cli.config.info.layouts: | ||
97 | show_layouts(kb_info_json, True) | ||
98 | |||
99 | if cli.config.info.matrix: | ||
100 | show_matrix(kb_info_json, True) | ||
101 | |||
102 | if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file': | ||
103 | show_keymap(kb_info_json, True) | ||
104 | |||
105 | 92 | ||
106 | def print_text_output(kb_info_json): | 93 | def print_text_output(kb_info_json): |
107 | """Print the info.json in a plain text format. | 94 | """Print the info.json in a plain text format. |
@@ -122,6 +109,24 @@ def print_text_output(kb_info_json): | |||
122 | show_keymap(kb_info_json, False) | 109 | show_keymap(kb_info_json, False) |
123 | 110 | ||
124 | 111 | ||
112 | def print_dotted_output(kb_info_json, prefix=''): | ||
113 | """Print the info.json in a plain text format with dot-joined keys. | ||
114 | """ | ||
115 | for key in sorted(kb_info_json): | ||
116 | new_prefix = f'{prefix}.{key}' if prefix else key | ||
117 | |||
118 | if key in ['parse_errors', 'parse_warnings']: | ||
119 | continue | ||
120 | elif key == 'layouts' and prefix == '': | ||
121 | cli.echo('{fg_blue}layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys()))) | ||
122 | elif isinstance(kb_info_json[key], dict): | ||
123 | print_dotted_output(kb_info_json[key], new_prefix) | ||
124 | elif isinstance(kb_info_json[key], list): | ||
125 | cli.echo('{fg_blue}%s{fg_reset}: %s', new_prefix, ', '.join(map(str, sorted(kb_info_json[key])))) | ||
126 | else: | ||
127 | cli.echo('{fg_blue}%s{fg_reset}: %s', new_prefix, kb_info_json[key]) | ||
128 | |||
129 | |||
125 | def print_parsed_rules_mk(keyboard_name): | 130 | def print_parsed_rules_mk(keyboard_name): |
126 | rules = rules_mk(keyboard_name) | 131 | rules = rules_mk(keyboard_name) |
127 | for k in sorted(rules.keys()): | 132 | for k in sorted(rules.keys()): |
@@ -162,10 +167,22 @@ def info(cli): | |||
162 | # Output in the requested format | 167 | # Output in the requested format |
163 | if cli.args.format == 'json': | 168 | if cli.args.format == 'json': |
164 | print(json.dumps(kb_info_json, cls=InfoJSONEncoder)) | 169 | print(json.dumps(kb_info_json, cls=InfoJSONEncoder)) |
170 | return True | ||
165 | elif cli.args.format == 'text': | 171 | elif cli.args.format == 'text': |
166 | print_text_output(kb_info_json) | 172 | print_dotted_output(kb_info_json) |
173 | title_caps = False | ||
167 | elif cli.args.format == 'friendly': | 174 | elif cli.args.format == 'friendly': |
168 | print_friendly_output(kb_info_json) | 175 | print_friendly_output(kb_info_json) |
176 | title_caps = True | ||
169 | else: | 177 | else: |
170 | cli.log.error('Unknown format: %s', cli.args.format) | 178 | cli.log.error('Unknown format: %s', cli.args.format) |
171 | return False | 179 | return False |
180 | |||
181 | if cli.config.info.layouts: | ||
182 | show_layouts(kb_info_json, title_caps) | ||
183 | |||
184 | if cli.config.info.matrix: | ||
185 | show_matrix(kb_info_json, title_caps) | ||
186 | |||
187 | if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file': | ||
188 | show_keymap(kb_info_json, title_caps) | ||
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 858fbab33..b6f2ecf64 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py | |||
@@ -61,8 +61,8 @@ def info_json(keyboard): | |||
61 | 61 | ||
62 | # Merge in the data from info.json, config.h, and rules.mk | 62 | # Merge in the data from info.json, config.h, and rules.mk |
63 | info_data = merge_info_jsons(keyboard, info_data) | 63 | info_data = merge_info_jsons(keyboard, info_data) |
64 | info_data = _extract_config_h(info_data) | ||
65 | info_data = _extract_rules_mk(info_data) | 64 | info_data = _extract_rules_mk(info_data) |
65 | info_data = _extract_config_h(info_data) | ||
66 | 66 | ||
67 | # Ensure that we have matrix row and column counts | 67 | # Ensure that we have matrix row and column counts |
68 | info_data = _matrix_size(info_data) | 68 | info_data = _matrix_size(info_data) |
@@ -161,10 +161,9 @@ def _extract_pins(pins): | |||
161 | return [_pin_name(pin) for pin in pins.split(',')] | 161 | return [_pin_name(pin) for pin in pins.split(',')] |
162 | 162 | ||
163 | 163 | ||
164 | def _extract_direct_matrix(info_data, direct_pins): | 164 | def _extract_direct_matrix(direct_pins): |
165 | """ | 165 | """ |
166 | """ | 166 | """ |
167 | info_data['matrix_pins'] = {} | ||
168 | direct_pin_array = [] | 167 | direct_pin_array = [] |
169 | 168 | ||
170 | while direct_pins[-1] != '}': | 169 | while direct_pins[-1] != '}': |
@@ -188,12 +187,157 @@ def _extract_direct_matrix(info_data, direct_pins): | |||
188 | return direct_pin_array | 187 | return direct_pin_array |
189 | 188 | ||
190 | 189 | ||
190 | def _extract_audio(info_data, config_c): | ||
191 | """Populate data about the audio configuration | ||
192 | """ | ||
193 | audio_pins = [] | ||
194 | |||
195 | for pin in 'B5', 'B6', 'B7', 'C4', 'C5', 'C6': | ||
196 | if config_c.get(f'{pin}_AUDIO'): | ||
197 | audio_pins.append(pin) | ||
198 | |||
199 | if audio_pins: | ||
200 | info_data['audio'] = {'pins': audio_pins} | ||
201 | |||
202 | |||
203 | def _extract_split_main(info_data, config_c): | ||
204 | """Populate data about the split configuration | ||
205 | """ | ||
206 | # Figure out how the main half is determined | ||
207 | if config_c.get('SPLIT_HAND_PIN') is True: | ||
208 | if 'split' not in info_data: | ||
209 | info_data['split'] = {} | ||
210 | |||
211 | if 'main' in info_data['split']: | ||
212 | _log_warning(info_data, 'Split main hand is specified in both config.h (SPLIT_HAND_PIN) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main']) | ||
213 | |||
214 | info_data['split']['main'] = 'pin' | ||
215 | |||
216 | if config_c.get('SPLIT_HAND_MATRIX_GRID'): | ||
217 | if 'split' not in info_data: | ||
218 | info_data['split'] = {} | ||
219 | |||
220 | if 'main' in info_data['split']: | ||
221 | _log_warning(info_data, 'Split main hand is specified in both config.h (SPLIT_HAND_MATRIX_GRID) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main']) | ||
222 | |||
223 | info_data['split']['main'] = 'matrix_grid' | ||
224 | info_data['split']['matrix_grid'] = _extract_pins(config_c['SPLIT_HAND_MATRIX_GRID']) | ||
225 | |||
226 | if config_c.get('EE_HANDS') is True: | ||
227 | if 'split' not in info_data: | ||
228 | info_data['split'] = {} | ||
229 | |||
230 | if 'main' in info_data['split']: | ||
231 | _log_warning(info_data, 'Split main hand is specified in both config.h (EE_HANDS) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main']) | ||
232 | |||
233 | info_data['split']['main'] = 'eeprom' | ||
234 | |||
235 | if config_c.get('MASTER_RIGHT') is True: | ||
236 | if 'split' not in info_data: | ||
237 | info_data['split'] = {} | ||
238 | |||
239 | if 'main' in info_data['split']: | ||
240 | _log_warning(info_data, 'Split main hand is specified in both config.h (MASTER_RIGHT) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main']) | ||
241 | |||
242 | info_data['split']['main'] = 'right' | ||
243 | |||
244 | if config_c.get('MASTER_LEFT') is True: | ||
245 | if 'split' not in info_data: | ||
246 | info_data['split'] = {} | ||
247 | |||
248 | if 'main' in info_data['split']: | ||
249 | _log_warning(info_data, 'Split main hand is specified in both config.h (MASTER_LEFT) and info.json (split.main) (Value: %s), the config.h value wins.' % info_data['split']['main']) | ||
250 | |||
251 | info_data['split']['main'] = 'left' | ||
252 | |||
253 | |||
254 | def _extract_split_transport(info_data, config_c): | ||
255 | # Figure out the transport method | ||
256 | if config_c.get('USE_I2C') is True: | ||
257 | if 'split' not in info_data: | ||
258 | info_data['split'] = {} | ||
259 | |||
260 | if 'transport' not in info_data['split']: | ||
261 | info_data['split']['transport'] = {} | ||
262 | |||
263 | if 'protocol' in info_data['split']['transport']: | ||
264 | _log_warning(info_data, 'Split transport is specified in both config.h (USE_I2C) and info.json (split.transport.protocol) (Value: %s), the config.h value wins.' % info_data['split']['transport']) | ||
265 | |||
266 | info_data['split']['transport']['protocol'] = 'i2c' | ||
267 | |||
268 | elif 'protocol' not in info_data.get('split', {}).get('transport', {}): | ||
269 | if 'split' not in info_data: | ||
270 | info_data['split'] = {} | ||
271 | |||
272 | if 'transport' not in info_data['split']: | ||
273 | info_data['split']['transport'] = {} | ||
274 | |||
275 | info_data['split']['transport']['protocol'] = 'serial' | ||
276 | |||
277 | |||
278 | def _extract_split_right_pins(info_data, config_c): | ||
279 | # Figure out the right half matrix pins | ||
280 | row_pins = config_c.get('MATRIX_ROW_PINS_RIGHT', '').replace('{', '').replace('}', '').strip() | ||
281 | col_pins = config_c.get('MATRIX_COL_PINS_RIGHT', '').replace('{', '').replace('}', '').strip() | ||
282 | unused_pin_text = config_c.get('UNUSED_PINS_RIGHT') | ||
283 | unused_pins = unused_pin_text.replace('{', '').replace('}', '').strip() if isinstance(unused_pin_text, str) else None | ||
284 | direct_pins = config_c.get('DIRECT_PINS_RIGHT', '').replace(' ', '')[1:-1] | ||
285 | |||
286 | if row_pins and col_pins: | ||
287 | if info_data.get('split', {}).get('matrix_pins', {}).get('right') in info_data: | ||
288 | _log_warning(info_data, 'Right hand matrix data is specified in both info.json and config.h, the config.h values win.') | ||
289 | |||
290 | if 'split' not in info_data: | ||
291 | info_data['split'] = {} | ||
292 | |||
293 | if 'matrix_pins' not in info_data['split']: | ||
294 | info_data['split']['matrix_pins'] = {} | ||
295 | |||
296 | if 'right' not in info_data['split']['matrix_pins']: | ||
297 | info_data['split']['matrix_pins']['right'] = {} | ||
298 | |||
299 | info_data['split']['matrix_pins']['right'] = { | ||
300 | 'cols': _extract_pins(col_pins), | ||
301 | 'rows': _extract_pins(row_pins), | ||
302 | } | ||
303 | |||
304 | if direct_pins: | ||
305 | if info_data.get('split', {}).get('matrix_pins', {}).get('right', {}): | ||
306 | _log_warning(info_data, 'Right hand matrix data is specified in both info.json and config.h, the config.h values win.') | ||
307 | |||
308 | if 'split' not in info_data: | ||
309 | info_data['split'] = {} | ||
310 | |||
311 | if 'matrix_pins' not in info_data['split']: | ||
312 | info_data['split']['matrix_pins'] = {} | ||
313 | |||
314 | if 'right' not in info_data['split']['matrix_pins']: | ||
315 | info_data['split']['matrix_pins']['right'] = {} | ||
316 | |||
317 | info_data['split']['matrix_pins']['right']['direct'] = _extract_direct_matrix(direct_pins) | ||
318 | |||
319 | if unused_pins: | ||
320 | if 'split' not in info_data: | ||
321 | info_data['split'] = {} | ||
322 | |||
323 | if 'matrix_pins' not in info_data['split']: | ||
324 | info_data['split']['matrix_pins'] = {} | ||
325 | |||
326 | if 'right' not in info_data['split']['matrix_pins']: | ||
327 | info_data['split']['matrix_pins']['right'] = {} | ||
328 | |||
329 | info_data['split']['matrix_pins']['right']['unused'] = _extract_pins(unused_pins) | ||
330 | |||
331 | |||
191 | def _extract_matrix_info(info_data, config_c): | 332 | def _extract_matrix_info(info_data, config_c): |
192 | """Populate the matrix information. | 333 | """Populate the matrix information. |
193 | """ | 334 | """ |
194 | row_pins = config_c.get('MATRIX_ROW_PINS', '').replace('{', '').replace('}', '').strip() | 335 | row_pins = config_c.get('MATRIX_ROW_PINS', '').replace('{', '').replace('}', '').strip() |
195 | col_pins = config_c.get('MATRIX_COL_PINS', '').replace('{', '').replace('}', '').strip() | 336 | col_pins = config_c.get('MATRIX_COL_PINS', '').replace('{', '').replace('}', '').strip() |
337 | unused_pin_text = config_c.get('UNUSED_PINS') | ||
338 | unused_pins = unused_pin_text.replace('{', '').replace('}', '').strip() if isinstance(unused_pin_text, str) else None | ||
196 | direct_pins = config_c.get('DIRECT_PINS', '').replace(' ', '')[1:-1] | 339 | direct_pins = config_c.get('DIRECT_PINS', '').replace(' ', '')[1:-1] |
340 | info_snippet = {} | ||
197 | 341 | ||
198 | if 'MATRIX_ROWS' in config_c and 'MATRIX_COLS' in config_c: | 342 | if 'MATRIX_ROWS' in config_c and 'MATRIX_COLS' in config_c: |
199 | if 'matrix_size' in info_data: | 343 | if 'matrix_size' in info_data: |
@@ -205,19 +349,35 @@ def _extract_matrix_info(info_data, config_c): | |||
205 | } | 349 | } |
206 | 350 | ||
207 | if row_pins and col_pins: | 351 | if row_pins and col_pins: |
208 | if 'matrix_pins' in info_data: | 352 | if 'matrix_pins' in info_data and 'cols' in info_data['matrix_pins'] and 'rows' in info_data['matrix_pins']: |
209 | _log_warning(info_data, 'Matrix pins are specified in both info.json and config.h, the config.h values win.') | 353 | _log_warning(info_data, 'Matrix pins are specified in both info.json and config.h, the config.h values win.') |
210 | 354 | ||
211 | info_data['matrix_pins'] = { | 355 | info_snippet['cols'] = _extract_pins(col_pins) |
212 | 'cols': _extract_pins(col_pins), | 356 | info_snippet['rows'] = _extract_pins(row_pins) |
213 | 'rows': _extract_pins(row_pins), | ||
214 | } | ||
215 | 357 | ||
216 | if direct_pins: | 358 | if direct_pins: |
217 | if 'matrix_pins' in info_data: | 359 | if 'matrix_pins' in info_data and 'direct' in info_data['matrix_pins']: |
218 | _log_warning(info_data, 'Direct pins are specified in both info.json and config.h, the config.h values win.') | 360 | _log_warning(info_data, 'Direct pins are specified in both info.json and config.h, the config.h values win.') |
219 | 361 | ||
220 | info_data['matrix_pins']['direct'] = _extract_direct_matrix(info_data, direct_pins) | 362 | info_snippet['direct'] = _extract_direct_matrix(direct_pins) |
363 | |||
364 | if unused_pins: | ||
365 | if 'matrix_pins' not in info_data: | ||
366 | info_data['matrix_pins'] = {} | ||
367 | |||
368 | info_snippet['unused'] = _extract_pins(unused_pins) | ||
369 | |||
370 | if config_c.get('CUSTOM_MATRIX', 'no') != 'no': | ||
371 | if 'matrix_pins' in info_data and 'custom' in info_data['matrix_pins']: | ||
372 | _log_warning(info_data, 'Custom Matrix is specified in both info.json and config.h, the config.h values win.') | ||
373 | |||
374 | info_snippet['custom'] = True | ||
375 | |||
376 | if config_c['CUSTOM_MATRIX'] == 'lite': | ||
377 | info_snippet['custom_lite'] = True | ||
378 | |||
379 | if info_snippet: | ||
380 | info_data['matrix_pins'] = info_snippet | ||
221 | 381 | ||
222 | return info_data | 382 | return info_data |
223 | 383 | ||
@@ -275,6 +435,10 @@ def _extract_config_h(info_data): | |||
275 | 435 | ||
276 | # Pull data that easily can't be mapped in json | 436 | # Pull data that easily can't be mapped in json |
277 | _extract_matrix_info(info_data, config_c) | 437 | _extract_matrix_info(info_data, config_c) |
438 | _extract_audio(info_data, config_c) | ||
439 | _extract_split_main(info_data, config_c) | ||
440 | _extract_split_transport(info_data, config_c) | ||
441 | _extract_split_right_pins(info_data, config_c) | ||
278 | 442 | ||
279 | return info_data | 443 | return info_data |
280 | 444 | ||
diff --git a/lib/python/qmk/json_schema.py b/lib/python/qmk/json_schema.py index cbc5bff51..ffc7c6bcd 100644 --- a/lib/python/qmk/json_schema.py +++ b/lib/python/qmk/json_schema.py | |||
@@ -2,6 +2,7 @@ | |||
2 | """ | 2 | """ |
3 | import json | 3 | import json |
4 | from collections.abc import Mapping | 4 | from collections.abc import Mapping |
5 | from functools import lru_cache | ||
5 | from pathlib import Path | 6 | from pathlib import Path |
6 | 7 | ||
7 | import hjson | 8 | import hjson |
@@ -25,6 +26,7 @@ def json_load(json_file): | |||
25 | exit(1) | 26 | exit(1) |
26 | 27 | ||
27 | 28 | ||
29 | @lru_cache(maxsize=0) | ||
28 | def load_jsonschema(schema_name): | 30 | def load_jsonschema(schema_name): |
29 | """Read a jsonschema file from disk. | 31 | """Read a jsonschema file from disk. |
30 | """ | 32 | """ |
@@ -39,8 +41,9 @@ def load_jsonschema(schema_name): | |||
39 | return json_load(schema_path) | 41 | return json_load(schema_path) |
40 | 42 | ||
41 | 43 | ||
42 | def create_validator(schema): | 44 | @lru_cache(maxsize=0) |
43 | """Creates a validator for the given schema id. | 45 | def compile_schema_store(): |
46 | """Compile all our schemas into a schema store. | ||
44 | """ | 47 | """ |
45 | schema_store = {} | 48 | schema_store = {} |
46 | 49 | ||
@@ -51,6 +54,14 @@ def create_validator(schema): | |||
51 | continue | 54 | continue |
52 | schema_store[schema_data['$id']] = schema_data | 55 | schema_store[schema_data['$id']] = schema_data |
53 | 56 | ||
57 | return schema_store | ||
58 | |||
59 | |||
60 | @lru_cache(maxsize=0) | ||
61 | def create_validator(schema): | ||
62 | """Creates a validator for the given schema id. | ||
63 | """ | ||
64 | schema_store = compile_schema_store() | ||
54 | resolver = jsonschema.RefResolver.from_schema(schema_store['qmk.keyboard.v1'], store=schema_store) | 65 | resolver = jsonschema.RefResolver.from_schema(schema_store['qmk.keyboard.v1'], store=schema_store) |
55 | 66 | ||
56 | return jsonschema.Draft7Validator(schema_store[schema], resolver=resolver).validate | 67 | return jsonschema.Draft7Validator(schema_store[schema], resolver=resolver).validate |