fix(docs): merge, not overwrite, COMPONENT lines on identity collision
collect_components() used dict.update(), which let conf.d silently overwrite functions/ (or vice versa) when the same bare identity appears in both, e.g. functions/auto-pull.fish and conf.d/auto-pull.fish. The runtime guard can only ever look up the bare status current-function/basename string, so both call sites genuinely share one identity and their raw COMPONENT lines must be concatenated, not replaced.
This commit is contained in:
@@ -23,10 +23,23 @@ OUTPUT = REPO / "conf.d" / "__fish_config_op_registry.fish"
|
|||||||
|
|
||||||
|
|
||||||
def collect_components() -> dict[str, list[str]]:
|
def collect_components() -> dict[str, list[str]]:
|
||||||
"""Gather every `# COMPONENT` header across the whole repo."""
|
"""Gather every `# COMPONENT` header across the whole repo.
|
||||||
out = mt.parse_components(REPO / "functions")
|
|
||||||
out.update(mt.parse_components(REPO / "conf.d"))
|
Concatenates raw component lines when the same identity appears in
|
||||||
out.update(mt.parse_component_file(REPO / "config.fish"))
|
more than one source (e.g. functions/auto-pull.fish and
|
||||||
|
conf.d/auto-pull.fish both self-identify as "auto-pull" at runtime,
|
||||||
|
since the guard can only ever look up the bare status
|
||||||
|
current-function/basename string) rather than letting one silently
|
||||||
|
overwrite the other.
|
||||||
|
"""
|
||||||
|
out: dict[str, list[str]] = {}
|
||||||
|
for source in (
|
||||||
|
mt.parse_components(REPO / "functions"),
|
||||||
|
mt.parse_components(REPO / "conf.d"),
|
||||||
|
mt.parse_component_file(REPO / "config.fish"),
|
||||||
|
):
|
||||||
|
for identity, lines in source.items():
|
||||||
|
out.setdefault(identity, []).extend(lines)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -938,6 +938,42 @@ def test_build_registry_keeps_sites_independent():
|
|||||||
assert not warnings
|
assert not warnings
|
||||||
|
|
||||||
|
|
||||||
|
def test_collect_components_merges_identity_collisions_across_sources():
|
||||||
|
"""functions/auto-pull.fish and conf.d/auto-pull.fish both self-identify
|
||||||
|
as "auto-pull" at runtime -- the guard only ever has the bare
|
||||||
|
status current-function/basename string to look up with -- so
|
||||||
|
collect_components must concatenate their raw COMPONENT lines
|
||||||
|
rather than letting conf.d's entry silently overwrite functions'."""
|
||||||
|
import generate_component_registry as gcr
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as d:
|
||||||
|
root = Path(d)
|
||||||
|
(root / "functions").mkdir()
|
||||||
|
(root / "conf.d").mkdir()
|
||||||
|
(root / "functions" / "auto-pull.fish").write_text(
|
||||||
|
"# COMPONENT\n"
|
||||||
|
"# autoexec/sync\n"
|
||||||
|
"function auto-pull\n"
|
||||||
|
"end\n"
|
||||||
|
)
|
||||||
|
(root / "conf.d" / "auto-pull.fish").write_text(
|
||||||
|
"# COMPONENT\n"
|
||||||
|
"# autoexec/sync\n"
|
||||||
|
)
|
||||||
|
(root / "config.fish").write_text("")
|
||||||
|
|
||||||
|
orig_repo = gcr.REPO
|
||||||
|
try:
|
||||||
|
gcr.REPO = root
|
||||||
|
got = gcr.collect_components()
|
||||||
|
finally:
|
||||||
|
gcr.REPO = orig_repo
|
||||||
|
|
||||||
|
assert got["auto-pull"] == ["autoexec/sync", "autoexec/sync"], (
|
||||||
|
f"both sources' tags should survive the merge, not overwrite: {got}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_render_registry_is_valid_fish_and_round_trips():
|
def test_render_registry_is_valid_fish_and_round_trips():
|
||||||
"""Sourcing render()'s output must leave the two arrays in the exact
|
"""Sourcing render()'s output must leave the two arrays in the exact
|
||||||
shape __fish_config_op_registry_lookup expects -- checked via the real
|
shape __fish_config_op_registry_lookup expects -- checked via the real
|
||||||
|
|||||||
Reference in New Issue
Block a user