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 | |
| 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')
| -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 |
