Commit | Line | Data |
---|---|---|
20a3229f MB |
1 | # encoding: utf-8 |
2 | from __future__ import division | |
3 | from __future__ import print_function | |
4 | from __future__ import absolute_import | |
5 | from __future__ import unicode_literals | |
6 | ||
7 | import os | |
8 | import io | |
9 | import re | |
10 | import sys | |
11 | import json | |
12 | import unicodedata | |
13 | import collections | |
14 | ||
15 | PY2 = sys.version_info.major == 2 | |
16 | ||
17 | if PY2: | |
18 | chr = unichr | |
19 | ||
20 | ||
21 | ONELINE_COMMENT_RE = re.compile(r"^\s*//.*$", re.MULTILINE) | |
22 | INLINE_COMMENT_RE = re.compile( | |
23 | r"([\,\"\[\]\{\}\d])\s+//\s[^\"\]\}\{\[]*$", re.MULTILINE | |
24 | ) | |
25 | TRAILING_COMMA_RE = re.compile( | |
26 | r",$\s*([\]\}])", re.MULTILINE | |
27 | ) | |
28 | ||
29 | def loads(raw_data): | |
30 | if isinstance(raw_data, bytes): | |
31 | raw_data = raw_data.decode('utf-8') | |
32 | raw_data = ONELINE_COMMENT_RE.sub(r"", raw_data) | |
33 | raw_data = INLINE_COMMENT_RE.sub(r"\1", raw_data) | |
34 | raw_data = TRAILING_COMMA_RE.sub(r"\1", raw_data) | |
35 | return json.loads(raw_data) | |
36 | ||
37 | with io.open("keymap.md", encoding="utf-8") as fh: | |
38 | lines = fh.readlines() | |
39 | ||
40 | SECTIONS = [ | |
41 | 'layout_config', | |
42 | 'layers', | |
43 | ] | |
44 | ||
45 | config = { | |
46 | "includes_basedir": "quantum/", | |
47 | "keymaps_includes": [ | |
48 | "keymap_common.h", | |
49 | ], | |
50 | 'filler': "-+.':x", | |
51 | 'separator': "|", | |
52 | 'default_key_prefix': ["KC_"], | |
53 | 'unicode_macros': [], | |
54 | 'macro_ids': ['UMS'], | |
55 | 'layers': collections.OrderedDict(), | |
56 | 'layer_lines': collections.OrderedDict(), | |
57 | } | |
58 | ||
59 | section_start_index = -1 | |
60 | current_section = None | |
61 | current_layer_name = None | |
62 | current_layer_lines = [] | |
63 | config_data = [] | |
64 | ||
65 | def end_section(): | |
66 | global section_start_index | |
67 | global current_layer_lines | |
68 | section_start_index = -1 | |
69 | if current_section == 'layout_config': | |
70 | config.update(loads("".join( | |
71 | config_data | |
72 | ))) | |
73 | elif current_section == 'layers': | |
74 | config['layer_lines'][current_layer_name] = current_layer_lines | |
75 | current_layer_lines = [] | |
76 | ||
77 | ||
78 | for i, line in enumerate(lines): | |
79 | if line.startswith("# "): | |
80 | section = line[2:].strip().replace(" ", "_").lower() | |
81 | if section in SECTIONS: | |
82 | current_section = section | |
83 | elif line.startswith("## "): | |
84 | sub_section = line[3:] | |
85 | if current_section == 'layers': | |
86 | current_layer_name = sub_section.strip() | |
87 | # TODO: parse descriptio | |
88 | config['layers'][current_layer_name] = "" | |
89 | elif line.startswith(" "): | |
90 | if section_start_index < 0: | |
91 | section_start_index = i | |
92 | if current_section == 'layout_config': | |
93 | config_data.append(line) | |
94 | elif current_section == 'layers': | |
95 | if not line.strip(): | |
96 | continue | |
97 | current_layer_lines.append(line) | |
98 | elif section_start_index > 0: | |
99 | end_section() | |
100 | ||
101 | end_section() | |
102 | ||
103 | KEYDEF_RE = re.compile(r"#define ((?:{})(?:\w+))".format( | |
104 | "|".join(config['key_prefixes']) | |
105 | )) | |
106 | IF0_RE = re.compile(r"^#if 0$.*?#endif", re.MULTILINE | re.DOTALL) | |
107 | COMMENT_RE = re.compile(r"/\*.*?\*/", re.MULTILINE | re.DOTALL) | |
108 | ENUM_RE = re.compile(r"(enum\s\w+\s\{.*?\};)", re.MULTILINE | re.DOTALL) | |
109 | ENUM_KEY_RE = re.compile(r"({}\w+)".format( | |
110 | "|".join(config['key_prefixes']) | |
111 | )) | |
112 | ||
113 | def parse_keydefs(path): | |
114 | with io.open(path, encoding="utf-8") as fh: | |
115 | data = fh.read() | |
116 | data, _ = COMMENT_RE.subn("", data) | |
117 | data, _ = IF0_RE.subn("", data) | |
118 | ||
119 | for match in KEYDEF_RE.finditer(data): | |
120 | yield match.groups()[0] | |
121 | ||
122 | for enum_match in ENUM_RE.finditer(data): | |
123 | enum = enum_match.groups()[0] | |
124 | for key_match in ENUM_KEY_RE.finditer(enum): | |
125 | yield key_match.groups()[0] | |
126 | ||
127 | valid_keycodes = set() | |
128 | basepath = os.path.abspath(os.path.join( | |
129 | os.path.dirname(__file__), "..", "..", "..", ".." | |
130 | )) | |
131 | ||
132 | valid_keycodes.update(parse_keydefs(os.path.join( | |
133 | basepath, "tmk_core", "common", "keycode.h" | |
134 | ))) | |
135 | ||
136 | for include_path in config['keymaps_includes']: | |
137 | path = os.path.join(basepath, config['includes_dir'], include_path) | |
138 | path = path.replace("/", os.sep) | |
139 | if os.path.exists(path): | |
140 | valid_keycodes.update(parse_keydefs(path)) | |
141 | ||
142 | LAYER_CHANGE_RE = re.compile(r"(DF|TG|MO)\(\d+\)") | |
143 | MACRO_RE = re.compile(r"M\(\w+\)") | |
144 | UNICODE_RE = re.compile(r"U[0-9A-F]{4}") | |
145 | NON_CODE = re.compile(r"^[^A-Z0-9_]$") | |
146 | ||
147 | ||
148 | def UNICODE_MACRO(config, c): | |
149 | # TODO: don't use macro for codepoints below 0x2000 | |
150 | macro_id = "UC_" + ( | |
151 | unicodedata.name(c) | |
152 | .replace(" ", "_") | |
153 | .replace("-", "_") | |
154 | .replace("SUPERSCRIPT_", "SUP_") | |
155 | .replace("SUBSCRIPT_", "SUB_") | |
156 | .replace("GREEK_SMALL_LETTER", "GR_LC") | |
157 | .replace("GREEK_CAPITAL_LETTER", "GR_UC") | |
158 | .replace("VULGAR_FRACTION_", "FR_") | |
159 | ) | |
160 | if macro_id not in config['macro_ids']: | |
161 | config['macro_ids'].append(macro_id) | |
162 | code = "{:04X}".format(ord(c)) | |
163 | if (macro_id, code) not in config['unicode_macros']: | |
164 | config['unicode_macros'].append((macro_id, code)) | |
165 | return "M({})".format(macro_id) | |
166 | ||
167 | ||
168 | def MACRO(config, code): | |
169 | macro_id = code[2:-1] | |
170 | if macro_id not in config['macro_ids']: | |
171 | config['macro_ids'].append(macro_id) | |
172 | return code | |
173 | ||
174 | # TODO: presumably we can have a macro or function which takes | |
175 | # the hex code and produces much smaller code. | |
176 | ||
177 | WIN_UNICODE_MACRO_TEMPLATE = """ | |
178 | case {0}: | |
179 | return MACRODOWN( | |
180 | D(LALT), T(KP_PLUS), {1}, U(LALT), END | |
181 | ); | |
182 | """ | |
183 | ||
184 | LINUX_UNICODE_MACRO_TEMPLATE = """ | |
185 | case {0}: | |
186 | return MACRODOWN( | |
187 | D(LCTRL), D(LSHIFT), T(U), U(LCTRL), U(LSHIFT), {1}, T(KP_ENTER), END | |
188 | ); | |
189 | """ | |
190 | ||
191 | def macro_cases(config, mode): | |
192 | if mode == 'win': | |
193 | template = WIN_UNICODE_MACRO_TEMPLATE | |
194 | elif mode == 'linux': | |
195 | template = LINUX_UNICODE_MACRO_TEMPLATE | |
196 | else: | |
197 | raise ValueError("Invalid mode: ", mode) | |
198 | template = template.strip() | |
199 | ||
200 | for macro_id, unimacro_chars in config['unicode_macros']: | |
201 | unimacro_keys = ", ".join( | |
202 | "T({})".format( | |
203 | "KP_" + char if char.isdigit() else char | |
204 | ) for char in unimacro_chars | |
205 | ) | |
206 | yield template.format(macro_id, unimacro_keys) | |
207 | ||
208 | ||
209 | MACROCODE = """ | |
210 | #define UC_MODE_WIN 0 | |
211 | #define UC_MODE_LINUX 1 | |
212 | ||
213 | static uint16_t unicode_mode = UC_MODE_WIN; | |
214 | ||
215 | const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt) {{ | |
216 | if (!record->event.pressed) {{ | |
217 | return MACRO_NONE; | |
218 | }} | |
219 | // MACRODOWN only works in this function | |
220 | switch(id) {{ | |
221 | case UMS: | |
222 | unicode_mode = (unicode_mode + 1) % 2; | |
223 | break; | |
224 | {macro_cases} | |
225 | default: | |
226 | break; | |
227 | }} | |
228 | if (unicode_mode == UC_MODE_WIN) {{ | |
229 | switch(id) {{ | |
230 | {win_macro_cases} | |
231 | default: | |
232 | break; | |
233 | }} | |
234 | }} else if (unicode_mode == UC_MODE_LINUX) {{ | |
235 | switch(id) {{ | |
236 | {linux_macro_cases} | |
237 | default: | |
238 | break; | |
239 | }} | |
240 | }} | |
241 | return MACRO_NONE; | |
242 | }}; | |
243 | """ | |
244 | ||
245 | ||
246 | def iter_keycodes(layer_lines, config): | |
247 | filler_re = re.compile("[" + | |
248 | config['filler'] + " " + | |
249 | "]") | |
250 | ||
251 | all_codes = [] | |
252 | for line in layer_lines: | |
253 | line, _ = filler_re.subn("", line.strip()) | |
254 | if not line: | |
255 | continue | |
256 | codes = line.split(config['separator']) | |
257 | all_codes.extend(codes[1:-1]) | |
258 | ||
259 | key_groups = {} | |
260 | for group_index, key_indexes in enumerate(config['keymap_indexes']): | |
261 | for key_index in key_indexes: | |
262 | key_groups[key_index] = group_index | |
263 | ||
264 | keymap_indexes = sum(config['keymap_indexes'], []) | |
265 | assert len(all_codes) == len(keymap_indexes) | |
266 | code_index_pairs = zip(all_codes, keymap_indexes) | |
267 | prev_index = None | |
268 | for i, (code, key_index) in enumerate(code_index_pairs): | |
269 | code = code.strip() | |
270 | layer_match = LAYER_CHANGE_RE.match(code) | |
271 | unicode_match = UNICODE_RE.match(code) | |
272 | noncode_match = NON_CODE.match(code) | |
273 | macro_match = MACRO_RE.match(code) | |
274 | ||
275 | ws = "\n" if key_groups[key_index] != prev_index else "" | |
276 | prev_index = key_groups[key_index] | |
277 | ||
278 | try: | |
279 | if not code: | |
280 | code = 'KC_TRNS' | |
281 | elif layer_match: | |
282 | pass | |
283 | elif macro_match: | |
284 | code = MACRO(config, code) | |
285 | elif unicode_match: | |
286 | hex_code = code[1:] | |
287 | code = UNICODE_MACRO(config, chr(int(hex_code, 16))) | |
288 | elif noncode_match: | |
289 | code = UNICODE_MACRO(config, code) | |
290 | elif "_" in code: | |
291 | assert code in valid_keycodes, "unknown code '{}'".format(code) | |
292 | else: | |
293 | for prefix in config['key_prefixes']: | |
294 | if prefix + code in valid_keycodes: | |
295 | code = prefix + code | |
296 | break | |
297 | assert code in valid_keycodes, "unknown code '{}'".format(code) | |
298 | yield code, key_index, ws | |
299 | except AssertionError: | |
300 | print("Error processing code", repr(code).encode("utf-8")) | |
301 | raise | |
302 | ||
303 | USERCODE = """ | |
304 | // Runs just one time when the keyboard initializes. | |
305 | void * matrix_init_user(void) { | |
306 | ||
307 | }; | |
308 | ||
309 | // Runs constantly in the background, in a loop. | |
310 | void * matrix_scan_user(void) { | |
311 | uint8_t layer = biton32(layer_state); | |
312 | ||
313 | ergodox_board_led_off(); | |
314 | ergodox_right_led_1_off(); | |
315 | ergodox_right_led_2_off(); | |
316 | ergodox_right_led_3_off(); | |
317 | switch (layer) { | |
318 | case L1: | |
319 | ergodox_right_led_1_on(); | |
320 | break; | |
321 | case L2: | |
322 | ergodox_right_led_2_on(); | |
323 | break; | |
324 | case L3: | |
325 | ergodox_right_led_3_on(); | |
326 | break; | |
327 | case L4: | |
328 | ergodox_right_led_1_on(); | |
329 | ergodox_right_led_2_on(); | |
330 | break; | |
331 | case L5: | |
332 | ergodox_right_led_1_on(); | |
333 | ergodox_right_led_3_on(); | |
334 | break; | |
335 | // case L6: | |
336 | // ergodox_right_led_2_on(); | |
337 | // ergodox_right_led_3_on(); | |
338 | // break; | |
339 | // case L7: | |
340 | // ergodox_right_led_1_on(); | |
341 | // ergodox_right_led_2_on(); | |
342 | // ergodox_right_led_3_on(); | |
343 | // break; | |
344 | default: | |
345 | ergodox_board_led_off(); | |
346 | break; | |
347 | } | |
348 | }; | |
349 | """ | |
350 | ||
351 | def parse_keymaps(config): | |
352 | keymaps = {} | |
353 | layer_line_items = config['layer_lines'].items() | |
354 | for i, (layer_name, layer_lines) in enumerate(layer_line_items): | |
355 | print("parseing layer", layer_name) | |
356 | keymap = {} | |
357 | for code, key_index, ws in iter_keycodes(layer_lines, config): | |
358 | keymap[key_index] = (code, ws) | |
359 | keymaps[layer_name] = [v for k, v in sorted(keymap.items())] | |
360 | return keymaps | |
361 | ||
362 | ||
363 | def iter_keymap_lines(config, keymaps): | |
364 | for include_path in config['keymaps_includes']: | |
365 | yield '#include "{}"\n'.format(include_path) | |
366 | ||
367 | yield "\n" | |
368 | ||
369 | layer_items = config['layers'].items() | |
370 | for i, (layer_name, description) in enumerate(layer_items): | |
371 | yield '#define L{0:<3} {0:<5} // {1}\n'.format(i, layer_name) | |
372 | ||
373 | for i, macro_id in enumerate(config['macro_ids']): | |
374 | yield "#define {} {}\n".format(macro_id, i) | |
375 | ||
376 | yield "\n" | |
377 | ||
378 | yield "const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\n" | |
379 | ||
380 | layer_line_items = config['layer_lines'].items() | |
381 | last_index = config['keymap_indexes'][-1] | |
382 | for i, (layer_name, layer_lines) in enumerate(layer_line_items): | |
383 | keymap = keymaps[layer_name] | |
384 | yield "/*\n" | |
385 | for line in layer_lines: | |
386 | yield " *{}".format(line) | |
387 | yield "*/\n" | |
388 | ||
389 | yield "[L{0}] = KEYMAP(\n".format(i) | |
390 | ||
391 | for key_index, (code, ws) in enumerate(keymap): | |
392 | yield "\t{}".format(code) | |
393 | if key_index < len(keymap) - 1: | |
394 | yield "," | |
395 | yield ws | |
396 | yield "),\n" | |
397 | ||
398 | yield "};\n\n" | |
399 | ||
400 | yield "const uint16_t PROGMEM fn_actions[] = {\n" | |
401 | yield "};\n" | |
402 | ||
403 | yield MACROCODE.format( | |
404 | macro_cases="", | |
405 | win_macro_cases="\n".join(macro_cases(config, mode='win')), | |
406 | linux_macro_cases="\n".join(macro_cases(config, mode='linux')), | |
407 | ) | |
408 | ||
409 | yield USERCODE | |
410 | ||
411 | ||
412 | with io.open("keymap.c", mode="w", encoding="utf-8") as fh: | |
413 | for data in iter_keymap_lines(config, parse_keymaps(config)): | |
414 | fh.write(data) | |
415 | ||
416 | ||
417 | # print("\n".join(sorted(valid_keycodes))) | |
418 | # print(json.dumps(config, indent=4)) |