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.

175 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()
  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 key in parsed_layout:
  52. if key['label'] in matrix_locations:
  53. key['matrix'] = matrix_locations[key['label']]
  54. parsed_layouts[macro_name] = {
  55. 'key_count': len(parsed_layout),
  56. 'layout': parsed_layout,
  57. 'filename': str(file),
  58. }
  59. elif '#define' in line:
  60. # Attempt to extract a new layout alias
  61. try:
  62. _, pp_macro_name, pp_macro_text = line.strip().split(' ', 2)
  63. aliases[pp_macro_name] = pp_macro_text
  64. except ValueError:
  65. continue
  66. # Populate our aliases
  67. for alias, text in aliases.items():
  68. if text in parsed_layouts and 'KEYMAP' not in alias:
  69. parsed_layouts[alias] = parsed_layouts[text]
  70. return parsed_layouts
  71. def parse_config_h_file(config_h_file, config_h=None):
  72. """Extract defines from a config.h file.
  73. """
  74. if not config_h:
  75. config_h = {}
  76. config_h_file = Path(config_h_file)
  77. if config_h_file.exists():
  78. config_h_text = config_h_file.read_text()
  79. config_h_text = config_h_text.replace('\\\n', '') # Why are you here?
  80. config_h_text = strip_multiline_comment(config_h_text)
  81. for linenum, line in enumerate(config_h_text.split('\n')):
  82. line = strip_line_comment(line).strip()
  83. if not line:
  84. continue
  85. line = line.split()
  86. if line[0] == '#define':
  87. if len(line) == 1:
  88. cli.log.error('%s: Incomplete #define! On or around line %s' % (config_h_file, linenum))
  89. elif len(line) == 2:
  90. config_h[line[1]] = True
  91. else:
  92. config_h[line[1]] = ' '.join(line[2:])
  93. elif line[0] == '#undef':
  94. if len(line) == 2:
  95. if line[1] in config_h:
  96. if config_h[line[1]] is True:
  97. del config_h[line[1]]
  98. else:
  99. config_h[line[1]] = False
  100. else:
  101. cli.log.error('%s: Incomplete #undef! On or around line %s' % (config_h_file, linenum))
  102. return config_h
  103. def _default_key(label=None):
  104. """Increment x and return a copy of the default_key_entry.
  105. """
  106. default_key_entry['x'] += 1
  107. new_key = default_key_entry.copy()
  108. if label:
  109. new_key['label'] = label
  110. return new_key
  111. def _parse_layout_macro(layout_macro):
  112. """Split the LAYOUT macro into its constituent parts
  113. """
  114. layout_macro = layout_macro.replace('\\', '').replace(' ', '').replace('\t', '').replace('#define', '')
  115. macro_name, layout = layout_macro.split('(', 1)
  116. layout, matrix = layout.split(')', 1)
  117. return macro_name, layout, matrix
  118. def _parse_matrix_locations(matrix, file, macro_name):
  119. """Parse raw matrix data into a dictionary keyed by the LAYOUT identifier.
  120. """
  121. matrix_locations = {}
  122. for row_num, row in enumerate(matrix.split('},{')):
  123. if row.startswith('LAYOUT'):
  124. cli.log.error('%s: %s: Nested layout macro detected. Matrix data not available!', file, macro_name)
  125. break
  126. row = row.replace('{', '').replace('}', '')
  127. for col_num, identifier in enumerate(row.split(',')):
  128. if identifier != 'KC_NO':
  129. matrix_locations[identifier] = [row_num, col_num]
  130. return matrix_locations