diff options
Diffstat (limited to 'lib/python/qmk/c_parse.py')
-rw-r--r-- | lib/python/qmk/c_parse.py | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/lib/python/qmk/c_parse.py b/lib/python/qmk/c_parse.py new file mode 100644 index 000000000..e41e271a4 --- /dev/null +++ b/lib/python/qmk/c_parse.py | |||
@@ -0,0 +1,161 @@ | |||
1 | """Functions for working with config.h files. | ||
2 | """ | ||
3 | from pathlib import Path | ||
4 | |||
5 | from milc import cli | ||
6 | |||
7 | from qmk.comment_remover import comment_remover | ||
8 | |||
9 | default_key_entry = {'x': -1, 'y': 0, 'w': 1} | ||
10 | |||
11 | |||
12 | def c_source_files(dir_names): | ||
13 | """Returns a list of all *.c, *.h, and *.cpp files for a given list of directories | ||
14 | |||
15 | Args: | ||
16 | |||
17 | dir_names | ||
18 | List of directories relative to `qmk_firmware`. | ||
19 | """ | ||
20 | files = [] | ||
21 | for dir in dir_names: | ||
22 | files.extend(file for file in Path(dir).glob('**/*') if file.suffix in ['.c', '.h', '.cpp']) | ||
23 | return files | ||
24 | |||
25 | |||
26 | def find_layouts(file): | ||
27 | """Returns list of parsed LAYOUT preprocessor macros found in the supplied include file. | ||
28 | """ | ||
29 | file = Path(file) | ||
30 | aliases = {} # Populated with all `#define`s that aren't functions | ||
31 | parsed_layouts = {} | ||
32 | |||
33 | # Search the file for LAYOUT macros and aliases | ||
34 | file_contents = file.read_text() | ||
35 | file_contents = comment_remover(file_contents) | ||
36 | file_contents = file_contents.replace('\\\n', '') | ||
37 | |||
38 | for line in file_contents.split('\n'): | ||
39 | if line.startswith('#define') and '(' in line and 'LAYOUT' in line: | ||
40 | # We've found a LAYOUT macro | ||
41 | macro_name, layout, matrix = _parse_layout_macro(line.strip()) | ||
42 | |||
43 | # Reject bad macro names | ||
44 | if macro_name.startswith('LAYOUT_kc') or not macro_name.startswith('LAYOUT'): | ||
45 | continue | ||
46 | |||
47 | # Parse the matrix data | ||
48 | matrix_locations = _parse_matrix_locations(matrix, file, macro_name) | ||
49 | |||
50 | # Parse the layout entries into a basic structure | ||
51 | default_key_entry['x'] = -1 # Set to -1 so _default_key(key) will increment it to 0 | ||
52 | layout = layout.strip() | ||
53 | parsed_layout = [_default_key(key) for key in layout.split(',')] | ||
54 | |||
55 | for key in parsed_layout: | ||
56 | key['matrix'] = matrix_locations.get(key['label']) | ||
57 | |||
58 | parsed_layouts[macro_name] = { | ||
59 | 'key_count': len(parsed_layout), | ||
60 | 'layout': parsed_layout, | ||
61 | 'filename': str(file), | ||
62 | } | ||
63 | |||
64 | elif '#define' in line: | ||
65 | # Attempt to extract a new layout alias | ||
66 | try: | ||
67 | _, pp_macro_name, pp_macro_text = line.strip().split(' ', 2) | ||
68 | aliases[pp_macro_name] = pp_macro_text | ||
69 | except ValueError: | ||
70 | continue | ||
71 | |||
72 | # Populate our aliases | ||
73 | for alias, text in aliases.items(): | ||
74 | if text in parsed_layouts and 'KEYMAP' not in alias: | ||
75 | parsed_layouts[alias] = parsed_layouts[text] | ||
76 | |||
77 | return parsed_layouts | ||
78 | |||
79 | |||
80 | def parse_config_h_file(config_h_file, config_h=None): | ||
81 | """Extract defines from a config.h file. | ||
82 | """ | ||
83 | if not config_h: | ||
84 | config_h = {} | ||
85 | |||
86 | config_h_file = Path(config_h_file) | ||
87 | |||
88 | if config_h_file.exists(): | ||
89 | config_h_text = config_h_file.read_text() | ||
90 | config_h_text = config_h_text.replace('\\\n', '') | ||
91 | |||
92 | for linenum, line in enumerate(config_h_text.split('\n')): | ||
93 | line = line.strip() | ||
94 | |||
95 | if '//' in line: | ||
96 | line = line[:line.index('//')].strip() | ||
97 | |||
98 | if not line: | ||
99 | continue | ||
100 | |||
101 | line = line.split() | ||
102 | |||
103 | if line[0] == '#define': | ||
104 | if len(line) == 1: | ||
105 | cli.log.error('%s: Incomplete #define! On or around line %s' % (config_h_file, linenum)) | ||
106 | elif len(line) == 2: | ||
107 | config_h[line[1]] = True | ||
108 | else: | ||
109 | config_h[line[1]] = ' '.join(line[2:]) | ||
110 | |||
111 | elif line[0] == '#undef': | ||
112 | if len(line) == 2: | ||
113 | if line[1] in config_h: | ||
114 | if config_h[line[1]] is True: | ||
115 | del config_h[line[1]] | ||
116 | else: | ||
117 | config_h[line[1]] = False | ||
118 | else: | ||
119 | cli.log.error('%s: Incomplete #undef! On or around line %s' % (config_h_file, linenum)) | ||
120 | |||
121 | return config_h | ||
122 | |||
123 | |||
124 | def _default_key(label=None): | ||
125 | """Increment x and return a copy of the default_key_entry. | ||
126 | """ | ||
127 | default_key_entry['x'] += 1 | ||
128 | new_key = default_key_entry.copy() | ||
129 | |||
130 | if label: | ||
131 | new_key['label'] = label | ||
132 | |||
133 | return new_key | ||
134 | |||
135 | |||
136 | def _parse_layout_macro(layout_macro): | ||
137 | """Split the LAYOUT macro into its constituent parts | ||
138 | """ | ||
139 | layout_macro = layout_macro.replace('\\', '').replace(' ', '').replace('\t', '').replace('#define', '') | ||
140 | macro_name, layout = layout_macro.split('(', 1) | ||
141 | layout, matrix = layout.split(')', 1) | ||
142 | |||
143 | return macro_name, layout, matrix | ||
144 | |||
145 | |||
146 | def _parse_matrix_locations(matrix, file, macro_name): | ||
147 | """Parse raw matrix data into a dictionary keyed by the LAYOUT identifier. | ||
148 | """ | ||
149 | matrix_locations = {} | ||
150 | |||
151 | for row_num, row in enumerate(matrix.split('},{')): | ||
152 | if row.startswith('LAYOUT'): | ||
153 | cli.log.error('%s: %s: Nested layout macro detected. Matrix data not available!', file, macro_name) | ||
154 | break | ||
155 | |||
156 | row = row.replace('{', '').replace('}', '') | ||
157 | for col_num, identifier in enumerate(row.split(',')): | ||
158 | if identifier != 'KC_NO': | ||
159 | matrix_locations[identifier] = (row_num, col_num) | ||
160 | |||
161 | return matrix_locations | ||