172 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			172 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """Functions for working with config.h files.
 | |
| """
 | |
| from pathlib import Path
 | |
| import re
 | |
| 
 | |
| from milc import cli
 | |
| 
 | |
| from qmk.comment_remover import comment_remover
 | |
| 
 | |
| default_key_entry = {'x': -1, 'y': 0, 'w': 1}
 | |
| single_comment_regex = re.compile(r'\s+/[/*].*$')
 | |
| multi_comment_regex = re.compile(r'/\*(.|\n)*?\*/', re.MULTILINE)
 | |
| 
 | |
| 
 | |
| 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 = file.read_text(encoding='utf-8')
 | |
|     file_contents = comment_remover(file_contents)
 | |
|     file_contents = file_contents.replace('\\\n', '')
 | |
| 
 | |
|     for line in file_contents.split('\n'):
 | |
|         if line.startswith('#define') 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'] in matrix_locations:
 | |
|                     key['matrix'] = matrix_locations[key['label']]
 | |
| 
 | |
|             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':
 | |
|                 matrix_locations[identifier] = [row_num, col_num]
 | |
| 
 | |
|     return matrix_locations
 |