| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Ecluse.Core.Version
Description
Version identity and ordering.
A Version carries the raw text verbatim (version strings are embedded in
artifact URLs and re-served, so fidelity matters) alongside a parsed, canonical
VersionKey -- present only when the raw text parses for its ecosystem. Ordering
goes through compareVersions, which is defined only on parsed keys, so
non-canonical text can never reach the comparator (parse, don't validate).
Parsing is per-ecosystem and selected by the Ecosystem tag from
Ecluse.Core.Ecosystem: semver for npm (Ecluse.Core.Version.Semver), PEP 440 for PyPI
(Ecluse.Core.Version.Pep440), Gem::Version for RubyGems (Ecluse.Core.Version.Gem).
Each grammar and its ordering rules live in its own module; this module is the
agnostic abstraction that dispatches to them on the Ecosystem tag. The grammar
modules are kept private -- callers build with mkVersion (total) or
parseVersionKey (reports the parse error) and compare with compareVersions.
This vocabulary is consumed by Ecluse.Core.Package (PackageDetails holds a
Version) and the rules engine (Ecluse.Core.Rules). See
docs/architecture/domain-model.md → Version.
Synopsis
- data Version
- versionKey :: Version -> Maybe VersionKey
- mkVersion :: Ecosystem -> Text -> Version
- unVersion :: Version -> Text
- renderVersion :: Version -> Text
- compareVersions :: Version -> Version -> Maybe Ordering
- data VersionKey
- parseVersionKey :: Ecosystem -> Text -> Either VersionError VersionKey
- newtype VersionError = VersionError {}
- isStable :: VersionKey -> Bool
- selectLatest :: Maybe Version -> [Version] -> Maybe Version
Versions
A package version.
The raw text is kept verbatim for faithful round-trip (version strings are
embedded in artifact URLs and re-served), while a parsed, canonical
VersionKey -- present only when the raw text parses for its ecosystem -- is what
ordering uses. Build with mkVersion (total: an unparseable version is still
represented, just with no key, so a proxy never drops a version over a parser
gap) or parseVersionKey when you want the parse error.
There is deliberately no Ord on Version: comparison goes through
compareVersions, which is defined only on parsed keys, so non-canonical text
can never reach the comparator.
versionKey :: Version -> Maybe VersionKey Source #
The parsed, canonical ordering key; Nothing if the raw text could not
be parsed for its ecosystem (ordering rules then abstain).
mkVersion :: Ecosystem -> Text -> Version Source #
Build a Version, parsing the raw text into a canonical key when possible.
Total: a version that does not parse is still represented (with no key) rather
than rejected, so a proxy never drops a version over a parser gap.
renderVersion :: Version -> Text Source #
Render a version in wire form (the raw text).
compareVersions :: Version -> Version -> Maybe Ordering Source #
Compare two versions by their canonical keys. Nothing if either version
did not parse (its key is absent) -- an ordering-based rule should then abstain,
mirroring the other "unknown signal" cases (CodeExecUnknown, TrustUnknown).
Canonical ordering keys
data VersionKey Source #
The parsed, canonical, comparable form of a version. Opaque: the only
way to obtain one is parseVersionKey, so a VersionKey always holds a
well-formed, normalised version -- the comparator structurally cannot see
non-canonical input (parse, don't validate). Its Ord is meaningful only within
a single ecosystem, which is the only case that ever arises (one compares
versions of one package).
Instances
| Show VersionKey Source # | |
Defined in Ecluse.Core.Version Methods showsPrec :: Int -> VersionKey -> ShowS # show :: VersionKey -> String # showList :: [VersionKey] -> ShowS # | |
| Eq VersionKey Source # | |
Defined in Ecluse.Core.Version | |
| Ord VersionKey Source # | |
Defined in Ecluse.Core.Version Methods compare :: VersionKey -> VersionKey -> Ordering # (<) :: VersionKey -> VersionKey -> Bool # (<=) :: VersionKey -> VersionKey -> Bool # (>) :: VersionKey -> VersionKey -> Bool # (>=) :: VersionKey -> VersionKey -> Bool # max :: VersionKey -> VersionKey -> VersionKey # min :: VersionKey -> VersionKey -> VersionKey # | |
parseVersionKey :: Ecosystem -> Text -> Either VersionError VersionKey Source #
Parse raw version text into a canonical VersionKey for its ecosystem, or
report why it could not be parsed. This is the parsing boundary: downstream code
holds a VersionKey and relies on it being valid.
newtype VersionError Source #
Why a version string failed to parse.
Constructors
| VersionError | |
Fields | |
Instances
| Show VersionError Source # | |
Defined in Ecluse.Core.Version Methods showsPrec :: Int -> VersionError -> ShowS # show :: VersionError -> String # showList :: [VersionError] -> ShowS # | |
| Eq VersionError Source # | |
Defined in Ecluse.Core.Version | |
isStable :: VersionKey -> Bool Source #
Whether a parsed version is a stable (final, non-prerelease) release. The notion is ecosystem-specific, dispatched on the key's constructor:
- semver (npm) -- stable iff there is no
-prereleasecomponent (the prerelease isSemverFinal). So1.0.0is stable;1.0.0-rc.1and2.0.0-betaare not. - PEP 440 (PyPI) -- stable iff it is neither a pre-release (
a/b/rc) nor a dev release. Post-releases are stable. So1.0and1.0.post1are stable;1.0a1,1.0rc1,1.0.dev1and1.0a1.dev2are not. - RubyGems -- stable iff no segment contains a letter (the version is
all-numeric). So
1.0.0is stable;1.0.0.preand1.2.0.rc1are not.
Used by selectLatest to prefer a stable release when dist-tags.latest must
be repointed.
>>>isStable <$> parseVersionKey Npm "1.0.0"Right True>>>isStable <$> parseVersionKey Npm "1.0.0-rc.1"Right False>>>isStable <$> parseVersionKey PyPI "1.0.post1"Right True>>>isStable <$> parseVersionKey PyPI "1.0a1.dev2"Right False>>>isStable <$> parseVersionKey RubyGems "1.0.0.pre"Right False
Resolving dist-tags.latest
selectLatest :: Maybe Version -> [Version] -> Maybe Version Source #
Resolve dist-tags.latest for a packument after denied/undecidable
versions have been filtered out -- the keep-unless-denied, stable-preferring
rule from docs/architecture/rules-engine.md ("Applying verdicts to a
packument"). chosen is the source's currently-tagged latest (if any);
survivors is the surviving versions. The result, when present, is always one
of survivors, so the caller can use its unVersion as the tag string.
The resolution, in order:
- If
survivorsis empty, there is nothing to point at --Nothing. - Keep: if
chosensurvives (by raw text), return it unchanged. This is the identity on a single-input packument and never promotes a prerelease over a maintainer's chosen stablelatest. - Repoint (only when the chosen
latestdid not survive): among survivors with a parseable key, prefer the maximum stable one; if none are stable, the maximum prerelease one. (Within one ecosystem parseable keys are totally ordered, socompareVersionsis total over them.) - No parseable survivor: to keep the result naming a present version, fall
back to the lexicographically-smallest survivor by
unVersion. An unparseable version never outranks a parseable one.