Add manuneo layout
[jackhill/qmk/firmware.git] / keyboard / ergodox_ez / keymaps / german-manuneo / compile_keymap.py
CommitLineData
20a3229f
MB
1# encoding: utf-8
2from __future__ import division
3from __future__ import print_function
4from __future__ import absolute_import
5from __future__ import unicode_literals
6
7import os
8import io
9import re
10import sys
11import json
12import unicodedata
13import collections
14
15PY2 = sys.version_info.major == 2
16
17if PY2:
18 chr = unichr
19
20
21ONELINE_COMMENT_RE = re.compile(r"^\s*//.*$", re.MULTILINE)
22INLINE_COMMENT_RE = re.compile(
23 r"([\,\"\[\]\{\}\d])\s+//\s[^\"\]\}\{\[]*$", re.MULTILINE
24)
25TRAILING_COMMA_RE = re.compile(
26 r",$\s*([\]\}])", re.MULTILINE
27)
28
29def 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
37with io.open("keymap.md", encoding="utf-8") as fh:
38 lines = fh.readlines()
39
40SECTIONS = [
41 'layout_config',
42 'layers',
43]
44
45config = {
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
59section_start_index = -1
60current_section = None
61current_layer_name = None
62current_layer_lines = []
63config_data = []
64
65def 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
78for 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
101end_section()
102
103KEYDEF_RE = re.compile(r"#define ((?:{})(?:\w+))".format(
104 "|".join(config['key_prefixes'])
105))
106IF0_RE = re.compile(r"^#if 0$.*?#endif", re.MULTILINE | re.DOTALL)
107COMMENT_RE = re.compile(r"/\*.*?\*/", re.MULTILINE | re.DOTALL)
108ENUM_RE = re.compile(r"(enum\s\w+\s\{.*?\};)", re.MULTILINE | re.DOTALL)
109ENUM_KEY_RE = re.compile(r"({}\w+)".format(
110 "|".join(config['key_prefixes'])
111))
112
113def 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
127valid_keycodes = set()
128basepath = os.path.abspath(os.path.join(
129 os.path.dirname(__file__), "..", "..", "..", ".."
130))
131
132valid_keycodes.update(parse_keydefs(os.path.join(
133 basepath, "tmk_core", "common", "keycode.h"
134)))
135
136for 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
142LAYER_CHANGE_RE = re.compile(r"(DF|TG|MO)\(\d+\)")
143MACRO_RE = re.compile(r"M\(\w+\)")
144UNICODE_RE = re.compile(r"U[0-9A-F]{4}")
145NON_CODE = re.compile(r"^[^A-Z0-9_]$")
146
147
148def 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
168def 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
177WIN_UNICODE_MACRO_TEMPLATE = """
178case {0}:
179 return MACRODOWN(
180 D(LALT), T(KP_PLUS), {1}, U(LALT), END
181 );
182"""
183
184LINUX_UNICODE_MACRO_TEMPLATE = """
185case {0}:
186 return MACRODOWN(
187 D(LCTRL), D(LSHIFT), T(U), U(LCTRL), U(LSHIFT), {1}, T(KP_ENTER), END
188 );
189"""
190
191def 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
209MACROCODE = """
210#define UC_MODE_WIN 0
211#define UC_MODE_LINUX 1
212
213static uint16_t unicode_mode = UC_MODE_WIN;
214
215const 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
246def 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
303USERCODE = """
304// Runs just one time when the keyboard initializes.
305void * matrix_init_user(void) {
306
307};
308
309// Runs constantly in the background, in a loop.
310void * 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
351def 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
363def 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
412with 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))