diff options
| author | Zach White <skullydazed@gmail.com> | 2020-10-25 14:48:44 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-10-25 14:48:44 -0700 |
| commit | 0c42f91f4ccf98a37f055afb777ed491da56335e (patch) | |
| tree | 547344d80fe7bf75ff3f348eefbc19dbdd346a8a /lib/python/qmk/keymap.py | |
| parent | 8ef82c466e73e555fd74107d4c57e678d7152ecc (diff) | |
| download | qmk_firmware-0c42f91f4ccf98a37f055afb777ed491da56335e.tar.gz qmk_firmware-0c42f91f4ccf98a37f055afb777ed491da56335e.zip | |
Generate api data on each push (#10609)
* add new qmk generate-api command, to generate a complete set of API data.
* Generate api data and push it to the keyboard repo
* fix typo
* Apply suggestions from code review
Co-authored-by: Joel Challis <git@zvecr.com>
* fixup api workflow
* remove file-changes-action
* use a more mainstream github action
* fix yaml error
* Apply suggestions from code review
Co-authored-by: Erovia <Erovia@users.noreply.github.com>
* more uniform date handling
* make flake8 happy
* Update lib/python/qmk/decorators.py
Co-authored-by: Erovia <Erovia@users.noreply.github.com>
Co-authored-by: Joel Challis <git@zvecr.com>
Co-authored-by: Erovia <Erovia@users.noreply.github.com>
Diffstat (limited to 'lib/python/qmk/keymap.py')
| -rw-r--r-- | lib/python/qmk/keymap.py | 219 |
1 files changed, 153 insertions, 66 deletions
diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py index 166697ee6..31c61ae6a 100644 --- a/lib/python/qmk/keymap.py +++ b/lib/python/qmk/keymap.py | |||
| @@ -29,33 +29,37 @@ __KEYMAP_GOES_HERE__ | |||
| 29 | """ | 29 | """ |
| 30 | 30 | ||
| 31 | 31 | ||
| 32 | def template(keyboard, type='c'): | 32 | def template_json(keyboard): |
| 33 | """Returns the `keymap.c` or `keymap.json` template for a keyboard. | 33 | """Returns a `keymap.json` template for a keyboard. |
| 34 | 34 | ||
| 35 | If a template exists in `keyboards/<keyboard>/templates/keymap.c` that | 35 | If a template exists in `keyboards/<keyboard>/templates/keymap.json` that text will be used instead of an empty dictionary. |
| 36 | text will be used instead of `DEFAULT_KEYMAP_C`. | ||
| 37 | |||
| 38 | If a template exists in `keyboards/<keyboard>/templates/keymap.json` that | ||
| 39 | text will be used instead of an empty dictionary. | ||
| 40 | 36 | ||
| 41 | Args: | 37 | Args: |
| 42 | keyboard | 38 | keyboard |
| 43 | The keyboard to return a template for. | 39 | The keyboard to return a template for. |
| 40 | """ | ||
| 41 | template_file = Path('keyboards/%s/templates/keymap.json' % keyboard) | ||
| 42 | template = {'keyboard': keyboard} | ||
| 43 | if template_file.exists(): | ||
| 44 | template.update(json.loads(template_file.read_text())) | ||
| 45 | |||
| 46 | return template | ||
| 47 | |||
| 44 | 48 | ||
| 45 | type | 49 | def template_c(keyboard): |
| 46 | 'json' for `keymap.json` and 'c' (or anything else) for `keymap.c` | 50 | """Returns a `keymap.c` template for a keyboard. |
| 51 | |||
| 52 | If a template exists in `keyboards/<keyboard>/templates/keymap.c` that text will be used instead of an empty dictionary. | ||
| 53 | |||
| 54 | Args: | ||
| 55 | keyboard | ||
| 56 | The keyboard to return a template for. | ||
| 47 | """ | 57 | """ |
| 48 | if type == 'json': | 58 | template_file = Path('keyboards/%s/templates/keymap.c' % keyboard) |
| 49 | template_file = Path('keyboards/%s/templates/keymap.json' % keyboard) | 59 | if template_file.exists(): |
| 50 | template = {'keyboard': keyboard} | 60 | template = template_file.read_text() |
| 51 | if template_file.exists(): | ||
| 52 | template.update(json.loads(template_file.read_text())) | ||
| 53 | else: | 61 | else: |
| 54 | template_file = Path('keyboards/%s/templates/keymap.c' % keyboard) | 62 | template = DEFAULT_KEYMAP_C |
| 55 | if template_file.exists(): | ||
| 56 | template = template_file.read_text() | ||
| 57 | else: | ||
| 58 | template = DEFAULT_KEYMAP_C | ||
| 59 | 63 | ||
| 60 | return template | 64 | return template |
| 61 | 65 | ||
| @@ -69,15 +73,65 @@ def _strip_any(keycode): | |||
| 69 | return keycode | 73 | return keycode |
| 70 | 74 | ||
| 71 | 75 | ||
| 72 | def is_keymap_dir(keymap): | 76 | def is_keymap_dir(keymap, c=True, json=True, additional_files=None): |
| 73 | """Return True if Path object `keymap` has a keymap file inside. | 77 | """Return True if Path object `keymap` has a keymap file inside. |
| 78 | |||
| 79 | Args: | ||
| 80 | keymap | ||
| 81 | A Path() object for the keymap directory you want to check. | ||
| 82 | |||
| 83 | c | ||
| 84 | When true include `keymap.c` keymaps. | ||
| 85 | |||
| 86 | json | ||
| 87 | When true include `keymap.json` keymaps. | ||
| 88 | |||
| 89 | additional_files | ||
| 90 | A sequence of additional filenames to check against to determine if a directory is a keymap. All files must exist for a match to happen. For example, if you want to match a C keymap with both a `config.h` and `rules.mk` file: `is_keymap_dir(keymap_dir, json=False, additional_files=['config.h', 'rules.mk'])` | ||
| 74 | """ | 91 | """ |
| 75 | for file in ('keymap.c', 'keymap.json'): | 92 | files = [] |
| 93 | |||
| 94 | if c: | ||
| 95 | files.append('keymap.c') | ||
| 96 | |||
| 97 | if json: | ||
| 98 | files.append('keymap.json') | ||
| 99 | |||
| 100 | for file in files: | ||
| 76 | if (keymap / file).is_file(): | 101 | if (keymap / file).is_file(): |
| 102 | if additional_files: | ||
| 103 | for file in additional_files: | ||
| 104 | if not (keymap / file).is_file(): | ||
| 105 | return False | ||
| 106 | |||
| 77 | return True | 107 | return True |
| 78 | 108 | ||
| 79 | 109 | ||
| 80 | def generate(keyboard, layout, layers, type='c', keymap=None): | 110 | def generate_json(keymap, keyboard, layout, layers): |
| 111 | """Returns a `keymap.json` for the specified keyboard, layout, and layers. | ||
| 112 | |||
| 113 | Args: | ||
| 114 | keymap | ||
| 115 | A name for this keymap. | ||
| 116 | |||
| 117 | keyboard | ||
| 118 | The name of the keyboard. | ||
| 119 | |||
| 120 | layout | ||
| 121 | The LAYOUT macro this keymap uses. | ||
| 122 | |||
| 123 | layers | ||
| 124 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. | ||
| 125 | """ | ||
| 126 | new_keymap = template_json(keyboard) | ||
| 127 | new_keymap['keymap'] = keymap | ||
| 128 | new_keymap['layout'] = layout | ||
| 129 | new_keymap['layers'] = layers | ||
| 130 | |||
| 131 | return new_keymap | ||
| 132 | |||
| 133 | |||
| 134 | def generate_c(keyboard, layout, layers): | ||
| 81 | """Returns a `keymap.c` or `keymap.json` for the specified keyboard, layout, and layers. | 135 | """Returns a `keymap.c` or `keymap.json` for the specified keyboard, layout, and layers. |
| 82 | 136 | ||
| 83 | Args: | 137 | Args: |
| @@ -89,33 +143,33 @@ def generate(keyboard, layout, layers, type='c', keymap=None): | |||
| 89 | 143 | ||
| 90 | layers | 144 | layers |
| 91 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. | 145 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. |
| 92 | |||
| 93 | type | ||
| 94 | 'json' for `keymap.json` and 'c' (or anything else) for `keymap.c` | ||
| 95 | """ | 146 | """ |
| 96 | new_keymap = template(keyboard, type) | 147 | new_keymap = template_c(keyboard) |
| 97 | if type == 'json': | 148 | layer_txt = [] |
| 98 | new_keymap['keymap'] = keymap | 149 | for layer_num, layer in enumerate(layers): |
| 99 | new_keymap['layout'] = layout | 150 | if layer_num != 0: |
| 100 | new_keymap['layers'] = layers | 151 | layer_txt[-1] = layer_txt[-1] + ',' |
| 101 | else: | 152 | layer = map(_strip_any, layer) |
| 102 | layer_txt = [] | 153 | layer_keys = ', '.join(layer) |
| 103 | for layer_num, layer in enumerate(layers): | 154 | layer_txt.append('\t[%s] = %s(%s)' % (layer_num, layout, layer_keys)) |
| 104 | if layer_num != 0: | 155 | |
| 105 | layer_txt[-1] = layer_txt[-1] + ',' | 156 | keymap = '\n'.join(layer_txt) |
| 157 | new_keymap = new_keymap.replace('__KEYMAP_GOES_HERE__', keymap) | ||
| 106 | 158 | ||
| 107 | layer = map(_strip_any, layer) | 159 | return new_keymap |
| 108 | layer_keys = ', '.join(layer) | ||
| 109 | layer_txt.append('\t[%s] = %s(%s)' % (layer_num, layout, layer_keys)) | ||
| 110 | 160 | ||
| 111 | keymap = '\n'.join(layer_txt) | ||
| 112 | new_keymap = new_keymap.replace('__KEYMAP_GOES_HERE__', keymap) | ||
| 113 | 161 | ||
| 114 | return new_keymap | 162 | def write_file(keymap_filename, keymap_content): |
| 163 | keymap_filename.parent.mkdir(parents=True, exist_ok=True) | ||
| 164 | keymap_filename.write_text(keymap_content) | ||
| 115 | 165 | ||
| 166 | cli.log.info('Wrote keymap to {fg_cyan}%s', keymap_filename) | ||
| 167 | |||
| 168 | return keymap_filename | ||
| 116 | 169 | ||
| 117 | def write(keyboard, keymap, layout, layers, type='c'): | 170 | |
| 118 | """Generate the `keymap.c` and write it to disk. | 171 | def write_json(keyboard, keymap, layout, layers): |
| 172 | """Generate the `keymap.json` and write it to disk. | ||
| 119 | 173 | ||
| 120 | Returns the filename written to. | 174 | Returns the filename written to. |
| 121 | 175 | ||
| @@ -131,23 +185,36 @@ def write(keyboard, keymap, layout, layers, type='c'): | |||
| 131 | 185 | ||
| 132 | layers | 186 | layers |
| 133 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. | 187 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. |
| 134 | |||
| 135 | type | ||
| 136 | 'json' for `keymap.json` and 'c' (or anything else) for `keymap.c` | ||
| 137 | """ | 188 | """ |
| 138 | keymap_content = generate(keyboard, layout, layers, type) | 189 | keymap_json = generate_json(keyboard, keymap, layout, layers) |
| 139 | if type == 'json': | 190 | keymap_content = json.dumps(keymap_json) |
| 140 | keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.json' | 191 | keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.json' |
| 141 | keymap_content = json.dumps(keymap_content) | 192 | |
| 142 | else: | 193 | return write_file(keymap_file, keymap_content) |
| 143 | keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.c' | 194 | |
| 195 | |||
| 196 | def write(keyboard, keymap, layout, layers): | ||
| 197 | """Generate the `keymap.c` and write it to disk. | ||
| 198 | |||
| 199 | Returns the filename written to. | ||
| 200 | |||
| 201 | Args: | ||
| 202 | keyboard | ||
| 203 | The name of the keyboard | ||
| 144 | 204 | ||
| 145 | keymap_file.parent.mkdir(parents=True, exist_ok=True) | 205 | keymap |
| 146 | keymap_file.write_text(keymap_content) | 206 | The name of the keymap |
| 147 | 207 | ||
| 148 | cli.log.info('Wrote keymap to {fg_cyan}%s', keymap_file) | 208 | layout |
| 209 | The LAYOUT macro this keymap uses. | ||
| 210 | |||
| 211 | layers | ||
| 212 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. | ||
| 213 | """ | ||
| 214 | keymap_content = generate_c(keyboard, layout, layers) | ||
| 215 | keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.c' | ||
| 149 | 216 | ||
| 150 | return keymap_file | 217 | return write_file(keymap_file, keymap_content) |
| 151 | 218 | ||
| 152 | 219 | ||
| 153 | def locate_keymap(keyboard, keymap): | 220 | def locate_keymap(keyboard, keymap): |
| @@ -189,38 +256,58 @@ def locate_keymap(keyboard, keymap): | |||
| 189 | return community_layout / 'keymap.c' | 256 | return community_layout / 'keymap.c' |
| 190 | 257 | ||
| 191 | 258 | ||
| 192 | def list_keymaps(keyboard): | 259 | def list_keymaps(keyboard, c=True, json=True, additional_files=None, fullpath=False): |
| 193 | """ List the available keymaps for a keyboard. | 260 | """List the available keymaps for a keyboard. |
| 194 | 261 | ||
| 195 | Args: | 262 | Args: |
| 196 | keyboard: the keyboards full name with vendor and revision if necessary, example: clueboard/66/rev3 | 263 | keyboard |
| 264 | The keyboards full name with vendor and revision if necessary, example: clueboard/66/rev3 | ||
| 265 | |||
| 266 | c | ||
| 267 | When true include `keymap.c` keymaps. | ||
| 268 | |||
| 269 | json | ||
| 270 | When true include `keymap.json` keymaps. | ||
| 271 | |||
| 272 | additional_files | ||
| 273 | A sequence of additional filenames to check against to determine if a directory is a keymap. All files must exist for a match to happen. For example, if you want to match a C keymap with both a `config.h` and `rules.mk` file: `is_keymap_dir(keymap_dir, json=False, additional_files=['config.h', 'rules.mk'])` | ||
| 274 | |||
| 275 | fullpath | ||
| 276 | When set to True the full path of the keymap relative to the `qmk_firmware` root will be provided. | ||
| 197 | 277 | ||
| 198 | Returns: | 278 | Returns: |
| 199 | a set with the names of the available keymaps | 279 | a sorted list of valid keymap names. |
| 200 | """ | 280 | """ |
| 201 | # parse all the rules.mk files for the keyboard | 281 | # parse all the rules.mk files for the keyboard |
| 202 | rules = rules_mk(keyboard) | 282 | rules = rules_mk(keyboard) |
| 203 | names = set() | 283 | names = set() |
| 204 | 284 | ||
| 205 | if rules: | 285 | if rules: |
| 206 | # qmk_firmware/keyboards | ||
| 207 | keyboards_dir = Path('keyboards') | 286 | keyboards_dir = Path('keyboards') |
| 208 | # path to the keyboard's directory | ||
| 209 | kb_path = keyboards_dir / keyboard | 287 | kb_path = keyboards_dir / keyboard |
| 288 | |||
| 210 | # walk up the directory tree until keyboards_dir | 289 | # walk up the directory tree until keyboards_dir |
| 211 | # and collect all directories' name with keymap.c file in it | 290 | # and collect all directories' name with keymap.c file in it |
| 212 | while kb_path != keyboards_dir: | 291 | while kb_path != keyboards_dir: |
| 213 | keymaps_dir = kb_path / "keymaps" | 292 | keymaps_dir = kb_path / "keymaps" |
| 214 | if keymaps_dir.exists(): | 293 | |
| 215 | names = names.union([keymap.name for keymap in keymaps_dir.iterdir() if is_keymap_dir(keymap)]) | 294 | if keymaps_dir.is_dir(): |
| 295 | for keymap in keymaps_dir.iterdir(): | ||
| 296 | if is_keymap_dir(keymap, c, json, additional_files): | ||
| 297 | keymap = keymap if fullpath else keymap.name | ||
| 298 | names.add(keymap) | ||
| 299 | |||
| 216 | kb_path = kb_path.parent | 300 | kb_path = kb_path.parent |
| 217 | 301 | ||
| 218 | # if community layouts are supported, get them | 302 | # if community layouts are supported, get them |
| 219 | if "LAYOUTS" in rules: | 303 | if "LAYOUTS" in rules: |
| 220 | for layout in rules["LAYOUTS"].split(): | 304 | for layout in rules["LAYOUTS"].split(): |
| 221 | cl_path = Path('layouts/community') / layout | 305 | cl_path = Path('layouts/community') / layout |
| 222 | if cl_path.exists(): | 306 | if cl_path.is_dir(): |
| 223 | names = names.union([keymap.name for keymap in cl_path.iterdir() if is_keymap_dir(keymap)]) | 307 | for keymap in cl_path.iterdir(): |
| 308 | if is_keymap_dir(keymap, c, json, additional_files): | ||
| 309 | keymap = keymap if fullpath else keymap.name | ||
| 310 | names.add(keymap) | ||
| 224 | 311 | ||
| 225 | return sorted(names) | 312 | return sorted(names) |
| 226 | 313 | ||
