compile_keymap.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """Compiler for keymap.c files
  4. This scrip will generate a keymap.c file from a simple
  5. markdown file with a specific layout.
  6. Usage:
  7. python compile_keymap.py INPUT_PATH [OUTPUT_PATH]
  8. """
  9. from __future__ import division
  10. from __future__ import print_function
  11. from __future__ import absolute_import
  12. from __future__ import unicode_literals
  13. import os
  14. import io
  15. import re
  16. import sys
  17. import json
  18. import unicodedata
  19. import collections
  20. import itertools as it
  21. PY2 = sys.version_info.major == 2
  22. if PY2:
  23. chr = unichr
  24. KEYBOARD_LAYOUTS = {
  25. # These map positions in the parsed layout to
  26. # positions in the KEYMAP MATRIX
  27. 'ergodox_ez': [
  28. [0, 1, 2, 3, 4, 5, 6],
  29. [38, 39, 40, 41, 42, 43, 44],
  30. [7, 8, 9, 10, 11, 12, 13],
  31. [45, 46, 47, 48, 49, 50, 51],
  32. [14, 15, 16, 17, 18, 19],
  33. [52, 53, 54, 55, 56, 57],
  34. [20, 21, 22, 23, 24, 25, 26],
  35. [58, 59, 60, 61, 62, 63, 64],
  36. [27, 28, 29, 30, 31],
  37. [65, 66, 67, 68, 69],
  38. [32, 33],
  39. [70, 71],
  40. [34],
  41. [72],
  42. [35, 36, 37],
  43. [73, 74, 75],
  44. ]
  45. }
  46. ROW_INDENTS = {'ergodox_ez': [0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 5, 0, 6, 0, 4, 0]}
  47. BLANK_LAYOUTS = [
  48. # Compact Layout
  49. """
  50. .------------------------------------.------------------------------------.
  51. | | | | | | | | | | | | | | |
  52. !-----+----+----+----+----+----------!-----+----+----+----+----+----+-----!
  53. | | | | | | | | | | | | | | |
  54. !-----+----+----+----x----x----! ! !----x----x----+----+----+-----!
  55. | | | | | | |-----!-----! | | | | | |
  56. !-----+----+----+----x----x----! ! !----x----x----+----+----+-----!
  57. | | | | | | | | | | | | | | |
  58. '-----+----+----+----+----+----------'----------+----+----+----+----+-----'
  59. | | | | | | ! | | | | |
  60. '------------------------' '------------------------'
  61. .-----------. .-----------.
  62. | | | ! | |
  63. .-----+-----+-----! !-----+-----+-----.
  64. ! ! | | ! | ! !
  65. ! ! !-----! !-----! ! !
  66. | | | | ! | | |
  67. '-----------------' '-----------------'
  68. """,
  69. # Wide Layout
  70. """
  71. .---------------------------------------------. .---------------------------------------------.
  72. | | | | | | | | ! | | | | | | |
  73. !-------+-----+-----+-----+-----+-------------! !-------+-----+-----+-----+-----+-----+-------!
  74. | | | | | | | | ! | | | | | | |
  75. !-------+-----+-----+-----x-----x-----! ! ! !-----x-----x-----+-----+-----+-------!
  76. | | | | | | |-------! !-------! | | | | | |
  77. !-------+-----+-----+-----x-----x-----! ! ! !-----x-----x-----+-----+-----+-------!
  78. | | | | | | | | ! | | | | | | |
  79. '-------+-----+-----+-----+-----+-------------' '-------------+-----+-----+-----+-----+-------'
  80. | | | | | | ! | | | | |
  81. '------------------------------' '------------------------------'
  82. .---------------. .---------------.
  83. | | | ! | |
  84. .-------+-------+-------! !-------+-------+-------.
  85. ! ! | | ! | ! !
  86. ! ! !-------! !-------! ! !
  87. | | | | ! | | |
  88. '-----------------------' '-----------------------'
  89. """,
  90. ]
  91. DEFAULT_CONFIG = {
  92. "keymaps_includes": ["keymap_common.h",],
  93. 'filler': "-+.'!:x",
  94. 'separator': "|",
  95. 'default_key_prefix': ["KC_"],
  96. }
  97. SECTIONS = [
  98. 'layout_config',
  99. 'layers',
  100. ]
  101. # Markdown Parsing
  102. ONELINE_COMMENT_RE = re.compile(
  103. r"""
  104. ^ # comment must be at the start of the line
  105. \s* # arbitrary whitespace
  106. // # start of the comment
  107. (.*) # the comment
  108. $ # until the end of line
  109. """, re.MULTILINE | re.VERBOSE
  110. )
  111. INLINE_COMMENT_RE = re.compile(
  112. r"""
  113. ([\,\"\[\]\{\}\d]) # anythig that might end a expression
  114. \s+ # comment must be preceded by whitespace
  115. // # start of the comment
  116. \s # and succeded by whitespace
  117. (?:[^\"\]\}\{\[]*) # the comment (except things which might be json)
  118. $ # until the end of line
  119. """, re.MULTILINE | re.VERBOSE
  120. )
  121. TRAILING_COMMA_RE = re.compile(
  122. r"""
  123. , # the comma
  124. (?:\s*) # arbitrary whitespace
  125. $ # only works if the trailing comma is followed by newline
  126. (\s*) # arbitrary whitespace
  127. ([\]\}]) # end of an array or object
  128. """, re.MULTILINE | re.VERBOSE
  129. )
  130. def loads(raw_data):
  131. if isinstance(raw_data, bytes):
  132. raw_data = raw_data.decode('utf-8')
  133. raw_data = ONELINE_COMMENT_RE.sub(r"", raw_data)
  134. raw_data = INLINE_COMMENT_RE.sub(r"\1", raw_data)
  135. raw_data = TRAILING_COMMA_RE.sub(r"\1\2", raw_data)
  136. return json.loads(raw_data)
  137. def parse_config(path):
  138. def reset_section():
  139. section.update({
  140. 'name': section.get('name', ""),
  141. 'sub_name': "",
  142. 'start_line': -1,
  143. 'end_line': -1,
  144. 'code_lines': [],
  145. })
  146. def start_section(line_index, line):
  147. end_section()
  148. if line.startswith("# "):
  149. name = line[2:]
  150. elif line.startswith("## "):
  151. name = line[3:]
  152. else:
  153. name = ""
  154. name = name.strip().replace(" ", "_").lower()
  155. if name in SECTIONS:
  156. section['name'] = name
  157. else:
  158. section['sub_name'] = name
  159. section['start_line'] = line_index
  160. def end_section():
  161. if section['start_line'] >= 0:
  162. if section['name'] == 'layout_config':
  163. config.update(loads("\n".join(section['code_lines'])))
  164. elif section['sub_name'].startswith('layer'):
  165. layer_name = section['sub_name']
  166. config['layer_lines'][layer_name] = section['code_lines']
  167. reset_section()
  168. def amend_section(line_index, line):
  169. section['end_line'] = line_index
  170. section['code_lines'].append(line)
  171. config = DEFAULT_CONFIG.copy()
  172. config.update({
  173. 'layer_lines': collections.OrderedDict(),
  174. 'macro_ids': {'UM'},
  175. 'unicode_macros': {},
  176. })
  177. section = {}
  178. reset_section()
  179. with io.open(path, encoding="utf-8") as fh:
  180. for i, line in enumerate(fh):
  181. if line.startswith("#"):
  182. start_section(i, line)
  183. elif line.startswith(" "):
  184. amend_section(i, line[4:])
  185. else:
  186. # TODO: maybe parse description
  187. pass
  188. end_section()
  189. assert 'layout' in config
  190. return config
  191. # header file parsing
  192. IF0_RE = re.compile(r"""
  193. ^
  194. #if 0
  195. $.*?
  196. #endif
  197. """, re.MULTILINE | re.DOTALL | re.VERBOSE)
  198. COMMENT_RE = re.compile(r"""
  199. /\*
  200. .*?
  201. \*/"
  202. """, re.MULTILINE | re.DOTALL | re.VERBOSE)
  203. def read_header_file(path):
  204. with io.open(path, encoding="utf-8") as fh:
  205. data = fh.read()
  206. data, _ = COMMENT_RE.subn("", data)
  207. data, _ = IF0_RE.subn("", data)
  208. return data
  209. def regex_partial(re_str_fmt, flags):
  210. def partial(*args, **kwargs):
  211. re_str = re_str_fmt.format(*args, **kwargs)
  212. return re.compile(re_str, flags)
  213. return partial
  214. KEYDEF_REP = regex_partial(r"""
  215. #define
  216. \s
  217. (
  218. (?:{}) # the prefixes
  219. (?:\w+) # the key name
  220. ) # capture group end
  221. """, re.MULTILINE | re.DOTALL | re.VERBOSE)
  222. ENUM_RE = re.compile(r"""
  223. (
  224. enum
  225. \s\w+\s
  226. \{
  227. .*? # the enum content
  228. \}
  229. ;
  230. ) # capture group end
  231. """, re.MULTILINE | re.DOTALL | re.VERBOSE)
  232. ENUM_KEY_REP = regex_partial(r"""
  233. (
  234. {} # the prefixes
  235. \w+ # the key name
  236. ) # capture group end
  237. """, re.MULTILINE | re.DOTALL | re.VERBOSE)
  238. def parse_keydefs(config, data):
  239. prefix_options = "|".join(config['key_prefixes'])
  240. keydef_re = KEYDEF_REP(prefix_options)
  241. enum_key_re = ENUM_KEY_REP(prefix_options)
  242. for match in keydef_re.finditer(data):
  243. yield match.groups()[0]
  244. for enum_match in ENUM_RE.finditer(data):
  245. enum = enum_match.groups()[0]
  246. for key_match in enum_key_re.finditer(enum):
  247. yield key_match.groups()[0]
  248. def parse_valid_keys(config, out_path):
  249. basepath = os.path.abspath(os.path.join(os.path.dirname(out_path)))
  250. dirpaths = []
  251. subpaths = []
  252. while len(subpaths) < 6:
  253. path = os.path.join(basepath, *subpaths)
  254. dirpaths.append(path)
  255. dirpaths.append(os.path.join(path, "tmk_core", "common"))
  256. dirpaths.append(os.path.join(path, "quantum"))
  257. subpaths.append('..')
  258. includes = set(config['keymaps_includes'])
  259. includes.add("keycode.h")
  260. valid_keycodes = set()
  261. for dirpath, include in it.product(dirpaths, includes):
  262. include_path = os.path.join(dirpath, include)
  263. if os.path.exists(include_path):
  264. header_data = read_header_file(include_path)
  265. valid_keycodes.update(parse_keydefs(config, header_data))
  266. return valid_keycodes
  267. # Keymap Parsing
  268. def iter_raw_codes(layer_lines, filler, separator):
  269. filler_re = re.compile("[" + filler + " ]")
  270. for line in layer_lines:
  271. line, _ = filler_re.subn("", line.strip())
  272. if not line:
  273. continue
  274. codes = line.split(separator)
  275. for code in codes[1:-1]:
  276. yield code
  277. def iter_indexed_codes(raw_codes, key_indexes):
  278. key_rows = {}
  279. key_indexes_flat = []
  280. for row_index, key_indexes in enumerate(key_indexes):
  281. for key_index in key_indexes:
  282. key_rows[key_index] = row_index
  283. key_indexes_flat.extend(key_indexes)
  284. assert len(raw_codes) == len(key_indexes_flat)
  285. for raw_code, key_index in zip(raw_codes, key_indexes_flat):
  286. # we keep track of the row mostly for layout purposes
  287. yield raw_code, key_index, key_rows[key_index]
  288. LAYER_CHANGE_RE = re.compile(r"""
  289. (DF|TG|MO)\(\d+\)
  290. """, re.VERBOSE)
  291. MACRO_RE = re.compile(r"""
  292. M\(\w+\)
  293. """, re.VERBOSE)
  294. UNICODE_RE = re.compile(r"""
  295. U[0-9A-F]{4}
  296. """, re.VERBOSE)
  297. NON_CODE = re.compile(r"""
  298. ^[^A-Z0-9_]$
  299. """, re.VERBOSE)
  300. def parse_uni_code(raw_code):
  301. macro_id = "UC_" + (unicodedata.name(raw_code).replace(" ", "_").replace("-", "_"))
  302. code = "M({})".format(macro_id)
  303. uc_hex = "{:04X}".format(ord(raw_code))
  304. return code, macro_id, uc_hex
  305. def parse_key_code(raw_code, key_prefixes, valid_keycodes):
  306. if raw_code in valid_keycodes:
  307. return raw_code
  308. for prefix in key_prefixes:
  309. code = prefix + raw_code
  310. if code in valid_keycodes:
  311. return code
  312. def parse_code(raw_code, key_prefixes, valid_keycodes):
  313. if not raw_code:
  314. return 'KC_TRNS', None, None
  315. if LAYER_CHANGE_RE.match(raw_code):
  316. return raw_code, None, None
  317. if MACRO_RE.match(raw_code):
  318. macro_id = raw_code[2:-1]
  319. return raw_code, macro_id, None
  320. if UNICODE_RE.match(raw_code):
  321. hex_code = raw_code[1:]
  322. return parse_uni_code(chr(int(hex_code, 16)))
  323. if NON_CODE.match(raw_code):
  324. return parse_uni_code(raw_code)
  325. code = parse_key_code(raw_code, key_prefixes, valid_keycodes)
  326. return code, None, None
  327. def parse_keymap(config, key_indexes, layer_lines, valid_keycodes):
  328. keymap = {}
  329. raw_codes = list(iter_raw_codes(layer_lines, config['filler'], config['separator']))
  330. indexed_codes = iter_indexed_codes(raw_codes, key_indexes)
  331. key_prefixes = config['key_prefixes']
  332. for raw_code, key_index, row_index in indexed_codes:
  333. code, macro_id, uc_hex = parse_code(raw_code, key_prefixes, valid_keycodes)
  334. # TODO: line numbers for invalid codes
  335. err_msg = "Could not parse key '{}' on row {}".format(raw_code, row_index)
  336. assert code is not None, err_msg
  337. # print(repr(raw_code), repr(code), macro_id, uc_hex)
  338. if macro_id:
  339. config['macro_ids'].add(macro_id)
  340. if uc_hex:
  341. config['unicode_macros'][macro_id] = uc_hex
  342. keymap[key_index] = (code, row_index)
  343. return keymap
  344. def parse_keymaps(config, valid_keycodes):
  345. keymaps = collections.OrderedDict()
  346. key_indexes = config.get('key_indexes', KEYBOARD_LAYOUTS[config['layout']])
  347. # TODO: maybe validate key_indexes
  348. for layer_name, layer_lines, in config['layer_lines'].items():
  349. keymaps[layer_name] = parse_keymap(config, key_indexes, layer_lines, valid_keycodes)
  350. return keymaps
  351. # keymap.c output
  352. USERCODE = """
  353. // Runs just one time when the keyboard initializes.
  354. void matrix_init_user(void) {
  355. };
  356. // Runs constantly in the background, in a loop.
  357. void matrix_scan_user(void) {
  358. uint8_t layer = get_highest_layer(layer_state);
  359. ergodox_board_led_off();
  360. ergodox_right_led_1_off();
  361. ergodox_right_led_2_off();
  362. ergodox_right_led_3_off();
  363. switch (layer) {
  364. case L1:
  365. ergodox_right_led_1_on();
  366. break;
  367. case L2:
  368. ergodox_right_led_2_on();
  369. break;
  370. case L3:
  371. ergodox_right_led_3_on();
  372. break;
  373. case L4:
  374. ergodox_right_led_1_on();
  375. ergodox_right_led_2_on();
  376. break;
  377. case L5:
  378. ergodox_right_led_1_on();
  379. ergodox_right_led_3_on();
  380. break;
  381. // case L6:
  382. // ergodox_right_led_2_on();
  383. // ergodox_right_led_3_on();
  384. // break;
  385. // case L7:
  386. // ergodox_right_led_1_on();
  387. // ergodox_right_led_2_on();
  388. // ergodox_right_led_3_on();
  389. // break;
  390. default:
  391. ergodox_board_led_off();
  392. break;
  393. }
  394. };
  395. """
  396. MACROCODE = """
  397. #define UC_MODE_WIN 0
  398. #define UC_MODE_LINUX 1
  399. #define UC_MODE_OSX 2
  400. // TODO: allow default mode to be configured
  401. static uint16_t unicode_mode = UC_MODE_WIN;
  402. uint16_t hextokeycode(uint8_t hex) {{
  403. if (hex == 0x0) {{
  404. return KC_P0;
  405. }}
  406. if (hex < 0xA) {{
  407. return KC_P1 + (hex - 0x1);
  408. }}
  409. return KC_A + (hex - 0xA);
  410. }}
  411. void unicode_action_function(uint16_t hi, uint16_t lo) {{
  412. switch (unicode_mode) {{
  413. case UC_MODE_WIN:
  414. register_code(KC_LALT);
  415. register_code(KC_PPLS);
  416. unregister_code(KC_PPLS);
  417. register_code(hextokeycode((hi & 0xF0) >> 4));
  418. unregister_code(hextokeycode((hi & 0xF0) >> 4));
  419. register_code(hextokeycode((hi & 0x0F)));
  420. unregister_code(hextokeycode((hi & 0x0F)));
  421. register_code(hextokeycode((lo & 0xF0) >> 4));
  422. unregister_code(hextokeycode((lo & 0xF0) >> 4));
  423. register_code(hextokeycode((lo & 0x0F)));
  424. unregister_code(hextokeycode((lo & 0x0F)));
  425. unregister_code(KC_LALT);
  426. break;
  427. case UC_MODE_LINUX:
  428. register_code(KC_LCTL);
  429. register_code(KC_LSFT);
  430. register_code(KC_U);
  431. unregister_code(KC_U);
  432. register_code(hextokeycode((hi & 0xF0) >> 4));
  433. unregister_code(hextokeycode((hi & 0xF0) >> 4));
  434. register_code(hextokeycode((hi & 0x0F)));
  435. unregister_code(hextokeycode((hi & 0x0F)));
  436. register_code(hextokeycode((lo & 0xF0) >> 4));
  437. unregister_code(hextokeycode((lo & 0xF0) >> 4));
  438. register_code(hextokeycode((lo & 0x0F)));
  439. unregister_code(hextokeycode((lo & 0x0F)));
  440. unregister_code(KC_LCTL);
  441. unregister_code(KC_LSFT);
  442. break;
  443. case UC_MODE_OSX:
  444. break;
  445. }}
  446. }}
  447. const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt) {{
  448. if (!record->event.pressed) {{
  449. return MACRO_NONE;
  450. }}
  451. // MACRODOWN only works in this function
  452. switch(id) {{
  453. case UM:
  454. unicode_mode = (unicode_mode + 1) % 2;
  455. break;
  456. {macro_cases}
  457. {unicode_macro_cases}
  458. default:
  459. break;
  460. }}
  461. return MACRO_NONE;
  462. }};
  463. """
  464. UNICODE_MACRO_TEMPLATE = """
  465. case {macro_id}:
  466. unicode_action_function(0x{hi:02x}, 0x{lo:02x});
  467. break;
  468. """.strip()
  469. def unicode_macro_cases(config):
  470. for macro_id, uc_hex in config['unicode_macros'].items():
  471. hi = int(uc_hex, 16) >> 8
  472. lo = int(uc_hex, 16) & 0xFF
  473. yield UNICODE_MACRO_TEMPLATE.format(macro_id=macro_id, hi=hi, lo=lo)
  474. def iter_keymap_lines(keymap, row_indents=None):
  475. col_widths = {}
  476. col = 0
  477. # first pass, figure out the column widths
  478. prev_row_index = None
  479. for code, row_index in keymap.values():
  480. if row_index != prev_row_index:
  481. col = 0
  482. if row_indents:
  483. col = row_indents[row_index]
  484. col_widths[col] = max(len(code), col_widths.get(col, 0))
  485. prev_row_index = row_index
  486. col += 1
  487. # second pass, yield the cell values
  488. col = 0
  489. prev_row_index = None
  490. for key_index in sorted(keymap):
  491. code, row_index = keymap[key_index]
  492. if row_index != prev_row_index:
  493. col = 0
  494. yield "\n"
  495. if row_indents:
  496. for indent_col in range(row_indents[row_index]):
  497. pad = " " * (col_widths[indent_col] - 4)
  498. yield (" /*-*/" + pad)
  499. col = row_indents[row_index]
  500. else:
  501. yield pad
  502. yield " {}".format(code)
  503. if key_index < len(keymap) - 1:
  504. yield ","
  505. # This will be yielded on the next iteration when
  506. # we know that we're not at the end of a line.
  507. pad = " " * (col_widths[col] - len(code))
  508. prev_row_index = row_index
  509. col += 1
  510. def iter_keymap_parts(config, keymaps):
  511. # includes
  512. for include_path in config['keymaps_includes']:
  513. yield '#include "{}"\n'.format(include_path)
  514. yield "\n"
  515. # definitions
  516. for i, macro_id in enumerate(sorted(config['macro_ids'])):
  517. yield "#define {} {}\n".format(macro_id, i)
  518. yield "\n"
  519. for i, layer_name in enumerate(config['layer_lines']):
  520. yield '#define L{0:<3} {0:<5} // {1}\n'.format(i, layer_name)
  521. yield "\n"
  522. # keymaps
  523. yield "const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\n"
  524. for i, layer_name in enumerate(config['layer_lines']):
  525. # comment
  526. layer_lines = config['layer_lines'][layer_name]
  527. prefixed_lines = " * " + " * ".join(layer_lines)
  528. yield "/*\n{} */\n".format(prefixed_lines)
  529. # keymap codes
  530. keymap = keymaps[layer_name]
  531. row_indents = ROW_INDENTS.get(config['layout'])
  532. keymap_lines = "".join(iter_keymap_lines(keymap, row_indents))
  533. yield "[L{0}] = KEYMAP({1}\n),\n".format(i, keymap_lines)
  534. yield "};\n\n"
  535. # macros
  536. yield MACROCODE.format(
  537. macro_cases="",
  538. unicode_macro_cases="\n".join(unicode_macro_cases(config)),
  539. )
  540. # TODO: dynamically create blinking lights
  541. yield USERCODE
  542. def main(argv=sys.argv[1:]):
  543. if not argv or '-h' in argv or '--help' in argv:
  544. print(__doc__)
  545. return 0
  546. in_path = os.path.abspath(argv[0])
  547. if not os.path.exists(in_path):
  548. print("No such file '{}'".format(in_path))
  549. return 1
  550. if len(argv) > 1:
  551. out_path = os.path.abspath(argv[1])
  552. else:
  553. dirname = os.path.dirname(in_path)
  554. out_path = os.path.join(dirname, "keymap.c")
  555. config = parse_config(in_path)
  556. valid_keys = parse_valid_keys(config, out_path)
  557. keymaps = parse_keymaps(config, valid_keys)
  558. with io.open(out_path, mode="w", encoding="utf-8") as fh:
  559. for part in iter_keymap_parts(config, keymaps):
  560. fh.write(part)
  561. if __name__ == '__main__':
  562. sys.exit(main())