aboutsummaryrefslogtreecommitdiff
path: root/lib/python
diff options
context:
space:
mode:
authorskullydazed <skullydazed@users.noreply.github.com>2020-02-17 11:42:11 -0800
committerGitHub <noreply@github.com>2020-02-17 11:42:11 -0800
commitc66930445f7d5941eb847568288046d51f853786 (patch)
tree273f290ca81a27bbbe7bdbf90221c02ac11f42cd /lib/python
parent58724f8dcb9eccb1c132b8ddab422790ddd26be0 (diff)
downloadqmk_firmware-c66930445f7d5941eb847568288046d51f853786.tar.gz
qmk_firmware-c66930445f7d5941eb847568288046d51f853786.zip
Use pathlib everywhere we can (#7872)
* Use pathlib everywhere we can * Update lib/python/qmk/path.py Co-Authored-By: Erovia <Erovia@users.noreply.github.com> * Update lib/python/qmk/path.py Co-Authored-By: Erovia <Erovia@users.noreply.github.com> * Improvements based on @erovia's feedback * rework qmk compile and qmk flash to use pathlib * style * Remove the subcommand_name argument from find_keyboard_keymap() Co-authored-by: Erovia <Erovia@users.noreply.github.com>
Diffstat (limited to 'lib/python')
-rw-r--r--lib/python/milc.py6
-rwxr-xr-xlib/python/qmk/cli/compile.py99
-rw-r--r--lib/python/qmk/cli/flash.py34
-rwxr-xr-xlib/python/qmk/cli/json/keymap.py31
-rw-r--r--lib/python/qmk/cli/list/keyboards.py1
-rwxr-xr-xlib/python/qmk/cli/new/keymap.py26
-rw-r--r--lib/python/qmk/commands.py96
-rw-r--r--lib/python/qmk/constants.py9
-rw-r--r--lib/python/qmk/keymap.py18
-rw-r--r--lib/python/qmk/path.py57
-rw-r--r--lib/python/qmk/tests/test_qmk_path.py5
11 files changed, 213 insertions, 169 deletions
diff --git a/lib/python/milc.py b/lib/python/milc.py
index 949bb0252..83edfc7f5 100644
--- a/lib/python/milc.py
+++ b/lib/python/milc.py
@@ -273,7 +273,7 @@ class MILC(object):
273 self._inside_context_manager = False 273 self._inside_context_manager = False
274 self.ansi = ansi_colors 274 self.ansi = ansi_colors
275 self.arg_only = [] 275 self.arg_only = []
276 self.config = None 276 self.config = self.config_source = None
277 self.config_file = None 277 self.config_file = None
278 self.default_arguments = {} 278 self.default_arguments = {}
279 self.version = 'unknown' 279 self.version = 'unknown'
@@ -473,6 +473,7 @@ class MILC(object):
473 """ 473 """
474 self.acquire_lock() 474 self.acquire_lock()
475 self.config = Configuration() 475 self.config = Configuration()
476 self.config_source = Configuration()
476 self.config_file = self.find_config_file() 477 self.config_file = self.find_config_file()
477 478
478 if self.config_file and self.config_file.exists(): 479 if self.config_file and self.config_file.exists():
@@ -498,6 +499,7 @@ class MILC(object):
498 value = int(value) 499 value = int(value)
499 500
500 self.config[section][option] = value 501 self.config[section][option] = value
502 self.config_source[section][option] = 'config_file'
501 503
502 self.release_lock() 504 self.release_lock()
503 505
@@ -530,12 +532,14 @@ class MILC(object):
530 arg_value = getattr(self.args, argument) 532 arg_value = getattr(self.args, argument)
531 if arg_value is not None: 533 if arg_value is not None:
532 self.config[section][argument] = arg_value 534 self.config[section][argument] = arg_value
535 self.config_source[section][argument] = 'argument'
533 else: 536 else:
534 if argument not in self.config[entrypoint_name]: 537 if argument not in self.config[entrypoint_name]:
535 # Check if the argument exist for this section 538 # Check if the argument exist for this section
536 arg = getattr(self.args, argument) 539 arg = getattr(self.args, argument)
537 if arg is not None: 540 if arg is not None:
538 self.config[section][argument] = arg 541 self.config[section][argument] = arg
542 self.config_source[section][argument] = 'argument'
539 543
540 self.release_lock() 544 self.release_lock()
541 545
diff --git a/lib/python/qmk/cli/compile.py b/lib/python/qmk/cli/compile.py
index 826b969ef..3068c97d8 100755
--- a/lib/python/qmk/cli/compile.py
+++ b/lib/python/qmk/cli/compile.py
@@ -3,16 +3,12 @@
3You can compile a keymap already in the repo or using a QMK Configurator export. 3You can compile a keymap already in the repo or using a QMK Configurator export.
4""" 4"""
5import subprocess 5import subprocess
6import os
7from argparse import FileType 6from argparse import FileType
8 7
9from milc import cli 8from milc import cli
10from qmk.commands import create_make_command
11from qmk.commands import parse_configurator_json
12from qmk.commands import compile_configurator_json
13 9
14import qmk.keymap
15import qmk.path 10import qmk.path
11from qmk.commands import compile_configurator_json, create_make_command, find_keyboard_keymap, parse_configurator_json
16 12
17 13
18@cli.argument('filename', nargs='?', arg_only=True, type=FileType('r'), help='The configurator export to compile') 14@cli.argument('filename', nargs='?', arg_only=True, type=FileType('r'), help='The configurator export to compile')
@@ -24,99 +20,28 @@ def compile(cli):
24 20
25 If a Configurator export is supplied this command will create a new keymap, overwriting an existing keymap if one exists. 21 If a Configurator export is supplied this command will create a new keymap, overwriting an existing keymap if one exists.
26 22
27 FIXME(skullydazed): add code to check and warn if the keymap already exists 23 If a keyboard and keymap are provided this command will build a firmware based on that.
28
29 If --keyboard and --keymap are provided this command will build a firmware based on that.
30
31 """ 24 """
32 # Set CWD as directory command was issued from
33 cwd = os.environ['ORIG_CWD']
34 qmk_path = os.getcwd()
35 current_folder = os.path.basename(cwd)
36 # Initialize boolean to check for being in a keyboard directory and initialize keyboard string
37 in_keyboard = False
38 in_layout = False
39 keyboard = ""
40 keymap = ""
41 user_keymap = ""
42 user_keyboard = ""
43
44 # Set path for '/keyboards/' directory
45 keyboards_path = os.path.join(qmk_path, "keyboards")
46 layouts_path = os.path.join(qmk_path, "layouts")
47
48 # If below 'keyboards' and not in 'keyboards' or 'keymaps', get current keyboard name
49 if cwd.startswith(keyboards_path):
50 if current_folder != "keyboards" and current_folder != "keymaps":
51 if os.path.basename(os.path.abspath(os.path.join(cwd, ".."))) == "keymaps":
52 # If in a keymap folder, set relative path, get everything before /keymaps, and the keymap name
53 relative_path = cwd[len(keyboards_path):][1:]
54 keyboard = str(relative_path).split("/keymaps", 1)[0]
55 keymap = str(relative_path.rsplit("/", 1)[-1])
56 else:
57 keyboard = str(cwd[len(keyboards_path):])[1:]
58
59 in_keyboard = True
60
61 # If in layouts dir
62 if cwd.startswith(layouts_path):
63 if current_folder != "layouts":
64 in_layout = True
65
66 # If user keyboard/keymap or compile keyboard/keymap are supplied, assign those
67 if cli.config.compile.keyboard:
68 user_keyboard = cli.config.compile.keyboard
69 if cli.config.compile.keymap and not in_layout:
70 user_keymap = cli.config.compile.keymap
71
72 if cli.args.filename: 25 if cli.args.filename:
73 # Parse the configurator json 26 # If a configurator JSON was provided skip straight to compiling it
27 # FIXME(skullydazed): add code to check and warn if the keymap already exists when compiling a json keymap.
74 user_keymap = parse_configurator_json(cli.args.filename) 28 user_keymap = parse_configurator_json(cli.args.filename)
75
76 # Generate the keymap
77 keymap_path = qmk.path.keymap(user_keymap['keyboard']) 29 keymap_path = qmk.path.keymap(user_keymap['keyboard'])
78 cli.log.info('Creating {fg_cyan}%s{style_reset_all} keymap in {fg_cyan}%s', user_keymap['keymap'], keymap_path)
79
80 # Compile the keymap
81 command = compile_configurator_json(user_keymap) 30 command = compile_configurator_json(user_keymap)
82 31
83 cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path, user_keymap['keymap']) 32 cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path, user_keymap['keymap'])
84 33
85 elif user_keyboard and user_keymap: 34 else:
86 # Generate the make command for a specific keyboard/keymap. 35 # Perform the action the user specified
87 command = create_make_command(user_keyboard, user_keymap) 36 user_keyboard, user_keymap = find_keyboard_keymap()
88 37 if user_keyboard and user_keymap:
89 elif in_keyboard: 38 # Generate the make command for a specific keyboard/keymap.
90 keyboard = user_keyboard if user_keyboard else keyboard 39 command = create_make_command(user_keyboard, user_keymap)
91 keymap = user_keymap if user_keymap else keymap
92
93 if not os.path.exists(os.path.join(keyboards_path, keyboard, "rules.mk")):
94 cli.log.error('This directory does not contain a rules.mk file. Change directory or supply --keyboard with optional --keymap')
95 return False
96
97 # Get path for keyboard directory
98 keymap_path = qmk.path.keymap(keyboard)
99
100 # Check for global keymap config first
101 if keymap:
102 command = create_make_command(keyboard, keymap)
103
104 else:
105 # If no default keymap exists and none provided
106 cli.log.error('This directory does not contain a keymap. Set one with `qmk config` or supply `--keymap` ')
107 return False
108 40
109 elif in_layout:
110 if user_keyboard:
111 keymap = current_folder
112 command = create_make_command(user_keyboard, keymap)
113 else: 41 else:
114 cli.log.error('You must supply a keyboard to compile a layout keymap. Set one with `qmk config` or supply `--keyboard` ') 42 cli.log.error('You must supply a configurator export, both `--keyboard` and `--keymap`, or be in a directory for a keyboard or keymap.')
43 cli.echo('usage: qmk compile [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [filename]')
115 return False 44 return False
116 45
117 else:
118 cli.log.error('You must supply a configurator export, both `--keyboard` and `--keymap`, or be in a directory for a keyboard or keymap.')
119 return False
120
121 cli.log.info('Compiling keymap with {fg_cyan}%s\n\n', ' '.join(command)) 46 cli.log.info('Compiling keymap with {fg_cyan}%s\n\n', ' '.join(command))
122 subprocess.run(command) 47 subprocess.run(command)
diff --git a/lib/python/qmk/cli/flash.py b/lib/python/qmk/cli/flash.py
index cc1e6235a..f669c3cb7 100644
--- a/lib/python/qmk/cli/flash.py
+++ b/lib/python/qmk/cli/flash.py
@@ -8,7 +8,7 @@ from argparse import FileType
8 8
9import qmk.path 9import qmk.path
10from milc import cli 10from milc import cli
11from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json 11from qmk.commands import compile_configurator_json, create_make_command, find_keyboard_keymap, parse_configurator_json
12 12
13 13
14def print_bootloader_help(): 14def print_bootloader_help():
@@ -45,39 +45,31 @@ def flash(cli):
45 If bootloader is omitted, the one according to the rules.mk will be used. 45 If bootloader is omitted, the one according to the rules.mk will be used.
46 46
47 """ 47 """
48 command = []
49 if cli.args.bootloaders: 48 if cli.args.bootloaders:
50 # Provide usage and list bootloaders 49 # Provide usage and list bootloaders
51 cli.echo('usage: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]') 50 cli.echo('usage: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
52 print_bootloader_help() 51 print_bootloader_help()
53 return False 52 return False
54 53
55 elif cli.config.flash.keymap and not cli.config.flash.keyboard: 54 if cli.args.filename:
56 # If only a keymap was given but no keyboard, suggest listing keyboards 55 # Handle compiling a configurator JSON
57 cli.echo('usage: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
58 cli.log.error('run \'qmk list_keyboards\' to find out the supported keyboards')
59 return False
60
61 elif cli.args.filename:
62 # Get keymap path to log info
63 user_keymap = parse_configurator_json(cli.args.filename) 56 user_keymap = parse_configurator_json(cli.args.filename)
64 keymap_path = qmk.path.keymap(user_keymap['keyboard']) 57 keymap_path = qmk.path.keymap(user_keymap['keyboard'])
65
66 cli.log.info('Creating {fg_cyan}%s{style_reset_all} keymap in {fg_cyan}%s', user_keymap['keymap'], keymap_path)
67
68 # Convert the JSON into a C file and write it to disk.
69 command = compile_configurator_json(user_keymap, cli.args.bootloader) 58 command = compile_configurator_json(user_keymap, cli.args.bootloader)
70 59
71 cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path, user_keymap['keymap']) 60 cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path, user_keymap['keymap'])
72 61
73 elif cli.config.flash.keyboard and cli.config.flash.keymap:
74 # Generate the make command for a specific keyboard/keymap.
75 command = create_make_command(cli.config.flash.keyboard, cli.config.flash.keymap, cli.args.bootloader)
76
77 else: 62 else:
78 cli.echo('usage: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]') 63 # Perform the action the user specified
79 cli.log.error('You must supply a configurator export or both `--keyboard` and `--keymap`. You can also specify a bootloader with --bootloader. Use --bootloaders to list the available bootloaders.') 64 user_keyboard, user_keymap = find_keyboard_keymap()
80 return False 65 if user_keyboard and user_keymap:
66 # Generate the make command for a specific keyboard/keymap.
67 command = create_make_command(user_keyboard, user_keymap, cli.args.bootloader)
68
69 else:
70 cli.log.error('You must supply a configurator export or both `--keyboard` and `--keymap`.')
71 cli.echo('usage: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
72 return False
81 73
82 cli.log.info('Flashing keymap with {fg_cyan}%s\n\n', ' '.join(command)) 74 cli.log.info('Flashing keymap with {fg_cyan}%s\n\n', ' '.join(command))
83 subprocess.run(command) 75 subprocess.run(command)
diff --git a/lib/python/qmk/cli/json/keymap.py b/lib/python/qmk/cli/json/keymap.py
index a030ab53d..c2b7dde7a 100755
--- a/lib/python/qmk/cli/json/keymap.py
+++ b/lib/python/qmk/cli/json/keymap.py
@@ -1,14 +1,15 @@
1"""Generate a keymap.c from a configurator export. 1"""Generate a keymap.c from a configurator export.
2""" 2"""
3import json 3import json
4import os 4from pathlib import Path
5 5
6from milc import cli 6from milc import cli
7 7
8import qmk.keymap 8import qmk.keymap
9import qmk.path
9 10
10 11
11@cli.argument('-o', '--output', arg_only=True, help='File to write to') 12@cli.argument('-o', '--output', arg_only=True, type=Path, help='File to write to')
12@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") 13@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
13@cli.argument('filename', arg_only=True, help='Configurator JSON file') 14@cli.argument('filename', arg_only=True, help='Configurator JSON file')
14@cli.subcommand('Creates a keymap.c from a QMK Configurator export.') 15@cli.subcommand('Creates a keymap.c from a QMK Configurator export.')
@@ -17,13 +18,17 @@ def json_keymap(cli):
17 18
18 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. 19 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.
19 """ 20 """
21 cli.args.filename = qmk.path.normpath(cli.args.filename)
22
20 # Error checking 23 # Error checking
21 if cli.args.filename == ('-'): 24 if not cli.args.filename.exists():
22 cli.log.error('Reading from STDIN is not (yet) supported.') 25 cli.log.error('JSON file does not exist!')
23 cli.print_usage() 26 cli.print_usage()
24 exit(1) 27 exit(1)
25 if not os.path.exists(qmk.path.normpath(cli.args.filename)): 28
26 cli.log.error('JSON file does not exist!') 29 if str(cli.args.filename) == '-':
30 # TODO(skullydazed/anyone): Read file contents from STDIN
31 cli.log.error('Reading from STDIN is not (yet) supported.')
27 cli.print_usage() 32 cli.print_usage()
28 exit(1) 33 exit(1)
29 34
@@ -32,21 +37,17 @@ def json_keymap(cli):
32 cli.args.output = None 37 cli.args.output = None
33 38
34 # Parse the configurator json 39 # Parse the configurator json
35 with open(qmk.path.normpath(cli.args.filename), 'r') as fd: 40 with cli.args.filename.open('r') as fd:
36 user_keymap = json.load(fd) 41 user_keymap = json.load(fd)
37 42
38 # Generate the keymap 43 # Generate the keymap
39 keymap_c = qmk.keymap.generate(user_keymap['keyboard'], user_keymap['layout'], user_keymap['layers']) 44 keymap_c = qmk.keymap.generate(user_keymap['keyboard'], user_keymap['layout'], user_keymap['layers'])
40 45
41 if cli.args.output: 46 if cli.args.output:
42 output_dir = os.path.dirname(cli.args.output) 47 cli.args.output.parent.mkdir(parents=True, exist_ok=True)
43 48 if cli.args.output.exists():
44 if not os.path.exists(output_dir): 49 cli.args.output.replace(cli.args.output.name + '.bak')
45 os.makedirs(output_dir) 50 cli.args.output.write_text(keymap_c)
46
47 output_file = qmk.path.normpath(cli.args.output)
48 with open(output_file, 'w') as keymap_fd:
49 keymap_fd.write(keymap_c)
50 51
51 if not cli.args.quiet: 52 if not cli.args.quiet:
52 cli.log.info('Wrote keymap to %s.', cli.args.output) 53 cli.log.info('Wrote keymap to %s.', cli.args.output)
diff --git a/lib/python/qmk/cli/list/keyboards.py b/lib/python/qmk/cli/list/keyboards.py
index 76e7760e8..ca0c5661a 100644
--- a/lib/python/qmk/cli/list/keyboards.py
+++ b/lib/python/qmk/cli/list/keyboards.py
@@ -1,5 +1,6 @@
1"""List the keyboards currently defined within QMK 1"""List the keyboards currently defined within QMK
2""" 2"""
3# We avoid pathlib here because this is performance critical code.
3import os 4import os
4import glob 5import glob
5 6
diff --git a/lib/python/qmk/cli/new/keymap.py b/lib/python/qmk/cli/new/keymap.py
index 96525e28e..cbe50692e 100755
--- a/lib/python/qmk/cli/new/keymap.py
+++ b/lib/python/qmk/cli/new/keymap.py
@@ -1,8 +1,9 @@
1"""This script automates the copying of the default keymap into your own keymap. 1"""This script automates the copying of the default keymap into your own keymap.
2""" 2"""
3import os
4import shutil 3import shutil
4from pathlib import Path
5 5
6import qmk.path
6from milc import cli 7from milc import cli
7 8
8 9
@@ -17,24 +18,27 @@ def new_keymap(cli):
17 keymap = cli.config.new_keymap.keymap if cli.config.new_keymap.keymap else input("Keymap Name: ") 18 keymap = cli.config.new_keymap.keymap if cli.config.new_keymap.keymap else input("Keymap Name: ")
18 19
19 # generate keymap paths 20 # generate keymap paths
20 kb_path = os.path.join(os.getcwd(), "keyboards", keyboard) 21 kb_path = Path('keyboards') / keyboard
21 keymap_path_default = os.path.join(kb_path, "keymaps/default") 22 keymap_path = qmk.path.keymap(keyboard)
22 keymap_path = os.path.join(kb_path, "keymaps/%s" % keymap) 23 keymap_path_default = keymap_path / 'default'
24 keymap_path_new = keymap_path / keymap
23 25
24 # check directories 26 # check directories
25 if not os.path.exists(kb_path): 27 if not kb_path.exists():
26 cli.log.error('Keyboard %s does not exist!', kb_path) 28 cli.log.error('Keyboard %s does not exist!', kb_path)
27 exit(1) 29 exit(1)
28 if not os.path.exists(keymap_path_default): 30
31 if not keymap_path_default.exists():
29 cli.log.error('Keyboard default %s does not exist!', keymap_path_default) 32 cli.log.error('Keyboard default %s does not exist!', keymap_path_default)
30 exit(1) 33 exit(1)
31 if os.path.exists(keymap_path): 34
32 cli.log.error('Keymap %s already exists!', keymap_path) 35 if keymap_path_new.exists():
36 cli.log.error('Keymap %s already exists!', keymap_path_new)
33 exit(1) 37 exit(1)
34 38
35 # create user directory with default keymap files 39 # create user directory with default keymap files
36 shutil.copytree(keymap_path_default, keymap_path, symlinks=True) 40 shutil.copytree(str(keymap_path_default), str(keymap_path_new), symlinks=True)
37 41
38 # end message to user 42 # end message to user
39 cli.log.info("%s keymap directory created in: %s", keymap, keymap_path) 43 cli.log.info("%s keymap directory created in: %s", keymap, keymap_path_new)
40 cli.log.info("Compile a firmware with your new keymap by typing: \n" + "qmk compile -kb %s -km %s", keyboard, keymap) 44 cli.log.info("Compile a firmware with your new keymap by typing: \n\n\tqmk compile -kb %s -km %s\n", keyboard, keymap)
diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py
index 6067d49ae..cdb8ee037 100644
--- a/lib/python/qmk/commands.py
+++ b/lib/python/qmk/commands.py
@@ -1,13 +1,19 @@
1"""Functions that build make commands 1"""Helper functions for commands.
2""" 2"""
3import json 3import json
4from pathlib import Path
5
6from milc import cli
7
4import qmk.keymap 8import qmk.keymap
9from qmk.path import is_keyboard, is_keymap_dir, under_qmk_firmware
5 10
6 11
7def create_make_command(keyboard, keymap, target=None): 12def create_make_command(keyboard, keymap, target=None):
8 """Create a make compile command 13 """Create a make compile command
9 14
10 Args: 15 Args:
16
11 keyboard 17 keyboard
12 The path of the keyboard, for example 'plank' 18 The path of the keyboard, for example 'plank'
13 19
@@ -18,24 +24,22 @@ def create_make_command(keyboard, keymap, target=None):
18 Usually a bootloader. 24 Usually a bootloader.
19 25
20 Returns: 26 Returns:
27
21 A command that can be run to make the specified keyboard and keymap 28 A command that can be run to make the specified keyboard and keymap
22 """ 29 """
23 if target is None: 30 make_args = [keyboard, keymap]
24 return ['make', ':'.join((keyboard, keymap))]
25 return ['make', ':'.join((keyboard, keymap, target))]
26 31
32 if target:
33 make_args.append(target)
27 34
28def parse_configurator_json(configurator_file): 35 return ['make', ':'.join(make_args)]
29 """Open and parse a configurator json export
30 """
31 user_keymap = json.load(configurator_file)
32 return user_keymap
33 36
34 37
35def compile_configurator_json(user_keymap, bootloader=None): 38def compile_configurator_json(user_keymap, bootloader=None):
36 """Convert a configurator export JSON file into a C file 39 """Convert a configurator export JSON file into a C file
37 40
38 Args: 41 Args:
42
39 configurator_filename 43 configurator_filename
40 The configurator JSON export file 44 The configurator JSON export file
41 45
@@ -43,6 +47,7 @@ def compile_configurator_json(user_keymap, bootloader=None):
43 A bootloader to flash 47 A bootloader to flash
44 48
45 Returns: 49 Returns:
50
46 A command to run to compile and flash the C file. 51 A command to run to compile and flash the C file.
47 """ 52 """
48 # Write the keymap C file 53 # Write the keymap C file
@@ -52,3 +57,76 @@ def compile_configurator_json(user_keymap, bootloader=None):
52 if bootloader is None: 57 if bootloader is None:
53 return create_make_command(user_keymap['keyboard'], user_keymap['keymap']) 58 return create_make_command(user_keymap['keyboard'], user_keymap['keymap'])
54 return create_make_command(user_keymap['keyboard'], user_keymap['keymap'], bootloader) 59 return create_make_command(user_keymap['keyboard'], user_keymap['keymap'], bootloader)
60
61
62def find_keyboard_keymap():
63 """Returns `(keyboard_name, keymap_name)` based on the user's current environment.
64
65 This determines the keyboard and keymap name using the following precedence order:
66
67 * Command line flags (--keyboard and --keymap)
68 * Current working directory
69 * `keyboards/<keyboard_name>`
70 * `keyboards/<keyboard_name>/keymaps/<keymap_name>`
71 * `layouts/**/<keymap_name>`
72 * `users/<keymap_name>`
73 * Configuration
74 * cli.config.<subcommand>.keyboard
75 * cli.config.<subcommand>.keymap
76 """
77 # Check to make sure their copy of MILC supports config_source
78 if not hasattr(cli, 'config_source'):
79 cli.log.error("Your QMK CLI is out of date. Please upgrade using pip3 or your package manager.")
80 exit(1)
81
82 # State variables
83 relative_cwd = under_qmk_firmware()
84 keyboard_name = ""
85 keymap_name = ""
86
87 # If the keyboard or keymap are passed as arguments use that in preference to anything else
88 if cli.config_source[cli._entrypoint.__name__]['keyboard'] == 'argument':
89 keyboard_name = cli.config[cli._entrypoint.__name__]['keyboard']
90 if cli.config_source[cli._entrypoint.__name__]['keymap'] == 'argument':
91 keymap_name = cli.config[cli._entrypoint.__name__]['keymap']
92
93 if not keyboard_name or not keymap_name:
94 # If we don't have a keyboard_name and keymap_name from arguments try to derive one or both
95 if relative_cwd and relative_cwd.parts and relative_cwd.parts[0] == 'keyboards':
96 # Try to determine the keyboard and/or keymap name
97 current_path = Path('/'.join(relative_cwd.parts[1:]))
98
99 if current_path.parts[-2] == 'keymaps':
100 if not keymap_name:
101 keymap_name = current_path.parts[-1]
102 if not keyboard_name:
103 keyboard_name = '/'.join(current_path.parts[:-2])
104 elif not keyboard_name and is_keyboard(current_path):
105 keyboard_name = str(current_path)
106
107 elif relative_cwd and relative_cwd.parts and relative_cwd.parts[0] == 'layouts':
108 # Try to determine the keymap name from the community layout
109 if is_keymap_dir(relative_cwd) and not keymap_name:
110 keymap_name = relative_cwd.name
111
112 elif relative_cwd and relative_cwd.parts and relative_cwd.parts[0] == 'users':
113 # Try to determine the keymap name based on which userspace they're in
114 if not keymap_name and len(relative_cwd.parts) > 1:
115 keymap_name = relative_cwd.parts[1]
116
117 # If we still don't have a keyboard and keymap check the config
118 if not keyboard_name and cli.config[cli._entrypoint.__name__]['keyboard']:
119 keyboard_name = cli.config[cli._entrypoint.__name__]['keyboard']
120
121 if not keymap_name and cli.config[cli._entrypoint.__name__]['keymap']:
122 keymap_name = cli.config[cli._entrypoint.__name__]['keymap']
123
124 return (keyboard_name, keymap_name)
125
126
127def parse_configurator_json(configurator_file):
128 """Open and parse a configurator json export
129 """
130 user_keymap = json.load(configurator_file)
131
132 return user_keymap
diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py
new file mode 100644
index 000000000..3e4709969
--- /dev/null
+++ b/lib/python/qmk/constants.py
@@ -0,0 +1,9 @@
1"""Information that should be available to the python library.
2"""
3from pathlib import Path
4
5# The root of the qmk_firmware tree.
6QMK_FIRMWARE = Path.cwd()
7
8# This is the number of directories under `qmk_firmware/keyboards` that will be traversed. This is currently a limitation of our make system.
9MAX_KEYBOARD_SUBFOLDERS = 5
diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py
index 15a91a276..b91ba89be 100644
--- a/lib/python/qmk/keymap.py
+++ b/lib/python/qmk/keymap.py
@@ -31,11 +31,10 @@ def template(keyboard):
31 keyboard 31 keyboard
32 The keyboard to return a template for. 32 The keyboard to return a template for.
33 """ 33 """
34 template_name = 'keyboards/%s/templates/keymap.c' % keyboard 34 template_file = Path('keyboards/%s/templates/keymap.c' % keyboard)
35 35
36 if os.path.exists(template_name): 36 if template_file.exists():
37 with open(template_name, 'r') as fd: 37 return template_file.read_text()
38 return fd.read()
39 38
40 return DEFAULT_KEYMAP_C 39 return DEFAULT_KEYMAP_C
41 40
@@ -85,15 +84,10 @@ def write(keyboard, keymap, layout, layers):
85 An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. 84 An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode.
86 """ 85 """
87 keymap_c = generate(keyboard, layout, layers) 86 keymap_c = generate(keyboard, layout, layers)
88 keymap_path = qmk.path.keymap(keyboard) 87 keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.c'
89 keymap_dir = os.path.join(keymap_path, keymap)
90 keymap_file = os.path.join(keymap_dir, 'keymap.c')
91 88
92 if not os.path.exists(keymap_dir): 89 keymap_file.parent.mkdir(parents=True, exist_ok=True)
93 os.makedirs(keymap_dir) 90 keymap_file.write_text(keymap_c)
94
95 with open(keymap_file, 'w') as keymap_fd:
96 keymap_fd.write(keymap_c)
97 91
98 return keymap_file 92 return keymap_file
99 93
diff --git a/lib/python/qmk/path.py b/lib/python/qmk/path.py
index cf087265f..d16928afb 100644
--- a/lib/python/qmk/path.py
+++ b/lib/python/qmk/path.py
@@ -2,34 +2,69 @@
2""" 2"""
3import logging 3import logging
4import os 4import os
5from pathlib import Path
5 6
7from qmk.constants import QMK_FIRMWARE, MAX_KEYBOARD_SUBFOLDERS
6from qmk.errors import NoSuchKeyboardError 8from qmk.errors import NoSuchKeyboardError
7 9
8 10
11def is_keymap_dir(keymap_path):
12 """Returns True if `keymap_path` is a valid keymap directory.
13 """
14 keymap_path = Path(keymap_path)
15 keymap_c = keymap_path / 'keymap.c'
16 keymap_json = keymap_path / 'keymap.json'
17
18 return any((keymap_c.exists(), keymap_json.exists()))
19
20
21def is_keyboard(keyboard_name):
22 """Returns True if `keyboard_name` is a keyboard we can compile.
23 """
24 keyboard_path = QMK_FIRMWARE / 'keyboards' / keyboard_name
25 rules_mk = keyboard_path / 'rules.mk'
26 return rules_mk.exists()
27
28
29def under_qmk_firmware():
30 """Returns a Path object representing the relative path under qmk_firmware, or None.
31 """
32 cwd = Path(os.environ['ORIG_CWD'])
33
34 try:
35 return cwd.relative_to(QMK_FIRMWARE)
36 except ValueError:
37 return None
38
39
9def keymap(keyboard): 40def keymap(keyboard):
10 """Locate the correct directory for storing a keymap. 41 """Locate the correct directory for storing a keymap.
11 42
12 Args: 43 Args:
44
13 keyboard 45 keyboard
14 The name of the keyboard. Example: clueboard/66/rev3 46 The name of the keyboard. Example: clueboard/66/rev3
15 """ 47 """
16 for directory in ['.', '..', '../..', '../../..', '../../../..', '../../../../..']: 48 keyboard_folder = Path('keyboards') / keyboard
17 basepath = os.path.normpath(os.path.join('keyboards', keyboard, directory, 'keymaps')) 49
50 for i in range(MAX_KEYBOARD_SUBFOLDERS):
51 if (keyboard_folder / 'keymaps').exists():
52 return (keyboard_folder / 'keymaps').resolve()
18 53
19 if os.path.exists(basepath): 54 keyboard_folder = keyboard_folder.parent
20 return basepath
21 55
22 logging.error('Could not find keymaps directory!') 56 logging.error('Could not find the keymaps directory!')
23 raise NoSuchKeyboardError('Could not find keymaps directory for: %s' % keyboard) 57 raise NoSuchKeyboardError('Could not find keymaps directory for: %s' % keyboard)
24 58
25 59
26def normpath(path): 60def normpath(path):
27 """Returns the fully resolved absolute path to a file. 61 """Returns a `pathlib.Path()` object for a given path.
28 62
29 This function will return the absolute path to a file as seen from the 63 This will use the path to a file as seen from the directory the script was called from. You should use this to normalize filenames supplied from the command line.
30 directory the script was called from.
31 """ 64 """
32 if path and path[0] == '/': 65 path = Path(path)
33 return os.path.normpath(path) 66
67 if path.is_absolute():
68 return Path(path)
34 69
35 return os.path.normpath(os.path.join(os.environ['ORIG_CWD'], path)) 70 return Path(os.environ['ORIG_CWD']) / path
diff --git a/lib/python/qmk/tests/test_qmk_path.py b/lib/python/qmk/tests/test_qmk_path.py
index d6961a0f6..74db7b3e2 100644
--- a/lib/python/qmk/tests/test_qmk_path.py
+++ b/lib/python/qmk/tests/test_qmk_path.py
@@ -1,13 +1,14 @@
1import os 1import os
2from pathlib import Path
2 3
3import qmk.path 4import qmk.path
4 5
5 6
6def test_keymap_onekey_pytest(): 7def test_keymap_onekey_pytest():
7 path = qmk.path.keymap('handwired/onekey/pytest') 8 path = qmk.path.keymap('handwired/onekey/pytest')
8 assert path == 'keyboards/handwired/onekey/keymaps' 9 assert path.samefile('keyboards/handwired/onekey/keymaps')
9 10
10 11
11def test_normpath(): 12def test_normpath():
12 path = qmk.path.normpath('lib/python') 13 path = qmk.path.normpath('lib/python')
13 assert path == os.path.join(os.environ['ORIG_CWD'], 'lib/python') 14 assert path.samefile(Path(os.environ['ORIG_CWD']) / 'lib/python')