home · contact · privacy
Underscore names not meant to be shared.
authorPlom Heller <plom@plomlompom.com>
Sun, 4 Oct 2026 17:29:55 +0000 (19:29 +0200)
committerPlom Heller <plom@plomlompom.com>
Sun, 4 Oct 2026 17:29:55 +0000 (19:29 +0200)
src/bricksplom/ldraw.py
src/bricksplom/misc.py

index 4fa56bc67b9d16c650b941c59306e2031960b5ee..3fb10d201dbcc16efd2b3898c7c1bf9fdfe9ad87 100644 (file)
@@ -7,7 +7,7 @@ from typing import Any, Callable, NamedTuple, Optional, Self
 # ourselves
 from bricksplom.constants import CHAR_NEWLINE, NUMBER_UNSET
 
-LDRAW_STUDS = {
+_LDRAW_STUDS = {
     'stud.dat',
     'studa.dat',
     'stud2.dat',
@@ -161,7 +161,7 @@ class LdrawDb:
                                       if jdx != idx and row[jdx] != 0]:
                         do_ignore = True
                         break
-            return (int((not do_ignore) and ref in LDRAW_STUDS) or walk(), )
+            return (int((not do_ignore) and ref in _LDRAW_STUDS) or walk(), )
 
         return self._walk_tree(filename,
                                collect=count_studs,
index 0bbe80ea371f9f1416f7237b2394550c8ab84199..ff8c5b336f8c45b33b267f51313ea65ca3455deb 100644 (file)
@@ -9,53 +9,53 @@ from typing import Any, Callable, Optional, Self
 from bricksplom.constants import CHAR_NEWLINE, NUMBER_UNSET
 from bricksplom.ldraw import LdrawDb, LdrawPathFailure, LdrawRefsTree, XYZ
 
-CHAR_SEP_TOKEN = ' '
-CHAR_COMMENT = '#'
-CHAR_DESIGN_ALT = '='
-CHAR_ATTR_EQ = '='
-CHAR_ATTR_LESS = '<'
-CHAR_ATTR_MORE = '>'
-CHAR_TEXTCONTAINS = ':'
-SEP_DESIGN_DESC = '#'
-CHAR_COLL_INDENT = ' '
+_CHAR_SEP_TOKEN = ' '
+_CHAR_COMMENT = '#'
+_CHAR_DESIGN_ALT = '='
+_CHAR_ATTR_EQ = '='
+_CHAR_ATTR_LESS = '<'
+_CHAR_ATTR_MORE = '>'
+_CHAR_TEXTCONTAINS = ':'
+_SEP_DESIGN_DESC = '#'
+_CHAR_COLL_INDENT = ' '
 CHAR_SEP_LDRAW_MODES = ','
-CHAR_COLL_IN = '+'
-CHAR_COLL_OUT = '-'
-CHAR_COLL_INACTIVE = '#'
-CHAR_COLL_SEP_COLUMN = '-'
-CHAR_COLL_SEP_PAGE = '='
-CHAR_COL_SOLID = '+'
-CHAR_COL_TRANSPARENT = '-'
-BOX_PREFIX = 'box:'
+_CHAR_COLL_IN = '+'
+_CHAR_COLL_OUT = '-'
+_CHAR_COLL_INACTIVE = '#'
+_CHAR_COLL_SEP_COLUMN = '-'
+_CHAR_COLL_SEP_PAGE = '='
+_CHAR_COL_SOLID = '+'
+_CHAR_COL_TRANSPARENT = '-'
+_BOX_PREFIX = 'box:'
 
 ARG_QMARK = '?'
-PARAM_Q_ID = f'id{CHAR_ATTR_EQ}'
-MATCH_OPERATORS = {
-        CHAR_ATTR_EQ,
-        CHAR_TEXTCONTAINS,
-        CHAR_ATTR_LESS,
-        CHAR_ATTR_MORE
+PARAM_Q_ID = f'id{_CHAR_ATTR_EQ}'
+_MATCH_OPERATORS = {
+        _CHAR_ATTR_EQ,
+        _CHAR_TEXTCONTAINS,
+        _CHAR_ATTR_LESS,
+        _CHAR_ATTR_MORE
         }
-TOK_SORT_BOX = 'box'
-TOK_SORT_ID = 'id'
-
-LDRAW_MODE_FILL_N_STUDS = 'fill_n_studs'
-LDRAW_MODE_FILL_PATHS = 'fill_paths'
-LDRAW_MODE_FILL_SIZES = 'fill_sizes'
-LDRAW_MODE_VERIFY_N_STUDS = 'verify_n_studs'
-LDRAW_MODE_VERIFY_PATHS = 'verify_paths'
-LDRAW_MODES = {LDRAW_MODE_FILL_N_STUDS,
-               LDRAW_MODE_FILL_PATHS,
-               LDRAW_MODE_FILL_SIZES,
-               LDRAW_MODE_VERIFY_N_STUDS,
-               LDRAW_MODE_VERIFY_PATHS}
-
-BrickListing = tuple[int, str, str]  # count, ID, comment
-PageColumn = tuple[BrickListing, ...]
-Page = tuple[PageColumn, ...]
-
-
-class Textfiled(ABC):
+_TOK_SORT_BOX = 'box'
+_TOK_SORT_ID = 'id'
+
+_LDRAW_MODE_FILL_N_STUDS = 'fill_n_studs'
+_LDRAW_MODE_FILL_PATHS = 'fill_paths'
+_LDRAW_MODE_FILL_SIZES = 'fill_sizes'
+_LDRAW_MODE_VERIFY_N_STUDS = 'verify_n_studs'
+_LDRAW_MODE_VERIFY_PATHS = 'verify_paths'
+LDRAW_MODES = {_LDRAW_MODE_FILL_N_STUDS,
+               _LDRAW_MODE_FILL_PATHS,
+               _LDRAW_MODE_FILL_SIZES,
+               _LDRAW_MODE_VERIFY_N_STUDS,
+               _LDRAW_MODE_VERIFY_PATHS}
+
+_BrickListing = tuple[int, str, str]  # count, ID, comment
+_PageColumn = tuple[_BrickListing, ...]
+_Page = tuple[_PageColumn, ...]
+
+
+class _Textfiled(ABC):
     'Table to be read from textfile, and compatible output in .raw().'
 
     @staticmethod
@@ -67,7 +67,7 @@ class Textfiled(ABC):
         return tuple(line.rstrip()
                      for line in joined_path.read_text(encoding='utf8'
                                                        ).split(CHAR_NEWLINE)
-                     if line.strip() and not line.startswith(CHAR_COMMENT))
+                     if line.strip() and not line.startswith(_CHAR_COMMENT))
 
     @staticmethod
     def tokify(
@@ -81,8 +81,8 @@ class Textfiled(ABC):
             if len(collected) == len_expected - 1:
                 tok = body
             else:
-                assert CHAR_SEP_TOKEN in body, body
-                tok, body = body.split(CHAR_SEP_TOKEN, maxsplit=1)
+                assert _CHAR_SEP_TOKEN in body, body
+                tok, body = body.split(_CHAR_SEP_TOKEN, maxsplit=1)
             collected += [tok]
         return tuple(collected)
 
@@ -105,7 +105,7 @@ class Textfiled(ABC):
         return self.raw()
 
 
-class WithDb:
+class _WithDb:
     'Add db:Optional[BricksDB] field to __init__, setting .db.'
 
     def __init__(
@@ -117,7 +117,7 @@ class WithDb:
         super().__init__(**kwargs)
 
 
-class Lookupable:
+class _Lookupable:
     'Provides methods for BricksDb.lookup and .id_ padding.'
     _id_indent: int = 0
     id_: str
@@ -152,7 +152,7 @@ class Lookupable:
                 'most commonly relevant infos',
                 lambda item: item.show())
             }
-        if issubclass(cls, Textfiled):
+        if issubclass(cls, _Textfiled):
             formatters['raw'] = (
                     'in source-file format',
                     lambda item: item.raw())
@@ -164,14 +164,14 @@ class Lookupable:
             ) -> dict[str, tuple[str, dict[str, Callable[[Self, str], bool]]]]:
         'Available lookup matchers.'
         return {
-            TOK_SORT_ID: (
+            _TOK_SORT_ID: (
                 'ID of individual item',
-                {CHAR_ATTR_EQ: (
+                {_CHAR_ATTR_EQ: (
                     lambda item, val: item.id_ == val)}
                 ),
             'text': (
                 'text contained in item description',
-                {CHAR_TEXTCONTAINS: (
+                {_CHAR_TEXTCONTAINS: (
                     lambda item, val: val.upper() in item.show().upper())}
                 ),
             }
@@ -220,7 +220,7 @@ class Lookupable:
                                                tuple[Self, ...]]]]:
         'Available sorters.'
         return {
-            TOK_SORT_ID: (
+            _TOK_SORT_ID: (
                 'by individual items\' IDs',
                 lambda _, pre_sorted:
                 tuple(sorted(pre_sorted,
@@ -228,7 +228,7 @@ class Lookupable:
             }
 
 
-class BrickColor(Textfiled, Lookupable, WithDb):
+class _BrickColor(_Textfiled, _Lookupable, _WithDb):
     'Color incl. solidness/transparency field.'
     _id_indent = 3
 
@@ -255,8 +255,8 @@ class BrickColor(Textfiled, Lookupable, WithDb):
         for id_, desc in [cls.tokify(line, 2) for line in cls.lines_of(path)]:
             assert id_ not in collected
             assert len(desc) > 1
-            assert desc[0] in {CHAR_COL_SOLID, CHAR_COL_TRANSPARENT}
-            collected[id_] = cls(id_, desc[0] == CHAR_COL_SOLID, desc[1:],
+            assert desc[0] in {_CHAR_COL_SOLID, _CHAR_COL_TRANSPARENT}
+            collected[id_] = cls(id_, desc[0] == _CHAR_COL_SOLID, desc[1:],
                                  db=db)
         return collected
 
@@ -266,13 +266,13 @@ class BrickColor(Textfiled, Lookupable, WithDb):
             ) -> dict[str, tuple[str,
                                  Callable[['BricksDb', tuple[Self, ...]],
                                           tuple[Self, ...]]]]:
-        return super().sorters() | {TOK_SORT_BOX: cls._by_box_sorter('color')}
+        return super().sorters() | {_TOK_SORT_BOX: cls._by_box_sorter('color')}
 
     def show(
             self
             ) -> str:
         def idx_in_sample_set(
-                pages: tuple[Page, ...]
+                pages: tuple[_Page, ...]
                 ) -> str:
             for idx, page in enumerate(pages):
                 for jdx, column in enumerate(page):
@@ -291,13 +291,13 @@ class BrickColor(Textfiled, Lookupable, WithDb):
             self
             ) -> str:
         return (f'{self.id_indented()} '
-                + (CHAR_COL_SOLID if self.solid else CHAR_COL_TRANSPARENT)
+                + (_CHAR_COL_SOLID if self.solid else _CHAR_COL_TRANSPARENT)
                 + self.wavelength)
 
 
 @dataclass
-class BrickDesignData:
-    'Lookupables for BrickDesign besides .id.'
+class _BrickDesignData:
+    'Lookupables for _BrickDesign besides .id.'
     n_studs: int = dc_field(default=NUMBER_UNSET, metadata={
         'usage': 'number of studs'})
     description: str = dc_field(default='?', metadata={
@@ -350,17 +350,17 @@ class BrickDesignData:
             cls,
             text: str
             ) -> Self:
-        'Parse from format expected by BrickDesign.from_textfile.'
-        assert SEP_DESIGN_DESC in text
-        parts = text.split(SEP_DESIGN_DESC, maxsplit=1)
+        'Parse from format expected by _BrickDesign.from_textfile.'
+        assert _SEP_DESIGN_DESC in text
+        parts = text.split(_SEP_DESIGN_DESC, maxsplit=1)
         attributables = cls.sortables()
         attrs = cls(description=parts[-1])
         metadata = parts[0].rstrip() if len(parts) > 1 else ''
-        for attr in [a for a in metadata.split(CHAR_SEP_TOKEN) if a]:
-            assert CHAR_ATTR_EQ in attr, attr
-            a_key, a_val_str = attr.split(CHAR_ATTR_EQ, maxsplit=1)
+        for attr in [a for a in metadata.split(_CHAR_SEP_TOKEN) if a]:
+            assert _CHAR_ATTR_EQ in attr, attr
+            a_key, a_val_str = attr.split(_CHAR_ATTR_EQ, maxsplit=1)
             assert a_key in attributables
-            if a_val_str[:1] == CHAR_ATTR_EQ:
+            if a_val_str[:1] == _CHAR_ATTR_EQ:
                 a_val_str = a_val_str[1:]
                 attrs.secure((a_key))
             if attributables[a_key] is int:
@@ -375,7 +375,7 @@ class BrickDesignData:
         return attrs
 
 
-class BrickDesign(Textfiled, WithDb, Lookupable):
+class _BrickDesign(_Textfiled, _WithDb, _Lookupable):
     'Shape and texture configurations with descriptions and equalities.'
     _id_indent = 6
     alternate_to: Optional[Self] = None
@@ -383,7 +383,7 @@ class BrickDesign(Textfiled, WithDb, Lookupable):
     def __init__(
             self,
             id_: str,
-            attrs: Optional[BrickDesignData] = None,
+            attrs: Optional[_BrickDesignData] = None,
             **kwargs
             ) -> None:
         self.id_ = id_
@@ -392,7 +392,7 @@ class BrickDesign(Textfiled, WithDb, Lookupable):
         super().__init__(**kwargs)
 
     def __getattribute__(self, key: str):
-        if key in BrickDesignData.__annotations__ or key == 'secured':
+        if key in _BrickDesignData.__annotations__ or key == 'secured':
             if self.direct_attrs:
                 attrs = self.direct_attrs
             else:
@@ -413,8 +413,8 @@ class BrickDesign(Textfiled, WithDb, Lookupable):
             self,
             attr_name: str
             ) -> bool:
-        'Compare current attribute value to BrickDesignData default.'
-        return getattr(self, attr_name) == getattr(BrickDesignData, attr_name)
+        'Compare current attribute value to _BrickDesignData default.'
+        return getattr(self, attr_name) == getattr(_BrickDesignData, attr_name)
 
     @classmethod
     def formatters(
@@ -473,13 +473,13 @@ class BrickDesign(Textfiled, WithDb, Lookupable):
                     ) -> float:
                 assert q_body.isdigit()
                 return int(q_body) - float(getattr(item, attr_name))
-            return 'by ' + BrickDesignData.usage(attr_name), {
-                    CHAR_ATTR_EQ: lambda item, v: 0 == cmp(item, v),
-                    CHAR_ATTR_LESS: lambda item, v: 0 < cmp(item, v),
-                    CHAR_ATTR_MORE: lambda item, v: 0 > cmp(item, v)}
+            return 'by ' + _BrickDesignData.usage(attr_name), {
+                    _CHAR_ATTR_EQ: lambda item, v: 0 == cmp(item, v),
+                    _CHAR_ATTR_LESS: lambda item, v: 0 < cmp(item, v),
+                    _CHAR_ATTR_MORE: lambda item, v: 0 > cmp(item, v)}
         return super().matchers() | {
             f'{attr_name}': attr_matcher(attr_name)
-            for attr_name, attr_type in BrickDesignData.sortables().items()
+            for attr_name, attr_type in _BrickDesignData.sortables().items()
             if attr_type in {int, float}}
 
     @classmethod
@@ -498,11 +498,11 @@ class BrickDesign(Textfiled, WithDb, Lookupable):
                     ) -> tuple[Self, ...]:
                 return tuple(sorted(pre_sorted,
                                     key=lambda item: getattr(item, attr_name)))
-            return 'by ' + BrickDesignData.usage(attr_name), f_sort
+            return 'by ' + _BrickDesignData.usage(attr_name), f_sort
         return super().sorters()\
-            | {TOK_SORT_BOX: cls._by_box_sorter('design')}\
+            | {_TOK_SORT_BOX: cls._by_box_sorter('design')}\
             | {attr_name: attr_sorter(attr_name)
-               for attr_name in BrickDesignData.sortables().keys()}
+               for attr_name in _BrickDesignData.sortables().keys()}
 
     @classmethod
     def from_textfile(
@@ -517,14 +517,14 @@ class BrickDesign(Textfiled, WithDb, Lookupable):
                                 for line in cls.lines_of(path)]:
             assert design_id not in collected, design_id
             assert len(body) > 1
-            if body[0] == CHAR_DESIGN_ALT:
+            if body[0] == _CHAR_DESIGN_ALT:
                 alt_id = body[1:]
                 alts[alt_id] = alts.get(alt_id, set())
                 alts[alt_id].add(design_id)
                 collected[design_id] = cls(design_id, db=db)
             else:
                 collected[design_id] = cls(
-                    design_id, db=db, attrs=BrickDesignData.from_text(body))
+                    design_id, db=db, attrs=_BrickDesignData.from_text(body))
         for id_, alternate_ids in alts.items():
             collected[id_].alternate_ids = alternate_ids
             for alt_id in alternate_ids:
@@ -536,17 +536,17 @@ class BrickDesign(Textfiled, WithDb, Lookupable):
             ) -> str:
         raw = f'{self.id_indented()} '
         if self.alternate_to:
-            return f'{raw}{CHAR_DESIGN_ALT}{self.alternate_to.id_}'
+            return f'{raw}{_CHAR_DESIGN_ALT}{self.alternate_to.id_}'
         attrs = []
-        for attr_key in [k for k in BrickDesignData.sortables()
+        for attr_key in [k for k in _BrickDesignData.sortables()
                          if k != 'description' and not self.at_default(k)]:
-            operator = CHAR_ATTR_EQ * (1 + int(self.secured(attr_key)))
+            operator = _CHAR_ATTR_EQ * (1 + int(self.secured(attr_key)))
             attrs += [f'{attr_key}{operator}{getattr(self, attr_key)}']
-        attrs += [f'{SEP_DESIGN_DESC}{self.description}']
-        return f'{raw}{CHAR_SEP_TOKEN.join(attrs)}'
+        attrs += [f'{_SEP_DESIGN_DESC}{self.description}']
+        return f'{raw}{_CHAR_SEP_TOKEN.join(attrs)}'
 
 
-class Brick(Textfiled, WithDb, Lookupable):
+class _Brick(_Textfiled, _WithDb, _Lookupable):
     'Individual configuration of design and color.'
     _id_indent = 7
 
@@ -569,7 +569,7 @@ class Brick(Textfiled, WithDb, Lookupable):
             cls
             ) -> dict[str, tuple[str, Callable[['BricksDb', tuple[Self, ...]],
                                                tuple[Self, ...]]]]:
-        return super().sorters() | {TOK_SORT_BOX: cls._by_box_sorter()}
+        return super().sorters() | {_TOK_SORT_BOX: cls._by_box_sorter()}
 
     @classmethod
     def from_textfile(
@@ -582,7 +582,7 @@ class Brick(Textfiled, WithDb, Lookupable):
         for toks in [cls.tokify(line, 3) for line in cls.lines_of(path)]:
             brick_id, design_id = toks[:2]
             assert brick_id not in collected, brick_id
-            color_id, comment = (toks[-1].split(CHAR_SEP_TOKEN, maxsplit=1)
+            color_id, comment = (toks[-1].split(_CHAR_SEP_TOKEN, maxsplit=1)
                                  + [''])[:2]
             collected[brick_id] = cls(brick_id, design_id, color_id, comment,
                                       db=db)
@@ -592,8 +592,8 @@ class Brick(Textfiled, WithDb, Lookupable):
             self
             ) -> str:
         return (f'{self.id_indented()} '
-                f'{BrickDesign.indent_id(self.design_id)} '
-                f'{BrickColor.indent_id(self.color_id)} {self.comment}'
+                f'{_BrickDesign.indent_id(self.design_id)} '
+                f'{_BrickColor.indent_id(self.color_id)} {self.comment}'
                 ).rstrip()
 
     def __str__(
@@ -603,11 +603,11 @@ class Brick(Textfiled, WithDb, Lookupable):
         color = str(self.db.colors[self.color_id]).strip()
         comment = f' # {self.comment}' if self.comment else ''
         return (f'{self.id_indented()} '
-                f'{BrickDesign.indent_id(self.design_id)} '
+                f'{_BrickDesign.indent_id(self.design_id)} '
                 f'{design.description} ({color}){comment}')
 
 
-class BrickSet(Textfiled, WithDb, Lookupable):
+class BrickSet(_Textfiled, _WithDb, _Lookupable):
     'Named collection of bricks in order of pages of columns of counts.'
 
     def __init__(
@@ -615,7 +615,7 @@ class BrickSet(Textfiled, WithDb, Lookupable):
             id_: str,
             is_in: Optional[bool],
             description: str,
-            brick_listings: tuple[Page, ...],
+            brick_listings: tuple[_Page, ...],
             **kwargs
             ) -> None:
         self.id_ = id_
@@ -633,28 +633,28 @@ class BrickSet(Textfiled, WithDb, Lookupable):
             ) -> dict[str, Self]:
         collected: dict[str, tuple[Optional[bool],
                                    str,
-                                   list[list[list[BrickListing]]]]]
+                                   list[list[list[_BrickListing]]]]]
         collected = {}
-        i_listings: list[list[list[BrickListing]]] = [[[]]]
+        i_listings: list[list[list[_BrickListing]]] = [[[]]]
         for line in cls.lines_of(path):
-            if not line.startswith(CHAR_COLL_INDENT):
+            if not line.startswith(_CHAR_COLL_INDENT):
                 id_, metadata = cls.tokify(line, 2)
                 assert metadata
-                assert metadata[0] in {CHAR_COLL_IN,
-                                       CHAR_COLL_OUT,
-                                       CHAR_COLL_INACTIVE}
-                is_in = (None if metadata[0] == CHAR_COLL_INACTIVE
-                         else metadata[0] == CHAR_COLL_IN)
+                assert metadata[0] in {_CHAR_COLL_IN,
+                                       _CHAR_COLL_OUT,
+                                       _CHAR_COLL_INACTIVE}
+                is_in = (None if metadata[0] == _CHAR_COLL_INACTIVE
+                         else metadata[0] == _CHAR_COLL_IN)
                 i_listings = [[[]]]
                 collected[id_] = is_in, metadata[1:], i_listings
-            elif line[1:2] == CHAR_COLL_SEP_COLUMN:
+            elif line[1:2] == _CHAR_COLL_SEP_COLUMN:
                 i_listings[-1] += [[]]
-            elif line[1:2] == CHAR_COLL_SEP_PAGE:
+            elif line[1:2] == _CHAR_COLL_SEP_PAGE:
                 i_listings += [[[]]]
             else:
                 count, remainder = cls.tokify(line, 2)
                 assert count.isdigit()
-                id_, comment = (remainder.split(CHAR_SEP_TOKEN, maxsplit=1)
+                id_, comment = (remainder.split(_CHAR_SEP_TOKEN, maxsplit=1)
                                 + [''])[:2]
                 assert len(id_) > 0
                 i_listings[-1][-1] += [(int(count), id_, comment)]
@@ -671,8 +671,8 @@ class BrickSet(Textfiled, WithDb, Lookupable):
     def _is_in_str(
             self
             ) -> str:
-        return (CHAR_COLL_INACTIVE if self.is_in is None
-                else (CHAR_COLL_IN if self.is_in else CHAR_COLL_OUT))
+        return (_CHAR_COLL_INACTIVE if self.is_in is None
+                else (_CHAR_COLL_IN if self.is_in else _CHAR_COLL_OUT))
 
     def raw(
             self
@@ -680,7 +680,7 @@ class BrickSet(Textfiled, WithDb, Lookupable):
         return (f'{self.id_indented()} '
                 f'{self._is_in_str}{self.description}{CHAR_NEWLINE}'
                 + self._format_paginated(lambda count, p_id, comment:
-                                         f' {count:4} {Brick.indent_id(p_id)}'
+                                         f' {count:4} {_Brick.indent_id(p_id)}'
                                          + (f' {comment}' if comment else '')))
 
     def __str__(
@@ -703,9 +703,9 @@ class BrickSet(Textfiled, WithDb, Lookupable):
                     lines += [format_line(count, brick_id, comment)]
         return CHAR_NEWLINE.join(lines) + CHAR_NEWLINE
 
-    def brick_listings_flat(self) -> tuple[BrickListing, ...]:
+    def brick_listings_flat(self) -> tuple[_BrickListing, ...]:
         'Flattened variant of .brick_listings, no division into pages/cols.'
-        collected: list[BrickListing] = []
+        collected: list[_BrickListing] = []
         for page in self.brick_listings:
             for column in page:
                 collected += list(column)
@@ -720,9 +720,9 @@ class BrickSet(Textfiled, WithDb, Lookupable):
             design_id = brick.design_id
             tail_comment = f' # {comment}' if comment else ''
             color = str(self.db.colors[self.db.bricks[brick_id].color_id]
-                        ).lstrip().split(CHAR_SEP_TOKEN, maxsplit=1)[1]
+                        ).lstrip().split(_CHAR_SEP_TOKEN, maxsplit=1)[1]
             if to_boxes:
-                box: Optional[Box] = None
+                box: Optional[_Box] = None
                 for i_box in self.db.boxes.values():
                     for idx_in_box in [
                             idx for idx, d_to_ls
@@ -744,13 +744,13 @@ class BrickSet(Textfiled, WithDb, Lookupable):
                         if box:
                             break
                 box_listing = ((box.id_indented() if box
-                                else Box.indent_id(''))
+                                else _Box.indent_id(''))
                                + ':' + (f'{idx_in_box:>2}' if box else '__'))
             else:
                 box_listing = ''
             return (
-                f'{count:>2}× {Brick.indent_id(brick_id)}:'
-                f'{BrickDesign.indent_id(design_id)} {box_listing} {color} '
+                f'{count:>2}× {_Brick.indent_id(brick_id)}:'
+                f'{_BrickDesign.indent_id(design_id)} {box_listing} {color} '
                 + self.db.designs[design_id].description + tail_comment)
 
         return f'{self}{CHAR_NEWLINE}{self._format_paginated(format_line)}'
@@ -767,7 +767,7 @@ class BrickSet(Textfiled, WithDb, Lookupable):
                 }
 
 
-class Box(WithDb, Lookupable):
+class _Box(_WithDb, _Lookupable):
     'Order of designs.'
     _id_indent = 5
 
@@ -780,7 +780,7 @@ class Box(WithDb, Lookupable):
         super().__init__(**kwargs)
         self.id_ = id_
         self._set = bricks_set
-        designed_listings: list[tuple[BrickDesign, list[BrickListing]]] = []
+        designed_listings: list[tuple[_BrickDesign, list[_BrickListing]]] = []
         for listing in self.brick_listings_flat():
             design = self.db.designs[self.db.bricks[listing[1]].design_id]
             design = design.alternate_to or design
@@ -800,7 +800,7 @@ class Box(WithDb, Lookupable):
 
     def brick_listings_flat(
             self
-            ) -> tuple[BrickListing, ...]:
+            ) -> tuple[_BrickListing, ...]:
         'Shortcut to BrickSet method of same name.'
         return self._set.brick_listings_flat()
 
@@ -813,23 +813,24 @@ class Box(WithDb, Lookupable):
                 f'=== {" / ".join(design.all_ids)}: {design.description} ===']
             for count, brick_id, comment in listings:
                 color = self.db.colors[self.db.bricks[brick_id].color_id]
