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 | ||