[BuildLibrary / OverlayLibrary] further simplifications
This commit is contained in:
parent
c410c4d381
commit
13d75dc079
3 changed files with 105 additions and 133 deletions
|
|
@ -15,10 +15,11 @@ Classes include:
|
|||
library. Generated with `ILibraryView.abstract_view()`.
|
||||
"""
|
||||
from typing import Self, TYPE_CHECKING, Any, cast, TypeAlias, Protocol, Literal
|
||||
from collections.abc import Iterator, Mapping, MutableMapping, Sequence, Callable
|
||||
from collections.abc import Container, Iterator, Mapping, MutableMapping, Sequence, Callable
|
||||
import logging
|
||||
import re
|
||||
import copy
|
||||
from functools import wraps
|
||||
from pprint import pformat
|
||||
from collections import defaultdict
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
|
@ -87,15 +88,11 @@ class CellProvenance:
|
|||
any. Imported source cells leave this as `None`.
|
||||
build_chain: Declared-cell dependency chain that was active when the
|
||||
cell was emitted.
|
||||
renamed_from: Original requested name when the final name differs.
|
||||
source_name: Original on-source name for imported cells.
|
||||
"""
|
||||
requested_name: str
|
||||
kind: Literal['declared', 'helper', 'source']
|
||||
owner_declared_name: str | None
|
||||
build_chain: tuple[str, ...]
|
||||
renamed_from: str | None = None
|
||||
source_name: str | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
|
@ -152,6 +149,47 @@ def _rename_patterns(lib: 'ILibraryView', name: str) -> str:
|
|||
return lib.get_name(SINGLE_USE_PREFIX + stem)
|
||||
|
||||
|
||||
def _plan_source_names(
|
||||
target: 'ILibraryView',
|
||||
source_order: Sequence[str],
|
||||
existing_names: Container[str],
|
||||
*,
|
||||
rename_theirs: Callable[['ILibraryView', str], str] | None = None,
|
||||
rename_when: Literal['conflict', 'always'] = 'conflict',
|
||||
) -> tuple[dict[str, str], dict[str, str]]:
|
||||
if rename_when not in ('conflict', 'always'):
|
||||
raise ValueError(f'Unknown source rename mode: {rename_when!r}')
|
||||
if rename_when == 'always' and rename_theirs is None:
|
||||
raise TypeError('rename_theirs is required when rename_when="always"')
|
||||
|
||||
source_to_visible: dict[str, str] = {}
|
||||
visible_names: set[str] = set()
|
||||
rename_map: dict[str, str] = {}
|
||||
|
||||
for name in source_order:
|
||||
visible = name
|
||||
renamed = False
|
||||
if rename_when == 'always':
|
||||
assert rename_theirs is not None
|
||||
visible = rename_theirs(target, name)
|
||||
renamed = True
|
||||
elif visible in existing_names or visible in visible_names:
|
||||
if rename_theirs is None:
|
||||
raise LibraryError(f'Conflicting name while adding source: {name!r}')
|
||||
visible = rename_theirs(target, name)
|
||||
renamed = True
|
||||
if visible in existing_names or visible in visible_names:
|
||||
if not renamed:
|
||||
raise LibraryError(f'Conflicting name while adding source: {name!r}')
|
||||
raise LibraryError(f'Unresolved duplicate key encountered while adding source: {name!r} -> {visible!r}')
|
||||
if visible != name:
|
||||
rename_map[name] = visible
|
||||
source_to_visible[name] = visible
|
||||
visible_names.add(visible)
|
||||
|
||||
return source_to_visible, rename_map
|
||||
|
||||
|
||||
class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
|
||||
"""
|
||||
Interface for a read-only library.
|
||||
|
|
@ -1453,23 +1491,6 @@ class Library(ILibrary):
|
|||
return tree, pat
|
||||
|
||||
|
||||
class _CellFactory:
|
||||
"""
|
||||
Adapter that turns a plain pattern factory into a deferred recipe factory.
|
||||
|
||||
Calling the wrapper captures arguments and returns a `_BuildRecipe`
|
||||
instead of executing the function immediately.
|
||||
"""
|
||||
|
||||
def __init__(self, func: Callable[..., 'Pattern']) -> None:
|
||||
self.func = func
|
||||
self.__name__ = getattr(func, '__name__', type(self).__name__)
|
||||
self.__doc__ = getattr(func, '__doc__')
|
||||
|
||||
def __call__(self, *args: Any, **kwargs: Any) -> '_BuildRecipe':
|
||||
return _BuildRecipe(func=self.func, args=args, kwargs=kwargs)
|
||||
|
||||
|
||||
@dataclass
|
||||
class _BuildRecipe:
|
||||
""" Captured deferred call to a pattern factory. """
|
||||
|
|
@ -1483,25 +1504,17 @@ class _BuildRecipe:
|
|||
return self
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _SourceDeclaration:
|
||||
"""
|
||||
Imported source-backed names registered with a `BuildLibrary`.
|
||||
|
||||
The declaration stores visible-name remapping. Underlying source cells stay
|
||||
lazy until a build session materializes or copies them through.
|
||||
"""
|
||||
library: ILibraryView
|
||||
source_to_visible: Mapping[str, str]
|
||||
|
||||
|
||||
def cell(func: Callable[..., 'Pattern']) -> _CellFactory:
|
||||
def cell(func: Callable[..., 'Pattern']) -> Callable[..., _BuildRecipe]:
|
||||
"""
|
||||
Wrap a plain pattern factory so calls return deferred build recipes.
|
||||
|
||||
Use as either `cell(fn)(...)` or `@cell`.
|
||||
"""
|
||||
return _CellFactory(func)
|
||||
@wraps(func)
|
||||
def wrapper(*args: Any, **kwargs: Any) -> _BuildRecipe:
|
||||
return _BuildRecipe(func=func, args=args, kwargs=kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class BuildCellsView:
|
||||
|
|
@ -1555,7 +1568,7 @@ class BuildLibrary(ILibrary):
|
|||
self.cells = BuildCellsView(self)
|
||||
self._frozen = False
|
||||
self._declarations: dict[str, 'Pattern | _BuildRecipe'] = {}
|
||||
self._sources: list[_SourceDeclaration] = []
|
||||
self._sources: list[tuple[ILibraryView, dict[str, str]]] = []
|
||||
self._names: set[str] = set()
|
||||
self._order: list[str] = []
|
||||
|
||||
|
|
@ -1694,8 +1707,8 @@ class BuildLibrary(ILibrary):
|
|||
|
||||
source_index: int | None = None
|
||||
source_name: str | None = None
|
||||
for idx, spec in enumerate(self._sources):
|
||||
for candidate_source, candidate_visible in spec.source_to_visible.items():
|
||||
for idx, (_source, source_to_visible) in enumerate(self._sources):
|
||||
for candidate_source, candidate_visible in source_to_visible.items():
|
||||
if candidate_visible == old_name:
|
||||
source_index = idx
|
||||
source_name = candidate_source
|
||||
|
|
@ -1708,16 +1721,13 @@ class BuildLibrary(ILibrary):
|
|||
'cells may be renamed on a BuildLibrary.'
|
||||
)
|
||||
|
||||
spec = self._sources[source_index]
|
||||
source_to_visible = dict(spec.source_to_visible)
|
||||
source_library, source_to_visible = self._sources[source_index]
|
||||
source_to_visible = dict(source_to_visible)
|
||||
assert source_name is not None
|
||||
|
||||
source_to_visible[source_name] = new_name
|
||||
|
||||
self._sources[source_index] = replace(
|
||||
spec,
|
||||
source_to_visible = source_to_visible,
|
||||
)
|
||||
self._sources[source_index] = (source_library, source_to_visible)
|
||||
self._names.remove(old_name)
|
||||
self._names.add(new_name)
|
||||
self._order[self._order.index(old_name)] = new_name
|
||||
|
|
@ -1761,48 +1771,23 @@ class BuildLibrary(ILibrary):
|
|||
Mapping of `{source_name: visible_name}` for imported names that
|
||||
were renamed while being added.
|
||||
"""
|
||||
if rename_when not in ('conflict', 'always'):
|
||||
raise ValueError(f'Unknown source rename mode: {rename_when!r}')
|
||||
if rename_when == 'always' and rename_theirs is None:
|
||||
raise TypeError('rename_theirs is required when rename_when="always"')
|
||||
if self._active_session() is not None:
|
||||
raise BuildError('BuildLibrary.add_source() is only available while authoring, not during validate() or build().')
|
||||
self._assert_editable()
|
||||
|
||||
view = source if isinstance(source, ILibraryView) else LibraryView(source)
|
||||
source_order = tuple(view.source_order())
|
||||
source_to_visible, rename_map = _plan_source_names(
|
||||
self,
|
||||
source_order,
|
||||
self._names,
|
||||
rename_theirs = rename_theirs,
|
||||
rename_when = rename_when,
|
||||
)
|
||||
|
||||
source_to_visible: dict[str, str] = {}
|
||||
visible_names: set[str] = set()
|
||||
rename_map: dict[str, str] = {}
|
||||
new_names: list[str] = []
|
||||
|
||||
for name in source_order:
|
||||
visible = name
|
||||
renamed = False
|
||||
if rename_when == 'always':
|
||||
visible = cast('Callable[[ILibraryView, str], str]', rename_theirs)(self, name)
|
||||
renamed = True
|
||||
elif visible in self._names or visible in visible_names:
|
||||
if rename_theirs is None:
|
||||
raise LibraryError(f'Conflicting name while adding source: {name!r}')
|
||||
visible = rename_theirs(self, name)
|
||||
renamed = True
|
||||
if visible in self._names or visible in visible_names:
|
||||
if not renamed:
|
||||
raise LibraryError(f'Conflicting name while adding source: {name!r}')
|
||||
raise LibraryError(f'Unresolved duplicate key encountered while adding source: {name!r} -> {visible!r}')
|
||||
if visible != name:
|
||||
rename_map[name] = visible
|
||||
source_to_visible[name] = visible
|
||||
visible_names.add(visible)
|
||||
new_names.append(visible)
|
||||
|
||||
self._sources.append(_SourceDeclaration(
|
||||
library = view,
|
||||
source_to_visible = dict(source_to_visible),
|
||||
))
|
||||
for visible in new_names:
|
||||
self._sources.append((view, dict(source_to_visible)))
|
||||
for source_name in source_order:
|
||||
visible = source_to_visible[source_name]
|
||||
self._names.add(visible)
|
||||
self._order.append(visible)
|
||||
return rename_map
|
||||
|
|
@ -1902,9 +1887,9 @@ class _BuildSessionLibrary(ILibrary):
|
|||
self._install_sources()
|
||||
|
||||
def _install_sources(self) -> None:
|
||||
for spec in self._builder._sources:
|
||||
source_order = spec.library.source_order()
|
||||
expected_names = set(spec.source_to_visible)
|
||||
for source_library, source_to_visible in self._builder._sources:
|
||||
source_order = source_library.source_order()
|
||||
expected_names = set(source_to_visible)
|
||||
actual_names = set(source_order)
|
||||
if actual_names != expected_names:
|
||||
added_names = sorted(actual_names - expected_names)
|
||||
|
|
@ -1920,24 +1905,22 @@ class _BuildSessionLibrary(ILibrary):
|
|||
'Do not structurally mutate source libraries between add_source() and build()/validate().'
|
||||
)
|
||||
|
||||
def rename_source(_lib: ILibraryView, name: str, *, mapping: Mapping[str, str] = spec.source_to_visible) -> str:
|
||||
def rename_source(_lib: ILibraryView, name: str, *, mapping: Mapping[str, str] = source_to_visible) -> str:
|
||||
return mapping[name]
|
||||
|
||||
self._overlay.add_source(
|
||||
spec.library,
|
||||
source_library,
|
||||
rename_theirs = rename_source,
|
||||
rename_when = 'always',
|
||||
)
|
||||
|
||||
for source_name in source_order:
|
||||
visible_name = spec.source_to_visible[source_name]
|
||||
visible_name = source_to_visible[source_name]
|
||||
self._provenance[visible_name] = CellProvenance(
|
||||
requested_name = source_name,
|
||||
kind = 'source',
|
||||
owner_declared_name = None,
|
||||
build_chain = (),
|
||||
renamed_from = source_name if visible_name != source_name else None,
|
||||
source_name = source_name,
|
||||
)
|
||||
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
|
|
@ -1947,7 +1930,7 @@ class _BuildSessionLibrary(ILibrary):
|
|||
return len(self._names)
|
||||
|
||||
def __contains__(self, key: object) -> bool:
|
||||
return key in self._names or key in self._overlay
|
||||
return key in self._names
|
||||
|
||||
def _touch_name(self, key: str) -> None:
|
||||
if key not in self._names:
|
||||
|
|
@ -1998,11 +1981,7 @@ class _BuildSessionLibrary(ILibrary):
|
|||
self._order[idx] = new_name
|
||||
|
||||
provenance = self._provenance.pop(old_name)
|
||||
requested_name = provenance.requested_name
|
||||
self._provenance[new_name] = replace(
|
||||
provenance,
|
||||
renamed_from = requested_name if new_name != requested_name else None,
|
||||
)
|
||||
self._provenance[new_name] = provenance
|
||||
return self
|
||||
|
||||
def __getitem__(self, key: str) -> 'Pattern':
|
||||
|
|
@ -2038,7 +2017,6 @@ class _BuildSessionLibrary(ILibrary):
|
|||
kind = kind,
|
||||
owner_declared_name = current if kind == 'helper' else key,
|
||||
build_chain = tuple(self._declared_stack),
|
||||
renamed_from = None,
|
||||
)
|
||||
|
||||
def __delitem__(self, key: str) -> None:
|
||||
|
|
@ -2072,7 +2050,6 @@ class _BuildSessionLibrary(ILibrary):
|
|||
self._provenance[new_name] = replace(
|
||||
self._provenance[new_name],
|
||||
requested_name = old_name,
|
||||
renamed_from = old_name,
|
||||
owner_declared_name = current if current is not None else self._provenance[new_name].owner_declared_name,
|
||||
)
|
||||
return rename_map
|
||||
|
|
@ -2085,14 +2062,12 @@ class _BuildSessionLibrary(ILibrary):
|
|||
kind: Literal['declared', 'helper'],
|
||||
owner_declared_name: str | None,
|
||||
build_chain: tuple[str, ...],
|
||||
renamed_from: str | None,
|
||||
) -> None:
|
||||
self._provenance[name] = CellProvenance(
|
||||
requested_name = requested_name,
|
||||
kind = kind,
|
||||
owner_declared_name = owner_declared_name,
|
||||
build_chain = build_chain,
|
||||
renamed_from = renamed_from,
|
||||
)
|
||||
|
||||
def _wrap_error(self, name: str, exc: Exception) -> BuildError:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue