diff options
Diffstat (limited to 'lib/python')
| -rw-r--r-- | lib/python/qmk/cli/__init__.py | 1 | ||||
| -rwxr-xr-x | lib/python/qmk/cli/generate/config_h.py | 4 | ||||
| -rwxr-xr-x | lib/python/qmk/cli/generate/keyboard_c.py | 38 | ||||
| -rw-r--r-- | lib/python/qmk/cli/generate/keymap_h.py | 51 | ||||
| -rw-r--r-- | lib/python/qmk/commands.py | 4 | ||||
| -rw-r--r-- | lib/python/qmk/constants.py | 2 | ||||
| -rw-r--r-- | lib/python/qmk/info.py | 28 | ||||
| -rw-r--r-- | lib/python/qmk/keymap.py | 39 | ||||
| -rw-r--r-- | lib/python/qmk/tests/test_cli_commands.py | 76 | ||||
| -rw-r--r-- | lib/python/qmk/tests/test_qmk_keymap.py | 44 | 
10 files changed, 209 insertions, 78 deletions
| diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index f331445870..0baf19a629 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -58,6 +58,7 @@ subcommands = [      'qmk.cli.generate.keyboard_h',      'qmk.cli.generate.keycodes',      'qmk.cli.generate.keycodes_tests', +    'qmk.cli.generate.keymap_h',      'qmk.cli.generate.make_dependencies',      'qmk.cli.generate.rgb_breathe_table',      'qmk.cli.generate.rules_mk', diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index fc681300a3..d613f7b92c 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py @@ -135,8 +135,8 @@ def generate_encoder_config(encoder_json, config_h_lines, postfix=''):          b_pads.append(encoder["pin_b"])          resolutions.append(encoder.get("resolution", None)) -    config_h_lines.append(generate_define(f'ENCODERS_PAD_A{postfix}', f'{{ {", ".join(a_pads)} }}')) -    config_h_lines.append(generate_define(f'ENCODERS_PAD_B{postfix}', f'{{ {", ".join(b_pads)} }}')) +    config_h_lines.append(generate_define(f'ENCODER_A_PINS{postfix}', f'{{ {", ".join(a_pads)} }}')) +    config_h_lines.append(generate_define(f'ENCODER_B_PINS{postfix}', f'{{ {", ".join(b_pads)} }}'))      if None in resolutions:          cli.log.debug(f"Unable to generate ENCODER_RESOLUTION{postfix} configuration") diff --git a/lib/python/qmk/cli/generate/keyboard_c.py b/lib/python/qmk/cli/generate/keyboard_c.py index 5a6c967486..228b320942 100755 --- a/lib/python/qmk/cli/generate/keyboard_c.py +++ b/lib/python/qmk/cli/generate/keyboard_c.py @@ -6,7 +6,7 @@ from qmk.info import info_json  from qmk.commands import dump_lines  from qmk.keyboard import keyboard_completer, keyboard_folder  from qmk.path import normpath -from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE +from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, JOYSTICK_AXES  def _gen_led_configs(info_data): @@ -91,6 +91,41 @@ def _gen_matrix_mask(info_data):      return lines +def _gen_joystick_axes(info_data): +    """Convert info.json content to joystick_axes +    """ +    if 'axes' not in info_data.get('joystick', {}): +        return [] + +    axes = info_data['joystick']['axes'] +    axes_keys = list(axes.keys()) + +    lines = [] +    lines.append('#ifdef JOYSTICK_ENABLE') +    lines.append('joystick_config_t joystick_axes[JOYSTICK_AXIS_COUNT] = {') + +    # loop over all available axes - injecting virtual axis for those not specified +    for index, cur in enumerate(JOYSTICK_AXES): +        # bail out if we have generated all requested axis +        if len(axes_keys) == 0: +            break + +        axis = 'virtual' +        if cur in axes: +            axis = axes[cur] +            axes_keys.remove(cur) + +        if axis == 'virtual': +            lines.append(f"    [{index}] = JOYSTICK_AXIS_VIRTUAL,") +        else: +            lines.append(f"    [{index}] = JOYSTICK_AXIS_IN({axis['input_pin']}, {axis['low']}, {axis['rest']}, {axis['high']}),") + +    lines.append('};') +    lines.append('#endif') + +    return lines + +  @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')  @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")  @cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='Keyboard to generate keyboard.c for.') @@ -105,6 +140,7 @@ def generate_keyboard_c(cli):      keyboard_h_lines.extend(_gen_led_configs(kb_info_json))      keyboard_h_lines.extend(_gen_matrix_mask(kb_info_json)) +    keyboard_h_lines.extend(_gen_joystick_axes(kb_info_json))      # Show the results      dump_lines(cli.args.output, keyboard_h_lines, cli.args.quiet) diff --git a/lib/python/qmk/cli/generate/keymap_h.py b/lib/python/qmk/cli/generate/keymap_h.py new file mode 100644 index 0000000000..a3aaa405c0 --- /dev/null +++ b/lib/python/qmk/cli/generate/keymap_h.py @@ -0,0 +1,51 @@ +from argcomplete.completers import FilesCompleter + +from milc import cli + +import qmk.path +from qmk.commands import dump_lines +from qmk.commands import parse_configurator_json +from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE + + +def _generate_keycodes_function(keymap_json): +    """Generates keymap level keycodes. +    """ +    lines = [] +    lines.append('enum keymap_keycodes {') + +    for index, item in enumerate(keymap_json.get('keycodes', [])): +        key = item["key"] +        if index == 0: +            lines.append(f'  {key} = QK_USER_0,') +        else: +            lines.append(f'  {key},') + +    lines.append('};') + +    for item in keymap_json.get('keycodes', []): +        key = item["key"] +        for alias in item.get("aliases", []): +            lines.append(f'#define {alias} {key}') + +    return lines + + [email protected]('-o', '--output', arg_only=True, type=qmk.path.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]('filename', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.json'), help='Configurator JSON file') [email protected]('Creates a keymap.h from a QMK Configurator export.') +def generate_keymap_h(cli): +    """Creates a keymap.h from a QMK Configurator export +    """ +    if cli.args.output and cli.args.output.name == '-': +        cli.args.output = None + +    keymap_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '// clang-format off'] + +    keymap_json = parse_configurator_json(cli.args.filename) + +    if 'keycodes' in keymap_json and keymap_json['keycodes'] is not None: +        keymap_h_lines += _generate_keycodes_function(keymap_json) + +    dump_lines(cli.args.output, keymap_h_lines, cli.args.quiet) diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py index df6ed6b88a..b0b8ccb244 100644 --- a/lib/python/qmk/commands.py +++ b/lib/python/qmk/commands.py @@ -108,7 +108,9 @@ def dump_lines(output_file, lines, quiet=True):                      cli.log.info(f'No changes to {output_file.name}.')                  return              output_file.replace(output_file.parent / (output_file.name + '.bak')) -        output_file.write_text(generated, encoding='utf-8') +        with open(output_file, 'w', encoding='utf-8', newline='\n') as f: +            f.write(generated) +        # output_file.write_text(generated, encoding='utf-8', newline='\n') # `newline` needs Python 3.10          if not quiet:              cli.log.info(f'Wrote {output_file.name} to {output_file}.') diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index 90e4452f2b..b6f46180b2 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py @@ -320,3 +320,5 @@ LICENSE_TEXTS = [          you may not use this file except in compliance with the License.          """]),  ] + +JOYSTICK_AXES = ['x', 'y', 'z', 'rx', 'ry', 'rz'] diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 833271c09c..5948b66b5e 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -7,7 +7,7 @@ from dotty_dict import dotty  from milc import cli -from qmk.constants import COL_LETTERS, ROW_LETTERS, CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS +from qmk.constants import COL_LETTERS, ROW_LETTERS, CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS, JOYSTICK_AXES  from qmk.c_parse import find_layouts, parse_config_h_file, find_led_config  from qmk.json_schema import deep_update, json_load, validate  from qmk.keyboard import config_h, rules_mk @@ -249,8 +249,9 @@ def info_json(keyboard):      info_data = _extract_rules_mk(info_data, rules_mk(str(keyboard)))      info_data = _extract_config_h(info_data, config_h(str(keyboard))) -    # Ensure that we have matrix row and column counts +    # Ensure that we have various calculated values      info_data = _matrix_size(info_data) +    info_data = _joystick_axis_count(info_data)      # Merge in data from <keyboard.c>      info_data = _extract_led_config(info_data, str(keyboard)) @@ -374,8 +375,8 @@ def _extract_audio(info_data, config_c):  def _extract_encoders_values(config_c, postfix=''):      """Common encoder extraction logic      """ -    a_pad = config_c.get(f'ENCODERS_PAD_A{postfix}', '').replace(' ', '')[1:-1] -    b_pad = config_c.get(f'ENCODERS_PAD_B{postfix}', '').replace(' ', '')[1:-1] +    a_pad = config_c.get(f'ENCODER_A_PINS{postfix}', '').replace(' ', '')[1:-1] +    b_pad = config_c.get(f'ENCODER_B_PINS{postfix}', '').replace(' ', '')[1:-1]      resolutions = config_c.get(f'ENCODER_RESOLUTIONS{postfix}', '').replace(' ', '')[1:-1]      default_resolution = config_c.get('ENCODER_RESOLUTION', None) @@ -460,6 +461,14 @@ def _extract_split_handedness(info_data, config_c):          split['handedness']['matrix_grid'] = split.pop('matrix_grid') +def _extract_split_serial(info_data, config_c): +    # Migrate +    split = info_data.get('split', {}) +    if 'soft_serial_pin' in split: +        split['serial'] = split.get('serial', {}) +        split['serial']['pin'] = split.pop('soft_serial_pin') + +  def _extract_split_transport(info_data, config_c):      # Figure out the transport method      if config_c.get('USE_I2C') is True: @@ -655,6 +664,7 @@ def _extract_config_h(info_data, config_c):      _extract_audio(info_data, config_c)      _extract_secure_unlock(info_data, config_c)      _extract_split_handedness(info_data, config_c) +    _extract_split_serial(info_data, config_c)      _extract_split_transport(info_data, config_c)      _extract_split_right_pins(info_data, config_c)      _extract_encoders(info_data, config_c) @@ -800,6 +810,16 @@ def _matrix_size(info_data):      return info_data +def _joystick_axis_count(info_data): +    """Add info_data['joystick.axis_count'] if required +    """ +    if 'axes' in info_data.get('joystick', {}): +        axes_keys = info_data['joystick']['axes'].keys() +        info_data['joystick']['axis_count'] = max(JOYSTICK_AXES.index(a) for a in axes_keys) + 1 if axes_keys else 0 + +    return info_data + +  def _check_matrix(info_data):      """Check the matrix to ensure that row/column count is consistent.      """ diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py index b7bf897377..8c09cc51dc 100644 --- a/lib/python/qmk/keymap.py +++ b/lib/python/qmk/keymap.py @@ -19,6 +19,9 @@ from qmk.info import info_json  # The `keymap.c` template to use when a keyboard doesn't have its own  DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H +#if __has_include("keymap.h") +#    include "keymap.h" +#endif  __INCLUDES__  /* THIS FILE WAS GENERATED! @@ -26,8 +29,6 @@ __INCLUDES__   * This file was generated by qmk json2c. You may or may not want to   * edit it directly.   */ -__KEYCODE_OUTPUT_GOES_HERE__ -  const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {  __KEYMAP_GOES_HERE__  }; @@ -39,7 +40,6 @@ __ENCODER_MAP_GOES_HERE__  #endif // defined(ENCODER_ENABLE) && defined(ENCODER_MAP_ENABLE)  __MACRO_OUTPUT_GOES_HERE__ -  """ @@ -50,7 +50,7 @@ def _generate_keymap_table(keymap_json):              lines[-1] = lines[-1] + ','          layer = map(_strip_any, layer)          layer_keys = ', '.join(layer) -        lines.append('\t[%s] = %s(%s)' % (layer_num, keymap_json['layout'], layer_keys)) +        lines.append('    [%s] = %s(%s)' % (layer_num, keymap_json['layout'], layer_keys))      return lines @@ -60,7 +60,7 @@ def _generate_encodermap_table(keymap_json):          if layer_num != 0:              lines[-1] = lines[-1] + ','          encoder_keycode_txt = ', '.join([f'ENCODER_CCW_CW({_strip_any(e["ccw"])}, {_strip_any(e["cw"])})' for e in layer]) -        lines.append('\t[%s] = {%s}' % (layer_num, encoder_keycode_txt)) +        lines.append('    [%s] = {%s}' % (layer_num, encoder_keycode_txt))      return lines @@ -125,29 +125,6 @@ def _generate_macros_function(keymap_json):      return macro_txt -def _generate_keycodes_function(keymap_json): -    """Generates keymap level keycodes. -    """ -    lines = [] -    lines.append('enum keymap_keycodes {') - -    for index, item in enumerate(keymap_json.get('keycodes', [])): -        key = item["key"] -        if index == 0: -            lines.append(f'  {key} = QK_USER_0,') -        else: -            lines.append(f'  {key},') - -    lines.append('};') - -    for item in keymap_json.get('keycodes', []): -        key = item["key"] -        for alias in item.get("aliases", []): -            lines.append(f'#define {alias} {key}') - -    return lines - -  def template_json(keyboard):      """Returns a `keymap.json` template for a keyboard. @@ -350,12 +327,6 @@ def generate_c(keymap_json):          hostlang = f'#include "keymap_{keymap_json["host_language"]}.h"\n#include "sendstring_{keymap_json["host_language"]}.h"\n'      new_keymap = new_keymap.replace('__INCLUDES__', hostlang) -    keycodes = '' -    if 'keycodes' in keymap_json and keymap_json['keycodes'] is not None: -        keycodes_txt = _generate_keycodes_function(keymap_json) -        keycodes = '\n'.join(keycodes_txt) -    new_keymap = new_keymap.replace('__KEYCODE_OUTPUT_GOES_HERE__', keycodes) -      return new_keymap diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index 8b50d1c340..2b3d6f4049 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py @@ -142,9 +142,33 @@ def test_list_keymaps_no_keyboard_found():  def test_json2c(): -    result = check_subcommand('json2c', 'keyboards/handwired/pytest/has_template/keymaps/default_json/keymap.json') +    result = check_subcommand('json2c', 'keyboards/handwired/pytest/basic/keymaps/default_json/keymap.json')      check_returncode(result) -    assert result.stdout == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT_ortho_1x1(KC_A)};\n\n\n' +    assert result.stdout == """#include QMK_KEYBOARD_H +#if __has_include("keymap.h") +#    include "keymap.h" +#endif + + +/* THIS FILE WAS GENERATED! + * + * This file was generated by qmk json2c. You may or may not want to + * edit it directly. + */ +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { +    [0] = LAYOUT_ortho_1x1(KC_A) +}; + +#if defined(ENCODER_ENABLE) && defined(ENCODER_MAP_ENABLE) +const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = { + +}; +#endif // defined(ENCODER_ENABLE) && defined(ENCODER_MAP_ENABLE) + + + + +"""  def test_json2c_macros(): @@ -156,9 +180,33 @@ def test_json2c_macros():  def test_json2c_stdin(): -    result = check_subcommand_stdin('keyboards/handwired/pytest/has_template/keymaps/default_json/keymap.json', 'json2c', '-') +    result = check_subcommand_stdin('keyboards/handwired/pytest/basic/keymaps/default_json/keymap.json', 'json2c', '-')      check_returncode(result) -    assert result.stdout == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT_ortho_1x1(KC_A)};\n\n\n' +    assert result.stdout == """#include QMK_KEYBOARD_H +#if __has_include("keymap.h") +#    include "keymap.h" +#endif + + +/* THIS FILE WAS GENERATED! + * + * This file was generated by qmk json2c. You may or may not want to + * edit it directly. + */ +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { +    [0] = LAYOUT_ortho_1x1(KC_A) +}; + +#if defined(ENCODER_ENABLE) && defined(ENCODER_MAP_ENABLE) +const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = { + +}; +#endif // defined(ENCODER_ENABLE) && defined(ENCODER_MAP_ENABLE) + + + + +"""  def test_json2c_wrong_json(): @@ -223,27 +271,15 @@ def test_info_matrix_render():  def test_c2json(): -    result = check_subcommand("c2json", "-kb", "handwired/pytest/has_template", "-km", "default", "keyboards/handwired/pytest/has_template/keymaps/default/keymap.c") +    result = check_subcommand("c2json", "-kb", "handwired/pytest/basic", "-km", "default", "keyboards/handwired/pytest/basic/keymaps/default/keymap.c")      check_returncode(result) -    assert result.stdout.strip() == '{"keyboard": "handwired/pytest/has_template", "documentation": "This file is a keymap.json file for handwired/pytest/has_template", "keymap": "default", "layout": "LAYOUT_ortho_1x1", "layers": [["KC_A"]]}' - - -def test_c2json_nocpp(): -    result = check_subcommand("c2json", "--no-cpp", "-kb", "handwired/pytest/has_template", "-km", "default", "keyboards/handwired/pytest/has_template/keymaps/nocpp/keymap.c") -    check_returncode(result) -    assert result.stdout.strip() == '{"keyboard": "handwired/pytest/has_template", "documentation": "This file is a keymap.json file for handwired/pytest/has_template", "keymap": "default", "layout": "LAYOUT", "layers": [["KC_ENTER"]]}' +    assert result.stdout.strip() == '{"keyboard": "handwired/pytest/basic", "keymap": "default", "layout": "LAYOUT_ortho_1x1", "layers": [["KC_A"]]}'  def test_c2json_stdin(): -    result = check_subcommand_stdin("keyboards/handwired/pytest/has_template/keymaps/default/keymap.c", "c2json", "-kb", "handwired/pytest/has_template", "-km", "default", "-") -    check_returncode(result) -    assert result.stdout.strip() == '{"keyboard": "handwired/pytest/has_template", "documentation": "This file is a keymap.json file for handwired/pytest/has_template", "keymap": "default", "layout": "LAYOUT_ortho_1x1", "layers": [["KC_A"]]}' - - -def test_c2json_nocpp_stdin(): -    result = check_subcommand_stdin("keyboards/handwired/pytest/has_template/keymaps/nocpp/keymap.c", "c2json", "--no-cpp", "-kb", "handwired/pytest/has_template", "-km", "default", "-") +    result = check_subcommand_stdin("keyboards/handwired/pytest/basic/keymaps/default/keymap.c", "c2json", "-kb", "handwired/pytest/basic", "-km", "default", "-")      check_returncode(result) -    assert result.stdout.strip() == '{"keyboard": "handwired/pytest/has_template", "documentation": "This file is a keymap.json file for handwired/pytest/has_template", "keymap": "default", "layout": "LAYOUT", "layers": [["KC_ENTER"]]}' +    assert result.stdout.strip() == '{"keyboard": "handwired/pytest/basic", "keymap": "default", "layout": "LAYOUT_ortho_1x1", "layers": [["KC_A"]]}'  def test_clean(): diff --git a/lib/python/qmk/tests/test_qmk_keymap.py b/lib/python/qmk/tests/test_qmk_keymap.py index 5e2efc1232..bec3f4006c 100644 --- a/lib/python/qmk/tests/test_qmk_keymap.py +++ b/lib/python/qmk/tests/test_qmk_keymap.py @@ -11,30 +11,42 @@ def test_template_json_pytest_basic():      assert templ == {'keyboard': 'handwired/pytest/basic'} -def test_template_c_pytest_has_template(): -    templ = qmk.keymap.template_c('handwired/pytest/has_template') -    assert templ == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {__KEYMAP_GOES_HERE__};\n' - - -def test_template_json_pytest_has_template(): -    templ = qmk.keymap.template_json('handwired/pytest/has_template') -    assert templ == {'keyboard': 'handwired/pytest/has_template', "documentation": "This file is a keymap.json file for handwired/pytest/has_template"} - - -def test_generate_c_pytest_has_template(): +def test_generate_c_pytest_basic():      keymap_json = { -        'keyboard': 'handwired/pytest/has_template', +        'keyboard': 'handwired/pytest/basic',          'layout': 'LAYOUT',          'layers': [['KC_A']],          'macros': None,      }      templ = qmk.keymap.generate_c(keymap_json) -    assert templ == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT(KC_A)};\n' +    assert templ == """#include QMK_KEYBOARD_H +#if __has_include("keymap.h") +#    include "keymap.h" +#endif + + +/* THIS FILE WAS GENERATED! + * + * This file was generated by qmk json2c. You may or may not want to + * edit it directly. + */ +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { +    [0] = LAYOUT(KC_A) +}; + +#if defined(ENCODER_ENABLE) && defined(ENCODER_MAP_ENABLE) +const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = { + +}; +#endif // defined(ENCODER_ENABLE) && defined(ENCODER_MAP_ENABLE) + + +""" -def test_generate_json_pytest_has_template(): -    templ = qmk.keymap.generate_json('default', 'handwired/pytest/has_template', 'LAYOUT', [['KC_A']]) -    assert templ == {"keyboard": "handwired/pytest/has_template", "documentation": "This file is a keymap.json file for handwired/pytest/has_template", "keymap": "default", "layout": "LAYOUT", "layers": [["KC_A"]]} +def test_generate_json_pytest_basic(): +    templ = qmk.keymap.generate_json('default', 'handwired/pytest/basic', 'LAYOUT', [['KC_A']]) +    assert templ == {"keyboard": "handwired/pytest/basic", "keymap": "default", "layout": "LAYOUT", "layers": [["KC_A"]]}  def test_parse_keymap_c(): | 
