diff options
| author | Erovia <[email protected]> | 2022-02-28 20:02:39 +0000 | 
|---|---|---|
| committer | GitHub <[email protected]> | 2022-02-28 20:02:39 +0000 | 
| commit | fbfd5312b995a32af690c183cad0dc988f695e89 (patch) | |
| tree | bf241a475ec51b79d6427ac422f197b6d3720661 /lib/python | |
| parent | 779c7debcfff1a4a3ad578a0c12bdd50cba11039 (diff) | |
| download | qmk_firmware-fbfd5312b995a32af690c183cad0dc988f695e89.tar.gz qmk_firmware-fbfd5312b995a32af690c183cad0dc988f695e89.zip | |
CLI: Validate JSON keymap input (#16261)
* Fix schema validator
It should use the passed schema.
* Add required attributes to keymap schema
* Rework subcommands to validate the JSON keymaps
The 'compile', 'flash' and 'json2c' subcommands were reworked to add
JSON keymap validation so error is reported for non-JSON and
non-compliant-JSON inputs.
* Fix required fields in keymap schema
* Add tests
* Fix compiling keymaps directly from keymap directory
* Schema should not require version for now.
Diffstat (limited to 'lib/python')
| -rwxr-xr-x | lib/python/qmk/cli/json2c.py | 13 | ||||
| -rw-r--r-- | lib/python/qmk/commands.py | 19 | ||||
| -rw-r--r-- | lib/python/qmk/json_schema.py | 8 | ||||
| -rw-r--r-- | lib/python/qmk/path.py | 6 | ||||
| -rw-r--r-- | lib/python/qmk/tests/test_cli_commands.py | 12 | 
5 files changed, 41 insertions, 17 deletions
| diff --git a/lib/python/qmk/cli/json2c.py b/lib/python/qmk/cli/json2c.py index ae8248e6b7..2873a9bfd3 100755 --- a/lib/python/qmk/cli/json2c.py +++ b/lib/python/qmk/cli/json2c.py @@ -1,12 +1,11 @@  """Generate a keymap.c from a configurator export.  """ -import json -  from argcomplete.completers import FilesCompleter  from milc import cli  import qmk.keymap  import qmk.path +from qmk.commands import parse_configurator_json  @cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') @@ -19,14 +18,8 @@ def json2c(cli):      This command uses the `qmk.keymap` module to generate a keymap.c from a configurator export. The generated keymap is written to stdout, or to a file if -o is provided.      """ -    try: -        # Parse the configurator from json file (or stdin) -        user_keymap = json.load(cli.args.filename) - -    except json.decoder.JSONDecodeError as ex: -        cli.log.error('The JSON input does not appear to be valid.') -        cli.log.error(ex) -        return False +    # Parse the configurator from json file (or stdin) +    user_keymap = parse_configurator_json(cli.args.filename)      # Environment processing      if cli.args.output and cli.args.output.name == '-': diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py index 275cd72e5c..e38f17156a 100644 --- a/lib/python/qmk/commands.py +++ b/lib/python/qmk/commands.py @@ -1,6 +1,5 @@  """Helper functions for commands.  """ -import json  import os  import sys  import shutil @@ -9,10 +8,11 @@ from subprocess import DEVNULL  from time import strftime  from milc import cli +import jsonschema  import qmk.keymap  from qmk.constants import QMK_FIRMWARE, KEYBOARD_OUTPUT_PREFIX -from qmk.json_schema import json_load +from qmk.json_schema import json_load, validate  time_fmt = '%Y-%m-%d-%H:%M:%S' @@ -185,6 +185,10 @@ def compile_configurator_json(user_keymap, bootloader=None, parallel=1, **env_va          A command to run to compile and flash the C file.      """ +    # In case the user passes a keymap.json from a keymap directory directly to the CLI. +    # e.g.: qmk compile - < keyboards/clueboard/california/keymaps/default/keymap.json +    user_keymap["keymap"] = user_keymap.get("keymap", "default_json") +      # Write the keymap.c file      keyboard_filesafe = user_keymap['keyboard'].replace('/', '_')      target = f'{keyboard_filesafe}_{user_keymap["keymap"]}' @@ -248,8 +252,15 @@ def compile_configurator_json(user_keymap, bootloader=None, parallel=1, **env_va  def parse_configurator_json(configurator_file):      """Open and parse a configurator json export      """ -    # FIXME(skullydazed/anyone): Add validation here -    user_keymap = json.load(configurator_file) +    user_keymap = json_load(configurator_file) +    # Validate against the jsonschema +    try: +        validate(user_keymap, 'qmk.keymap.v1') + +    except jsonschema.ValidationError as e: +        cli.log.error(f'Invalid JSON keymap: {configurator_file} : {e.message}') +        exit(1) +      orig_keyboard = user_keymap['keyboard']      aliases = json_load(Path('data/mappings/keyboard_aliases.json')) diff --git a/lib/python/qmk/json_schema.py b/lib/python/qmk/json_schema.py index ffc7c6bcd1..2b48782fbb 100644 --- a/lib/python/qmk/json_schema.py +++ b/lib/python/qmk/json_schema.py @@ -16,7 +16,11 @@ def json_load(json_file):      Note: file must be a Path object.      """      try: -        return hjson.load(json_file.open(encoding='utf-8')) +        # Get the IO Stream for Path objects +        # Not necessary if the data is provided via stdin +        if isinstance(json_file, Path): +            json_file = json_file.open(encoding='utf-8') +        return hjson.load(json_file)      except (json.decoder.JSONDecodeError, hjson.HjsonDecodeError) as e:          cli.log.error('Invalid JSON encountered attempting to load {fg_cyan}%s{fg_reset}:\n\t{fg_red}%s', json_file, e) @@ -62,7 +66,7 @@ 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) +    resolver = jsonschema.RefResolver.from_schema(schema_store[schema], store=schema_store)      return jsonschema.Draft7Validator(schema_store[schema], resolver=resolver).validate diff --git a/lib/python/qmk/path.py b/lib/python/qmk/path.py index dfb8371f84..9b94abbc12 100644 --- a/lib/python/qmk/path.py +++ b/lib/python/qmk/path.py @@ -70,9 +70,13 @@ def normpath(path):  class FileType(argparse.FileType): +    def __init__(self, encoding='UTF-8'): +        # Use UTF8 by default for stdin +        return super().__init__(encoding=encoding) +      def __call__(self, string):          """normalize and check exists              otherwise magic strings like '-' for stdin resolve to bad paths          """          norm = normpath(string) -        return super().__call__(norm if norm.exists() else string) +        return norm if norm.exists() else super().__call__(string) diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index 54b143c64f..55e69175e6 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py @@ -156,6 +156,18 @@ def test_json2c_stdin():      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' +def test_json2c_wrong_json(): +    result = check_subcommand('json2c', 'keyboards/handwired/pytest/info.json') +    check_returncode(result, [1]) +    assert 'Invalid JSON keymap' in result.stdout + + +def test_json2c_no_json(): +    result = check_subcommand('json2c', 'keyboards/handwired/pytest/pytest.h') +    check_returncode(result, [1]) +    assert 'Invalid JSON encountered' in result.stdout + +  def test_info():      result = check_subcommand('info', '-kb', 'handwired/pytest/basic')      check_returncode(result) | 
