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'
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, ...]
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
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:
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,
print(line)
-class Box(Textfiled):
+class Box:
'Order of designs.'
def __init__(
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
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} ===')
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:
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),