mirror of
				https://github.com/mfulz/qmk_firmware.git
				synced 2025-10-31 21:32:31 +01:00 
			
		
		
		
	 8b832c494c
			
		
	
	
		8b832c494c
		
	
	
	
	
		
			
			* add beautifier * add example * Update keyboards/ergodox_ez/util/keymap_beautifier.py Co-Authored-By: tsankuanglee <1425438+tsankuanglee@users.noreply.github.com> * Update keyboards/ergodox_ez/util/keymap_beautifier.py Co-Authored-By: tsankuanglee <1425438+tsankuanglee@users.noreply.github.com> * works for regular layout * all planned features implemented * add justification switch * docker support * doc and starting script * clean up the container after done
		
			
				
	
	
		
			400 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			400 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python
 | |
| 
 | |
| import argparse
 | |
| import pycparser
 | |
| import re
 | |
| 
 | |
| class KeymapBeautifier:
 | |
|     justify_toward_center = False
 | |
|     filename_in = None
 | |
|     filename_out = None
 | |
|     output_layout = None
 | |
|     output = None
 | |
| 
 | |
|     column_max_widths = {}
 | |
| 
 | |
|     KEY_ALIASES = {
 | |
|         "KC_TRANSPARENT": "_______",
 | |
|         "KC_TRNS": "_______",
 | |
|         "KC_NO": "XXXXXXX",
 | |
|     }
 | |
|     KEYMAP_START = 'const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\n'
 | |
|     KEYMAP_END = '};\n'
 | |
|     KEYMAP_START_REPLACEMENT = "const int keymaps[]={\n"
 | |
|     KEY_CHART = """
 | |
|     /*
 | |
|      * ,--------------------------------------------------.    ,--------------------------------------------------.
 | |
|      * |    0   |   1  |   2  |   3  |   4  |   5  |  6   |    |  38  |  39  |  40  |  41  |  42  |  43  |   44   |
 | |
|      * |--------+------+------+------+------+------+------|    |------+------+------+------+------+------+--------|
 | |
|      * |    7   |   8  |   9  |  10  |  11  |  12  |  13  |    |  45  |  46  |  47  |  48  |  49  |  50  |   51   |
 | |
|      * |--------+------+------+------+------+------|      |    |      |------+------+------+------+------+--------|
 | |
|      * |   14   |  15  |  16  |  17  |  18  |  19  |------|    |------|  52  |  53  |  54  |  55  |  56  |   57   |
 | |
|      * |--------+------+------+------+------+------|  26  |    |  58  |------+------+------+------+------+--------|
 | |
|      * |   20   |  21  |  22  |  23  |  24  |  25  |      |    |      |  59  |  60  |  61  |  62  |  63  |   64   |
 | |
|      * `--------+------+------+------+------+-------------'    `-------------+------+------+------+------+--------'
 | |
|      *   |  27  |  28  |  29  |  30  |  31  |                                |  65  |  66  |  67  |  68  |  69  |
 | |
|      *   `----------------------------------'                                `----------------------------------'
 | |
|      *                                       ,-------------.  ,-------------.
 | |
|      *                                       |  32  |  33  |  |  70  |  71  |
 | |
|      *                                ,------+------+------|  |------+------+------.
 | |
|      *                                |      |      |  34  |  |  72  |      |      |
 | |
|      *                                |  35  |  36  |------|  |------|  74  |  75  |
 | |
|      *                                |      |      |  37  |  |  73  |      |      |
 | |
|      *                                `--------------------'  `--------------------'
 | |
|      */
 | |
| """
 | |
|     KEY_COORDINATES = {
 | |
|         'LAYOUT_ergodox': [
 | |
|             # left hand
 | |
|             (0,0), (0,1), (0,2), (0,3), (0,4), (0,5), (0,6),
 | |
|             (1,0), (1,1), (1,2), (1,3), (1,4), (1,5), (1,6),
 | |
|             (2,0), (2,1), (2,2), (2,3), (2,4), (2,5),
 | |
|             (3,0), (3,1), (3,2), (3,3), (3,4), (3,5), (3,6),
 | |
|             (4,0), (4,1), (4,2), (4,3), (4,4),
 | |
|             # left thumb
 | |
|                                                (5,5), (5,6),
 | |
|                                                       (6,6),
 | |
|                                         (7,4), (7,5), (7,6),
 | |
|             # right hand
 | |
|              (8,0),  (8,1),  (8,2),  (8,3),  (8,4),  (8,5),  (8,6),
 | |
|              (9,0),  (9,1),  (9,2),  (9,3),  (9,4),  (9,5),  (9,6),
 | |
|                     (10,1), (10,2), (10,3), (10,4), (10,5), (10,6),
 | |
|             (11,0), (11,1), (11,2), (11,3), (11,4), (11,5), (11,6),
 | |
|                             (12,2), (12,3), (12,4), (12,5), (12,6),
 | |
|             # right thumb
 | |
|             (13,0), (13,1),
 | |
|             (14,0),
 | |
|             (15,0), (15,1), (15,2)
 | |
|         ],
 | |
|         'LAYOUT_ergodox_pretty': [
 | |
|             # left hand and right hand
 | |
|             (0,0), (0,1), (0,2), (0,3), (0,4), (0,5), (0,6),   (0,7), (0,8), (0,9), (0,10), (0,11), (0,12), (0,13),
 | |
|             (1,0), (1,1), (1,2), (1,3), (1,4), (1,5), (1,6),   (1,7), (1,8), (1,9), (1,10), (1,11), (1,12), (1,13),
 | |
|             (2,0), (2,1), (2,2), (2,3), (2,4), (2,5),                 (2,8), (2,9), (2,10), (2,11), (2,12), (2,13),
 | |
|             (3,0), (3,1), (3,2), (3,3), (3,4), (3,5), (3,6),   (3,7), (3,8), (3,9), (3,10), (3,11), (3,12), (3,13),
 | |
|             (4,0), (4,1), (4,2), (4,3), (4,4),                               (4,9), (4,10), (4,11), (4,12), (4,13),
 | |
| 
 | |
|             # left thumb and right thumb
 | |
|                                                (5,5), (5,6),   (5,7), (5,8),
 | |
|                                                       (6,6),   (6,7),
 | |
|                                         (7,4), (7,5), (7,6),   (7,7), (7,8), (7,9)
 | |
|         ],
 | |
|     }
 | |
|     current_converted_KEY_COORDINATES = []
 | |
| 
 | |
|     # each column is aligned within each group (tuples of row indexes are inclusive)
 | |
|     KEY_ROW_GROUPS = {
 | |
|         'LAYOUT_ergodox': [(0,4),(5,7),(8,12),(13,15)],
 | |
|         'LAYOUT_ergodox_pretty': [(0,7)],
 | |
|         #'LAYOUT_ergodox_pretty': [(0,5),(6,7)],
 | |
|         #'LAYOUT_ergodox_pretty': [(0,3),(4,4),(5,7)],
 | |
|         #'LAYOUT_ergodox_pretty': [(0,4),(5,7)],
 | |
|     }
 | |
| 
 | |
| 
 | |
|     INDEX_CONVERSTION_LAYOUT_ergodox_pretty_to_LAYOUT_ergodox = [
 | |
|          0, 1, 2, 3, 4, 5, 6,  38,39,40,41,42,43,44,
 | |
|          7, 8, 9,10,11,12,13,  45,46,47,48,49,50,51,
 | |
|         14,15,16,17,18,19,        52,53,54,55,56,57,
 | |
|         20,21,22,23,24,25,26,  58,59,60,61,62,63,64,
 | |
|         27,28,29,30,31,              65,66,67,68,69,
 | |
|                        32,33,  70,71,
 | |
|                           34,  72,
 | |
|                     35,36,37,  73,74,75,
 | |
|     ]
 | |
| 
 | |
| 
 | |
|     def index_conversion_map_reversed(self, conversion_map):
 | |
|         return [conversion_map.index(i) for i in range(len(conversion_map))]
 | |
| 
 | |
| 
 | |
|     def __init__(self, source_code = "",  output_layout="LAYOUT_ergodox", justify_toward_center = False):
 | |
|         self.output_layout = output_layout
 | |
|         self.justify_toward_center = justify_toward_center
 | |
|         # determine the conversion map
 | |
|         #if input_layout == self.output_layout:
 | |
|         #    conversion_map = [i for i in range(len(self.INDEX_CONVERSTION_LAYOUT_ergodox_pretty_to_LAYOUT_ergodox))]
 | |
|         #conversion_map = self.INDEX_CONVERSTION_LAYOUT_ergodox_pretty_to_LAYOUT_ergodox
 | |
|         if self.output_layout == "LAYOUT_ergodox_pretty":
 | |
|             index_conversion_map = self.index_conversion_map_reversed(self.INDEX_CONVERSTION_LAYOUT_ergodox_pretty_to_LAYOUT_ergodox)
 | |
|         else:
 | |
|             index_conversion_map = list(range(len(self.INDEX_CONVERSTION_LAYOUT_ergodox_pretty_to_LAYOUT_ergodox)))
 | |
|         self.current_converted_KEY_COORDINATES = [
 | |
|             self.KEY_COORDINATES[self.output_layout][index_conversion_map[i]]
 | |
|             for i in range(len(self.KEY_COORDINATES[self.output_layout]))
 | |
|         ]
 | |
| 
 | |
|         self.output = self.beautify_source_code(source_code)
 | |
| 
 | |
|     def beautify_source_code(self, source_code):
 | |
|         # to keep it simple for the parser, we only use the parser to parse the key definition part
 | |
|         src = {
 | |
|             "before": [],
 | |
|             "keys": [],
 | |
|             "after": [],
 | |
|         }
 | |
| 
 | |
|         current_section = "before"
 | |
|         for line in source_code.splitlines(True):
 | |
|             if current_section == 'before' and line == self.KEYMAP_START:
 | |
|                 src[current_section].append("\n")
 | |
|                 current_section = 'keys'
 | |
|                 src[current_section].append(self.KEYMAP_START_REPLACEMENT)
 | |
|                 continue
 | |
|             elif current_section == 'keys' and line == self.KEYMAP_END:
 | |
|                 src[current_section].append(self.KEYMAP_END)
 | |
|                 current_section = 'after'
 | |
|                 continue
 | |
|             src[current_section].append(line)
 | |
|         output_lines = src['before'] + self.beautify_keys_section("".join(src['keys'])) + src['after']
 | |
|         return "".join(output_lines)
 | |
| 
 | |
|     def beautify_keys_section(self, src):
 | |
|         parsed = self.parser(src)
 | |
|         layer_output = []
 | |
| 
 | |
|         keymap = parsed.children()[0]
 | |
|         layers = keymap[1]
 | |
|         for layer in layers.init.exprs:
 | |
|             input_layout = layer.expr.name.name
 | |
| 
 | |
|             key_symbols = self.layer_expr(layer)
 | |
|             # re-order keys from input_layout to regular layout
 | |
|             if input_layout == "LAYOUT_ergodox_pretty":
 | |
|                 key_symbols = [key_symbols[i] for i in self.index_conversion_map_reversed(self.INDEX_CONVERSTION_LAYOUT_ergodox_pretty_to_LAYOUT_ergodox)]
 | |
| 
 | |
|             padded_key_symbols = self.pad_key_symbols(key_symbols, input_layout)
 | |
|             current_pretty_output_layer = self.pretty_output_layer(layer.name[0].value, padded_key_symbols)
 | |
|             # strip trailing spaces from padding
 | |
|             layer_output.append(re.sub(r" +\n", "\n", current_pretty_output_layer))
 | |
| 
 | |
