forked from qmk/qmk_firmware
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
161 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
"""Command to search through all keyboards and keymaps for a given search criteria. | ||
""" | ||
from milc import cli | ||
from qmk.search import search_keymap_targets | ||
|
||
|
||
@cli.argument( | ||
'-f', | ||
'--filter', | ||
arg_only=True, | ||
action='append', | ||
default=[], | ||
help= # noqa: `format-python` and `pytest` don't agree here. | ||
"Filter the list of keyboards based on the supplied value in rules.mk. Matches info.json structure, and accepts the formats 'features.rgblight=true' or 'exists(matrix_pins.direct)'. May be passed multiple times, all filters need to match. Value may include wildcards such as '*' and '?'." # noqa: `format-python` and `pytest` don't agree here. | ||
) | ||
@cli.argument('-km', '--keymap', type=str, default='default', help="The keymap name to build. Default is 'default'.") | ||
@cli.subcommand('Find builds which match supplied search criteria.') | ||
def find(cli): | ||
"""Search through all keyboards and keymaps for a given search criteria. | ||
""" | ||
targets = search_keymap_targets(cli.args.keymap, cli.args.filter) | ||
for target in targets: | ||
print(f'{target[0]}:{target[1]}') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
"""Functions for searching through QMK keyboards and keymaps. | ||
""" | ||
import contextlib | ||
import fnmatch | ||
import logging | ||
import multiprocessing | ||
import re | ||
from dotty_dict import dotty | ||
from milc import cli | ||
|
||
from qmk.info import keymap_json | ||
import qmk.keyboard | ||
import qmk.keymap | ||
|
||
|
||
def _set_log_level(level): | ||
cli.acquire_lock() | ||
old = cli.log_level | ||
cli.log_level = level | ||
cli.log.setLevel(level) | ||
logging.root.setLevel(level) | ||
cli.release_lock() | ||
return old | ||
|
||
|
||
@contextlib.contextmanager | ||
def ignore_logging(): | ||
old = _set_log_level(logging.CRITICAL) | ||
yield | ||
_set_log_level(old) | ||
|
||
|
||
def _all_keymaps(keyboard): | ||
with ignore_logging(): | ||
return (keyboard, qmk.keymap.list_keymaps(keyboard)) | ||
|
||
|
||
def _keymap_exists(keyboard, keymap): | ||
with ignore_logging(): | ||
return keyboard if qmk.keymap.locate_keymap(keyboard, keymap) is not None else None | ||
|
||
|
||
def _load_keymap_info(keyboard, keymap): | ||
with ignore_logging(): | ||
return (keyboard, keymap, keymap_json(keyboard, keymap)) | ||
|
||
|
||
def search_keymap_targets(keymap='default', filters=[]): | ||
targets = [] | ||
|
||
with multiprocessing.Pool() as pool: | ||
cli.log.info(f'Retrieving list of keyboards with keymap "{keymap}"...') | ||
target_list = [] | ||
if keymap == 'all': | ||
kb_to_kms = pool.map(_all_keymaps, qmk.keyboard.list_keyboards()) | ||
for targets in kb_to_kms: | ||
keyboard = targets[0] | ||
keymaps = targets[1] | ||
target_list.extend([(keyboard, keymap) for keymap in keymaps]) | ||
else: | ||
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()]))] | ||
|
||
if len(filters) == 0: | ||
targets = target_list | ||
else: | ||
cli.log.info('Parsing data for all matching keyboard/keymap combinations...') | ||
valid_keymaps = [(e[0], e[1], dotty(e[2])) for e in pool.starmap(_load_keymap_info, target_list)] | ||
|
||
equals_re = re.compile(r'^(?P<key>[a-zA-Z0-9_\.]+)\s*=\s*(?P<value>[^#]+)$') | ||
exists_re = re.compile(r'^exists\((?P<key>[a-zA-Z0-9_\.]+)\)$') | ||
for filter_txt in filters: | ||
f = equals_re.match(filter_txt) | ||
if f is not None: | ||
key = f.group('key') | ||
value = f.group('value') | ||
cli.log.info(f'Filtering on condition ("{key}" == "{value}")...') | ||
|
||
def _make_filter(k, v): | ||
expr = fnmatch.translate(v) | ||
rule = re.compile(f'^{expr}$', re.IGNORECASE) | ||
|
||
def f(e): | ||
lhs = e[2].get(k) | ||
lhs = str(False if lhs is None else lhs) | ||
return rule.search(lhs) is not None | ||
|
||
return f | ||
|
||
valid_keymaps = filter(_make_filter(key, value), valid_keymaps) | ||
|
||
f = exists_re.match(filter_txt) | ||
if f is not None: | ||
key = f.group('key') | ||
cli.log.info(f'Filtering on condition (exists: "{key}")...') | ||
valid_keymaps = filter(lambda e: e[2].get(key) is not None, valid_keymaps) | ||
|
||
targets = [(e[0], e[1]) for e in valid_keymaps] | ||
|
||
return targets |