build an info.json from KLE

This commit is contained in:
Zach White 2021-01-02 17:27:44 -08:00
parent 34446b79d7
commit 0f8b34771d
3 changed files with 124 additions and 44 deletions

View file

@ -7,31 +7,78 @@ from pathlib import Path
from milc import cli from milc import cli
from kle2xy import KLE2xy from kle2xy import KLE2xy
import qmk.path
from qmk.converter import kle2qmk from qmk.converter import kle2qmk
from qmk.decorators import automagic_keyboard
from qmk.info import info_json
from qmk.info_json_encoder import InfoJSONEncoder from qmk.info_json_encoder import InfoJSONEncoder
@cli.argument('filename', help='The KLE raw txt to convert') @cli.argument('kle', arg_only=True, help='A file or KLE id to convert')
@cli.argument('-f', '--force', action='store_true', help='Flag to overwrite current info.json') @cli.argument('--vid', arg_only=True, default='0x03A8', help='USB VID.')
@cli.subcommand('Convert a KLE layout to a Configurator JSON', hidden=False if cli.config.user.developer else True) @cli.argument('--pid', arg_only=True, default='0x0000', help='USB PID.')
@cli.argument('-m', '--manufacturer', arg_only=True, default='', help='Manufacturer of the keyboard.')
@cli.argument('-l', '--layout', arg_only=True, default='LAYOUT', help='The LAYOUT name this KLE represents.')
@cli.argument('-kb', '--keyboard', help='The folder for the keyboard.')
@cli.argument('-d', '--diode', arg_only=True, default='COL2ROW', help='The diode direction for the keyboard. (COL2ROW, ROW2COL)')
@cli.subcommand('Use a KLE layout to build info.json and a default keymap', hidden=False if cli.config.user.developer else True)
@automagic_keyboard
def kle2json(cli): def kle2json(cli):
"""Convert a KLE layout to QMK's layout format. """Convert a KLE layout to QMK's layout format.
""" # If filename is a path """
if cli.args.filename.startswith("/") or cli.args.filename.startswith("./"): file_path = Path(os.environ['ORIG_CWD'], cli.args.kle)
file_path = Path(cli.args.filename)
# Otherwise assume it is a file name # Find our KLE text
if file_path.exists():
raw_code = file_path.open().read()
else: else:
file_path = Path(os.environ['ORIG_CWD'], cli.args.filename) if cli.args.kle.startswith('http') and '#' in cli.args.kle:
# Check for valid file_path for more graceful failure kle_path = cli.args.kle.split('#', 1)[1]
if not file_path.exists(): if 'gists' not in kle_path:
cli.log.error('File {fg_cyan}%s{style_reset_all} was not found.', file_path) cli.log.error('Invalid KLE url: {fg_cyan}%s', cli.args.kle)
return False return False
out_path = file_path.parent else:
raw_code = file_path.open().read() print('FIXME: fetch gist')
# Check if info.json exists, allow overwrite with force return False
if Path(out_path, "info.json").exists() and not cli.args.force: else:
cli.log.error('File {fg_cyan}%s/info.json{style_reset_all} already exists, use -f or --force to overwrite.', out_path) cli.log.error('File {fg_cyan}%s{style_reset_all} was not found.', file_path)
return False
# Make sure the user supplied a keyboard
if not cli.config.kle2json.keyboard:
cli.log.error('You must pass --keyboard or be in a keyboard directory!')
cli.print_usage()
return False return False
# Check for an existing info.json
if qmk.path.is_keyboard(cli.config.kle2json.keyboard):
kb_info_json = info_json(cli.config.kle2json.keyboard)
else:
kb_info_json = {
"manufacturer": cli.args.manufacturer,
"keyboard_name": cli.config.kle2json.keyboard,
"maintainer": "",
"diode_direction": cli.args.diode,
"features": {
"console": True,
"extrakey": True,
"mousekey": True,
"nkro": True
},
"matrix_pins": {
"cols": [],
"rows": [],
},
"usb": {
"device_ver": "0x0001",
"pid": cli.args.pid,
"vid": cli.args.vid
},
"layouts": {},
}
# Build and merge in the new layout
try: try:
# Convert KLE raw to x/y coordinates (using kle2xy package from skullydazed) # Convert KLE raw to x/y coordinates (using kle2xy package from skullydazed)
kle = KLE2xy(raw_code) kle = KLE2xy(raw_code)
@ -39,22 +86,18 @@ def kle2json(cli):
cli.log.error('Could not parse KLE raw data: %s', raw_code) cli.log.error('Could not parse KLE raw data: %s', raw_code)
cli.log.exception(e) cli.log.exception(e)
return False return False
keyboard = {
'keyboard_name': kle.name, if 'layouts' not in kb_info_json:
'url': '', kb_info_json['layouts'] = {}
'maintainer': 'qmk',
'width': kle.columns, if cli.args.layout not in kb_info_json['layouts']:
'height': kle.rows, kb_info_json['layouts'][cli.args.layout] = {}
'layouts': {
'LAYOUT': { kb_info_json['layouts'][cli.args.layout]['layout'] = kle2qmk(kle)
'layout': kle2qmk(kle)
}
},
}
# Write our info.json # Write our info.json
keyboard = json.dumps(keyboard, indent=4, separators=(', ', ': '), sort_keys=False, cls=InfoJSONEncoder) keyboard_dir = qmk.path.keyboard(cli.config.kle2json.keyboard)
info_json_file = out_path / 'info.json' keyboard_dir.mkdir(exist_ok=True, parents=True)
info_json_file = keyboard_dir / 'info.json'
info_json_file.write_text(keyboard) json.dump(kb_info_json, info_json_file.open('w'), indent=4, separators=(', ', ': '), sort_keys=False, cls=InfoJSONEncoder)
cli.log.info('Wrote out {fg_cyan}%s/info.json', out_path) cli.log.info('Wrote file %s', info_json_file)

