home · contact · privacy
For matching on numerical values, allow "<" and ">" comparisons. master
authorPlom Heller <plom@plomlompom.com>
Fri, 14 Aug 2026 23:42:26 +0000 (01:42 +0200)
committerPlom Heller <plom@plomlompom.com>
Fri, 14 Aug 2026 23:42:26 +0000 (01:42 +0200)
src/bricksplom/misc.py

index e7d276de68210f60a74ddf3cc7582790d6d32635..7cce3cf0d632d4ed79fccd4cced628d4efdd36cd 100644 (file)
@@ -13,6 +13,8 @@ CHAR_SEP_TOKEN = ' '
 CHAR_COMMENT = '#'
 CHAR_DESIGN_ALT = '='
 CHAR_ATTR_EQ = '='
+CHAR_ATTR_LESS = '<'
+CHAR_ATTR_MORE = '>'
 CHAR_TEXTCONTAINS = ':'
 SEP_DESIGN_DESC = '#'
 CHAR_COLL_INDENT = ' '
@@ -28,7 +30,12 @@ BOX_PREFIX = 'box:'
 
 ARG_QMARK = '?'
 PARAM_Q_ID = f'id{CHAR_ATTR_EQ}'
-PARAM_Q_DELIMITERS = {CHAR_ATTR_EQ, CHAR_TEXTCONTAINS}
+MATCH_OPERATORS = {
+        CHAR_ATTR_EQ,
+        CHAR_TEXTCONTAINS,
+        CHAR_ATTR_LESS,
+        CHAR_ATTR_MORE
+        }
 TOK_SORT_BOX = 'box'
 TOK_SORT_ID = 'id'
 
@@ -154,24 +161,31 @@ class Lookupable:
     @classmethod
     def matchers(
             cls
-            ) -> dict[str, tuple[str, Callable[[Self, str], bool]]]:
+            ) -> dict[str, tuple[str, dict[str, Callable[[Self, str], bool]]]]:
         'Available lookup matchers.'
         return {
-            PARAM_Q_ID: (
-                'by ID of individual item',
-                lambda item, q_body: item.id_ == q_body),
-            f'text{CHAR_TEXTCONTAINS}': (
+            TOK_SORT_ID: (
+                'ID of individual item',
+                {CHAR_ATTR_EQ: (
+                    lambda item, val: item.id_ == val)}
+                ),
+            'text': (
                 'text contained in item description',
-                lambda item, q_body: q_body.upper() in item.show().upper())
+                {CHAR_TEXTCONTAINS: (
+                    lambda item, val: val.upper() in item.show().upper())}
+                ),
             }
 
     def match(
             self,
-            query_char: str,
-            query_body: str
+            target: str,
+            operator: str,
+            val: str
             ) -> bool:
         'Return if self matches query.'
-        return self.matchers()[query_char][1](self, query_body)
+        target_matchers = self.matchers()[target][1]
+        assert operator in target_matchers, (target, operator)
+        return target_matchers[operator](self, val)
 
     @classmethod
     def _by_box_sorter(
@@ -426,19 +440,22 @@ class BrickDesign(Textfiled, WithDb, Lookupable):
     @classmethod
     def matchers(
             cls
-            ) -> dict[str, tuple[str, Callable[[Self, str], bool]]]:
+            ) -> dict[str, tuple[str, dict[str, Callable[[Self, str], bool]]]]:
         def attr_matcher(
                 attr_name: str
-                ) -> tuple[str, Callable[[Self, str], bool]]:
-            def match(
+                ) -> tuple[str, dict[str, Callable[[Self, str], bool]]]:
+            def cmp(
                     item: Self,
                     q_body: str
-                    ) -> bool:
+                    ) -> float:
                 assert q_body.isdigit()
-                return getattr(item, attr_name) == int(q_body)
-            return 'by ' + BrickDesignData.usage(attr_name), match
+                return int(q_body) - float(getattr(item, attr_name))
+            return 'by ' + BrickDesignData.usage(attr_name), {
+                    CHAR_ATTR_EQ: lambda item, v: 0 == cmp(item, v),
+                    CHAR_ATTR_LESS: lambda item, v: 0 < cmp(item, v),
+                    CHAR_ATTR_MORE: lambda item, v: 0 > cmp(item, v)}
         return super().matchers() | {
-            f'{attr_name}{CHAR_ATTR_EQ}': attr_matcher(attr_name)
+            f'{attr_name}': attr_matcher(attr_name)
             for attr_name, attr_type in BrickDesignData.sortables().items()
             if attr_type in {int, float}}
 
@@ -922,18 +939,19 @@ class BricksDb:
             ) -> str:
         'Return formatted result of inquiry on table of table_name.'
         assert table_name in self.lookupables
-        match_keys_vals = []
+        matchers = []
         for instruction in match_by:
-            match_key = match_val = ''
+            match_key = match_val = match_operator = ''
             for idx, c in enumerate(instruction):
-                match_key += c
-                if c in PARAM_Q_DELIMITERS:
+                if c in MATCH_OPERATORS:
+                    match_operator = c
                     match_val = instruction[idx+1:]
                     break
-            match_keys_vals += [(match_key, match_val)]
+                match_key += c
+            matchers += [(match_key, match_operator, match_val)]
         cls = self.lookupables[table_name]
         for arg_keys, x_ers in [
-            (arg, m()) for arg, m in ((tuple(t[0] for t in match_keys_vals),
+            (arg, m()) for arg, m in ((tuple(t[0] for t in matchers),
                                        cls.matchers),
                                       (sort_by, cls.sorters),
                                       ((formatter, ), cls.formatters))]:
@@ -944,9 +962,8 @@ class BricksDb:
             for arg_key in arg_keys:
                 assert (not arg_key) or arg_key in keys, (arg_key, keys)
         if (items := tuple(getattr(self, table_name).values())):
-            for match_key, match_val in match_keys_vals:
-                items = tuple(x for x in items
-                              if x.match(match_key, match_val))
+            for key, operator, val in matchers:
+                items = tuple(x for x in items if x.match(key, operator, val))
             for sorter in sort_by:
                 items = cls.sorters()[sorter][1](self, items)
         return CHAR_NEWLINE.join([cls.formatters()[formatter][1](item)