|         return [self.KEYMAP_START + "\n",
 | |
|                 self.KEY_CHART + "\n",
 | |
|                 ",\n\n".join(layer_output) + "\n",
 | |
|                 self.KEYMAP_END + "\n"]
 | |
| 
 | |
|     def get_row_group(self, row):
 | |
|         for low, high in self.KEY_ROW_GROUPS[self.output_layout]:
 | |
|             if low <= row <= high:
 | |
|                 return (low, high)
 | |
|         raise Exception("Cannot find row groups in KEY_ROW_GROUPS")
 | |
| 
 | |
| 
 | |
|     def calculate_column_max_widths(self, key_symbols):
 | |
|         # calculate the max width for each column
 | |
|         self.column_max_widths = {}
 | |
|         for i in range(len(key_symbols)):
 | |
|             row_index, column_index = self.current_converted_KEY_COORDINATES[i]
 | |
|             row_group = self.get_row_group(row_index)
 | |
|             if (row_group, column_index) in self.column_max_widths:
 | |
|                 self.column_max_widths[(row_group, column_index)] = max(self.column_max_widths[(row_group, column_index)], len(key_symbols[i]))
 | |
|             else:
 | |
|                 self.column_max_widths[(row_group, column_index)] = len(key_symbols[i])
 | |
| 
 | |
| 
 | |
|     def pad_key_symbols(self, key_symbols, input_layout, just='left'):
 | |
|         self.calculate_column_max_widths(key_symbols)
 | |
| 
 | |
|         padded_key_symbols = []
 | |
|         # pad each key symbol
 | |
|         for i in range(len(key_symbols)):
 | |
|             key = key_symbols[i]
 | |
|             # look up column coordinate to determine number of spaces to pad
 | |
|             row_index, column_index = self.current_converted_KEY_COORDINATES[i]
 | |
|             row_group = self.get_row_group(row_index)
 | |
|             if just == 'left':
 | |
|                 padded_key_symbols.append(key.ljust(self.column_max_widths[(row_group, column_index)]))
 | |
|             else:
 | |
|                 padded_key_symbols.append(key.rjust(self.column_max_widths[(row_group, column_index)]))
 | |
|         return padded_key_symbols
 | |
| 
 | |
| 
 | |
|     layer_keys_pointer = 0
 | |
|     layer_keys = None
 | |
|     def grab_next_n_columns(self, n_columns, input_layout, layer_keys = None, from_beginning = False):
 | |
|         if layer_keys:
 | |
|             self.layer_keys = layer_keys
 | |
|         if from_beginning:
 | |
|             self.layer_keys_pointer = 0
 | |
| 
 | |
|         begin = self.layer_keys_pointer
 | |
|         end = begin + n_columns
 | |
|         return self.layer_keys[self.layer_keys_pointer-n_keys:self.layer_keys_pointer]
 | |
| 
 | |
|     key_coordinates_counter = 0
 | |
|     def get_padded_line(self, source_keys, key_from, key_to, just="left"):
 | |
|         if just == "right":
 | |
|             keys = [k.strip().rjust(len(k)) for k in source_keys[key_from:key_to]]
 | |
|         else:
 | |
|             keys = [k for k in source_keys[key_from:key_to]]
 | |
| 
 | |
|         from_row, from_column = self.KEY_COORDINATES[self.output_layout][self.key_coordinates_counter]
 | |
|         row_group = self.get_row_group(from_row)
 | |
|         self.key_coordinates_counter += key_to - key_from
 | |
|         columns_before_key_from = sorted([col for row, col in self.KEY_COORDINATES[self.output_layout] if row == from_row and col < from_column])
 | |
|         # figure out which columns in this row needs padding; only pad empty columns to the right of an existing column
 | |
|         columns_to_pad = { c: True for c in range(from_column) }
 | |
|         if columns_before_key_from:
 | |
|             for c in range(max(columns_before_key_from)+1):
 | |
|                 columns_to_pad[c] = False
 | |
| 
 | |
|         # for rows with fewer columns that don't start with column 0, we need to insert leading spaces
 | |
