Source code for rs_metadata.normalize.best_effort
"""Comparison for CodeMeta properties with no strategy of their own.
The dedicated strategies each know something about their concept: that a
version is ordered, that an SPDX identifier has synonyms, that two spellings of
a person's name may denote the same person. That knowledge is what makes them
strict enough to be worth failing a build over.
CodeMeta has sixty-odd record-level properties and the profile speaks to
twenty-five of them. The rest are still worth *comparing* — a `citation` or a
`dateModified` that two files disagree about is worth showing someone — but
there is no concept-specific knowledge to apply, and inventing some would be
guessing.
So this strategy is deliberately shallow. It compares what it can reduce to a
stable key and, crucially, **declines rather than guesses**: a value it cannot
reduce is reported as not comparable, which the report renders as "seen, not
judged". That is the honest answer for a property nobody has thought about
carefully, and it is why every rule built on this strategy is informational.
The one thing it does know is JSON-LD's shape. A bare string, a node with an
``@id``, and a node with a ``name`` are all ordinary ways to write the same
value, so it reduces each the same way the rest of the package does.
"""
from __future__ import annotations
import datetime as dt
from typing import Any
from .scalars import IDENTITY_FIRST, normalize_text, scalarize
from .strategies import ComparisonResult, Strategy
__all__ = ["BestEffortStrategy"]
def _date_key(text: str) -> str | None:
"""An ISO date reduced to its calendar day, ignoring any time part.
GitHub timestamps a repository to the second while a metadata file records
the day. Comparing the two literally would differ every time.
"""
head = text.strip().replace("Z", "+00:00")
for parse in (dt.datetime.fromisoformat, dt.date.fromisoformat):
try:
parsed = parse(head)
except ValueError:
continue
return (
parsed.date() if isinstance(parsed, dt.datetime) else parsed
).isoformat()
return None
[docs]
class BestEffortStrategy(Strategy):
"""Shape-aware, meaning-agnostic comparison.
Examples
--------
Scalars compare on normalized text, so incidental spacing and case do not
register as a disagreement:
>>> strategy = BestEffortStrategy()
>>> strategy.compare(["Some Value"], ["some value"]).identical
True
A node is reduced the way JSON-LD intends, so the bare and node forms of
one value agree:
>>> strategy.compare(
... ["https://example.org/x"],
... [{"@id": "https://example.org/x", "name": "X"}],
... ).identical
True
Dates agree on the calendar day, because one file records a day and
another a timestamp:
>>> strategy.compare(["2026-06-01"], ["2026-06-01T09:31:07Z"]).identical
True
A value it cannot reduce is declined rather than guessed at, which the
report shows as considered but not judged:
>>> strategy.compare([{"unexpected": ["shape"]}], [{"other": 1}]).comparable
False
"""
name = "best-effort"
[docs]
def key(self, value: Any) -> str | None:
scalar = scalarize(value, IDENTITY_FIRST)
if scalar is None:
return None
date = _date_key(scalar)
if date is not None:
return date
return normalize_text(scalar)
[docs]
def compare(self, anchor: list[Any], source: list[Any]) -> ComparisonResult:
result = super().compare(anchor, source)
# A property this strategy could not reduce on either side is not a
# disagreement, it is a shape nobody taught it about. Saying so is
# more useful than reporting a difference between two things it never
# understood.
if result.uncomparable_anchor and result.uncomparable_source:
return ComparisonResult(
uncomparable_anchor=result.uncomparable_anchor,
uncomparable_source=result.uncomparable_source,
)
return result