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',
'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(
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(
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())))
}
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
'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(
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__}
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(
) -> 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(
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,
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:
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
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
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(
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