c_parse.py 5.1 KB

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