View file

@ -1,33 +1,59 @@
"""Functions to convert to and from QMK formats """Functions to convert to and from QMK formats
""" """
from collections import OrderedDict from pprint import pprint
from milc import cli
def kle2qmk(kle): def kle2qmk(kle):
"""Convert a KLE layout to QMK's layout format. """Convert a KLE layout to QMK's layout format.
""" """
layout = [] layout = []
layout_alternatives = {}
top_left_corner = None
# Iterate through the KLE classifying keys by layout
for row in kle: for row in kle:
for key in row: for key in row:
if key['decal']: if key['decal']:
continue continue
qmk_key = OrderedDict( if key['label_style'] in [0, 4]:
label="", matrix, _, _, alt_layout, layout_name, _, keycode = key['name'].split('\n')
x=key['column'], else:
y=key['row'], cli.log.error('Unknown label style: %s', key['label_style'])
) continue
matrix = list(map(int, matrix.split(',', 1)))
qmk_key = {
'label': keycode,
'x': key['column'],
'y': key['row'],
'matrix': matrix,
}
if not top_left_corner and (not alt_layout or alt_layout.endswith(',0')):
top_left_corner = key['column'], key['row']
# Figure out what layout this key is part of
# FIXME(skullydazed): In the future this will populate `layout_options` in info.json
if alt_layout:
alt_group, layout_index = map(int, alt_layout.split(',', 1))
if layout_index != 0:
continue
# Set the key size
if key['width'] != 1: if key['width'] != 1:
qmk_key['w'] = key['width'] qmk_key['w'] = key['width']
if key['height'] != 1: if key['height'] != 1:
qmk_key['h'] = key['height'] qmk_key['h'] = key['height']
if 'name' in key and key['name']:
qmk_key['label'] = key['name'].split('\n', 1)[0]
else:
del (qmk_key['label'])
layout.append(qmk_key) layout.append(qmk_key)
# Adjust the keys to account for the top-left corner
for key in layout:
key['x'] -= top_left_corner[0]
key['y'] -= top_left_corner[1]
return layout return layout

View file

@ -17,6 +17,17 @@ class InfoJSONEncoder(json.JSONEncoder):
if not self.indent: if not self.indent:
self.indent = 4 self.indent = 4
def default(self, obj):
"""Fix certain objects that don't encode.
"""
if isinstance(obj, Decimal):
if obj == int(obj):
return int(obj)
return float(obj)
return json.JSONEncoder.default(self, obj)
def encode(self, obj): def encode(self, obj):
"""Encode JSON objects for QMK. """Encode JSON objects for QMK.
""" """