From: Plom Heller Date: Wed, 29 Apr 2026 22:03:13 +0000 (+0200) Subject: Generate Box from Collections, count Collections as additive/subtractive/neither. X-Git-Url: https://plomlompom.com/repos/%7B%7Bdb.prefix%7D%7D/%22https:/validator.w3.org/%7B%7B%20web_path%20%7D%7D/edit?a=commitdiff_plain;p=bricksplom Generate Box from Collections, count Collections as additive/subtractive/neither. --- diff --git a/bricksplom.py b/bricksplom.py index fc1c2c7..7018671 100755 --- a/bricksplom.py +++ b/bricksplom.py @@ -4,7 +4,6 @@ from abc import ABC, abstractmethod from pathlib import Path from typing import Callable, Optional, Self -PATH_BOXES = 'boxes.txt' PATH_COLLECTIONS = 'collections.txt' PATH_COLORS = 'colors.txt' PATH_DESIGNS = 'designs.txt' @@ -17,10 +16,16 @@ CHAR_MINUS = '-' CHAR_COMMA = ',' CHAR_EQ = '=' CHAR_UNDER = '_' -CHAR_COMMENT = '#' -CHAR_SEPARATOR_COLUMN = '-' +CHAR_HASH = '#' +CHAR_IN = '+' +CHAR_OUT = '-' +CHAR_INACTIVE = CHAR_HASH +CHAR_COMMENT = CHAR_HASH +CHAR_SEPARATOR_COLUMN = CHAR_MINUS CHAR_SEPARATOR_PAGE = CHAR_EQ +BOX_PREFIX = 'box:' + PieceListing = tuple[int, str, str] PageColumn = tuple[PieceListing, ...] Page = tuple[PageColumn, ...] @@ -192,10 +197,12 @@ class Collection(Textfiled): def __init__( self, id_: str, + is_in: Optional[bool], description: str, piece_listings: tuple[Page, ...] ) -> None: self.id_ = id_ + self.is_in = is_in self.description = description self.piece_listings = piece_listings @@ -204,13 +211,19 @@ class Collection(Textfiled): cls, path: str ) -> dict[str, Self]: - collected: dict[str, tuple[str, list[list[list[PieceListing]]]]] = {} + collected: dict[str, tuple[Optional[bool], + str, + list[list[list[PieceListing]]]]] + collected = {} i_listings: list[list[list[PieceListing]]] = [[[]]] for line in cls.lines_of(path): if not line.startswith(CHAR_SPACE): - id_, description = cls.tokify(line, 2) + id_, is_in_str, description = cls.tokify(line, 3) + assert is_in_str in {CHAR_IN, CHAR_OUT, CHAR_INACTIVE} + is_in = (None if is_in_str == CHAR_INACTIVE + else is_in_str == CHAR_IN) i_listings = [[[]]] - collected[id_] = description, i_listings + collected[id_] = is_in, description, i_listings elif line[1:2] == CHAR_SEPARATOR_COLUMN: i_listings[-1] += [[]] elif line[1:2] == CHAR_SEPARATOR_PAGE: @@ -223,15 +236,18 @@ class Collection(Textfiled): assert len(id_) > 0 i_listings[-1][-1] += [(int(count), id_, comment)] return { - k: cls(k, v[0], tuple(tuple(tuple(column) for column in page) - for page in v[1])) + k: cls(k, v[0], v[1], tuple(tuple(tuple(column) for column in page) + for page in v[2])) for k, v in collected.items()} def __str__( self ) -> str: - return f'\n{self.id_} {self.description}\n' + self._format_paginated( - lambda count, p_id, comment: f' {count:2} {p_id:>7} {comment}') + is_in_str = (CHAR_INACTIVE if self.is_in is None + else (CHAR_IN if self.is_in else CHAR_OUT)) + return (f'\n{self.id_} {is_in_str} {self.description}\n' + + self._format_paginated(lambda count, p_id, comment: + f' {count:2} {p_id:>7} {comment}')) def _format_paginated( self, @@ -303,7 +319,7 @@ class Collection(Textfiled): print(line) -class Box(Textfiled): +class Box: 'Order of designs.' def __init__( @@ -315,13 +331,27 @@ class Box(Textfiled): self.designs = designs @classmethod - def from_textfile( + def from_collections( cls, - path: str + collections: dict[str, 'Collection'], + pieces: dict[str, 'Piece'], + designs: dict[str, 'Design'] ) -> dict[str, Self]: - return {id_: cls(id_, tuple(order.split(CHAR_COMMA))) - for id_, order in [cls.tokify(line, 2) - for line in cls.lines_of(path)]} + '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 def __str__( self @@ -333,9 +363,10 @@ class Box(Textfiled): pieces: dict[str, 'Piece'], designs: dict[str, 'Design'], colors: dict[str, 'Color'], - collections: dict[str, 'Collections'] + collections: dict[str, 'Collection'] ) -> None: 'Print explanatory listings of pieces expected by .designs.' + print(f'box:{self.id_} - storage box') for design_id in self.designs: design = designs[design_id] print(f'=== {design_id:>6}: {design.description} ===') @@ -394,6 +425,17 @@ def check_consistencies_between_tables( 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 + direction = 1 if coll.is_in else (-1) + for count, piece_id, _ in coll.piece_listings_flat(): + counts[piece_id] = counts.get(piece_id, 0) + (direction * count) + for piece_id, count in counts.items(): + assert count == 0, (piece_id, count) + def main( ) -> None: @@ -401,7 +443,7 @@ def main( pieces = Piece.from_textfile(PATH_PIECES) collections = Collection.from_textfile(PATH_COLLECTIONS) designs = Design.from_textfile(PATH_DESIGNS) - boxes = Box.from_textfile(PATH_BOXES) + boxes = Box.from_collections(collections, pieces, designs) check_consistencies_between_tables(colors, pieces, collections, designs, boxes) for title, items in (('COLORS', colors),