Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

qmk find: expand operator support #24468

Merged
merged 2 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions docs/cli_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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**:
Expand Down
40 changes: 30 additions & 10 deletions lib/python/qmk/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,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<function>[a-zA-Z]+)\((?P<key>[a-zA-Z0-9_\.]+)(,\s*(?P<value>[^#]+))?\)$')
equals_re = re.compile(r'^(?P<key>[a-zA-Z0-9_\.]+)\s*=\s*(?P<value>[^#]+)$')
comparison_re = re.compile(r'^(?P<key>[a-zA-Z0-9_\.]+)\s*(?P<op>[\<\>\!=]=|\<|\>)\s*(?P<value>[^#]+)$')

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()
Expand All @@ -256,23 +256,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
Expand Down
2 changes: 1 addition & 1 deletion lib/python/qmk/tests/test_cli_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down