From 65a8a5ff69289a5cb8fce6555b774573e4452a79 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 21 Nov 2024 22:57:36 +1100 Subject: [PATCH] `qmk find`: expand operator support (#24468) --- docs/cli_commands.md | 18 ++++++---- lib/python/qmk/search.py | 40 +++++++++++++++++------ lib/python/qmk/tests/test_cli_commands.py | 2 +- 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 7d74d8e6177..4cd5ae98c39 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -153,20 +153,26 @@ qmk cd This command allows for searching through keyboard/keymap targets, filtering by specific criteria. `info.json` and `rules.mk` files contribute to the search data, as well as keymap configurations, and the results can be filtered using "dotty" syntax matching the overall `info.json` file format. -For example, one could search for all keyboards using STM32F411: +For example, one could search for all keyboards powered by the STM32F411 microcontroller: ``` -qmk find -f 'processor=STM32F411' +qmk find -f 'processor==STM32F411' ``` -...and one can further constrain the list to keyboards using STM32F411 as well as rgb_matrix support: +The list can be further constrained by passing additional filter expressions: ``` -qmk find -f 'processor=STM32F411' -f 'features.rgb_matrix=true' +qmk find -f 'processor==STM32F411' -f 'features.rgb_matrix==true' ``` -The following filter expressions are also supported: +The following filter expressions are supported: + - `key == value`: Match targets where `key` is equal to `value`. May include wildcards such as `*` and `?`. + - `key != value`: Match targets where `key` is not `value`. May include wildcards such as `*` and `?`. + - `key < value`: Match targets where `key` is a number less than `value`. + - `key > value`: Match targets where `key` is a number greater than `value`. + - `key <= value`: Match targets where `key` is a number less than or equal to `value`. + - `key >= value`: Match targets where `key` is a number greater than or equal to `value`. - `exists(key)`: Match targets where `key` is present. - `absent(key)`: Match targets where `key` is not present. - `contains(key, value)`: Match targets where `key` contains `value`. Can be used for strings, arrays and object keys. @@ -175,7 +181,7 @@ The following filter expressions are also supported: You can also list arbitrary values for each matched target with `--print`: ``` -qmk find -f 'processor=STM32F411' -p 'keyboard_name' -p 'features.rgb_matrix' +qmk find -f 'processor==STM32F411' -p 'keyboard_name' -p 'features.rgb_matrix' ``` **Usage**: diff --git a/lib/python/qmk/search.py b/lib/python/qmk/search.py index 6517bb4951d..c7bce344ad1 100644 --- a/lib/python/qmk/search.py +++ b/lib/python/qmk/search.py @@ -239,11 +239,11 @@ def _filter_keymap_targets(target_list: List[KeyboardKeymapDesc], filters: List[ valid_targets = parallel_map(_load_keymap_info, target_list) function_re = re.compile(r'^(?P[a-zA-Z]+)\((?P[a-zA-Z0-9_\.]+)(,\s*(?P[^#]+))?\)$') - equals_re = re.compile(r'^(?P[a-zA-Z0-9_\.]+)\s*=\s*(?P[^#]+)$') + comparison_re = re.compile(r'^(?P[a-zA-Z0-9_\.]+)\s*(?P[\<\>\!=]=|\<|\>)\s*(?P[^#]+)$') for filter_expr in filters: function_match = function_re.match(filter_expr) - equals_match = equals_re.match(filter_expr) + comparison_match = comparison_re.match(filter_expr) if function_match is not None: func_name = function_match.group('function').lower() @@ -259,23 +259,43 @@ def _filter_keymap_targets(target_list: List[KeyboardKeymapDesc], filters: List[ value_str = f", {{fg_cyan}}{value}{{fg_reset}}" if value is not None else "" cli.log.info(f'Filtering on condition: {{fg_green}}{func_name}{{fg_reset}}({{fg_cyan}}{key}{{fg_reset}}{value_str})...') - elif equals_match is not None: - key = equals_match.group('key') - value = equals_match.group('value') - cli.log.info(f'Filtering on condition: {{fg_cyan}}{key}{{fg_reset}} == {{fg_cyan}}{value}{{fg_reset}}...') + elif comparison_match is not None: + key = comparison_match.group('key') + op = comparison_match.group('op') + value = comparison_match.group('value') + cli.log.info(f'Filtering on condition: {{fg_cyan}}{key}{{fg_reset}} {op} {{fg_cyan}}{value}{{fg_reset}}...') - def _make_filter(k, v): + def _make_filter(k, o, v): expr = fnmatch.translate(v) rule = re.compile(f'^{expr}$', re.IGNORECASE) def f(e: KeyboardKeymapDesc): lhs = e.dotty.get(k) - lhs = str(False if lhs is None else lhs) - return rule.search(lhs) is not None + rhs = v + + if o in ['<', '>', '<=', '>=']: + lhs = int(False if lhs is None else lhs) + rhs = int(rhs) + + if o == '<': + return lhs < rhs + elif o == '>': + return lhs > rhs + elif o == '<=': + return lhs <= rhs + elif o == '>=': + return lhs >= rhs + else: + lhs = str(False if lhs is None else lhs) + + if o == '!=': + return rule.search(lhs) is None + elif o == '==': + return rule.search(lhs) is not None return f - valid_targets = filter(_make_filter(key, value), valid_targets) + valid_targets = filter(_make_filter(key, op, value), valid_targets) else: cli.log.warning(f'Unrecognized filter expression: {filter_expr}') continue diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index f18bd12f820..b10fd8d19d5 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py @@ -390,7 +390,7 @@ def test_find_contains(): def test_find_multiple_conditions(): # this is intended to match at least 'crkbd/rev1' result = check_subcommand( - 'find', '-f', 'exists(rgb_matrix.split_count)', '-f', 'contains(matrix_pins.cols, B1)', '-f', 'length(matrix_pins.cols, 6)', '-f', 'absent(eeprom.driver)', '-f', 'ws2812.pin=D3', '-p', 'rgb_matrix.split_count', '-p', 'matrix_pins.cols', '-p', + 'find', '-f', 'exists(rgb_matrix.split_count)', '-f', 'contains(matrix_pins.cols, B1)', '-f', 'length(matrix_pins.cols, 6)', '-f', 'absent(eeprom.driver)', '-f', 'ws2812.pin == D3', '-p', 'rgb_matrix.split_count', '-p', 'matrix_pins.cols', '-p', 'eeprom.driver', '-p', 'ws2812.pin' ) check_returncode(result)