|         spaces = 0
 | |
|         for c, v in columns_to_pad.items():
 | |
|             if not v:
 | |
|                 continue
 | |
|             if (row_group,c) in self.column_max_widths:
 | |
|                 spaces += self.column_max_widths[(row_group,c)] + len(", ")
 | |
|             else:
 | |
|                 spaces += 0
 | |
|         return " " * spaces + ", ".join(keys) + ","
 | |
| 
 | |
|     def pretty_output_layer(self, layer, keys):
 | |
|         self.key_coordinates_counter = 0
 | |
|         if self.output_layout == "LAYOUT_ergodox":
 | |
|             formatted_key_symbols = """
 | |
| // left hand
 | |
| 
 | |
| {}
 | |
| {}
 | |
| {}
 | |
| {}
 | |
| {}
 | |
| 
 | |
| // left thumb
 | |
| 
 | |
| {}
 | |
| {}
 | |
| {}
 | |
| 
 | |
| // right hand
 | |
| 
 | |
| {}
 | |
| {}
 | |
| {}
 | |
| {}
 | |
| {}
 | |
| 
 | |
| // right thumb
 | |
| 
 | |
| {}
 | |
| {}
 | |
| {}
 | |
| """.format(
 | |
|             # left hand
 | |
|             self.get_padded_line(keys,  0,  7, just="left"),
 | |
|             self.get_padded_line(keys,  7, 14, just="left"),
 | |
|             self.get_padded_line(keys, 14, 20, just="left"),
 | |
|             self.get_padded_line(keys, 20, 27, just="left"),
 | |
|             self.get_padded_line(keys, 27, 32, just="left"),
 | |
|             # left thumb
 | |
|             self.get_padded_line(keys, 32, 34, just="left"),
 | |
|             self.get_padded_line(keys, 34, 35, just="left"),
 | |
|             self.get_padded_line(keys, 35, 38, just="left"),
 | |
|             # right hand
 | |
|             self.get_padded_line(keys, 38, 45, just="left"),
 | |
|             self.get_padded_line(keys, 45, 52, just="left"),
 | |
|             self.get_padded_line(keys, 52, 58, just="left"),
 | |
|             self.get_padded_line(keys, 58, 65, just="left"),
 | |
|             self.get_padded_line(keys, 65, 70, just="left"),
 | |
|             # right thumb
 | |
|             self.get_padded_line(keys, 70, 72, just="left"),
 | |
|             self.get_padded_line(keys, 72, 73, just="left"),
 | |
|             self.get_padded_line(keys, 73, 76, just="left"),
 | |
|             )
 | |
|         elif self.output_layout == "LAYOUT_ergodox_pretty":
 | |
|             left_half_justification = "right" if self.justify_toward_center else "left"
 | |
|             formatted_key_symbols = """
 | |
| {}      {}
 | |
| {}      {}
 | |
| {}      {}
 | |
| {}      {}
 | |
| {}      {}
 | |
| 
 | |
| {}      {}
 | |
| {}      {}
 | |
| {}      {}
 | |
| """.format(
 | |
|                 self.get_padded_line(keys,  0,  7, just=left_half_justification), self.get_padded_line(keys, 38, 45, just="left"),
 | |
|                 self.get_padded_line(keys,  7, 14, just=left_half_justification), self.get_padded_line(keys, 45, 52, just="left"),
 | |
|                 self.get_padded_line(keys, 14, 20, just=left_half_justification), self.get_padded_line(keys, 52, 58, just="left"),
 | |
|                 self.get_padded_line(keys, 20, 27, just=left_half_justification), self.get_padded_line(keys, 58, 65, just="left"),
 | |
|                 self.get_padded_line(keys, 27, 32, just=left_half_justification), self.get_padded_line(keys, 65, 70, just="left"),
 | |
| 
 | |
|                 self.get_padded_line(keys, 32, 34, just=left_half_justification), self.get_padded_line(keys, 70, 72, just="left"),
 | |
|                 self.get_padded_line(keys, 34, 35, just=left_half_justification), self.get_padded_line(keys, 72, 73, just="left"),
 | |
|                 self.get_padded_line(keys, 35, 38, just=left_half_justification), self.get_padded_line(keys, 73, 76, just="left"),
 | |
| 
 | |
|                 )
 | |
