forked from mirrors/qmk_userspace
		
	Add cli convert subcommand, from raw KLE to JSON (#6898)
* Add initial pass at KLE convert * Add cli log on convert * Move kle2xy, add absolute filepath arg support * Add overwrite flag, and context sensitive conversion * Update docs/cli.md * Fix converter.py typo * Add convert unit test * Rename to kle2qmk * Rename subcommand * Rename subcommand to kle2json * Change tests to cover rename * Rename in __init__.py * Update CLI docs with new subcommand name * Fix from suggestions in PR #6898 * Help with cases of case sensitivity * Update cli.md * Use angle brackets to indicate required option * Make the output text more accurate
This commit is contained in:
		
					parent
					
						
							
								00fb1bd1f0
							
						
					
				
			
			
				commit
				
					
						7329c2d02d
					
				
			
		
					 8 changed files with 298 additions and 0 deletions
				
			
		
							
								
								
									
										22
									
								
								docs/cli.md
									
										
									
									
									
								
							
							
						
						
									
										22
									
								
								docs/cli.md
									
										
									
									
									
								
							| 
						 | 
					@ -135,6 +135,28 @@ Creates a keymap.c from a QMK Configurator export.
 | 
				
			||||||
qmk json-keymap [-o OUTPUT] filename
 | 
					qmk json-keymap [-o OUTPUT] filename
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## `qmk kle2json`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This command allows you to convert from raw KLE data to QMK Configurator JSON. It accepts either an absolute file path, or a file name in the current directory. By default it will not overwrite `info.json` if it is already present. Use the `-f` or `--force` flag to overwrite.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Usage**:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					qmk kle2json [-f] <filename>
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Examples**:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					$ qmk kle2json kle.txt 
 | 
				
			||||||
 | 
					☒ File info.json already exists, use -f or --force to overwrite.
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					$ qmk kle2json -f kle.txt -f
 | 
				
			||||||
 | 
					Ψ Wrote out to info.json
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## `qmk list-keyboards`
 | 
					## `qmk list-keyboards`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This command lists all the keyboards currently defined in `qmk_firmware`
 | 
					This command lists all the keyboards currently defined in `qmk_firmware`
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										155
									
								
								lib/python/kle2xy.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								lib/python/kle2xy.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,155 @@
 | 
				
			||||||
 | 
					""" Original code from https://github.com/skullydazed/kle2xy
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import hjson
 | 
				
			||||||
 | 
					from decimal import Decimal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class KLE2xy(list):
 | 
				
			||||||
 | 
					    """Abstract interface for interacting with a KLE layout.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def __init__(self, layout=None, name='', invert_y=True):
 | 
				
			||||||
 | 
					        super(KLE2xy, self).__init__()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.name = name
 | 
				
			||||||
 | 
					        self.invert_y = invert_y
 | 
				
			||||||
 | 
					        self.key_width = Decimal('19.05')
 | 
				
			||||||
 | 
					        self.key_skel = {
 | 
				
			||||||
 | 
					            'decal': False,
 | 
				
			||||||
 | 
					            'border_color': 'none',
 | 
				
			||||||
 | 
					            'keycap_profile': '',
 | 
				
			||||||
 | 
					            'keycap_color': 'grey',
 | 
				
			||||||
 | 
					            'label_color': 'black',
 | 
				
			||||||
 | 
					            'label_size': 3,
 | 
				
			||||||
 | 
					            'label_style': 4,
 | 
				
			||||||
 | 
					            'width': Decimal('1'), 'height': Decimal('1'),
 | 
				
			||||||
 | 
					            'x': Decimal('0'), 'y': Decimal('0')
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        self.rows = Decimal(0)
 | 
				
			||||||
 | 
					        self.columns = Decimal(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if layout:
 | 
				
			||||||
 | 
					            self.parse_layout(layout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def width(self):
 | 
				
			||||||
 | 
					        """Returns the width of the keyboard plate.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return (Decimal(self.columns) * self.key_width) + self.key_width/2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def height(self):
 | 
				
			||||||
 | 
					        """Returns the height of the keyboard plate.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return (self.rows * self.key_width) + self.key_width/2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def size(self):
 | 
				
			||||||
 | 
					        """Returns the size of the keyboard plate.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return (self.width, self.height)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def attrs(self, properties):
 | 
				
			||||||
 | 
					        """Parse the keyboard properties dictionary.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        # FIXME: Store more than just the keyboard name.
 | 
				
			||||||
 | 
					        if 'name' in properties:
 | 
				
			||||||
 | 
					            self.name = properties['name']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def parse_layout(self, layout):
 | 
				
			||||||
 | 
					        # Wrap this in a dictionary so hjson will parse KLE raw data
 | 
				
			||||||
 | 
					        layout = '{"layout": [' + layout + ']}'
 | 
				
			||||||
 | 
					        layout = hjson.loads(layout)['layout']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Initialize our state machine
 | 
				
			||||||
 | 
					        current_key = self.key_skel.copy()
 | 
				
			||||||
 | 
					        current_row = Decimal(0)
 | 
				
			||||||
 | 
					        current_col = Decimal(0)
 | 
				
			||||||
 | 
					        current_x = 0
 | 
				
			||||||
 | 
					        current_y = self.key_width / 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if isinstance(layout[0], dict):
 | 
				
			||||||
 | 
					            self.attrs(layout[0])
 | 
				
			||||||
 | 
					            layout = layout[1:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for row_num, row in enumerate(layout):
 | 
				
			||||||
 | 
					            self.append([])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Process the current row
 | 
				
			||||||
 | 
					            for key in row:
 | 
				
			||||||
 | 
					                if isinstance(key, dict):
 | 
				
			||||||
 | 
					                    if 'w' in key and key['w'] != Decimal(1):
 | 
				
			||||||
 | 
					                        current_key['width'] = Decimal(key['w'])
 | 
				
			||||||
 | 
					                    if 'w2' in key and 'h2' in key and key['w2'] == 1.5 and key['h2'] == 1:
 | 
				
			||||||
 | 
					                        # FIXME: ISO Key uses these params: {x:0.25,w:1.25,h:2,w2:1.5,h2:1,x2:-0.25}
 | 
				
			||||||
 | 
					                        current_key['isoenter'] = True
 | 
				
			||||||
 | 
					                    if 'h' in key and key['h'] != Decimal(1):
 | 
				
			||||||
 | 
					                        current_key['height'] = Decimal(key['h'])
 | 
				
			||||||
 | 
					                    if 'a' in key:
 | 
				
			||||||
 | 
					                        current_key['label_style'] = self.key_skel['label_style'] = int(key['a'])
 | 
				
			||||||
 | 
					                        if current_key['label_style'] < 0:
 | 
				
			||||||
 | 
					                            current_key['label_style'] = 0
 | 
				
			||||||
 | 
					                        elif current_key['label_style'] > 9:
 | 
				
			||||||
 | 
					                            current_key['label_style'] = 9
 | 
				
			||||||
 | 
					                    if 'f' in key:
 | 
				
			||||||
 | 
					                        font_size = int(key['f'])
 | 
				
			||||||
 | 
					                        if font_size > 9:
 | 
				
			||||||
 | 
					                            font_size = 9
 | 
				
			||||||
 | 
					                        elif font_size < 1:
 | 
				
			||||||
 | 
					                            font_size = 1
 | 
				
			||||||
 | 
					                        current_key['label_size'] = self.key_skel['label_size'] = font_size
 | 
				
			||||||
 | 
					                    if 'p' in key:
 | 
				
			||||||
 | 
					                        current_key['keycap_profile'] = self.key_skel['keycap_profile'] = key['p']
 | 
				
			||||||
 | 
					                    if 'c' in key:
 | 
				
			||||||
 | 
					                        current_key['keycap_color'] = self.key_skel['keycap_color'] = key['c']
 | 
				
			||||||
 | 
					                    if 't' in key:
 | 
				
			||||||
 | 
					                        # FIXME: Need to do better validation, plus figure out how to support multiple colors
 | 
				
			||||||
 | 
					                        if '\n' in key['t']:
 | 
				
			||||||
 | 
					                            key['t'] = key['t'].split('\n')[0]
 | 
				
			||||||
 | 
					                        if key['t'] == "0":
 | 
				
			||||||
 | 
					                            key['t'] = "#000000"
 | 
				
			||||||
 | 
					                        current_key['label_color'] = self.key_skel['label_color'] = key['t']
 | 
				
			||||||
 | 
					                    if 'x' in key:
 | 
				
			||||||
 | 
					                        current_col += Decimal(key['x'])
 | 
				
			||||||
 | 
					                        current_x += Decimal(key['x']) * self.key_width
 | 
				
			||||||
 | 
					                    if 'y' in key:
 | 
				
			||||||
 | 
					                        current_row += Decimal(key['y'])
 | 
				
			||||||
 | 
					                        current_y += Decimal(key['y']) * self.key_width
 | 
				
			||||||
 | 
					                    if 'd' in key:
 | 
				
			||||||
 | 
					                        current_key['decal'] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    current_key['name'] = key
 | 
				
			||||||
 | 
					                    current_key['row'] = current_row
 | 
				
			||||||
 | 
					                    current_key['column'] = current_col
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # Determine the X center
 | 
				
			||||||
 | 
					                    x_center = (current_key['width'] * self.key_width) / 2
 | 
				
			||||||
 | 
					                    current_x += x_center
 | 
				
			||||||
 | 
					                    current_key['x'] = current_x
 | 
				
			||||||
 | 
					                    current_x += x_center
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # Determine the Y center
 | 
				
			||||||
 | 
					                    y_center = (current_key['height'] * self.key_width) / 2
 | 
				
			||||||
 | 
					                    y_offset = y_center - (self.key_width / 2)
 | 
				
			||||||
 | 
					                    current_key['y'] = (current_y + y_offset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # Tend to our row/col count
 | 
				
			||||||
 | 
					                    current_col += current_key['width']
 | 
				
			||||||
 | 
					                    if current_col > self.columns:
 | 
				
			||||||
 | 
					                        self.columns = current_col
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # Invert the y-axis if neccesary
 | 
				
			||||||
 | 
					                    if self.invert_y:
 | 
				
			||||||
 | 
					                        current_key['y'] = -current_key['y']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # Store this key
 | 
				
			||||||
 | 
					                    self[-1].append(current_key)
 | 
				
			||||||
 | 
					                    current_key = self.key_skel.copy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Move to the next row
 | 
				
			||||||
 | 
					            current_x = 0
 | 
				
			||||||
 | 
					            current_y += self.key_width
 | 
				
			||||||
 | 
					            current_col = Decimal(0)
 | 
				
			||||||
 | 
					            current_row += Decimal(1)
 | 
				
			||||||
 | 
					            if current_row > self.rows:
 | 
				
			||||||
 | 
					                self.rows = Decimal(current_row)
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@ from . import doctor
 | 
				
			||||||
from . import hello
 | 
					from . import hello
 | 
				
			||||||
from . import json
 | 
					from . import json
 | 
				
			||||||
from . import list
 | 
					from . import list
 | 
				
			||||||
 | 
					from . import kle2json
 | 
				
			||||||
from . import new
 | 
					from . import new
 | 
				
			||||||
from . import pyformat
 | 
					from . import pyformat
 | 
				
			||||||
from . import pytest
 | 
					from . import pytest
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										79
									
								
								lib/python/qmk/cli/kle2json.py
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										79
									
								
								lib/python/qmk/cli/kle2json.py
									
										
									
									
									
										Executable file
									
								
							| 
						 | 
					@ -0,0 +1,79 @@
 | 
				
			||||||
 | 
					"""Convert raw KLE to JSON
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					from argparse import FileType
 | 
				
			||||||
 | 
					from decimal import Decimal
 | 
				
			||||||
 | 
					from collections import OrderedDict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from milc import cli
 | 
				
			||||||
 | 
					from kle2xy import KLE2xy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from qmk.converter import kle2qmk
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CustomJSONEncoder(json.JSONEncoder):
 | 
				
			||||||
 | 
					    def default(self, obj):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if isinstance(obj, Decimal):
 | 
				
			||||||
 | 
					                if obj % 2 in (Decimal(0), Decimal(1)):
 | 
				
			||||||
 | 
					                    return int(obj)
 | 
				
			||||||
 | 
					                return float(obj)
 | 
				
			||||||
 | 
					        except TypeError:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        return JSONEncoder.default(self, obj)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@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')
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					    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():
 | 
				
			||||||
 | 
					        return cli.log.error('File {fg_cyan}%s{style_reset_all} was not found.', str(file_path))
 | 
				
			||||||
 | 
					    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.', str(out_path))
 | 
				
			||||||
 | 
					        return False;
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        # Convert KLE raw to x/y coordinates (using kle2xy package from skullydazed)
 | 
				
			||||||
 | 
					        kle = KLE2xy(raw_code)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        cli.log.error('Could not parse KLE raw data: %s', raw_code)
 | 
				
			||||||
 | 
					        cli.log.exception(e)
 | 
				
			||||||
 | 
					        # FIXME: This should be better
 | 
				
			||||||
 | 
					        return cli.log.error('Could not parse KLE raw data.')
 | 
				
			||||||
 | 
					    keyboard = OrderedDict(
 | 
				
			||||||
 | 
					        keyboard_name=kle.name,
 | 
				
			||||||
 | 
					        url='',
 | 
				
			||||||
 | 
					        maintainer='qmk',
 | 
				
			||||||
 | 
					        width=kle.columns,
 | 
				
			||||||
 | 
					        height=kle.rows,
 | 
				
			||||||
 | 
					        layouts={'LAYOUT': {
 | 
				
			||||||
 | 
					            'layout': 'LAYOUT_JSON_HERE'
 | 
				
			||||||
 | 
					        }},
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    # Initialize keyboard with json encoded from ordered dict
 | 
				
			||||||
 | 
					    keyboard = json.dumps(keyboard, indent=4, separators=(
 | 
				
			||||||
 | 
					        ', ', ': '), sort_keys=False, cls=CustomJSONEncoder)
 | 
				
			||||||
 | 
					    # Initialize layout with kle2qmk from converter module
 | 
				
			||||||
 | 
					    layout = json.dumps(kle2qmk(kle), separators=(
 | 
				
			||||||
 | 
					        ', ', ':'), cls=CustomJSONEncoder)
 | 
				
			||||||
 | 
					    # Replace layout in keyboard json
 | 
				
			||||||
 | 
					    keyboard = keyboard.replace('"LAYOUT_JSON_HERE"', layout)
 | 
				
			||||||
 | 
					    # Write our info.json
 | 
				
			||||||
 | 
					    file = open(str(out_path) + "/info.json", "w")
 | 
				
			||||||
 | 
					    file.write(keyboard)
 | 
				
			||||||
 | 
					    file.close()
 | 
				
			||||||
 | 
					    cli.log.info('Wrote out {fg_cyan}%s/info.json', str(out_path))
 | 
				
			||||||
							
								
								
									
										33
									
								
								lib/python/qmk/converter.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								lib/python/qmk/converter.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,33 @@
 | 
				
			||||||
 | 
					"""Functions to convert to and from QMK formats
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					from collections import OrderedDict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def kle2qmk(kle):
 | 
				
			||||||
 | 
					    """Convert a KLE layout to QMK's layout format.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    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['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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return layout
 | 
				
			||||||
							
								
								
									
										5
									
								
								lib/python/qmk/tests/kle.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								lib/python/qmk/tests/kle.txt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					["¬\n`","!\n1","\"\n2","£\n3","$\n4","%\n5","^\n6","&\n7","*\n8","(\n9",")\n0","_\n-","+\n=",{w:2},"Backspace"],
 | 
				
			||||||
 | 
					[{w:1.5},"Tab","Q","W","E","R","T","Y","U","I","O","P","{\n[","}\n]",{x:0.25,w:1.25,h:2,w2:1.5,h2:1,x2:-0.25},"Enter"],
 | 
				
			||||||
 | 
					[{w:1.75},"Caps Lock","A","S","D","F","G","H","J","K","L",":\n;","@\n'","~\n#"],
 | 
				
			||||||
 | 
					[{w:1.25},"Shift","|\n\\","Z","X","C","V","B","N","M","<\n,",">\n.","?\n/",{w:2.75},"Shift"],
 | 
				
			||||||
 | 
					[{w:1.25},"Ctrl",{w:1.25},"Win",{w:1.25},"Alt",{a:7,w:6.25},"",{a:4,w:1.25},"AltGr",{w:1.25},"Win",{w:1.25},"Menu",{w:1.25},"Ctrl"]
 | 
				
			||||||
| 
						 | 
					@ -19,6 +19,8 @@ def test_config():
 | 
				
			||||||
    assert result.returncode == 0
 | 
					    assert result.returncode == 0
 | 
				
			||||||
    assert 'general.color' in result.stdout
 | 
					    assert 'general.color' in result.stdout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_kle2json():
 | 
				
			||||||
 | 
					    assert check_subcommand('kle2json', 'kle.txt', '-f').returncode == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_doctor():
 | 
					def test_doctor():
 | 
				
			||||||
    result = check_subcommand('doctor')
 | 
					    result = check_subcommand('doctor')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,3 +3,4 @@
 | 
				
			||||||
appdirs
 | 
					appdirs
 | 
				
			||||||
argcomplete
 | 
					argcomplete
 | 
				
			||||||
colorama
 | 
					colorama
 | 
				
			||||||
 | 
					hjson
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue