Browse Source

build an info.json from KLE

Zach White 5 years ago
parent
commit
0f8b34771d
3 changed files with 124 additions and 44 deletions
  1. 77 34
      lib/python/qmk/cli/kle2json.py
  2. 36 10
      lib/python/qmk/converter.py
  3. 11 0
      lib/python/qmk/info_json_encoder.py

+ 77 - 34
lib/python/qmk/cli/kle2json.py

@@ -7,31 +7,78 @@ from pathlib import Path
 from milc import cli
 from kle2xy import KLE2xy
 
+import qmk.path
 from qmk.converter import kle2qmk
+from qmk.decorators import automagic_keyboard
+from qmk.info import info_json
 from qmk.info_json_encoder import InfoJSONEncoder
 
 
-@cli.argument('filename', help='The KLE raw txt to convert')
-@cli.argument('-f', '--force', action='store_true', help='Flag to overwrite current info.json')
-@cli.subcommand('Convert a KLE layout to a Configurator JSON', hidden=False if cli.config.user.developer else True)
+@cli.argument('kle', arg_only=True, help='A file or KLE id to convert')
+@cli.argument('--vid', arg_only=True, default='0x03A8', help='USB VID.')
+@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):
     """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(cli.args.filename)
-    # Otherwise assume it is a file name
+    """
+    file_path = Path(os.environ['ORIG_CWD'], cli.args.kle)
+
+    # Find our KLE text
+    if file_path.exists():
+        raw_code = file_path.open().read()
+
     else:
-        file_path = Path(os.environ['ORIG_CWD'], cli.args.filename)
-    # Check for valid file_path for more graceful failure
-    if not file_path.exists():
-        cli.log.error('File {fg_cyan}%s{style_reset_all} was not found.', file_path)
-        return False
-    out_path = file_path.parent
-    raw_code = file_path.open().read()
-    # Check if info.json exists, allow overwrite with force
-    if Path(out_path, "info.json").exists() and not cli.args.force:
-        cli.log.error('File {fg_cyan}%s/info.json{style_reset_all} already exists, use -f or --force to overwrite.', out_path)
+        if cli.args.kle.startswith('http') and '#' in cli.args.kle:
+            kle_path = cli.args.kle.split('#', 1)[1]
+            if 'gists' not in kle_path:
+                cli.log.error('Invalid KLE url: {fg_cyan}%s', cli.args.kle)
+                return False
+            else:
+                print('FIXME: fetch gist')
+                return False
+        else:
+            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
+
+    # 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:
         # Convert KLE raw to x/y coordinates (using kle2xy package from skullydazed)
         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.exception(e)
         return False
-    keyboard = {
-        'keyboard_name': kle.name,
-        'url': '',
-        'maintainer': 'qmk',
-        'width': kle.columns,
-        'height': kle.rows,
-        'layouts': {
-            'LAYOUT': {
-                'layout': kle2qmk(kle)
-            }
-        },
-    }
 
-    # Write our info.json
-    keyboard = json.dumps(keyboard, indent=4, separators=(', ', ': '), sort_keys=False, cls=InfoJSONEncoder)
-    info_json_file = out_path / 'info.json'
+    if 'layouts' not in kb_info_json:
+        kb_info_json['layouts'] = {}
+
+    if cli.args.layout not in kb_info_json['layouts']:
+        kb_info_json['layouts'][cli.args.layout] = {}
 
-    info_json_file.write_text(keyboard)
-    cli.log.info('Wrote out {fg_cyan}%s/info.json', out_path)
+    kb_info_json['layouts'][cli.args.layout]['layout'] = kle2qmk(kle)
+
+    # Write our info.json
+    keyboard_dir = qmk.path.keyboard(cli.config.kle2json.keyboard)
+    keyboard_dir.mkdir(exist_ok=True, parents=True)
+    info_json_file = keyboard_dir / 'info.json'
+    json.dump(kb_info_json, info_json_file.open('w'), indent=4, separators=(', ', ': '), sort_keys=False, cls=InfoJSONEncoder)
+    cli.log.info('Wrote file %s', info_json_file)

+ 36 - 10
lib/python/qmk/converter.py

@@ -1,33 +1,59 @@
 """Functions to convert to and from QMK formats
 """
-from collections import OrderedDict
+from pprint import pprint
+
+from milc import cli
 
 
 def kle2qmk(kle):
     """Convert a KLE layout to QMK's layout format.
     """
     layout = []
+    layout_alternatives = {}
+    top_left_corner = None
 
+    # Iterate through the KLE classifying keys by layout
     for row in kle:
         for key in row:
             if key['decal']:
                 continue
 
-            qmk_key = OrderedDict(
-                label="",
-                x=key['column'],
-                y=key['row'],
-            )
+            if key['label_style'] in [0, 4]:
+                matrix, _, _, alt_layout, layout_name, _, keycode = key['name'].split('\n')
+            else:
+                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:
                 qmk_key['w'] = key['width']
             if key['height'] != 1:
                 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)
 
+    # 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

+ 11 - 0
lib/python/qmk/info_json_encoder.py

@@ -17,6 +17,17 @@ class InfoJSONEncoder(json.JSONEncoder):
         if not self.indent:
             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):
         """Encode JSON objects for QMK.
         """