Browse Source

`qmk find`: expand operator support (#24468)

Ryan 1 year ago
parent
commit
65a8a5ff69
3 changed files with 43 additions and 17 deletions
  1. 12 6
      docs/cli_commands.md
  2. 30 10
      lib/python/qmk/search.py
  3. 1 1
      lib/python/qmk/tests/test_cli_commands.py

+ 12 - 6
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**:

+ 30 - 10
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<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()
@@ -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

+ 1 - 1
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)