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.

125 lines
5.0 KiB

  1. """Functions for searching through QMK keyboards and keymaps.
  2. """
  3. import contextlib
  4. import fnmatch
  5. import logging
  6. import multiprocessing
  7. import re
  8. from dotty_dict import dotty
  9. from milc import cli
  10. from qmk.info import keymap_json
  11. import qmk.keyboard
  12. import qmk.keymap
  13. def _set_log_level(level):
  14. cli.acquire_lock()
  15. old = cli.log_level
  16. cli.log_level = level
  17. cli.log.setLevel(level)
  18. logging.root.setLevel(level)
  19. cli.release_lock()
  20. return old
  21. @contextlib.contextmanager
  22. def ignore_logging():
  23. old = _set_log_level(logging.CRITICAL)
  24. yield
  25. _set_log_level(old)
  26. def _all_keymaps(keyboard):
  27. with ignore_logging():
  28. return (keyboard, qmk.keymap.list_keymaps(keyboard))
  29. def _keymap_exists(keyboard, keymap):
  30. with ignore_logging():
  31. return keyboard if qmk.keymap.locate_keymap(keyboard, keymap) is not None else None
  32. def _load_keymap_info(keyboard, keymap):
  33. with ignore_logging():
  34. return (keyboard, keymap, keymap_json(keyboard, keymap))
  35. def search_keymap_targets(keymap='default', filters=[], print_vals=[]):
  36. targets = []
  37. with multiprocessing.Pool() as pool:
  38. cli.log.info(f'Retrieving list of keyboards with keymap "{keymap}"...')
  39. target_list = []
  40. if keymap == 'all':
  41. kb_to_kms = pool.map(_all_keymaps, qmk.keyboard.list_keyboards())
  42. for targets in kb_to_kms:
  43. keyboard = targets[0]
  44. keymaps = targets[1]
  45. target_list.extend([(keyboard, keymap) for keymap in keymaps])
  46. else:
  47. target_list = [(kb, keymap) for kb in filter(lambda kb: kb is not None, pool.starmap(_keymap_exists, [(kb, keymap) for kb in qmk.keyboard.list_keyboards()]))]
  48. if len(filters) == 0:
  49. targets = [(kb, km, {}) for kb, km in target_list]
  50. else:
  51. cli.log.info('Parsing data for all matching keyboard/keymap combinations...')
  52. valid_keymaps = [(e[0], e[1], dotty(e[2])) for e in pool.starmap(_load_keymap_info, target_list)]
  53. function_re = re.compile(r'^(?P<function>[a-zA-Z]+)\((?P<key>[a-zA-Z0-9_\.]+)(,\s*(?P<value>[^#]+))?\)$')
  54. equals_re = re.compile(r'^(?P<key>[a-zA-Z0-9_\.]+)\s*=\s*(?P<value>[^#]+)$')
  55. for filter_expr in filters:
  56. function_match = function_re.match(filter_expr)
  57. equals_match = equals_re.match(filter_expr)
  58. if function_match is not None:
  59. func_name = function_match.group('function').lower()
  60. key = function_match.group('key')
  61. value = function_match.group('value')
  62. if value is not None:
  63. if func_name == 'length':
  64. valid_keymaps = filter(lambda e, key=key, value=value: key in e[2] and len(e[2].get(key)) == int(value), valid_keymaps)
  65. elif func_name == 'contains':
  66. valid_keymaps = filter(lambda e, key=key, value=value: key in e[2] and value in e[2].get(key), valid_keymaps)
  67. else:
  68. cli.log.warning(f'Unrecognized filter expression: {function_match.group(0)}')
  69. continue
  70. cli.log.info(f'Filtering on condition: {{fg_green}}{func_name}{{fg_reset}}({{fg_cyan}}{key}{{fg_reset}}, {{fg_cyan}}{value}{{fg_reset}})...')
  71. else:
  72. if func_name == 'exists':
  73. valid_keymaps = filter(lambda e, key=key: key in e[2], valid_keymaps)
  74. elif func_name == 'absent':
  75. valid_keymaps = filter(lambda e, key=key: key not in e[2], valid_keymaps)
  76. else:
  77. cli.log.warning(f'Unrecognized filter expression: {function_match.group(0)}')
  78. continue
  79. cli.log.info(f'Filtering on condition: {{fg_green}}{func_name}{{fg_reset}}({{fg_cyan}}{key}{{fg_reset}})...')
  80. elif equals_match is not None:
  81. key = equals_match.group('key')
  82. value = equals_match.group('value')
  83. cli.log.info(f'Filtering on condition: {{fg_cyan}}{key}{{fg_reset}} == {{fg_cyan}}{value}{{fg_reset}}...')
  84. def _make_filter(k, v):
  85. expr = fnmatch.translate(v)
  86. rule = re.compile(f'^{expr}$', re.IGNORECASE)
  87. def f(e):
  88. lhs = e[2].get(k)
  89. lhs = str(False if lhs is None else lhs)
  90. return rule.search(lhs) is not None
  91. return f
  92. valid_keymaps = filter(_make_filter(key, value), valid_keymaps)
  93. else:
  94. cli.log.warning(f'Unrecognized filter expression: {filter_expr}')
  95. continue
  96. targets = [(e[0], e[1], [(p, e[2].get(p)) for p in print_vals]) for e in valid_keymaps]
  97. return targets