-                lines += [f'{count:>2}× {Brick.indent_id(brick_id)} / {color} '
-                          f'# {comment}']
+                lines += [
+                        f'{count:>2}× {_Brick.indent_id(brick_id)} / {color} '
+                        f'# {comment}']
         return CHAR_NEWLINE.join(lines)
 
 
 class BricksDb:
     'Collection of all the tables enabling their combined processing.'
-    lookupables: dict[str, type[Lookupable]] = {
-            'boxes': Box,
-            'bricks': Brick,
-            'colors': BrickColor,
-            'designs': BrickDesign,
+    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]
+    bricks: dict[str, _Brick]
+    colors: dict[str, _BrickColor]
+    designs: dict[str, _BrickDesign]
     sets: dict[str, BrickSet]
 
     def __init__(
@@ -841,13 +842,13 @@ class BricksDb:
             ) -> None:
         self._name_set_colors = name_set_colors
         for name, cls in [(name, cls) for name, cls in self.lookupables.items()
-                          if issubclass(cls, Textfiled)]:
-            kwargs = {'db': self} if issubclass(cls, WithDb) else {}
+                          if issubclass(cls, _Textfiled)]:
+            kwargs = {'db': self} if issubclass(cls, _WithDb) else {}
             setattr(self, name, cls.from_textfile((path_tables, f'{name}.txt'),
                                                   **kwargs))
         self.ldraw = LdrawDb(Path(path_ldraw))
         self._ldraw_modes = ldraw_modes.split(CHAR_SEP_LDRAW_MODES)
