home · contact · privacy
Add --sort-by {id,box,studs}.
authorPlom Heller <plom@plomlompom.com>
Mon, 1 Jun 2026 01:22:55 +0000 (03:22 +0200)
committerPlom Heller <plom@plomlompom.com>
Mon, 1 Jun 2026 01:22:55 +0000 (03:22 +0200)
bricksplom.py

index f8ae1f9b0cc8f58bdc1a4f259f2aa96a92dd96e9..7f675e2759006f70bdd87072bd77e431eaf68083 100755 (executable)
@@ -29,8 +29,11 @@ CHAR_Q_ID = '='
 CHAR_Q_STUDS = 's'
 CHAR_Q_TEXT = '?'
 BOX_PREFIX = 'box:'
+TOK_SORT_BOX = 'box'
+TOK_SORT_ID = 'id'
+TOK_SORT_STUDS = 'studs'
 
-BrickListing = tuple[int, str, str]
+BrickListing = tuple[int, str, str]  # count, ID, comment
 PageColumn = tuple[BrickListing, ...]
 Page = tuple[PageColumn, ...]
 
@@ -141,6 +144,41 @@ class Lookupable:
         assert query_char in self.matchers, query_char
         return self.matchers[query_char](query_body)
 
+    @classmethod
+    def _by_box_sorter(
+            cls,
+            to_id_of=''
+            ) -> Callable[['BricksDb', tuple[Self, ...]], tuple[Self, ...]]:
+        def by_box(
+                db: 'BricksDb',
+                pre_sorted: tuple[Self, ...]
+                ) -> tuple[Self, ...]:
+            remains = list(pre_sorted)
+            items: list[Self] = []
+            for box in sorted(db.boxes().values(),
+                              key=lambda box: box.id_indented()):
+                for item_id in [t[1] for t in box.brick_listings_flat()]:
+                    if to_id_of:
+                        item_id = getattr(db.bricks[item_id], f'{to_id_of}_id')
+                    if item_id not in [item.id_ for item in items]:
+                        to_move = [item for item in remains
+                                   if item.id_ == item_id][0]
+                        items += [to_move]
+                        remains.remove(to_move)
+            return tuple(items + remains)
+        return by_box
+
+    @property
+    def sorters(
+            self
+            ) -> dict[str, Callable[['BricksDb', tuple[Self, ...]],
+                                    tuple[Self, ...]]]:
+        'Available sorters.'
+        return {
+            TOK_SORT_ID: lambda _, pre_sorted: tuple(
+                sorted(pre_sorted, key=lambda item: item.id_indented()))
+            }
+
 
 class BrickColor(Textfiled, Lookupable):
     'Color incl. solidness/transparency field.'
@@ -170,6 +208,13 @@ class BrickColor(Textfiled, Lookupable):
             collected[id_] = cls(id_, desc[0] == CHAR_COL_SOLID, desc[1:])
         return collected
 
+    @property
+    def sorters(
+            self
+            ) -> dict[str, Callable[['BricksDb', tuple[Self, ...]],
+                                    tuple[Self, ...]]]:
+        return super().sorters | {TOK_SORT_BOX: self._by_box_sorter('color')}
+
     def raw(
             self
             ) -> str:
@@ -221,11 +266,24 @@ class BrickDesign(Textfiled, Lookupable):
     def matchers(
             self
             ) -> dict[str, Callable[[str], bool]]:
-        def q_studs(q_body) -> bool:
+        def q_studs(
+                q_body
+                ) -> bool:
             assert q_body.isdigit()
             return self.n_studs == int(q_body)
         return super().matchers | {CHAR_Q_STUDS: q_studs}
 
+    @property
+    def sorters(
+            self
+            ) -> dict[str, Callable[['BricksDb', tuple[Self, ...]],
+                                    tuple[Self, ...]]]:
+        return super().sorters | {
+            TOK_SORT_BOX: self._by_box_sorter('design'),
+            TOK_SORT_STUDS: lambda _, pre_sorted: tuple(
+                sorted(pre_sorted, key=lambda item: item.n_studs))
+                }
+
     @classmethod
     def from_textfile(
             cls,
@@ -282,6 +340,13 @@ class Brick(Textfiled, WithDb, Lookupable):
         self.comment = comment
         super().__init__(**kwargs)
 
+    @property
+    def sorters(
+            self
+            ) -> dict[str, Callable[['BricksDb', tuple[Self, ...]],
+                                    tuple[Self, ...]]]:
+        return super().sorters | {TOK_SORT_BOX: self._by_box_sorter()}
+
     @classmethod
     def from_textfile(
             cls,
@@ -486,7 +551,7 @@ class Box(WithDb, Lookupable):
         self.id_ = id_
         self._set = bricks_set
         designed_listings: list[tuple[BrickDesign, list[BrickListing]]] = []
-        for listing in self._set.brick_listings_flat():
+        for listing in self.brick_listings_flat():
             design = self._db.designs[self._db.bricks[listing[1]].design_id]
             design = design.alternate_to or design
             if not (designed_listings and designed_listings[-1][0] == design):
@@ -503,6 +568,12 @@ class Box(WithDb, Lookupable):
                 + ', '.join('/'.join(design.all_ids)
                             for design, _ in self.designs_to_listings))
 
+    def brick_listings_flat(
+            self
+            ) -> tuple[BrickListing, ...]:
+        'Shortcut to BrickSet method of same name.'
+        return self._set.brick_listings_flat()
+
     def show(
             self,
             ) -> str:
@@ -598,7 +669,8 @@ class BricksDb:
             table_name: str,
             item_id: str,
             show_raw: bool,
-            filter_by: str
+            filter_by: str,
+            sort_by: str
             ) -> str:
         'Return result of inquiry on table of table_name.'
         assert table_name in self.lookupable
@@ -606,12 +678,16 @@ class BricksDb:
         table = maybe_dict if isinstance(maybe_dict, dict) else maybe_dict()
         if item_id:
             filter_by = CHAR_Q_ID + item_id
-        items = sorted(table.values(), key=lambda x: x.id_indented())
+        items = tuple(table.values())
+        if items and sort_by:
+            items = items[0].sorters[sort_by](self, items)
         if filter_by:
-            items = [item for item in items
-                     if item.match(filter_by[0], filter_by[1:])]
-        return CHAR_NEWLINE.join([(item.raw() if show_raw else str(item))
-                                  for item in items])
+            items = tuple(item for item in items
+                          if item.match(filter_by[0], filter_by[1:]))
+        return CHAR_NEWLINE.join(
+                [(item.raw() if isinstance(item, Textfiled) and show_raw
+                  else str(item))
+                 for item in items])
 
 
 def main(
@@ -639,13 +715,15 @@ def main(
     parser.add_argument('item_id', nargs='?', metavar='ITEM_ID')
     parser.add_argument('-r', '--raw', action='store_true')
     parser.add_argument('-f', '--filter-by', action='store')
+    parser.add_argument('-s', '--sort-by', action='store', default=TOK_SORT_ID)
     args = parser.parse_args()
     print(
         db.lookup(
             table_name=args.table,
             item_id=args.item_id,
             show_raw=args.raw,
-            filter_by=args.filter_by
+            filter_by=args.filter_by,
+            sort_by=args.sort_by
         ).rstrip())