Skip to content
Merged
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
27 changes: 11 additions & 16 deletions autoform_cli/project/_lake_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@

_TARGET_KINDS = ("lean_lib", "lean_exe", "input_file", "input_dir") # the kinds lakefile.toml declares
_MATHLIB_NAME = (("str", "mathlib"),)
_LAKE_VERSION = re.compile(r"[0-9]+\.[0-9]+\.[0-9]+(?:-[^ \t\r\n]+)?") # Lake's StdVer
_MANIFEST_VERSION = re.compile(r"([0-9]+)\.([0-9]+)\.([0-9]+)(?:-[^ \t\r\n]+)?")
_LAKE_VERSION = re.compile(r"([0-9]+)\.([0-9]+)\.([0-9]+)(?:-[^ \t\r\n]+)?") # Lake's StdVer
_URL_CREDENTIALS = re.compile(r"^([A-Za-z][A-Za-z0-9+.-]*://)[^/@]*@")
_LEAN_ID_BEGIN_ESCAPE = "«"
_LEAN_ID_END_ESCAPE = "»"
Expand Down Expand Up @@ -274,7 +273,7 @@ def _validate_manifest_root(payload: dict[str, object]) -> None:
raise ValueError("name")
_json_default(payload, "lakeDir", ".lake", str)
_json_default(payload, "fixedToolchain", False, bool)
_json_optional(payload, "packagesDir", str)
_json_default(payload, "packagesDir", None, str)


