You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

172 lines
5.6 KiB

  1. """Functions for working with config.h files.
  2. """
  3. from pathlib import Path
  4. import re
  5. from milc import cli
  6. from qmk.comment_remover import comment_remover
  7. default_key_entry = {'x': -1, 'y': 0, 'w': 1}
  8. single_comment_regex = re.compile(r' */[/*].*$')
  9. multi_comment_regex = re.compile(r'/\*(.|\n)*?\*/', re.MULTILINE)
  10. def strip_line_comment(string):
  11. """Removes comments from a single line string.
  12. """
  13. return single_comment_regex.sub('', string)
  14. def strip_multiline_comment(string):
  15. """Removes comments from a single line string.
  16. """
  17. return multi_comment_regex.sub('', string)
  18. def c_source_files(dir_names):
  19. """Returns a list of all *.c, *.h, and *.cpp files for a given list of directories
  20. Args:
  21. dir_names
  22. List of directories relative to `qmk_firmware`.
  23. """
  24. files = []
  25. for dir in dir_names:
  26. files.extend(file for file in Path(dir).glob('**/*') if file.suffix in ['.c', '.h', '.cpp'])
  27. return files
  28. def find_layouts(file):
  29. """Returns list of parsed LAYOUT preprocessor macros found in the supplied include file.
  30. """
  31. file = Path(file)
  32. aliases = {} # Populated with all `#define`s that aren't functions
  33. parsed_layouts = {}
  34. # Search the file for LAYOUT macros and aliases
  35. file_contents = file.read_text(encoding='utf-8')
  36. file_contents = comment_remover(file_contents)
  37. file_contents = file_contents.replace('\\\n', '')
  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. # Reject bad macro names
  43. if macro_name.startswith('LAYOUT_kc') or not macro_name.startswith('LAYOUT'):
  44. continue
  45. # Parse the matrix data
  46. matrix_locations = _parse_matrix_locations(matrix, file, macro_name)
  47. # Parse the layout entries into a basic structure
  48. default_key_entry['x'] = -1 # Set to -1 so _default_key(key) will increment it to 0
  49. layout = layout.strip()
  50. parsed_layout = [_default_key(key) for key in layout.split(',')]
  51. for i, key in enumerate(parsed_layout):
  52. if 'label' not in key:
  53. cli.log.error('Invalid LAYOUT macro in %s: Empty parameter name in macro %s at pos %s.', file, macro_name, i)
  54. elif key['label'] in matrix_locations:
  55. key['matrix'] = matrix_locations[key['label']]
  56. parsed_layouts[macro_name] = {
  57. 'key_count': len(parsed_layout),
  58. 'layout': parsed_layout,
  59. 'filename': str(file),
  60. }
  61. elif '#define' in line:
  62. # Attempt to extract a new layout alias
  63. try:
  64. _, pp_macro_name, pp_macro_text = line.strip().split(' ', 2)
  65. aliases[pp_macro_name] = pp_macro_text
  66. except ValueError:
  67. continue
  68. return parsed_layouts, aliases
  69. def parse_config_h_file(config_h_file, config_h=None):
  70. """Extract defines from a config.h file.
  71. """
  72. if not config_h:
  73. config_h = {}
  74. config_h_file = Path(config_h_file)
  75. if config_h_file.exists():
  76. config_h_text = config_h_file.read_text(encoding='utf-8')
  77. config_h_text = config_h_text.replace('\\\n', '')
  78. config_h_text = strip_multiline_comment(config_h_text)
  79. for linenum, line in enumerate(config_h_text.split('\n')):
  80. line = strip_line_comment(line).strip()
  81. if not line:
  82. continue
  83. line = line.split()
  84. if line[0] == '#define':
  85. if len(line) == 1:
  86. cli.log.error('%s: Incomplete #define! On or around line %s' % (config_h_file, linenum))
  87. elif len(line) == 2:
  88. config_h[line[1]] = True
  89. else:
  90. config_h[line[1]] = ' '.join(line[2:])
  91. elif line[0] == '#undef':
  92. if len(line) == 2:
  93. if line[1] in config_h:
  94. if config_h[line[1]] is True:
  95. del config_h[line[1]]
  96. else:
  97. config_h[line[1]] = False
  98. else:
  99. cli.log.error('%s: Incomplete #undef! On or around line %s' % (config_h_file, linenum))
  100. return config_h
  101. def _default_key(label=None):
  102. """Increment x and return a copy of the default_key_entry.
  103. """
  104. default_key_entry['x'] += 1
  105. new_key = default_key_entry.copy()
  106. if label:
  107. new_key['label'] = label
  108. return new_key
  109. def _parse_layout_macro(layout_macro):
  110. """Split the LAYOUT macro into its constituent parts
  111. """
  112. layout_macro = layout_macro.replace('\\', '').replace(' ', '').replace('\t', '').replace('#define', '')
  113. macro_name, layout = layout_macro.split('(', 1)
  114. layout, matrix = layout.split(')', 1)
  115. return macro_name, layout, matrix
  116. def _parse_matrix_locations(matrix, file, macro_name):
  117. """Parse raw matrix data into a dictionary keyed by the LAYOUT identifier.
  118. """
  119. matrix_locations = {}
  120. for row_num, row in enumerate(matrix.split('},{')):
  121. if row.startswith('LAYOUT'):
  122. cli.log.error('%s: %s: Nested layout macro detected. Matrix data not available!', file, macro_name)
  123. break
  124. row = row.replace('{', '').replace('}', '')
  125. for col_num, identifier in enumerate(row.split(',')):
  126. if identifier != 'KC_NO':
  127. matrix_locations[identifier] = [row_num, col_num]
  128. return matrix_locations