|         else:
 | |
|             formatted_key_symbols = ""
 | |
| 
 | |
|         # rid of the trailing comma
 | |
|         formatted_key_symbols = formatted_key_symbols[0:len(formatted_key_symbols)-2] + "\n"
 | |
|         s = "[{}] = {}({})".format(layer, self.output_layout, formatted_key_symbols)
 | |
|         return s
 | |
| 
 | |
|     # helper functions for pycparser
 | |
|     def parser(self, src):
 | |
|         src = self.comment_remover(src)
 | |
|         return pycparser.CParser().parse(src)
 | |
|     def comment_remover(self, text):
 | |
|         # remove comments since pycparser cannot deal with them
 | |
|         # credit: https://stackoverflow.com/a/241506
 | |
|         def replacer(match):
 | |
|             s = match.group(0)
 | |
|             if s.startswith('/'):
 | |
|                 return " " # note: a space and not an empty string
 | |
|             else:
 | |
|                 return s
 | |
|         pattern = re.compile(
 | |
|             r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
 | |
|             re.DOTALL | re.MULTILINE
 | |
|         )
 | |
|         return re.sub(pattern, replacer, text)
 | |
| 
 | |
|     def function_expr(self, f):
 | |
|         name = f.name.name
 | |
|         args = []
 | |
|         for arg in f.args.exprs:
 | |
|             if type(arg) is pycparser.c_ast.Constant:
 | |
|                 args.append(arg.value)
 | |
|             elif type(arg) is pycparser.c_ast.ID:
 | |
|                 args.append(arg.name)
 | |
|         return "{}({})".format(name, ",".join(args))
 | |
| 
 | |
|     def key_expr(self, raw):
 | |
|         if type(raw) is pycparser.c_ast.ID:
 | |
|             if raw.name in self.KEY_ALIASES:
 | |
|                 return self.KEY_ALIASES[raw.name]
 | |
|             return raw.name
 | |
|         elif type(raw) is pycparser.c_ast.FuncCall:
 | |
|             return self.function_expr(raw)
 | |
| 
 | |
|     def layer_expr(self, layer):
 | |
|         transformed = [self.key_expr(k) for k in layer.expr.args.exprs]
 | |
|         return transformed
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
| 
 | |
|     parser = argparse.ArgumentParser(description="Beautify keymap.c downloaded from ErgoDox-Ez Configurator for easier customization.")
 | |
|     parser.add_argument("input_filename", help="input file: c source code file that has the layer keymaps")
 | |
|     parser.add_argument("-o", "--output-filename", help="output file: beautified c filename. If not given, output to STDOUT.")
 | |
|     parser.add_argument("-p", "--pretty-output-layout", action="store_true", help="use LAYOUT_ergodox_pretty for output instead of LAYOUT_ergodox")
 | |
|     parser.add_argument("-c", "--justify-toward-center", action="store_true", help="for LAYOUT_ergodox_pretty, align right for the left half, and align left for the right half. Default is align left for both halves.")
 | |
|     args = parser.parse_args()
 | |
|     if args.pretty_output_layout:
 | |
|         output_layout="LAYOUT_ergodox_pretty"
 | |
|     else:
 | |
|         output_layout="LAYOUT_ergodox"
 | |
|     with open(args.input_filename) as f:
 | |
|         source_code = f.read()
 | |
|     result = KeymapBeautifier(source_code, output_layout=output_layout, justify_toward_center=args.justify_toward_center).output
 | |
|     if args.output_filename:
 | |
|         with open(args.output_filename, "w") as f:
 | |
|             f.write(result)
 | |
|     else:
 | |
|         print(result)
 | |
| 
 |