Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions scripts/bump-leg.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,8 @@ def open(cls, root_spec) -> "Project":
if not present:
raise Refusal(
"bump-leg-root-not-a-project",
f"{path} does not exist, so this is not an assembly root of a "
"project that has elected this shape",
f"{shown_path(path)} does not exist, so this is not an "
"assembly root of a project that has elected this shape",
"Remediation: --root takes the ASSEMBLY ROOT — the repository "
"an engineer clones — not a leg and not a family holder. A "
"family's member pins are moved with `scripts/family.py "
Expand All @@ -238,7 +238,7 @@ def open(cls, root_spec) -> "Project":
if data.get("kind") != "project-manifest":
raise Refusal(
"bump-leg-wrong-kind",
f"{path}: kind is {data.get('kind')!r}, expected "
f"{shown_path(path)}: kind is {data.get('kind')!r}, expected "
"'project-manifest'",
"Remediation: a family holder declares `kind: "
"family-manifest` and its members move with `scripts/"
Expand Down
5 changes: 3 additions & 2 deletions scripts/family.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,11 +573,12 @@ def open(cls, root_spec) -> "Family":
exc.remediation = _unparsable_in_an_act(root)
raise
if not isinstance(data, dict):
raise Refusal("family-unreadable", f"{path}: not a mapping")
raise Refusal("family-unreadable",
f"{shown_path(path)}: not a mapping")
if data.get("kind") != "family-manifest":
raise Refusal(
"family-wrong-kind",
f"{path}: kind is {data.get('kind')!r}, expected "
f"{shown_path(path)}: kind is {data.get('kind')!r}, expected "
"'family-manifest'")
_checked_members_readable(path, data.get("members"))
return cls(root, data)
Expand Down
6 changes: 3 additions & 3 deletions templates/assembly-root/scripts/validate-manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1429,9 +1429,9 @@ def main(argv: list[str] | None = None) -> int:
if not _is_file(root, manifest_path):
raise Refusal(
"manifest-missing",
f"{manifest_path} does not exist. A project that has elected "
"this schema declares it in `project.yaml`; a project that has "
"not, does not run this validator.",
f"{_shown(manifest_path)} does not exist. A project that has "
"elected this schema declares it in `project.yaml`; a project "
"that has not, does not run this validator.",
)
manifest = _load(root, manifest_path)
if not isinstance(manifest, dict):
Expand Down
15 changes: 12 additions & 3 deletions templates/family-root/scripts/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,15 @@ def _anything(mode: int) -> bool:
return stat.S_IFMT(mode) != 0


def _shown(path) -> str:
"""A path as it is, or as Python's `repr` spells it where a character in
it is not printable: `repo_shape.shown_path`, which a holder's library
may predate (#610), as the assembly root's `validate-manifest.py` spells
it for itself (#1288)."""
text = str(path)
return text if text.isprintable() else repr(text)


def unreadable(code: str, named: str, exc: OSError, what: str) -> Refusal:
"""`code`'s refusal of a path this command must ask about and the
operating system will not let it: its `stat` refused (`_is_a`), as in a
Expand Down Expand Up @@ -1977,14 +1986,14 @@ def members_of(root: Path) -> tuple[str, list[dict]]:
if not present:
raise Refusal(
"family-manifest-missing",
f"{manifest_path} does not exist, so there is no declaration of "
"which members to bootstrap",
f"{_shown(manifest_path)} does not exist, so there is no "
"declaration of which members to bootstrap",
"Remediation: this command bootstraps a FAMILY holder. A project "
"runs its own `scripts/bootstrap.py` instead.")
manifest = load_yaml(manifest_path)
if not isinstance(manifest, dict):
raise Refusal("family-manifest-unreadable",
f"{manifest_path}: not a mapping")
f"{_shown(manifest_path)}: not a mapping")
name = str(manifest.get("name") or root.name)
# A `members:` THAT IS NOT A LIST, OR HOLDS AN ENTRY THAT IS NOT A
# MAPPING, IS REFUSED BY NAME (#864), in `validate-family.py`'s words:
Expand Down
13 changes: 12 additions & 1 deletion templates/family-root/scripts/siblings.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,17 @@ def _anything(mode: int) -> bool:
return stat.S_IFMT(mode) != 0


def _shown(path) -> str:
"""A path as it is, or as Python's `repr` spells it where a character in
it is not printable: `repo_shape.shown_path`, which a holder's library
may predate (#610), as the assembly root's `validate-manifest.py` spells
it for itself (#1288). Written here, not imported from `bootstrap.py`:
a holder that kept its own `bootstrap.py` (`--accept-local`) would be
refused `update-accept-local-missing-name` for the name (#536)."""
text = str(path)
return text if text.isprintable() else repr(text)


def member_unreadable(named: str, exc: OSError, what: str,
then: str = "Then run the same command again.",
code: str = "member-unreadable") -> Refusal:
Expand Down Expand Up @@ -1744,7 +1755,7 @@ def _load_family(args: argparse.Namespace) -> tuple[Path, str, list[dict],
manifest = load_yaml(root / MANIFEST)
if not isinstance(manifest, dict):
raise Refusal("family-manifest-unreadable",
f"{root / MANIFEST}: not a mapping")
f"{_shown(root / MANIFEST)}: not a mapping")
return root, name, rows, manifest


Expand Down
23 changes: 17 additions & 6 deletions templates/family-root/scripts/validate-family.py
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,15 @@ def _refuse_an_unreadable_root(start: Path) -> None:
return


def _shown(path) -> str:
"""A path as it is, or as Python's `repr` spells it where a character in
it is not printable: `repo_shape.shown_path`, which a holder's library
may predate (#610), as the assembly root's `validate-manifest.py` spells
it for itself (#1288)."""
text = str(path)
return text if text.isprintable() else repr(text)


def _asked(path: Path, kind: Callable[[int], bool], code: str,
named: str) -> bool:
"""`_is_a(path, kind)`, or `code`'s refusal naming `path` as `named`
Expand Down Expand Up @@ -1838,14 +1847,16 @@ def _load_shape_pin(root: Path) -> tuple[dict, str]:
pin_path = root / "contracts" / "shape-pin.yaml"
if not _asked(pin_path, stat.S_ISREG, "shape-pin-unreadable",
"contracts/shape-pin.yaml"):
raise Refusal("shape-pin-missing", f"{pin_path} does not exist")
raise Refusal("shape-pin-missing",
f"{_shown(pin_path)} does not exist")
pin = load_yaml(pin_path)
# POSIX, on every platform: a finding names a path in THIS repository,
# and `str()` of a relative Path on Windows spells it with backslashes —
# a path the reader cannot paste back into git.
rel = pin_path.relative_to(root).as_posix()
if not isinstance(pin, dict):
raise Refusal("shape-pin-unreadable", f"{pin_path}: not a mapping")
raise Refusal("shape-pin-unreadable",
f"{_shown(pin_path)}: not a mapping")
return pin, rel


Expand Down Expand Up @@ -4819,16 +4830,16 @@ def _run_checks(args: argparse.Namespace,
MANIFEST):
raise Refusal(
"family-manifest-missing",
f"{manifest_path} does not exist. A family holder declares "
f"itself in `{MANIFEST}`; a repository without one is not a "
"family and does not run this validator.",
f"{_shown(manifest_path)} does not exist. A family holder "
f"declares itself in `{MANIFEST}`; a repository without one is "
"not a family and does not run this validator.",
f"Remediation: `{PYTHON} scripts/family.py init --org <org> "
"--family <Name>` creates one. An ordinary project runs "
"`validate-manifest.py` instead.")
manifest = load_yaml(manifest_path)
if not isinstance(manifest, dict):
raise Refusal("family-manifest-unreadable",
f"{manifest_path}: not a mapping")
f"{_shown(manifest_path)}: not a mapping")
policy_path = args.policy or (root / "contracts" /
"repository-naming.yaml")
policy = NamingPolicy.load(policy_path)
Expand Down
49 changes: 42 additions & 7 deletions tests/test_pinned_copy_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
from test_link_source_chain_defaults import ( # noqa: F401
VALIDATOR, manifest, naming, pinned, scaffold, tree, validate_pins,
)
from test_pinned_copy_columns import copies # noqa: F401

sys.path.insert(0, str(REPO / "scripts"))
from repo_shape import ( # noqa: E402
Expand All @@ -61,6 +62,8 @@
"validate-pins.py")
TEMPLATE_FAMILY = (REPO / "templates" / "family-root" / "scripts" /
"validate-family.py")
FAMILY_BOOTSTRAP = (REPO / "templates" / "family-root" / "scripts" /
"bootstrap.py")
TEMPLATE_MANIFEST = (REPO / "templates" / "assembly-root" / "scripts" /
"validate-manifest.py")
POLICY = REPO / "contracts" / "repository-naming.yaml"
Expand Down Expand Up @@ -772,17 +775,24 @@ def test_the_naming_gate_names_a_printable_link_source_as_typed(tmp_path):

@pytest.mark.parametrize("path", [
"plain/dir", "x\x1b[31mRED", "a\nb", "a\rb", "caf\u00e9", "\udcff",
"tab\there", "",
"tab\there", "", "pl\u202ean",
])
def test_the_pinned_scripts_spell_a_path_as_the_library_does(path):
"""`validate-pins.py`, `validate-repository-naming.py` and, since #909,
`validate-manifest.py` each write their own `_shown`, because a root
whose `scripts/repo_shape.py` is older than they are has no
`shown_path` to import. Each must spell as `shown_path` spells."""
def test_the_pinned_scripts_spell_a_path_as_the_library_does(copies, path):
"""`validate-pins.py`, `validate-repository-naming.py`, since #909
`validate-manifest.py`, and since #1288 a family holder's
`validate-family.py`, `bootstrap.py` and `siblings.py` each write their
own `_shown`, because a root or a holder whose `scripts/repo_shape.py`
is older than they are has no `shown_path` to import. Each must spell
as `shown_path` spells, a bidi control, U+202E, among the rest.
`siblings.py` is `test_pinned_copy_columns.py`'s copy, its `bootstrap`
import answered by the holder's `bootstrap.py` beside it."""
for script, name in ((TEMPLATE_PINS, "pins"), (VALIDATOR, "naming"),
(TEMPLATE_MANIFEST, "manifest")):
(TEMPLATE_MANIFEST, "manifest"),
(TEMPLATE_FAMILY, "family"),
(FAMILY_BOOTSTRAP, "family_bootstrap")):
module = load_without_bytecode(script, f"pinned_copy_text_{name}")
assert module._shown(path) == shown_path(path), script
assert copies["siblings"]._shown(path) == shown_path(path)


#: Every pinned script that imports from `repo_shape`, each beside whatever
Expand Down Expand Up @@ -823,3 +833,28 @@ def test_no_pinned_script_imports_what_an_older_library_lacks(name):
assert imported, f"{script.name} imports nothing from repo_shape?"
for newer in NEWER_THAN_A_KEPT_LIBRARY:
assert newer not in imported, (script, newer)


def test_siblings_keeps_its_own_shown_and_takes_none_from_bootstrap():
"""THE #536 CHOICE, held by itself. `siblings.py` takes names from
`bootstrap.py` already, and a holder that kept its own `bootstrap.py`
(`update-shape.py apply --accept-local`) is refused
`update-accept-local-missing-name` for each name `siblings.py` takes that
its kept copy does not define. So `siblings.py` defines its own `_shown`,
as `validate-manifest.py` does, and takes none from `bootstrap.py`: not
by a `from bootstrap import` at module level or inside a function, and
not as `bootstrap._shown`."""
tree = ast.parse(PINNED_IMPORTERS["siblings"].read_text(encoding="utf-8"))
defined = [node for node in tree.body
if isinstance(node, ast.FunctionDef) and node.name == "_shown"]
assert len(defined) == 1, "siblings.py defines no `_shown` of its own"
taken = [alias.name
for node in ast.walk(tree)
if isinstance(node, ast.ImportFrom) and node.module == "bootstrap"
for alias in node.names]
assert "_shown" not in taken, "siblings.py imports `_shown` from bootstrap"
reached = [node for node in ast.walk(tree)
if isinstance(node, ast.Attribute) and node.attr == "_shown"
and isinstance(node.value, ast.Name)
and node.value.id == "bootstrap"]
assert not reached, "siblings.py reads `bootstrap._shown`"
Loading
Loading