From: Plom Heller Date: Thu, 30 Jul 2026 01:32:00 +0000 (+0200) Subject: De-hardcode special treatment of boxes. X-Git-Url: https://plomlompom.com/repos/%22https:/validator.w3.org/static/task?a=commitdiff_plain;h=87646fda0801b31c7e7d690a2f715e4a01dc3845;p=bricksplom De-hardcode special treatment of boxes. --- diff --git a/bricksplom.py b/bricksplom.py index 8c96719..acbe7b7 100755 --- a/bricksplom.py +++ b/bricksplom.py @@ -9,14 +9,9 @@ from argparse import ( from dataclasses import dataclass from os import environ from pathlib import Path -from typing import Callable, Optional, Self +from typing import Any, Callable, Optional, Self NAME_ENV_DIRNAME = 'BRICKSPLOM_DIR' -PATH_SETS = 'sets.txt' -PATH_COLORS = 'colors.txt' -PATH_DESIGNS = 'designs.txt' -PATH_BRICKS = 'bricks.txt' - NAME_ENV_LDRAW = 'PATH_LDRAW' LDRAW_STUDS = { 'stud.dat', @@ -144,14 +139,16 @@ class Lookupable: 'Default single-item display of self.' return str(self) - @property + @classmethod def matchers( - self - ) -> dict[str, Callable[[str], bool]]: + cls + ) -> dict[str, Callable[[Self, str], bool]]: 'Available lookup matchers.' return { - PARAM_Q_ID: lambda q_body: self.id_ == q_body, - PARAM_Q_TEXT: lambda q_body: q_body.upper() in str(self).upper() + PARAM_Q_ID: (lambda item, q_body: + item.id_ == q_body), + PARAM_Q_TEXT: (lambda item, q_body: + q_body.upper() in str(item).upper()) } def match( @@ -160,8 +157,7 @@ class Lookupable: query_body: str ) -> bool: 'Return if self matches query.' - assert query_char in self.matchers, query_char - return self.matchers[query_char](query_body) + return self.matchers()[query_char](self, query_body) @classmethod def _by_box_sorter( @@ -187,15 +183,16 @@ class Lookupable: return tuple(items + remains) return by_box - @property + @classmethod def sorters( - self + cls ) -> 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())) + TOK_SORT_ID: (lambda _, pre_sorted: + tuple(sorted(pre_sorted, + key=lambda item: item.id_indented()))) } @@ -227,12 +224,12 @@ class BrickColor(Textfiled, Lookupable): collected[id_] = cls(id_, desc[0] == CHAR_COL_SOLID, desc[1:]) return collected - @property + @classmethod def sorters( - self + cls ) -> dict[str, Callable[['BricksDb', tuple[Self, ...]], tuple[Self, ...]]]: - return super().sorters | {TOK_SORT_BOX: self._by_box_sorter('color')} + return super().sorters() | {TOK_SORT_BOX: cls._by_box_sorter('color')} def raw( self @@ -282,27 +279,28 @@ class BrickDesign(Textfiled, Lookupable): 'Own .id_ plus (sorted) .alternate_ids.' return tuple([self.id_] + sorted(list(self.alternate_ids))) - @property + @classmethod def matchers( - self - ) -> dict[str, Callable[[str], bool]]: + cls + ) -> dict[str, Callable[[Self, str], bool]]: def attr_matcher( attr_name: str - ) -> Callable[[str], bool]: + ) -> Callable[[Self, str], bool]: def match( + item: Self, q_body: str ) -> bool: assert q_body.isdigit() - return getattr(self, attr_name) == int(q_body) + return getattr(item, attr_name) == int(q_body) return match - return super().matchers | { + return super().matchers() | { f'{attr_name}{CHAR_ATTR_EQ}': attr_matcher(attr_name) for attr_name, attr_type in BrickDesignData.__annotations__.items() if attr_type is int} - @property + @classmethod def sorters( - self + cls ) -> dict[str, Callable[['BricksDb', tuple[Self, ...]], tuple[Self, ...]]]: def attr_sorter( @@ -315,8 +313,8 @@ class BrickDesign(Textfiled, Lookupable): return tuple(sorted(pre_sorted, key=lambda item: getattr(item, attr_name))) return f_sort - return super().sorters\ - | {TOK_SORT_BOX: self._by_box_sorter('design')}\ + return super().sorters()\ + | {TOK_SORT_BOX: cls._by_box_sorter('design')}\ | {attr_name: attr_sorter(attr_name) for attr_name in BrickDesignData.__annotations__} @@ -391,12 +389,12 @@ class Brick(Textfiled, WithDb, Lookupable): self.comment = comment super().__init__(**kwargs) - @property + @classmethod def sorters( - self + cls ) -> dict[str, Callable[['BricksDb', tuple[Self, ...]], tuple[Self, ...]]]: - return super().sorters | {TOK_SORT_BOX: self._by_box_sorter()} + return super().sorters() | {TOK_SORT_BOX: cls._by_box_sorter()} @classmethod def from_textfile( @@ -516,12 +514,13 @@ class BrickSet(Textfiled, WithDb, Lookupable): ) -> str: return f'{self.id_} {self._is_in_str} {self.description}' - @property + @classmethod def matchers( - self - ) -> dict[str, Callable[[str], bool]]: - return super().matchers | { - PARAM_Q_TEXT: lambda q_body: q_body.upper() in self.show().upper() + cls + ) -> dict[str, Callable[[Self, str], bool]]: + return super().matchers() | { + PARAM_Q_TEXT: (lambda item, q_body: + q_body.upper() in item.show().upper()) } def _format_paginated( @@ -642,7 +641,17 @@ class Box(WithDb, Lookupable): class BricksDb: 'Collection of all the tables enabling their combined processing.' - lookupable = frozenset({'boxes', 'bricks', 'colors', 'designs', 'sets'}) + lookupables: dict[str, type[Lookupable]] = { + 'boxes': Box, + 'bricks': Brick, + 'colors': BrickColor, + 'designs': BrickDesign, + 'sets': BrickSet + } + bricks: dict[str, Brick] + colors: dict[str, BrickColor] + designs: dict[str, BrickDesign] + sets: dict[str, BrickSet] def __init__( self, @@ -650,10 +659,10 @@ class BricksDb: path_ldraw: str, ldraw_mode: str ) -> None: - self.colors = BrickColor.from_textfile((path_tables, PATH_COLORS)) - self.bricks = Brick.from_textfile((path_tables, PATH_BRICKS), db=self) - self.sets = BrickSet.from_textfile((path_tables, PATH_SETS), db=self) - self.designs = BrickDesign.from_textfile((path_tables, PATH_DESIGNS)) + for name, cls in self.textfiled_tables().items(): + kwargs = {'db': self} if issubclass(cls, WithDb) else {} + setattr(self, name, cls.from_textfile((path_tables, f'{name}.txt'), + **kwargs)) self._path_ldraw = Path(path_ldraw).joinpath('parts') self._ldraw_mode = ldraw_mode.split(',') if 'fill' in self._ldraw_mode: @@ -665,6 +674,14 @@ class BricksDb: design.direct_attrs.ldraw = filename self._check_consistencies(verify_ldraw='verify' in self._ldraw_mode) + @classmethod + def textfiled_tables( + cls + ) -> dict[str, Any]: + 'Subset of .lookupables of only Textfiled subclasses.' + return {key: value for (key, value) in cls.lookupables.items() + if issubclass(value, Textfiled)} + @property def boxes( self @@ -783,17 +800,18 @@ class BricksDb: sort_by: str ) -> str: 'Return result of inquiry on table of table_name.' - assert (not show_raw) or table_name != 'boxes' - assert hasattr(self, table_name) and table_name in self.lookupable + assert table_name in self.lookupables + assert (not show_raw) or table_name in self.textfiled_tables() + cls = self.lookupables[table_name] table = getattr(self, table_name) if item_id: match_by = PARAM_Q_ID + item_id items = tuple(table.values()) if items and sort_by: - items = items[0].sorters[sort_by](self, items) + items = cls.sorters()[sort_by](self, items) if items and match_by: p_key = '' - for p_key in [p_key for p_key in items[0].matchers + for p_key in [p_key for p_key in cls.matchers() if match_by.startswith(p_key)]: break assert p_key @@ -821,7 +839,7 @@ def main( action, f'{arg} – ambiguous, matches: {", ".join(candidates)}') return (candidates + (arg, ))[0] - choices = BricksDb.lookupable + choices = BricksDb.lookupables.keys() msg = 'one of: ' + ', '.join(sorted(list(choices))) msg += '; NB: abbreviable to first unambiguous character sequence' action = parser.add_argument( @@ -841,12 +859,13 @@ def main( parser.add_argument( '-l', '--ldraw', action='store', default='') - hint_raw = 'not available for boxes table' + hint_raw = ('only available for tables: ' + + ', '.join(BricksDb.textfiled_tables().keys())) action_raw = parser.add_argument( '-r', '--raw', action='store_true', help=f'NB: {hint_raw}') args = parser.parse_args() - if args.table == 'boxes' and args.raw: + if args.table not in BricksDb.textfiled_tables() and args.raw: parser.error(str(ArgumentError(action_raw, hint_raw))) return args