-        if LDRAW_MODE_FILL_PATHS in self._ldraw_modes:
+        if _LDRAW_MODE_FILL_PATHS in self._ldraw_modes:
             for design in [design for design in self.designs.values()
                            if not (design.alternate_to or design.ldraw)]:
                 filename = f'{design.id_}.dat'
@@ -861,12 +862,12 @@ class BricksDb:
                     design.direct_attrs.ldraw = filename
         self._check_consistencies()
         for design in self._designs_ldrawed():
-            if LDRAW_MODE_FILL_N_STUDS in self._ldraw_modes\
+            if _LDRAW_MODE_FILL_N_STUDS in self._ldraw_modes\
                     and design.at_default('n_studs'):
                 assert design.direct_attrs is not None
                 design.direct_attrs.n_studs\
                     = self.ldraw.count_studs(design.ldraw)
-            if LDRAW_MODE_FILL_SIZES in self._ldraw_modes:
+            if _LDRAW_MODE_FILL_SIZES in self._ldraw_modes:
                 xyz_calc = XYZ()
                 for k in [k for k in ('x', 'y', 'z') if design.at_default(k)]:
                     if set(xyz_calc) == {NUMBER_UNSET}:
@@ -876,18 +877,18 @@ class BricksDb:
     @property
     def boxes(
             self
-            ) -> dict[str, Box]:
-        'Parsed from BOX_PREFIX-prefixed entries in .sets.'
+            ) -> dict[str, _Box]:
+        'Parsed from _BOX_PREFIX-prefixed entries in .sets.'
         collected = {}
         for bricks_set in [c for c in self.sets.values()
-                           if c.id_.startswith(BOX_PREFIX)]:
-            box_id = bricks_set.id_[len(BOX_PREFIX):]
-            collected[box_id] = Box(box_id, bricks_set, db=self)
+                           if c.id_.startswith(_BOX_PREFIX)]:
+            box_id = bricks_set.id_[len(_BOX_PREFIX):]
+            collected[box_id] = _Box(box_id, bricks_set, db=self)
         return collected
 
     def _designs_ldrawed(
             self
-            ) -> tuple[BrickDesign, ...]:
+            ) -> tuple[_BrickDesign, ...]:
         return tuple(design for design in self.designs.values()
                      if (not design.alternate_to)
                      and design.ldraw and design.ldraw != '!')
@@ -934,13 +935,13 @@ class BricksDb:
 
         for design in self._designs_ldrawed():
             # hunt for dangling Design.ldraw references
-            if LDRAW_MODE_VERIFY_PATHS in self._ldraw_modes:
+            if _LDRAW_MODE_VERIFY_PATHS in self._ldraw_modes:
                 if not self.ldraw.path(design.ldraw).exists():
                     fails += ['unresolved ldraw reference for design of ID: '
                               f'{design.id_} ({design.ldraw})']
                     continue
             # compare n_studs calculations to explicit records
-            if LDRAW_MODE_VERIFY_N_STUDS in self._ldraw_modes\
+            if _LDRAW_MODE_VERIFY_N_STUDS in self._ldraw_modes\
                     and design.n_studs > -1\
                     and not design.secured('n_studs'):
                 studs_counted = self.ldraw.count_studs(design.ldraw)
@@ -968,7 +969,7 @@ class BricksDb:
         for instruction in match_by:
             match_key = match_val = match_operator = ''
             for idx, c in enumerate(instruction):
-                if c in MATCH_OPERATORS:
+                if c in _MATCH_OPERATORS:
                     match_operator = c
                     match_val = instruction[idx+1:]
                     break