diff options
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 54cd5b96a8..c0c148f1c0 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  from qmk.path import is_keyboard, normpath -def direct_pins(direct_pins): +def direct_pins(direct_pins, postfix):      """Return the config.h lines that set the direct pins.      """      rows = [] @@ -24,81 +24,60 @@ def direct_pins(direct_pins):      col_count = len(direct_pins[0])      row_count = len(direct_pins) -    return """ -#ifndef MATRIX_COLS -#   define MATRIX_COLS %s -#endif // MATRIX_COLS +    return f""" +#ifndef MATRIX_COLS{postfix} +#   define MATRIX_COLS{postfix} {col_count} +#endif // MATRIX_COLS{postfix} -#ifndef MATRIX_ROWS -#   define MATRIX_ROWS %s -#endif // MATRIX_ROWS +#ifndef MATRIX_ROWS{postfix} +#   define MATRIX_ROWS{postfix} {row_count} +#endif // MATRIX_ROWS{postfix} -#ifndef DIRECT_PINS -#   define DIRECT_PINS {%s} -#endif // DIRECT_PINS -""" % (col_count, row_count, ','.join(rows)) +#ifndef DIRECT_PINS{postfix} +#   define DIRECT_PINS{postfix} {{ {", ".join(rows)} }} +#endif // DIRECT_PINS{postfix} +""" -def pin_array(define, pins): +def pin_array(define, pins, postfix):      """Return the config.h lines that set a pin array.      """      pin_num = len(pins)      pin_array = ', '.join(map(str, [pin or 'NO_PIN' for pin in pins]))      return f""" -#ifndef {define}S -#   define {define}S {pin_num} -#endif // {define}S +#ifndef {define}S{postfix} +#   define {define}S{postfix} {pin_num} +#endif // {define}S{postfix} -#ifndef {define}_PINS -#   define {define}_PINS {{ {pin_array} }} -#endif // {define}_PINS +#ifndef {define}_PINS{postfix} +#   define {define}_PINS{postfix} {{ {pin_array} }} +#endif // {define}_PINS{postfix}  """ -def matrix_pins(matrix_pins): +def matrix_pins(matrix_pins, postfix=''):      """Add the matrix config to the config.h.      """      pins = []      if 'direct' in matrix_pins: -        pins.append(direct_pins(matrix_pins['direct'])) +        pins.append(direct_pins(matrix_pins['direct'], postfix))      if 'cols' in matrix_pins: -        pins.append(pin_array('MATRIX_COL', matrix_pins['cols'])) +        pins.append(pin_array('MATRIX_COL', matrix_pins['cols'], postfix))      if 'rows' in matrix_pins: -        pins.append(pin_array('MATRIX_ROW', matrix_pins['rows'])) +        pins.append(pin_array('MATRIX_ROW', matrix_pins['rows'], postfix))      return '\n'.join(pins) [email protected]('-o', '--output', arg_only=True, type=normpath, help='File to write to') [email protected]('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") [email protected]('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.') [email protected]('Used by the make system to generate info_config.h from info.json', hidden=True) -@automagic_keyboard -@automagic_keymap -def generate_config_h(cli): -    """Generates the info_config.h file. +def generate_config_items(kb_info_json, config_h_lines): +    """Iterate through the info_config map to generate basic config values.      """ -    # Determine our keyboard(s) -    if not cli.config.generate_config_h.keyboard: -        cli.log.error('Missing parameter: --keyboard') -        cli.subcommands['info'].print_help() -        return False - -    if not is_keyboard(cli.config.generate_config_h.keyboard): -        cli.log.error('Invalid keyboard: "%s"', cli.config.generate_config_h.keyboard) -        return False - -    # Build the info_config.h file. -    kb_info_json = dotty(info_json(cli.config.generate_config_h.keyboard))      info_config_map = json_load(Path('data/mappings/info_config.json')) -    config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once'] - -    # Iterate through the info_config map to generate basic things      for config_key, info_dict in info_config_map.items():          info_key = info_dict['info_key']          key_type = info_dict.get('value_type', 'str') @@ -135,9 +114,78 @@ def generate_config_h(cli):              config_h_lines.append(f'#   define {config_key} {config_value}')              config_h_lines.append(f'#endif // {config_key}') + +def generate_split_config(kb_info_json, config_h_lines): +    """Generate the config.h lines for split boards.""" +    if 'primary' in kb_info_json['split']: +        if kb_info_json['split']['primary'] in ('left', 'right'): +            config_h_lines.append('') +            config_h_lines.append('#ifndef MASTER_LEFT') +            config_h_lines.append('#   ifndef MASTER_RIGHT') +            if kb_info_json['split']['primary'] == 'left': +                config_h_lines.append('#       define MASTER_LEFT') +            elif kb_info_json['split']['primary'] == 'right': +                config_h_lines.append('#       define MASTER_RIGHT') +            config_h_lines.append('#   endif // MASTER_RIGHT') +            config_h_lines.append('#endif // MASTER_LEFT') +        elif kb_info_json['split']['primary'] == 'pin': +            config_h_lines.append('') +            config_h_lines.append('#ifndef SPLIT_HAND_PIN') +            config_h_lines.append('#   define SPLIT_HAND_PIN') +            config_h_lines.append('#endif // SPLIT_HAND_PIN') +        elif kb_info_json['split']['primary'] == 'matrix_grid': +            config_h_lines.append('') +            config_h_lines.append('#ifndef SPLIT_HAND_MATRIX_GRID') +            config_h_lines.append('#   define SPLIT_HAND_MATRIX_GRID {%s}' % (','.join(kb_info_json["split"]["matrix_grid"],))) +            config_h_lines.append('#endif // SPLIT_HAND_MATRIX_GRID') +        elif kb_info_json['split']['primary'] == 'eeprom': +            config_h_lines.append('') +            config_h_lines.append('#ifndef EE_HANDS') +            config_h_lines.append('#   define EE_HANDS') +            config_h_lines.append('#endif // EE_HANDS') + +    if 'protocol' in kb_info_json['split'].get('transport', {}): +        if kb_info_json['split']['transport']['protocol'] == 'i2c': +            config_h_lines.append('') +            config_h_lines.append('#ifndef USE_I2C') +            config_h_lines.append('#   define USE_I2C') +            config_h_lines.append('#endif // USE_I2C') + +    if 'right' in kb_info_json['split'].get('matrix_pins', {}): +        config_h_lines.append(matrix_pins(kb_info_json['split']['matrix_pins']['right'], '_RIGHT')) + + [email protected]('-o', '--output', arg_only=True, type=normpath, help='File to write to') [email protected]('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") [email protected]('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.') [email protected]('Used by the make system to generate info_config.h from info.json', hidden=True) +@automagic_keyboard +@automagic_keymap +def generate_config_h(cli): +    """Generates the info_config.h file. +    """ +    # Determine our keyboard(s) +    if not cli.config.generate_config_h.keyboard: +        cli.log.error('Missing parameter: --keyboard') +        cli.subcommands['info'].print_help() +        return False + +    if not is_keyboard(cli.config.generate_config_h.keyboard): +        cli.log.error('Invalid keyboard: "%s"', cli.config.generate_config_h.keyboard) +        return False + +    # Build the info_config.h file. +    kb_info_json = dotty(info_json(cli.config.generate_config_h.keyboard)) +    config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.' ' */', '', '#pragma once'] + +    generate_config_items(kb_info_json, config_h_lines) +      if 'matrix_pins' in kb_info_json:          config_h_lines.append(matrix_pins(kb_info_json['matrix_pins'])) +    if 'split' in kb_info_json: +        generate_split_config(kb_info_json, config_h_lines) +      # Show the results      config_h = '\n'.join(config_h_lines) diff --git a/lib/python/qmk/cli/generate/info_json.py b/lib/python/qmk/cli/generate/info_json.py index 8931b68b6f..284d1a8510 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.  """  import json -from jsonschema import Draft7Validator, validators +from argcomplete.completers import FilesCompleter +from jsonschema import Draft7Validator, RefResolver, validators  from milc import cli +from pathlib import Path  from qmk.decorators import automagic_keyboard, automagic_keymap  from qmk.info import info_json  from qmk.json_encoders import InfoJSONEncoder -from qmk.json_schema import load_jsonschema +from qmk.json_schema import compile_schema_store  from qmk.keyboard import keyboard_completer, keyboard_folder -from qmk.path import is_keyboard +from qmk.path import is_keyboard, normpath  def pruning_validator(validator_class): @@ -34,15 +36,19 @@ def pruning_validator(validator_class):  def strip_info_json(kb_info_json):      """Remove the API-only properties from the info.json.      """ +    schema_store = compile_schema_store()      pruning_draft_7_validator = pruning_validator(Draft7Validator) -    schema = load_jsonschema('keyboard') -    validator = pruning_draft_7_validator(schema).validate +    schema = schema_store['qmk.keyboard.v1'] +    resolver = RefResolver.from_schema(schema_store['qmk.keyboard.v1'], store=schema_store) +    validator = pruning_draft_7_validator(schema, resolver=resolver).validate      return validator(kb_info_json)  @cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to show info for.')  @cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.') [email protected]('-o', '--output', arg_only=True, completer=FilesCompleter, help='Write the output the specified file, overwriting if necessary.') [email protected]('-ow', '--overwrite', arg_only=True, action='store_true', help='Overwrite the existing info.json. (Overrides the location of --output)')  @cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True)  @automagic_keyboard  @automagic_keymap @@ -59,9 +65,29 @@ def generate_info_json(cli):          cli.log.error('Invalid keyboard: "%s"', cli.config.generate_info_json.keyboard)          return False +    if cli.args.overwrite: +        output_path = (Path('keyboards') / cli.config.generate_info_json.keyboard / 'info.json').resolve() + +        if cli.args.output: +            cli.log.warning('Overwriting user supplied --output with %s', output_path) + +        cli.args.output = output_path +      # Build the info.json file      kb_info_json = info_json(cli.config.generate_info_json.keyboard)      strip_info_json(kb_info_json) +    info_json_text = json.dumps(kb_info_json, indent=4, cls=InfoJSONEncoder) + +    if cli.args.output: +        # Write to a file +        output_path = normpath(cli.args.output) + +        if output_path.exists(): +            cli.log.warning('Overwriting output file %s', output_path) + +        output_path.write_text(info_json_text + '\n') +        cli.log.info('Wrote info.json to %s.', output_path) -    # Display the results -    print(json.dumps(kb_info_json, indent=2, cls=InfoJSONEncoder)) +    else: +        # Display the results +        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 41c94e16b5..2712b81cb5 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):                  enabled = 'yes' if enabled else 'no'                  rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}') +    # Set SPLIT_TRANSPORT, if needed +    if kb_info_json.get('split', {}).get('transport', {}).get('protocol') == 'custom': +        rules_mk_lines.append('SPLIT_TRANSPORT ?= custom') + +    # Set CUSTOM_MATRIX, if needed +    if kb_info_json.get('matrix_pins', {}).get('custom'): +        if kb_info_json.get('matrix_pins', {}).get('custom_lite'): +            rules_mk_lines.append('CUSTOM_MATRIX ?= lite') +        else: +            rules_mk_lines.append('CUSTOM_MATRIX ?= yes') +      # Show the results      rules_mk = '\n'.join(rules_mk_lines) + '\n' diff --git a/lib/python/qmk/cli/info.py b/lib/python/qmk/cli/info.py index 337b494a99..3131d4b53f 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):      keymap_path = locate_keymap(cli.config.info.keyboard, cli.config.info.keymap)      if keymap_path and keymap_path.suffix == '.json': -        if title_caps: -            cli.echo('{fg_blue}Keymap "%s"{fg_reset}:', cli.config.info.keymap) -        else: -            cli.echo('{fg_blue}keymap_%s{fg_reset}:', cli.config.info.keymap) -          keymap_data = json.load(keymap_path.open(encoding='utf-8'))          layout_name = keymap_data['layout'] +        layout_name = kb_info_json.get('layout_aliases', {}).get(layout_name, layout_name)  # Resolve alias names          for layer_num, layer in enumerate(keymap_data['layers']):              if title_caps: -                cli.echo('{fg_cyan}Layer %s{fg_reset}:', layer_num) +                cli.echo('{fg_cyan}Keymap %s Layer %s{fg_reset}:', cli.config.info.keymap, layer_num)              else: -                cli.echo('{fg_cyan}layer_%s{fg_reset}:', layer_num) +                cli.echo('{fg_cyan}keymap.%s.layer.%s{fg_reset}:', cli.config.info.keymap, layer_num)              print(render_layout(kb_info_json['layouts'][layout_name]['layout'], cli.config.info.ascii, layer)) @@ -45,7 +41,7 @@ def show_layouts(kb_info_json, title_caps=True):      """Render the layouts with info.json labels.      """      for layout_name, layout_art in render_layouts(kb_info_json, cli.config.info.ascii).items(): -        title = layout_name.title() if title_caps else layout_name +        title = f'Layout {layout_name.title()}' if title_caps else f'layouts.{layout_name}'          cli.echo('{fg_cyan}%s{fg_reset}:', title)          print(layout_art)  # Avoid passing dirty data to cli.echo() @@ -93,15 +89,6 @@ def print_friendly_output(kb_info_json):          aliases = [f'{key}={value}' for key, value in kb_info_json['layout_aliases'].items()]          cli.echo('{fg_blue}Layout aliases:{fg_reset} %s' % (', '.join(aliases),)) -    if cli.config.info.layouts: -        show_layouts(kb_info_json, True) - -    if cli.config.info.matrix: -        show_matrix(kb_info_json, True) - -    if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file': -        show_keymap(kb_info_json, True) -  def print_text_output(kb_info_json):      """Print the info.json in a plain text format. @@ -122,6 +109,24 @@ def print_text_output(kb_info_json):          show_keymap(kb_info_json, False) +def print_dotted_output(kb_info_json, prefix=''): +    """Print the info.json in a plain text format with dot-joined keys. +    """ +    for key in sorted(kb_info_json): +        new_prefix = f'{prefix}.{key}' if prefix else key + +        if key in ['parse_errors', 'parse_warnings']: +            continue +        elif key == 'layouts' and prefix == '': +            cli.echo('{fg_blue}layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys()))) +        elif isinstance(kb_info_json[key], dict): +            print_dotted_output(kb_info_json[key], new_prefix) +        elif isinstance(kb_info_json[key], list): +            cli.echo('{fg_blue}%s{fg_reset}: %s', new_prefix, ', '.join(map(str, sorted(kb_info_json[key])))) +        else: +            cli.echo('{fg_blue}%s{fg_reset}: %s', new_prefix, kb_info_json[key]) + +  def print_parsed_rules_mk(keyboard_name):      rules = rules_mk(keyboard_name)      for k in sorted(rules.keys()): @@ -162,10 +167,22 @@ def info(cli):      # Output in the requested format      if cli.args.format == 'json':          print(json.dumps(kb_info_json, cls=InfoJSONEncoder)) +        return True      elif cli.args.format == 'text': -        print_text_output(kb_info_json) +        print_dotted_output(kb_info_json) +        title_caps = False      elif cli.args.format == 'friendly':          print_friendly_output(kb_info_json) +        title_caps = True      else:          cli.log.error('Unknown format: %s', cli.args.format)          return False + +    if cli.config.info.layouts: +        show_layouts(kb_info_json, title_caps) + +    if cli.config.info.matrix: +        show_matrix(kb_info_json, title_caps) + +    if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file': +        show_keymap(kb_info_json, title_caps) diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 858fbab335..b6f2ecf644 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -61,8 +61,8 @@ def info_json(keyboard):      # Merge in the data from info.json, config.h, and rules.mk      info_data = merge_info_jsons(keyboard, info_data) -    info_data = _extract_config_h(info_data)      info_data = _extract_rules_mk(info_data) +    info_data = _extract_config_h(info_data)      # Ensure that we have matrix row and column counts      info_data = _matrix_size(info_data) @@ -161,10 +161,9 @@ def _extract_pins(pins):      return [_pin_name(pin) for pin in pins.split(',')] -def _extract_direct_matrix(info_data, direct_pins): +def _extract_direct_matrix(direct_pins):      """      """ -    info_data['matrix_pins'] = {}      direct_pin_array = []      while direct_pins[-1] != '}': @@ -188,12 +187,157 @@ def _extract_direct_matrix(info_data, direct_pins):      return direct_pin_array +def _extract_audio(info_data, config_c): +    """Populate data about the audio configuration +    """ +    audio_pins = [] + +    for pin in 'B5', 'B6', 'B7', 'C4', 'C5', 'C6': +        if config_c.get(f'{pin}_AUDIO'): +            audio_pins.append(pin) + +    if audio_pins: +        info_data['audio'] = {'pins': audio_pins} + + +def _extract_split_main(info_data, config_c): +    """Populate data about the split configuration +    """ +    # Figure out how the main half is determined +    if config_c.get('SPLIT_HAND_PIN') is True: +        if 'split' not in info_data: +            info_data['split'] = {} + +        if 'main' in info_data['split']: +            _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']) + +        info_data['split']['main'] = 'pin' + +    if config_c.get('SPLIT_HAND_MATRIX_GRID'): +        if 'split' not in info_data: +            info_data['split'] = {} + +        if 'main' in info_data['split']: +            _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']) + +        info_data['split']['main'] = 'matrix_grid' +        info_data['split']['matrix_grid'] = _extract_pins(config_c['SPLIT_HAND_MATRIX_GRID']) + +    if config_c.get('EE_HANDS') is True: +        if 'split' not in info_data: +            info_data['split'] = {} + +        if 'main' in info_data['split']: +            _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']) + +        info_data['split']['main'] = 'eeprom' + +    if config_c.get('MASTER_RIGHT') is True: +        if 'split' not in info_data: +            info_data['split'] = {} + +        if 'main' in info_data['split']: +            _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']) + +        info_data['split']['main'] = 'right' + +    if config_c.get('MASTER_LEFT') is True: +        if 'split' not in info_data: +            info_data['split'] = {} + +        if 'main' in info_data['split']: +            _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']) + +        info_data['split']['main'] = 'left' + + +def _extract_split_transport(info_data, config_c): +    # Figure out the transport method +    if config_c.get('USE_I2C') is True: +        if 'split' not in info_data: +            info_data['split'] = {} + +        if 'transport' not in info_data['split']: +            info_data['split']['transport'] = {} + +        if 'protocol' in info_data['split']['transport']: +            _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']) + +        info_data['split']['transport']['protocol'] = 'i2c' + +    elif 'protocol' not in info_data.get('split', {}).get('transport', {}): +        if 'split' not in info_data: +            info_data['split'] = {} + +        if 'transport' not in info_data['split']: +            info_data['split']['transport'] = {} + +        info_data['split']['transport']['protocol'] = 'serial' + + +def _extract_split_right_pins(info_data, config_c): +    # Figure out the right half matrix pins +    row_pins = config_c.get('MATRIX_ROW_PINS_RIGHT', '').replace('{', '').replace('}', '').strip() +    col_pins = config_c.get('MATRIX_COL_PINS_RIGHT', '').replace('{', '').replace('}', '').strip() +    unused_pin_text = config_c.get('UNUSED_PINS_RIGHT') +    unused_pins = unused_pin_text.replace('{', '').replace('}', '').strip() if isinstance(unused_pin_text, str) else None +    direct_pins = config_c.get('DIRECT_PINS_RIGHT', '').replace(' ', '')[1:-1] + +    if row_pins and col_pins: +        if info_data.get('split', {}).get('matrix_pins', {}).get('right') in info_data: +            _log_warning(info_data, 'Right hand matrix data is specified in both info.json and config.h, the config.h values win.') + +        if 'split' not in info_data: +            info_data['split'] = {} + +        if 'matrix_pins' not in info_data['split']: +            info_data['split']['matrix_pins'] = {} + +        if 'right' not in info_data['split']['matrix_pins']: +            info_data['split']['matrix_pins']['right'] = {} + +        info_data['split']['matrix_pins']['right'] = { +            'cols': _extract_pins(col_pins), +            'rows': _extract_pins(row_pins), +        } + +    if direct_pins: +        if info_data.get('split', {}).get('matrix_pins', {}).get('right', {}): +            _log_warning(info_data, 'Right hand matrix data is specified in both info.json and config.h, the config.h values win.') + +        if 'split' not in info_data: +            info_data['split'] = {} + +        if 'matrix_pins' not in info_data['split']: +            info_data['split']['matrix_pins'] = {} + +        if 'right' not in info_data['split']['matrix_pins']: +            info_data['split']['matrix_pins']['right'] = {} + +        info_data['split']['matrix_pins']['right']['direct'] = _extract_direct_matrix(direct_pins) + +    if unused_pins: +        if 'split' not in info_data: +            info_data['split'] = {} + +        if 'matrix_pins' not in info_data['split']: +            info_data['split']['matrix_pins'] = {} + +        if 'right' not in info_data['split']['matrix_pins']: +            info_data['split']['matrix_pins']['right'] = {} + +        info_data['split']['matrix_pins']['right']['unused'] = _extract_pins(unused_pins) + +  def _extract_matrix_info(info_data, config_c):      """Populate the matrix information.      """      row_pins = config_c.get('MATRIX_ROW_PINS', '').replace('{', '').replace('}', '').strip()      col_pins = config_c.get('MATRIX_COL_PINS', '').replace('{', '').replace('}', '').strip() +    unused_pin_text = config_c.get('UNUSED_PINS') +    unused_pins = unused_pin_text.replace('{', '').replace('}', '').strip() if isinstance(unused_pin_text, str) else None      direct_pins = config_c.get('DIRECT_PINS', '').replace(' ', '')[1:-1] +    info_snippet = {}      if 'MATRIX_ROWS' in config_c and 'MATRIX_COLS' in config_c:          if 'matrix_size' in info_data: @@ -205,19 +349,35 @@ def _extract_matrix_info(info_data, config_c):          }      if row_pins and col_pins: -        if 'matrix_pins' in info_data: +        if 'matrix_pins' in info_data and 'cols' in info_data['matrix_pins'] and 'rows' in info_data['matrix_pins']:              _log_warning(info_data, 'Matrix pins are specified in both info.json and config.h, the config.h values win.') -        info_data['matrix_pins'] = { -            'cols': _extract_pins(col_pins), -            'rows': _extract_pins(row_pins), -        } +        info_snippet['cols'] = _extract_pins(col_pins) +        info_snippet['rows'] = _extract_pins(row_pins)      if direct_pins: -        if 'matrix_pins' in info_data: +        if 'matrix_pins' in info_data and 'direct' in info_data['matrix_pins']:              _log_warning(info_data, 'Direct pins are specified in both info.json and config.h, the config.h values win.') -        info_data['matrix_pins']['direct'] = _extract_direct_matrix(info_data, direct_pins) +        info_snippet['direct'] = _extract_direct_matrix(direct_pins) + +    if unused_pins: +        if 'matrix_pins' not in info_data: +            info_data['matrix_pins'] = {} + +        info_snippet['unused'] = _extract_pins(unused_pins) + +    if config_c.get('CUSTOM_MATRIX', 'no') != 'no': +        if 'matrix_pins' in info_data and 'custom' in info_data['matrix_pins']: +            _log_warning(info_data, 'Custom Matrix is specified in both info.json and config.h, the config.h values win.') + +        info_snippet['custom'] = True + +        if config_c['CUSTOM_MATRIX'] == 'lite': +            info_snippet['custom_lite'] = True + +    if info_snippet: +        info_data['matrix_pins'] = info_snippet      return info_data @@ -275,6 +435,10 @@ def _extract_config_h(info_data):      # Pull data that easily can't be mapped in json      _extract_matrix_info(info_data, config_c) +    _extract_audio(info_data, config_c) +    _extract_split_main(info_data, config_c) +    _extract_split_transport(info_data, config_c) +    _extract_split_right_pins(info_data, config_c)      return info_data diff --git a/lib/python/qmk/json_schema.py b/lib/python/qmk/json_schema.py index cbc5bff518..ffc7c6bcd1 100644 --- a/lib/python/qmk/json_schema.py +++ b/lib/python/qmk/json_schema.py @@ -2,6 +2,7 @@  """  import json  from collections.abc import Mapping +from functools import lru_cache  from pathlib import Path  import hjson @@ -25,6 +26,7 @@ def json_load(json_file):          exit(1) +@lru_cache(maxsize=0)  def load_jsonschema(schema_name):      """Read a jsonschema file from disk.      """ @@ -39,8 +41,9 @@ def load_jsonschema(schema_name):      return json_load(schema_path) -def create_validator(schema): -    """Creates a validator for the given schema id. +@lru_cache(maxsize=0) +def compile_schema_store(): +    """Compile all our schemas into a schema store.      """      schema_store = {} @@ -51,6 +54,14 @@ def create_validator(schema):              continue          schema_store[schema_data['$id']] = schema_data +    return schema_store + + +@lru_cache(maxsize=0) +def create_validator(schema): +    """Creates a validator for the given schema id. +    """ +    schema_store = compile_schema_store()      resolver = jsonschema.RefResolver.from_schema(schema_store['qmk.keyboard.v1'], store=schema_store)      return jsonschema.Draft7Validator(schema_store[schema], resolver=resolver).validate | 
