From 5802d7457a8b19c08ea48051c42eab9bf6be4c0b Mon Sep 17 00:00:00 2001 From: Plom Heller Date: Fri, 1 May 2026 19:59:23 +0200 Subject: [PATCH] Coordinate all tables in new BricksDb super-structure. --- bricksplom.py | 289 +++++++++++++++++++++++++++----------------------- 1 file changed, 155 insertions(+), 134 deletions(-) diff --git a/bricksplom.py b/bricksplom.py index 6a8fc73..58c846d 100755 --- a/bricksplom.py +++ b/bricksplom.py @@ -68,11 +68,23 @@ class Textfiled(ABC): @abstractmethod def from_textfile( cls, - path: tuple[str, str] + path: tuple[str, str], + **kwargs ) -> dict[str, Self]: 'Build from file at path.' +class WithDb(ABC): + + def __init__( + self, + db: Optional['BricksDb'] = None, + **kwargs + ) -> None: + self._db = db + super().__init__(**kwargs) + + class Color(Textfiled): 'Color incl. solidness/transparency field.' @@ -89,7 +101,8 @@ class Color(Textfiled): @classmethod def from_textfile( cls, - path: tuple[str, str] + path: tuple[str, str], + **_ ) -> dict[str, Self]: collected = {} for id_, desc in [cls.tokify(line, 2) for line in cls.lines_of(path)]: @@ -120,7 +133,8 @@ class Design(Textfiled): @classmethod def from_textfile( cls, - path: tuple[str, str] + path: tuple[str, str], + **_ ) -> dict[str, Self]: collected = {} alts: dict[str, set[str]] = {} @@ -177,7 +191,8 @@ class Piece(Textfiled): @classmethod def from_textfile( cls, - path: tuple[str, str] + path: tuple[str, str], + **_ ) -> dict[str, Self]: collected = {} for toks in [cls.tokify(line, 3) for line in cls.lines_of(path)]: @@ -194,7 +209,7 @@ class Piece(Textfiled): f'{self.color_id:>3} {self.comment}').rstrip() -class Collection(Textfiled): +class Collection(Textfiled, WithDb): 'Named collection of pieces in order of pages of columns of counts.' def __init__( @@ -202,17 +217,21 @@ class Collection(Textfiled): id_: str, is_in: Optional[bool], description: str, - piece_listings: tuple[Page, ...] + piece_listings: tuple[Page, ...], + **kwargs ) -> None: self.id_ = id_ self.is_in = is_in self.description = description self.piece_listings = piece_listings + super().__init__(**kwargs) @classmethod def from_textfile( cls, - path: tuple[str, str] + path: tuple[str, str], + db: Optional['BricksDb'] = None, + **_ ) -> dict[str, Self]: collected: dict[str, tuple[Optional[bool], str, @@ -240,8 +259,12 @@ class Collection(Textfiled): assert len(id_) > 0 i_listings[-1][-1] += [(int(count), id_, comment)] return { - k: cls(k, v[0], v[1], tuple(tuple(tuple(column) for column in page) - for page in v[2])) + k: cls(id_=k, + is_in=v[0], + description=v[1], + piece_listings=tuple(tuple(tuple(column) for column in page) + for page in v[2]), + db=db) for k, v in collected.items()} def __str__( @@ -276,86 +299,44 @@ class Collection(Textfiled): collected += list(column) return tuple(collected) - def _piece_to_box_idx_and_description( - self, - piece: 'Piece', - designs: dict[str, 'Design'], - boxes: dict[str, 'Box'] - ) -> tuple[str, str]: - design = Design.possibly_by_alt(piece.design_id, designs) - assert design is not None - description = design.description - box = [b for b in 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 - def print_packing_instructions( - self, - pieces: dict[str, 'Piece'], - designs: dict[str, 'Design'], - boxes: dict[str, 'Box'], + self ) -> None: 'Print helper for putting into boxes all pieces of self.' def format_line(_, piece_id, __) -> str: - box_listing, description = self._piece_to_box_idx_and_description( - pieces[piece_id], designs, boxes) + assert self._db + box_listing, description = self._db.piece_to_box_listing(piece_id) return f'{piece_id:>7} {box_listing} {description}' print(self._format_paginated(format_line)) def print_unpacking_instructions( - self, - pieces: dict[str, 'Piece'], - designs: dict[str, 'Design'], - boxes: dict[str, 'Box'], - colors: dict[str, 'Color'] + self ) -> None: 'Print helper for collecting from boxes all pieces of self.' + assert self._db lines = [] for count, piece_id, _ in self.piece_listings_flat(): - piece = pieces[piece_id] - box_listing, description = self._piece_to_box_idx_and_description( - piece, designs, boxes) - color = str(colors[piece.color_id]).lstrip().split(CHAR_SPACE, - maxsplit=1)[1] + box_listing, description = self._db.piece_to_box_listing(piece_id) + color = str(self._db.colors[self._db.pieces[piece_id].color_id] + ).lstrip().split(CHAR_SPACE, maxsplit=1)[1] lines += [f'{box_listing} {count}× {color} {description}'] for line in sorted(lines): print(line) -class Box: +class Box(WithDb): 'Order of designs.' def __init__( self, id_: str, - designs: tuple[str, ...] + designs: tuple[str, ...], + **kwargs ) -> None: self.id_ = id_ self.designs = designs - - @classmethod - def from_collections( - cls, - collections: dict[str, 'Collection'], - pieces: dict[str, 'Piece'], - designs: dict[str, 'Design'] - ) -> dict[str, Self]: - 'Parse from "box:"-prefixed entries in collections.' - collected = {} - for coll in [c for c in 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(pieces[piece_id].design_id, - designs) - assert design - if [design.id_] != boxed_designs[-1:]: - boxed_designs += [design.id_] - collected[box_id] = cls(box_id, tuple(boxed_designs)) - return collected + super().__init__(**kwargs) def __str__( self @@ -364,94 +345,134 @@ class Box: def print_pieces( self, - pieces: dict[str, 'Piece'], - designs: dict[str, 'Design'], - colors: dict[str, 'Color'], - collections: dict[str, 'Collection'] ) -> None: 'Print explanatory listings of pieces expected by .designs.' - print(f'box:{self.id_} - storage box') + assert self._db + print(f'box:{self.id_}') for design_id in self.designs: - design = designs[design_id] + design = self._db.designs[design_id] print(f'=== {design_id:>6}: {design.description} ===') - for piece in [p for p in pieces.values() + for piece in [p for p in self._db.pieces.values() if p.design_id in {design.id_} | design.alternates]: count_pieces = 0 - color = colors[piece.color_id] + color = self._db.colors[piece.color_id] for listings in [c.piece_listings_flat() - for c in collections.values()]: + for c in self._db.colls.values()]: for count in [t[0] for t in listings if t[1] == piece.id_]: count_pieces += count print(f'{count_pieces:>2}× {piece.id_:>7} / {color}') -def check_consistencies_between_tables( - colors: dict[str, Color], - pieces: dict[str, Piece], - collections: dict[str, Collection], - designs: dict[str, Design], - boxes: dict[str, Box] - ) -> None: - 'Ensure intra-table consistencies between inputs.' - - # check all items listed in collections recorded in pieces - for coll in collections.values(): - for _, piece_id, _ in coll.piece_listings_flat(): - assert piece_id in pieces, piece_id - - # check all designs listed in boxes recorded in designs - for design_ids in [b.designs for b in boxes.values()]: - for design_id in design_ids: - assert design_id in designs, design_id - - # check all pieces' designs recorded in designs - for design_id in [piece.design_id for piece in pieces.values()]: - assert Design.possibly_by_alt(design_id, designs), design_id - - # check all recorded designs have matching pieces (at least via alts) - for design_id, alts in [(k, v.alternates) for k, v in designs.items()]: - pieces_found = False - for id_ in [id_ for id_ in {design_id} | alts - if id_ in [piece.design_id for piece in pieces.values()]]: - pieces_found = True - break - assert pieces_found, design_id - - # check all pieces' colors are recorded - for color_id in [v.color_id for v in pieces.values()]: - assert color_id in colors, color_id - - # check collections in-out directions even out - counts: dict[str, int] = {} - for coll in collections.values(): - if coll.is_in is None: - continue - for count, piece_id, _ in coll.piece_listings_flat(): - counts[piece_id] = (counts.get(piece_id, 0) - + ((1 if coll.is_in else (-1)) * count)) - for piece_id, count in counts.items(): - assert count == 0, (piece_id, count) +class BricksDb: + 'Collection of all the tables enabling their combined processing.' + + def __init__( + self, + path_tables: str + ) -> None: + self.colors = Color.from_textfile((path_tables, PATH_COLORS)) + self.pieces = Piece.from_textfile((path_tables, PATH_PIECES)) + self.colls = Collection.from_textfile((path_tables, PATH_COLLECTIONS), + db=self) + self.designs = Design.from_textfile((path_tables, PATH_DESIGNS)) + self._check_consistencies_between_tables() + + def boxes( + self + ) -> dict[str, Box]: + 'Parsed from "box:"-prefixed entries in .colls.' + collected = {} + for coll in [c for c in self.colls.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) + return collected + + def _check_consistencies_between_tables( + self + ) -> None: + + # check all items listed in collections recorded in pieces + for coll in self.colls.values(): + for _, piece_id, _ in coll.piece_listings_flat(): + assert piece_id in self.pieces, 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 design_ids: + assert design_id in self.designs, design_id + + # check all pieces' designs recorded in designs + for design_id in [piece.design_id for piece in self.pieces.values()]: + assert Design.possibly_by_alt(design_id, self.designs), design_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()]: + pieces_found = False + for id_ in [id_ for id_ in {design_id} | alts + if id_ in [piece.design_id + for piece in self.pieces.values()]]: + pieces_found = True + break + assert pieces_found, design_id + + # check all pieces' colors are recorded + for color_id in [v.color_id for v in self.pieces.values()]: + assert color_id in self.colors, color_id + + # check collections in-out directions even out + counts: dict[str, int] = {} + for coll in self.colls.values(): + if coll.is_in is None: + continue + for count, piece_id, _ in coll.piece_listings_flat(): + counts[piece_id] = (counts.get(piece_id, 0) + + ((1 if coll.is_in else (-1)) * count)) + for piece_id, count in counts.items(): + assert count == 0, (piece_id, count) + + def piece_to_box_listing( + 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 + + def print( + self + ) -> None: + 'Show all we know.' + for title, items in (('COLORS', self.colors), + ('PIECES', self.pieces), + ('DESIGNS', self.designs), + ('BOXES', self.boxes()), + ('COLLECTIONS', self.colls)): + print(title) + for item in items.values(): + print(item) def main( ) -> None: 'Load tables from BRICKSPLOM_DIR and put out in reproducible listings.' - dir_tables = environ.get(NAME_ENV_DIRNAME, '.') - colors = Color.from_textfile((dir_tables, PATH_COLORS)) - pieces = Piece.from_textfile((dir_tables, PATH_PIECES)) - collections = Collection.from_textfile((dir_tables, PATH_COLLECTIONS)) - designs = Design.from_textfile((dir_tables, PATH_DESIGNS)) - boxes = Box.from_collections(collections, pieces, designs) - check_consistencies_between_tables(colors, pieces, collections, designs, - boxes) - for title, items in (('COLORS', colors), - ('PIECES', pieces), - ('DESIGNS', designs), - ('BOXES', boxes), - ('COLLECTIONS', collections)): - print(title) - for item in items.values(): - print(item) + db = BricksDb(environ.get(NAME_ENV_DIRNAME, '.')) + db.print() main() -- 2.30.2