class Design(Textfiled, Lookupable):
'Shape and texture configurations with descriptions and equalities.'
+ alternate_to: Optional[Self] = None
def __init__(
self,
id_: str,
- description: str
+ description=''
) -> None:
self.id_ = id_
- self.description = description
- self.alternates: set[str] = set()
+ self._description = description
+ self.alternate_ids: set[str] = set()
+
+ @property
+ def description(
+ self
+ ) -> str:
+ 'Description text wherever found between self and .alternate_to.'
+ return (f'={self.alternate_to.id_}: {self.alternate_to.description}'
+ if self.alternate_to
+ else self._description)
+
+ @property
+ def all_ids(
+ self
+ ) -> tuple[str, ...]:
+ 'Own .id_ plus (sorted) .alternate_ids.'
+ return tuple([self.id_] + sorted(list(self.alternate_ids)))
@classmethod
def from_textfile(
if char_type == CHAR_DESIGN_ALT:
alts[body] = alts.get(body, set())
alts[body].add(design_id)
+ collected[design_id] = cls(design_id)
else: # == CHAR_DESIGN_DESC
collected[design_id] = cls(design_id, body)
- for id_, alternatives in alts.items():
- collected[id_].alternates = alternatives
+ for id_, alternate_ids in alts.items():
+ collected[id_].alternate_ids = alternate_ids
+ for alt_id in alternate_ids:
+ collected[alt_id].alternate_to = collected[id_]
return collected
def raw(
self
) -> str:
- return '\n'.join([f'{self.id_:>6} _{self.description}']
- + [f'{a:>6} ={self.id_}' for a in self.alternates])
-
- @classmethod
- def possibly_by_alt(
- cls,
- id_queried: str,
- designs: dict[str, Self]
- ) -> Optional[Self]:
- 'In designs find one of id_queried, by way of alternates if necessary.'
- if id_queried in designs:
- return designs[id_queried]
- for design in designs.values():
- if id_queried in design.alternates:
- return design
- return None
+ return f'{self.id_:>6} ' + (f'={self.alternate_to.id_}'
+ if self.alternate_to
+ else f'_{self._description}')
class Piece(Textfiled, WithDb, Lookupable):
self
) -> str:
assert self._db
- design = Design.possibly_by_alt(self.design_id, self._db.designs)
- color = self._db.colors[self.color_id]
+ design = self._db.designs[self.design_id]
+ color = str(self._db.colors[self.color_id]).strip()
comment = f' # {self.comment}' if self.comment else ''
- return f'{self.id_:>7} {self.design_id:>6} {design.description} ({str(color).strip()}){comment}'
+ return (f'{self.id_:>7} {self.design_id:>6} '
+ f'{design.description} ({color}){comment}')
class Collection(Textfiled, WithDb, Lookupable):
def __init__(
self,
id_: str,
- designs: tuple[str, ...],
+ collection: Collection,
**kwargs
) -> None:
- self.id_ = id_
- self.designs = designs
super().__init__(**kwargs)
+ assert self._db
+ self.id_ = id_
+ self._collection = collection
+ designed_listings: list[tuple[Design, list[PieceListing]]] = []
+ for listing in self._collection.piece_listings_flat():
+ design = self._db.designs[self._db.pieces[listing[1]].design_id]
+ design = design.alternate_to or design
+ if not (designed_listings and designed_listings[-1][0] == design):
+ designed_listings += [(design, []), ]
+ designed_listings[-1][1] += [listing]
+ self.designs_to_listings = tuple((d, tuple(ls))
+ for d, ls in designed_listings)
def __str__(
self
) -> str:
- return f'{self.id_:>2} {",".join(self.designs)}'
+ return (f'{self.id_:>2} '
+ + ', '.join('/'.join(design.all_ids)
+ for design, _ in self.designs_to_listings))
def show(
self,
) -> str:
assert self._db
lines = []
- for design_id in self.designs:
- design = self._db.designs[design_id]
- lines += [f'=== {design_id:>6}: {design.description} ===']
- for piece in [p for p in self._db.pieces.values()
- if p.design_id in {design.id_} | design.alternates]:
- count_pieces = 0
- color = self._db.colors[piece.color_id]
- for listings in [c.piece_listings_flat()
- for c in self._db.collections.values()]:
- for count in [t[0] for t in listings if t[1] == piece.id_]:
- count_pieces += count
- lines += [f'{count_pieces:>2}× {piece.id_:>7} / {color}']
+ for design, listings in self.designs_to_listings:
+ lines += [
+ f'=== {" / ".join(design.all_ids)}: {design.description} ===']
+ for count, piece_id, comment in listings:
+ color = self._db.colors[self._db.pieces[piece_id].color_id]
+ lines += [f'{count:>2}× {piece_id:>7} / {color} # {comment}']
return '\n'.join(lines)
def raw(
self
) -> str:
'Call .raw() of base Collection.'
- assert self._db
- return self._db.collections[f'{BOX_PREFIX}{self.id_}'].raw()
+ return self._collection.raw()
class BricksDb:
) -> dict[str, Box]:
'Parsed from BOX_PREFIX-prefixed entries in .collections.'
collected = {}
- for coll in [c for c in self.collections.values()
- if c.id_.startswith(BOX_PREFIX)]:
- box_id = coll.id_[len(BOX_PREFIX):]
- assert box_id
- boxed_designs: list[str] = []
- for piece_id in [t[1] for t in coll.piece_listings_flat()]:
- design = Design.possibly_by_alt(
- self.pieces[piece_id].design_id,
- self.designs)
- assert design
- if [design.id_] != boxed_designs[-1:]:
- boxed_designs += [design.id_]
- collected[box_id] = Box(box_id, tuple(boxed_designs), db=self)
+ for collection in [c for c in self.collections.values()
+ if c.id_.startswith(BOX_PREFIX)]:
+ box_id = collection.id_[len(BOX_PREFIX):]
+ collected[box_id] = Box(box_id, collection, db=self)
return collected
def _check_consistencies_between_tables(
if t[1] not in self.pieces]:
fails += [f'missing Piece of ID: {piece_id}']
- # check all designs listed in boxes recorded in designs
- for design_ids in [b.designs for b in self.boxes().values()]:
- for design_id in [d_id for d_id in design_ids
- if d_id not in self.designs]:
- fails += [f'missing Design of ID: {design_id}']
-
# check all pieces' designs recorded in designs
for d_id in [p.design_id for p in self.pieces.values()
- if not Design.possibly_by_alt(p.design_id, self.designs)]:
+ if p.design_id not in self.designs]:
fails += [f'missing Design of ID: {d_id}']
# check all recorded designs have matching pieces (at least via alts)
- for design_id, alts in [(k, v.alternates)
- for k, v in self.designs.items()]:
- if not [id_ for id_ in {design_id} | alts
- if id_ in [p.design_id for p in self.pieces.values()]]:
+ for design_id in self.designs:
+ if not [p for p in self.pieces.values()
+ if p.design_id == design_id]:
fails += [f'missing Pieces for design of ID: {design_id}']
# check all pieces' colors are recorded
self,
piece_id: str,
) -> tuple[str, str]:
- 'For Piece of piece_id, print bosition in .boxes() and description.'
- design = Design.possibly_by_alt(self.pieces[piece_id].design_id,
- self.designs)
- assert design is not None
- description = design.description
- box = [b for b in self.boxes().values() if design.id_ in b.designs][0]
- idx_in_box = box.designs.index(design.id_) + 1
- return f'{box.id_:>2}:{idx_in_box:>2}', description
+ 'For Piece of piece_id, print position in .boxes() and description.'
+ design = self.designs[self.pieces[piece_id].design_id]
+ owning_box: Optional[Box] = None
+ for box in self.boxes().values():
+ for idx_in_box in [
+ idx for idx, d_to_ls in enumerate(box.designs_to_listings)
+ if piece_id in [listing[1] for listing in d_to_ls[1]]]:
+ owning_box = box
+ break
+ if owning_box:
+ break
+ if not owning_box:
+ for box in self.boxes().values():
+ for idx_in_box in [
+ idx for idx, t in enumerate(box.designs_to_listings)
+ if design.id_ in t[0].all_ids]:
+ owning_box = box
+ break
+ if owning_box:
+ break
+ assert owning_box
+ return f'{box.id_:>2}:{idx_in_box:>2}', design.description
def print(
self