Something went wrong. Try again.
keyboard stuff
Something went wrong. Try again.
12 kB · 324 lines
Python
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325"""Functions for working with config.h files."""from pygments.lexers.c_cpp import CLexerfrom pygments.token import Tokenfrom pygments import lexfrom itertools import islicefrom pathlib import Pathimport re
from milc import cli
from qmk.comment_remover import comment_remover
default_key_entry = {'x': -1, 'y': 0}single_comment_regex = re.compile(r'\s+/[/*].*$')multi_comment_regex = re.compile(r'/\*(.|\n)*?\*/', re.MULTILINE)layout_macro_define_regex = re.compile(r'^#\s*define')
def _get_chunks(it, size): """Break down a collection into smaller parts """ it = iter(it) return iter(lambda: tuple(islice(it, size)), ())
def preprocess_c_file(file): """Load file and strip comments """ file_contents = file.read_text(encoding='utf-8') file_contents = comment_remover(file_contents) return file_contents.replace('\\\n', '')
def strip_line_comment(string): """Removes comments from a single line string. """ return single_comment_regex.sub('', string)
def strip_multiline_comment(string): """Removes comments from a single line string. """ return multi_comment_regex.sub('', string)
def c_source_files(dir_names): """Returns a list of all *.c, *.h, and *.cpp files for a given list of directories
Args:
dir_names List of directories relative to `qmk_firmware`. """ files = [] for dir in dir_names: files.extend(file for file in Path(dir).glob('**/*') if file.suffix in ['.c', '.h', '.cpp']) return files
def find_layouts(file): """Returns list of parsed LAYOUT preprocessor macros found in the supplied include file. """ file = Path(file) aliases = {} # Populated with all `#define`s that aren't functions parsed_layouts = {}
# Search the file for LAYOUT macros and aliases file_contents = preprocess_c_file(file)
for line in file_contents.split('\n'): if layout_macro_define_regex.match(line.lstrip()) and '(' in line and 'LAYOUT' in line: # We've found a LAYOUT macro macro_name, layout, matrix = _parse_layout_macro(line.strip())
# Reject bad macro names if macro_name.startswith('LAYOUT_kc') or not macro_name.startswith('LAYOUT'): continue
# Parse the matrix data matrix_locations = _parse_matrix_locations(matrix, file, macro_name)
# Parse the layout entries into a basic structure default_key_entry['x'] = -1 # Set to -1 so _default_key(key) will increment it to 0 layout = layout.strip() parsed_layout = [_default_key(key) for key in layout.split(',')]
for i, key in enumerate(parsed_layout): if 'label' not in key: cli.log.error('Invalid LAYOUT macro in %s: Empty parameter name in macro %s at pos %s.', file, macro_name, i) elif key['label'] not in matrix_locations: cli.log.error('Invalid LAYOUT macro in %s: Key %s in macro %s has no matrix position!', file, key['label'], macro_name) elif len(matrix_locations.get(key['label'])) > 1: cli.log.error('Invalid LAYOUT macro in %s: Key %s in macro %s has multiple matrix positions (%s)', file, key['label'], macro_name, ', '.join(str(x) for x in matrix_locations[key['label']])) else: key['matrix'] = matrix_locations[key['label']][0]
parsed_layouts[macro_name] = { 'layout': parsed_layout, 'filename': str(file), }
elif '#define' in line: # Attempt to extract a new layout alias try: _, pp_macro_name, pp_macro_text = line.strip().split(' ', 2) aliases[pp_macro_name] = pp_macro_text except ValueError: continue
return parsed_layouts, aliases
def parse_config_h_file(config_h_file, config_h=None): """Extract defines from a config.h file. """ if not config_h: config_h = {}
config_h_file = Path(config_h_file)
if config_h_file.exists(): config_h_text = config_h_file.read_text(encoding='utf-8') config_h_text = config_h_text.replace('\\\n', '') config_h_text = strip_multiline_comment(config_h_text)
for linenum, line in enumerate(config_h_text.split('\n')): line = strip_line_comment(line).strip()
if not line: continue
line = line.split()
if line[0] == '#define': if len(line) == 1: cli.log.error('%s: Incomplete #define! On or around line %s' % (config_h_file, linenum)) elif len(line) == 2: config_h[line[1]] = True else: config_h[line[1]] = ' '.join(line[2:])
elif line[0] == '#undef': if len(line) == 2: if line[1] in config_h: if config_h[line[1]] is True: del config_h[line[1]] else: config_h[line[1]] = False else: cli.log.error('%s: Incomplete #undef! On or around line %s' % (config_h_file, linenum))
return config_h
def _default_key(label=None): """Increment x and return a copy of the default_key_entry. """ default_key_entry['x'] += 1 new_key = default_key_entry.copy()
if label: new_key['label'] = label
return new_key
def _parse_layout_macro(layout_macro): """Split the LAYOUT macro into its constituent parts """ layout_macro = layout_macro.replace('\\', '').replace(' ', '').replace('\t', '').replace('#define', '') macro_name, layout = layout_macro.split('(', 1) layout, matrix = layout.split(')', 1)
return macro_name, layout, matrix
def _parse_matrix_locations(matrix, file, macro_name): """Parse raw matrix data into a dictionary keyed by the LAYOUT identifier. """ matrix_locations = {}
for row_num, row in enumerate(matrix.split('},{')): if row.startswith('LAYOUT'): cli.log.error('%s: %s: Nested layout macro detected. Matrix data not available!', file, macro_name) break
row = row.replace('{', '').replace('}', '') for col_num, identifier in enumerate(row.split(',')): if identifier != 'KC_NO': if identifier not in matrix_locations: matrix_locations[identifier] = [] matrix_locations[identifier].append([row_num, col_num])
return matrix_locations
def _coerce_led_token(_type, value): """ Convert token to valid info.json content """ value_map = { 'NO_LED': None, 'LED_FLAG_ALL': 0xFF, 'LED_FLAG_NONE': 0x00, 'LED_FLAG_MODIFIER': 0x01, 'LED_FLAG_UNDERGLOW': 0x02, 'LED_FLAG_KEYLIGHT': 0x04, 'LED_FLAG_INDICATOR': 0x08, } if _type is Token.Literal.Number.Integer: return int(value) if _type is Token.Literal.Number.Float: return float(value) if _type is Token.Literal.Number.Hex: return int(value, 0) if _type is Token.Name and value in value_map.keys(): return value_map[value]
def _validate_led_config(matrix, matrix_rows, matrix_cols, matrix_indexes, position, position_raw, flags): # TODO: Improve crude parsing/validation if len(matrix) != matrix_rows and len(matrix) != (matrix_rows / 2): raise ValueError("Unable to parse g_led_config matrix data") for index, row in enumerate(matrix): if len(row) != matrix_cols: raise ValueError(f"Number of columns in row {index} ({len(row)}) does not match matrix ({matrix_cols})") if len(position) != len(flags): raise ValueError(f"Number of g_led_config physical positions ({len(position)}) does not match number of flags ({len(flags)})") if len(matrix_indexes) and (max(matrix_indexes) >= len(flags)): raise ValueError(f"LED index {max(matrix_indexes)} is OOB in g_led_config - should be < {len(flags)}") if not all(isinstance(n, int) for n in matrix_indexes): raise ValueError("matrix indexes are not all ints") if (len(position_raw) % 2) != 0: raise ValueError("Malformed g_led_config position data")
def _parse_led_config(file, matrix_cols, matrix_rows): """Return any 'raw' led/rgb matrix config """ matrix = [] position_raw = [] flags = []
found_led_config_t = False found_g_led_config = False bracket_count = 0 section = 0 current_row_index = 0 current_row = []
for _type, value in lex(preprocess_c_file(file), CLexer()): if not found_g_led_config: # Check for type if value == 'led_config_t': found_led_config_t = True # Type found, now check for name elif found_led_config_t and value == 'g_led_config': found_g_led_config = True elif value == ';': found_g_led_config = False else: # Assume bracket count hints to section of config we are within if value == '{': bracket_count += 1 if bracket_count == 2: section += 1 elif value == '}': if section == 1 and bracket_count == 3: matrix.append(current_row) current_row = [] current_row_index += 1 bracket_count -= 1 else: # Assume any non whitespace value here is important enough to stash if _type in [Token.Literal.Number.Integer, Token.Literal.Number.Float, Token.Literal.Number.Hex, Token.Name]: if section == 1 and bracket_count == 3: current_row.append(_coerce_led_token(_type, value)) if section == 2 and bracket_count == 3: position_raw.append(_coerce_led_token(_type, value)) if section == 3 and bracket_count == 2: flags.append(_coerce_led_token(_type, value)) elif _type in [Token.Comment.Preproc]: # TODO: Promote to error return None
# Slightly better intrim format position = list(_get_chunks(position_raw, 2)) matrix_indexes = list(filter(lambda x: x is not None, sum(matrix, [])))
# If we have not found anything - bail with no error if not section: return None
# Throw any validation errors _validate_led_config(matrix, matrix_rows, matrix_cols, matrix_indexes, position, position_raw, flags)
return (matrix, position, flags)
def find_led_config(file, matrix_cols, matrix_rows): """Search file for led/rgb matrix config """ found = _parse_led_config(file, matrix_cols, matrix_rows) if not found: return None
# Expand collected content (matrix, position, flags) = found
# Align to output format led_config = [] for index, item in enumerate(position, start=0): led_config.append({ 'x': item[0], 'y': item[1], 'flags': flags[index], }) for r in range(len(matrix)): for c in range(len(matrix[r])): index = matrix[r][c] if index is not None: led_config[index]['matrix'] = [r, c]
return led_config