def _decode_package_entry(entry: object, source: str) -> tuple[tuple[tuple[str, str], ...], MathlibLock]:
Expand Down Expand Up @@ -303,9 +302,9 @@ def _decode_package_entry(entry: object, source: str) -> tuple[tuple[tuple[str,
return name, MathlibLock(
"git",
url=_redact(_json_required(entry, "url", str)),
input_rev=_json_optional(entry, "inputRev", str),
input_rev=_json_default(entry, "inputRev", None, str),
rev=_json_required(entry, "rev", str),
sub_dir=_json_optional(entry, "subDir", str),
sub_dir=_json_default(entry, "subDir", None, str),
**common,
)

Expand All @@ -325,6 +324,7 @@ def _json_default(mapping: dict[str, object], key: str, default, expected: type)
return value


# Unused here; the library locator (#204) imports it.
def _json_optional(mapping: dict[str, object], key: str, expected: type):
value = mapping.get(key)
if value is None:
Expand All @@ -342,7 +342,7 @@ def _manifest_layout(version: object) -> str | None:
if numeric_version is None or _decimal_less_than(numeric_version, "5"):
return None
return "legacy" if _decimal_less_than(numeric_version, "7") else "current"
if type(version) is not str or (match := _MANIFEST_VERSION.fullmatch(version)) is None:
if type(version) is not str or (match := _LAKE_VERSION.fullmatch(version)) is None:
return None
major, minor, _patch = (_normalize_decimal(part) for part in match.groups())
if major == "1":
Expand Down Expand Up @@ -428,22 +428,17 @@ def _canonical_manifest_name(value: str) -> tuple[tuple[str, str], ...] | None:

if value == "[anonymous]":
return ()
parts = _split_lean_name(value)
if parts is None:
return None
return tuple((kind, _normalize_decimal(text) if kind == "num" else text) for kind, text in parts)
return _split_lean_name(value)


def _canonical_toml_name(value: str) -> tuple[tuple[str, str], ...]:
"""Return Lake's Name, including TOML's simple-name fallback."""

parts = _split_lean_name(value)
if parts is None:
return (("str", value),)
return tuple((kind, _normalize_decimal(text) if kind == "num" else text) for kind, text in parts)
return (("str", value),) if parts is None else parts


def _split_lean_name(value: str) -> list[tuple[str, str]] | None:
def _split_lean_name(value: str) -> tuple[tuple[str, str], ...] | None:
parts: list[tuple[str, str]] = []
index = 0
while index < len(value):
Expand All @@ -464,11 +459,11 @@ def _split_lean_name(value: str) -> list[tuple[str, str]] | None:
start = index
while index < len(value) and "0" <= value[index] <= "9":
index += 1
parts.append(("num", value[start:index]))
parts.append(("num", _normalize_decimal(value[start:index])))
else:
return None
if index == len(value):
return parts
return tuple(parts)
if value[index] != ".":
return None
index += 1
Expand Down
43 changes: 15 additions & 28 deletions autoform_cli/project/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
# Rust's ``char::is_whitespace`` set, which ``str::trim`` uses in elan.
# Python additionally treats U+001C..U+001F as whitespace; accepting those
# would disagree with elan because they remain control characters there.
_ELAN_WHITESPACE = frozenset(
_ELAN_WHITESPACE = (
"\u0009\u000a\u000b\u000c\u000d\u0020\u0085\u00a0\u1680"
"\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a"
"\u2028\u2029\u202f\u205f\u3000"
Expand Down Expand Up @@ -125,14 +125,7 @@ def inspect_project(target: str | Path, *, catalog: ReleaseCatalog | None = None
project_root = "/".join([".."] * (len(start.parts) - len(root.parts))) or "."
autoform_paths = _inspect_autoform_paths(root)
snapshot = _snapshot._capture_decision_snapshot(root)
attempt_diagnostics: list[ProjectDiagnostic] = []
result = _inspect_snapshot(
catalog,
snapshot,
attempt_diagnostics,
project_root=project_root,
autoform_paths=autoform_paths,
)
result = _inspect_snapshot(catalog, snapshot, project_root=project_root, autoform_paths=autoform_paths)
verified = _snapshot._capture_decision_snapshot(root)
if (
snapshot.stable
Expand Down Expand Up @@ -176,11 +169,11 @@ def _inspect_autoform_paths(root: Path) -> tuple[str, ...]:
def _inspect_snapshot(
catalog: ReleaseCatalog,
snapshot: _DecisionSnapshot,
diagnostics: list[ProjectDiagnostic],
*,
project_root: str,
autoform_paths: tuple[str, ...],
) -> ProjectInspection:
diagnostics: list[ProjectDiagnostic] = []
lake, requirements = _inspect_lake(snapshot, diagnostics)
requirement = requirements.mathlib if requirements is not None else None
toolchain = _inspect_toolchain(snapshot, diagnostics)
Expand Down Expand Up @@ -414,6 +407,7 @@ def _unused_mathlib(requirements: _Requirements) -> str | None:
)


# Unused here; the library locator (#204) imports it.
def _trim_elan_whitespace(value: str) -> str:
"""Mirror Rust's Unicode whitespace trim without Python's extra C0 separators."""

Expand All @@ -435,7 +429,7 @@ def _inspect_toolchain(snapshot: _DecisionSnapshot, diagnostics: list[ProjectDia
return None
# elan reads only the trimmed first line and rejects an existing file when
# that line is empty or malformed.
toolchain = _trim_elan_whitespace(text.split("\n", 1)[0])
toolchain = text.split("\n", 1)[0].strip(_ELAN_WHITESPACE)
if not toolchain or not toolchain.isprintable() or any(character.isspace() for character in toolchain):
diagnostics.append(
ProjectDiagnostic(
Expand Down Expand Up @@ -467,23 +461,16 @@ def _locked_mathlib(
layout = _manifest_layout(version)
if layout is None:
raise ValueError(relative)
except (AttributeError, RecursionError, ValueError):
kind = "Lake manifest" if relative == _MANIFEST else "Lake package-overrides file"
diagnostics.append(
ProjectDiagnostic("error", "invalid-lake-manifest", f"{relative} is not a {kind} Autoform reads.", relative)
)
return False, None
if layout == "legacy":
if relative == _MANIFEST:
message = (
f"Lake still reads the legacy layout of {relative}, but Autoform does not; "
"`lake update` rewrites it."
)
else:
message = f"Lake still reads the legacy layout of {relative}, but Autoform does not decode it."
diagnostics.append(ProjectDiagnostic("warning", "unsupported-lake-manifest", message, relative))
return False, None
try:
if layout == "legacy":
if relative == _MANIFEST:
message = (
f"Lake still reads the legacy layout of {relative}, but Autoform does not; "
"`lake update` rewrites it."
)
else:
message = f"Lake still reads the legacy layout of {relative}, but Autoform does not decode it."
diagnostics.append(ProjectDiagnostic("warning", "unsupported-lake-manifest", message, relative))
return False, None
if relative == _MANIFEST:
_validate_manifest_root(payload)
packages = payload.get("packages")
Expand Down
Loading