7 class KeymapBeautifier
:
8 justify_toward_center
= False
14 column_max_widths
= {}
17 "KC_TRANSPARENT": "_______",
21 KEYMAP_START
= 'const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\n'
23 KEYMAP_START_REPLACEMENT
= "const int keymaps[]={\n"
26 * ,--------------------------------------------------. ,--------------------------------------------------.
27 * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
28 * |--------+------+------+------+------+------+------| |------+------+------+------+------+------+--------|
29 * | 7 | 8 | 9 | 10 | 11 | 12 | 13 | | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
30 * |--------+------+------+------+------+------| | | |------+------+------+------+------+--------|
31 * | 14 | 15 | 16 | 17 | 18 | 19 |------| |------| 52 | 53 | 54 | 55 | 56 | 57 |
32 * |--------+------+------+------+------+------| 26 | | 58 |------+------+------+------+------+--------|
33 * | 20 | 21 | 22 | 23 | 24 | 25 | | | | 59 | 60 | 61 | 62 | 63 | 64 |
34 * `--------+------+------+------+------+-------------' `-------------+------+------+------+------+--------'
35 * | 27 | 28 | 29 | 30 | 31 | | 65 | 66 | 67 | 68 | 69 |
36 * `----------------------------------' `----------------------------------'
37 * ,-------------. ,-------------.
38 * | 32 | 33 | | 70 | 71 |
39 * ,------+------+------| |------+------+------.
40 * | | | 34 | | 72 | | |
41 * | 35 | 36 |------| |------| 74 | 75 |
42 * | | | 37 | | 73 | | |
43 * `--------------------' `--------------------'
49 (0,0), (0,1), (0,2), (0,3), (0,4), (0,5), (0,6),
50 (1,0), (1,1), (1,2), (1,3), (1,4), (1,5), (1,6),
51 (2,0), (2,1), (2,2), (2,3), (2,4), (2,5),
52 (3,0), (3,1), (3,2), (3,3), (3,4), (3,5), (3,6),
53 (4,0), (4,1), (4,2), (4,3), (4,4),
59 (8,0), (8,1), (8,2), (8,3), (8,4), (8,5), (8,6),
60 (9,0), (9,1), (9,2), (9,3), (9,4), (9,5), (9,6),
61 (10,1), (10,2), (10,3), (10,4), (10,5), (10,6),
62 (11,0), (11,1), (11,2), (11,3), (11,4), (11,5), (11,6),
63 (12,2), (12,3), (12,4), (12,5), (12,6),
67 (15,0), (15,1), (15,2)
69 'LAYOUT_ergodox_pretty': [
70 # left hand and right hand
71 (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),
72 (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),
73 (2,0), (2,1), (2,2), (2,3), (2,4), (2,5), (2,8), (2,9), (2,10), (2,11), (2,12), (2,13),
74 (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),
75 (4,0), (4,1), (4,2), (4,3), (4,4), (4,9), (4,10), (4,11), (4,12), (4,13),
77 # left thumb and right thumb
78 (5,5), (5,6), (5,7), (5,8),
80 (7,4), (7,5), (7,6), (7,7), (7,8), (7,9)
83 current_converted_KEY_COORDINATES
= []
85 # each column is aligned within each group (tuples of row indexes are inclusive)
87 'LAYOUT_ergodox': [(0,4),(5,7),(8,12),(13,15)],
88 'LAYOUT_ergodox_pretty': [(0,7)],
89 #'LAYOUT_ergodox_pretty': [(0,5),(6,7)],
90 #'LAYOUT_ergodox_pretty': [(0,3),(4,4),(5,7)],
91 #'LAYOUT_ergodox_pretty': [(0,4),(5,7)],
95 INDEX_CONVERSTION_LAYOUT_ergodox_pretty_to_LAYOUT_ergodox
= [
96 0, 1, 2, 3, 4, 5, 6, 38,39,40,41,42,43,44,
97 7, 8, 9,10,11,12,13, 45,46,47,48,49,50,51,
98 14,15,16,17,18,19, 52,53,54,55,56,57,
99 20,21,22,23,24,25,26, 58,59,60,61,62,63,64,
100 27,28,29,30,31, 65,66,67,68,69,
107 def index_conversion_map_reversed(self
, conversion_map
):
108 return [conversion_map
.index(i
) for i
in range(len(conversion_map
))]
111 def __init__(self
, source_code
= "", output_layout
="LAYOUT_ergodox", justify_toward_center
= False):
112 self
.output_layout
= output_layout
113 self
.justify_toward_center
= justify_toward_center
114 # determine the conversion map
115 #if input_layout == self.output_layout:
116 # conversion_map = [i for i in range(len(self.INDEX_CONVERSTION_LAYOUT_ergodox_pretty_to_LAYOUT_ergodox))]
117 #conversion_map = self.INDEX_CONVERSTION_LAYOUT_ergodox_pretty_to_LAYOUT_ergodox
118 if self
.output_layout
== "LAYOUT_ergodox_pretty":
119 index_conversion_map
= self
.index_conversion_map_reversed(self
.INDEX_CONVERSTION_LAYOUT_ergodox_pretty_to_LAYOUT_ergodox
)
121 index_conversion_map
= list(range(len(self
.INDEX_CONVERSTION_LAYOUT_ergodox_pretty_to_LAYOUT_ergodox
)))
122 self
.current_converted_KEY_COORDINATES
= [
123 self
.KEY_COORDINATES
[self
.output_layout
][index_conversion_map
[i
]]
124 for i
in range(len(self
.KEY_COORDINATES
[self
.output_layout
]))
127 self
.output
= self
.beautify_source_code(source_code
)
129 def beautify_source_code(self
, source_code
):
130 # to keep it simple for the parser, we only use the parser to parse the key definition part
137 current_section
= "before"
138 for line
in source_code
.splitlines(True):
139 if current_section
== 'before' and line
== self
.KEYMAP_START
:
140 src
[current_section
].append("\n")
141 current_section
= 'keys'
142 src
[current_section
].append(self
.KEYMAP_START_REPLACEMENT
)
144 elif current_section
== 'keys' and line
== self
.KEYMAP_END
:
145 src
[current_section
].append(self
.KEYMAP_END
)
146 current_section
= 'after'
148 src
[current_section
].append(line
)
149 output_lines
= src
['before'] + self
.beautify_keys_section("".join(src
['keys'])) + src
['after']
150 return "".join(output_lines
)
152 def beautify_keys_section(self
, src
):
153 parsed
= self
.parser(src
)
156 keymap
= parsed
.children()[0]
158 for layer
in layers
.init
.exprs
:
159 input_layout
= layer
.expr
.name
.name
161 key_symbols
= self
.layer_expr(layer
)
162 # re-order keys from input_layout to regular layout
163 if input_layout
== "LAYOUT_ergodox_pretty":
164 key_symbols
= [key_symbols
[i
] for i
in self
.index_conversion_map_reversed(self
.INDEX_CONVERSTION_LAYOUT_ergodox_pretty_to_LAYOUT_ergodox
)]
166 padded_key_symbols
= self
.pad_key_symbols(key_symbols
, input_layout
)
167 current_pretty_output_layer
= self
.pretty_output_layer(layer
.name
[0].value
, padded_key_symbols
)
168 # strip trailing spaces from padding
169 layer_output
.append(re
.sub(r
" +\n", "\n", current_pretty_output_layer
))
171 return [self
.KEYMAP_START
+ "\n",
172 self
.KEY_CHART
+ "\n",
173 ",\n\n".join(layer_output
) + "\n",
174 self
.KEYMAP_END
+ "\n"]
176 def get_row_group(self
, row
):
177 for low
, high
in self
.KEY_ROW_GROUPS
[self
.output_layout
]:
178 if low
<= row
<= high
:
180 raise Exception("Cannot find row groups in KEY_ROW_GROUPS")
183 def calculate_column_max_widths(self
, key_symbols
):
184 # calculate the max width for each column
185 self
.column_max_widths
= {}
186 for i
in range(len(key_symbols
)):
187 row_index
, column_index
= self
.current_converted_KEY_COORDINATES
[i
]
188 row_group
= self
.get_row_group(row_index
)
189 if (row_group
, column_index
) in self
.column_max_widths
:
190 self
.column_max_widths
[(row_group
, column_index
)] = max(self
.column_max_widths
[(row_group
, column_index
)], len(key_symbols
[i
]))
192 self
.column_max_widths
[(row_group
, column_index
)] = len(key_symbols
[i
])
195 def pad_key_symbols(self
, key_symbols
, input_layout
, just
='left'):
196 self
.calculate_column_max_widths(key_symbols
)
198 padded_key_symbols
= []
199 # pad each key symbol
200 for i
in range(len(key_symbols
)):
202 # look up column coordinate to determine number of spaces to pad
203 row_index
, column_index
= self
.current_converted_KEY_COORDINATES
[i
]
204 row_group
= self
.get_row_group(row_index
)
206 padded_key_symbols
.append(key
.ljust(self
.column_max_widths
[(row_group
, column_index
)]))
208 padded_key_symbols
.append(key
.rjust(self
.column_max_widths
[(row_group
, column_index
)]))
209 return padded_key_symbols
212 layer_keys_pointer
= 0
214 def grab_next_n_columns(self
, n_columns
, input_layout
, layer_keys
= None, from_beginning
= False):
216 self
.layer_keys
= layer_keys
218 self
.layer_keys_pointer
= 0
220 begin
= self
.layer_keys_pointer
221 end
= begin
+ n_columns
222 return self
.layer_keys
[self
.layer_keys_pointer
-n_keys
:self
.layer_keys_pointer
]
224 key_coordinates_counter
= 0
225 def get_padded_line(self
, source_keys
, key_from
, key_to
, just
="left"):
227 keys
= [k
.strip().rjust(len(k
)) for k
in source_keys
[key_from
:key_to
]]
229 keys
= [k
for k
in source_keys
[key_from
:key_to
]]
231 from_row
, from_column
= self
.KEY_COORDINATES
[self
.output_layout
][self
.key_coordinates_counter
]
232 row_group
= self
.get_row_group(from_row
)
233 self
.key_coordinates_counter
+= key_to
- key_from
234 columns_before_key_from
= sorted([col
for row
, col
in self
.KEY_COORDINATES
[self
.output_layout
] if row
== from_row
and col
< from_column
])
235 # figure out which columns in this row needs padding; only pad empty columns to the right of an existing column
236 columns_to_pad
= { c
: True for c
in range(from_column
) }
237 if columns_before_key_from
:
238 for c
in range(max(columns_before_key_from
)+1):
239 columns_to_pad
[c
] = False
241 # for rows with fewer columns that don't start with column 0, we need to insert leading spaces
243 for c
, v
in columns_to_pad
.items():
246 if (row_group
,c
) in self
.column_max_widths
:
247 spaces
+= self
.column_max_widths
[(row_group
,c
)] + len(", ")
250 return " " * spaces
+ ", ".join(keys
) + ","
252 def pretty_output_layer(self
, layer
, keys
):
253 self
.key_coordinates_counter
= 0
254 if self
.output_layout
== "LAYOUT_ergodox":
255 formatted_key_symbols
= """
285 self
.get_padded_line(keys
, 0, 7, just
="left"),
286 self
.get_padded_line(keys
, 7, 14, just
="left"),
287 self
.get_padded_line(keys
, 14, 20, just
="left"),
288 self
.get_padded_line(keys
, 20, 27, just
="left"),
289 self
.get_padded_line(keys
, 27, 32, just
="left"),
291 self
.get_padded_line(keys
, 32, 34, just
="left"),
292 self
.get_padded_line(keys
, 34, 35, just
="left"),
293 self
.get_padded_line(keys
, 35, 38, just
="left"),
295 self
.get_padded_line(keys
, 38, 45, just
="left"),
296 self
.get_padded_line(keys
, 45, 52, just
="left"),
297 self
.get_padded_line(keys
, 52, 58, just
="left"),
298 self
.get_padded_line(keys
, 58, 65, just
="left"),
299 self
.get_padded_line(keys
, 65, 70, just
="left"),
301 self
.get_padded_line(keys
, 70, 72, just
="left"),
302 self
.get_padded_line(keys
, 72, 73, just
="left"),
303 self
.get_padded_line(keys
, 73, 76, just
="left"),
305 elif self
.output_layout
== "LAYOUT_ergodox_pretty":
306 left_half_justification
= "right" if self
.justify_toward_center
else "left"
307 formatted_key_symbols
= """
318 self
.get_padded_line(keys
, 0, 7, just
=left_half_justification
), self
.get_padded_line(keys
, 38, 45, just
="left"),
319 self
.get_padded_line(keys
, 7, 14, just
=left_half_justification
), self
.get_padded_line(keys
, 45, 52, just
="left"),
320 self
.get_padded_line(keys
, 14, 20, just
=left_half_justification
), self
.get_padded_line(keys
, 52, 58, just
="left"),
321 self
.get_padded_line(keys
, 20, 27, just
=left_half_justification
), self
.get_padded_line(keys
, 58, 65, just
="left"),
322 self
.get_padded_line(keys
, 27, 32, just
=left_half_justification
), self
.get_padded_line(keys
, 65, 70, just
="left"),
324 self
.get_padded_line(keys
, 32, 34, just
=left_half_justification
), self
.get_padded_line(keys
, 70, 72, just
="left"),
325 self
.get_padded_line(keys
, 34, 35, just
=left_half_justification
), self
.get_padded_line(keys
, 72, 73, just
="left"),
326 self
.get_padded_line(keys
, 35, 38, just
=left_half_justification
), self
.get_padded_line(keys
, 73, 76, just
="left"),
330 formatted_key_symbols
= ""
332 # rid of the trailing comma
333 formatted_key_symbols
= formatted_key_symbols
[0:len(formatted_key_symbols
)-2] + "\n"
334 s
= "[{}] = {}({})".format(layer
, self
.output_layout
, formatted_key_symbols
)
337 # helper functions for pycparser
338 def parser(self
, src
):
339 src
= self
.comment_remover(src
)
340 return pycparser
.CParser().parse(src
)
341 def comment_remover(self
, text
):
342 # remove comments since pycparser cannot deal with them
343 # credit: https://stackoverflow.com/a/241506
346 if s
.startswith('/'):
347 return " " # note: a space and not an empty string
350 pattern
= re
.compile(
351 r
'//.*?$|/\*.*?\*/|\'(?
:\\.|
[^
\\\'])*\'|
"(?:\\.|[^\\"])*"',
352 re.DOTALL | re.MULTILINE
354 return re.sub(pattern, replacer, text)
356 def function_expr(self, f):
359 for arg in f.args.exprs:
360 if type(arg) is pycparser.c_ast.Constant:
361 args.append(arg.value)
362 elif type(arg) is pycparser.c_ast.ID:
363 args.append(arg.name)
364 return "{}({})".format(name, ",".join(args))
366 def key_expr(self, raw):
367 if type(raw) is pycparser.c_ast.ID:
368 if raw.name in self.KEY_ALIASES:
369 return self.KEY_ALIASES[raw.name]
371 elif type(raw) is pycparser.c_ast.FuncCall:
372 return self.function_expr(raw)
374 def layer_expr(self, layer):
375 transformed = [self.key_expr(k) for k in layer.expr.args.exprs]
379 if __name__ == "__main__
":
381 parser = argparse.ArgumentParser(description="Beautify keymap
.c downloaded
from ErgoDox
-Ez Configurator
for easier customization
.")
382 parser.add_argument("input_filename
", help="input file: c source code
file that has the layer keymaps
")
383 parser.add_argument("-o
", "--output
-filename
", help="output
file: beautified c filename
. If
not given
, output to STDOUT
.")
384 parser.add_argument("-p
", "--pretty
-output
-layout
", action="store_true
", help="use LAYOUT_ergodox_pretty
for output instead of LAYOUT_ergodox
")
385 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
.")
386 args = parser.parse_args()
387 if args.pretty_output_layout:
388 output_layout="LAYOUT_ergodox_pretty
"
390 output_layout="LAYOUT_ergodox
"
391 with open(args.input_filename) as f:
392 source_code = f.read()
393 result = KeymapBeautifier(source_code, output_layout=output_layout, justify_toward_center=args.justify_toward_center).output
394 if args.output_filename:
395 with open(args.output_filename, "w
") as f: