///|
fn valid_value(text : String) -> Bool {
!text.is_empty() &&
!text.iter().all(c => c == ' ') &&
!text.iter().any(c => c.to_int() < 32 || c.to_int() == 127)
}
///|
pub struct RepositoryId {
priv value : String
} derive(Eq, Hash, Compare, Debug)
///|
pub fn RepositoryId::parse(text : String) -> RepositoryId? {
if valid_value(text) {
Some({ value: text, })
} else {
None
}
}
///|
pub fn RepositoryId::to_string(self : RepositoryId) -> String {
self.value
}
///|
pub impl Show for RepositoryId with fn output(self, logger) {
logger.write_string(self.value)
}
///|
pub struct ProviderId {
priv value : String
} derive(Eq, Hash, Compare, Debug)
///|
pub fn ProviderId::parse(text : String) -> ProviderId? {
if valid_value(text) {
Some({ value: text, })
} else {
None
}
}
///|
pub fn ProviderId::to_string(self : ProviderId) -> String {
self.value
}
///|
pub impl Show for ProviderId with fn output(self, logger) {
logger.write_string(self.value)
}
///|
pub struct ArtifactKey {
priv value : String
} derive(Eq, Hash, Compare, Debug)
///|
pub fn ArtifactKey::parse(text : String) -> ArtifactKey? {
if valid_value(text) {
Some({ value: text, })
} else {
None
}
}
///|
pub fn ArtifactKey::to_string(self : ArtifactKey) -> String {
self.value
}
///|
pub impl Show for ArtifactKey with fn output(self, logger) {
logger.write_string(self.value)
}
///|
pub struct ArtifactIdentity {
priv value : String
} derive(Eq, Hash, Compare, Debug)
///|
pub fn ArtifactIdentity::parse(text : String) -> ArtifactIdentity? {
if valid_value(text) {
Some({ value: text, })
} else {
None
}
}
///|
pub fn ArtifactIdentity::to_string(self : ArtifactIdentity) -> String {
self.value
}
///|
pub impl Show for ArtifactIdentity with fn output(self, logger) {
logger.write_string(self.value)
}
///|
pub struct RepositoryLocation {
priv value : String
} derive(Eq, Hash, Compare, Debug)
///|
pub fn RepositoryLocation::parse(text : String) -> RepositoryLocation? {
if valid_value(text) {
Some({ value: text, })
} else {
None
}
}
///|
pub fn RepositoryLocation::to_string(self : RepositoryLocation) -> String {
self.value
}
///|
pub impl Show for RepositoryLocation with fn output(self, logger) {
logger.write_string(self.value)
}
///|
pub struct EntryPath {
priv value : String
} derive(Eq, Hash, Debug)
///|
pub impl Compare for EntryPath with fn compare(self, other) {
self.value.lexical_compare(other.value)
}
///|
pub fn EntryPath::parse(text : String) -> EntryPath? {
if valid_path(text) {
Some({ value: text, })
} else {
None
}
}
///|
pub fn EntryPath::to_string(self : EntryPath) -> String {
self.value
}
///|
pub impl Show for EntryPath with fn output(self, logger) {
logger.write_string(self.value)
}
///|
pub struct ResourceName {
priv value : String
} derive(Eq, Hash, Debug)
///|
pub impl Compare for ResourceName with fn compare(self, other) {
self.value.lexical_compare(other.value)
}
///|
pub fn ResourceName::parse(text : String) -> ResourceName? {
if valid_path(text) {
Some({ value: text, })
} else {
None
}
}
///|
pub fn ResourceName::to_string(self : ResourceName) -> String {
self.value
}
///|
pub impl Show for ResourceName with fn output(self, logger) {
logger.write_string(self.value)
}
///|
/// A relative entry path joined without normalizing or escaping its container.
pub fn EntryPath::join(self : EntryPath, child : EntryPath) -> EntryPath {
{ value: self.value + "/" + child.value, }
}
///|
pub fn EntryPath::relative_to(self : EntryPath, root : EntryPath) -> EntryPath? {
let prefix = root.value + "/"
if self.value.has_prefix(prefix) {
Some({ value: self.value[prefix.length():].to_owned(), })
} else {
None
}
}
///|
pub fn EntryPath::resource_name(self : EntryPath) -> ResourceName {
{ value: self.value, }
}
///|
pub(all) enum PathStyle {
Slash
Backslash
} derive(Eq, Debug)
///|
/// Physical location relationships remain data until a presentation boundary.
pub(all) enum Location {
Source(RepositoryLocation)
FilesystemDirectory(CacheDirectory)
DirectoryEntry(Location, EntryPath, PathStyle)
ArchiveEntry(Location, EntryPath)
} derive(Eq, Debug)
///|
pub fn Location::parse(text : String) -> Location? {
RepositoryLocation::parse(text).map(value => Source(value))
}
///|
pub fn Location::directory_entry(
self : Location,
path : EntryPath,
style~ : PathStyle = Slash,
) -> Location {
DirectoryEntry(self, path, style)
}
///|
pub fn Location::archive_entry(self : Location, path : EntryPath) -> Location {
ArchiveEntry(self, path)
}
///|
fn Location::within(self : Location, parent : Location) -> Bool {
let mut current = self
while true {
if current == parent {
return true
}
match current {
Source(_) | FilesystemDirectory(_) => return false
DirectoryEntry(container, _, _) | ArchiveEntry(container, _) =>
current = container
}
}
false
}
///|
pub fn Location::to_string(self : Location) -> String {
let suffixes = []
let mut current = self
let mut base = ""
while true {
match current {
Source(location) => {
base = location.to_string()
break
}
FilesystemDirectory(directory) => {
base = directory.to_string()
break
}
DirectoryEntry(container, path, style) => {
let separator = if style == Backslash { "\\" } else { "/" }
suffixes.push(
separator + path.value.replace_all(old="/", new=separator),
)
current = container
}
ArchiveEntry(container, path) => {
suffixes.push("!/" + path.value)
current = container
}
}
}
let out = StringBuilder()
out.write_string(base)
for i = suffixes.length() - 1; i >= 0; i = i - 1 {
let suffix = suffixes[i]
if i == suffixes.length() - 1 &&
(
(base.has_suffix("/") && suffix.has_prefix("/")) ||
(base.has_suffix("\\") && suffix.has_prefix("\\"))
) {
out.write_string(suffix[1:].to_owned())
} else {
out.write_string(suffix)
}
}
out.to_string()
}
///|
pub impl Show for Location with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
pub(all) struct Resolution {
reference : ArtifactRef
identity : ArtifactIdentity?
} derive(Eq, Debug)
///|
pub(all) enum ProvenanceStep {
NpmRequestStep(RepositoryUrl, NpmPackageRef)
NpmStep(NpmResolution)
MavenRequestStep(RepositoryUrl, MavenCoordinates)
MavenStep(MavenResolution)
GitHubRequestStep(GitHubRepository, GitHubAssetRef)
GitHubStep(GitHubResolution)
AcquisitionStep(Acquisition)
ResolutionStep(Resolution)
ContainerStep(Location)
ProviderRegistrationStep(ProviderId)
RepositoryReferenceStep(RepositoryId)
} derive(Eq, Debug)
///|
pub(all) enum ProvenanceOrigin {
Physical(Location)
ProviderRegistration(ProviderId)
RepositoryReference(RepositoryId)
} derive(Eq, Debug)
///|
pub struct Provenance {
origin : ProvenanceOrigin
priv history : Array[ProvenanceStep]
} derive(Eq, Debug)
///|
pub fn Provenance::new(location : Location) -> Provenance {
{ origin: Physical(location), history: [], }
}
///|
pub fn Provenance::provider_registration(provider : ProviderId) -> Provenance {
{ origin: ProviderRegistration(provider), history: [], }
}
///|
pub fn Provenance::repository_reference(
repository : RepositoryId,
) -> Provenance {
{ origin: RepositoryReference(repository), history: [], }
}
///|
pub fn Provenance::location(self : Provenance) -> Location? {
match self.origin {
Physical(location) => Some(location)
_ => None
}
}
///|
pub fn Provenance::steps(self : Provenance) -> Array[ProvenanceStep] {
self.history.copy()
}
///|
pub fn Provenance::acquired(
self : Provenance,
acquisition : Acquisition,
) -> Provenance {
let history = [AcquisitionStep(acquisition)]
history.append(self.history)
{ ..self, history, }
}
///|
pub fn Provenance::resolved(
self : Provenance,
reference : ArtifactRef,
identity : ArtifactIdentity?,
) -> Provenance {
let history = [ResolutionStep({ reference, identity, })]
history.append(self.history)
{ ..self, history, }
}
///|
/// Compose histories and locations structurally, without inspecting rendered paths.
pub fn Provenance::through(
self : Provenance,
parent : Provenance,
) -> Provenance {
let history = parent.history.copy()
match parent.origin {
Physical(container) =>
if !(self.location() is Some(location) && location.within(container)) {
history.push(ContainerStep(container))
}
ProviderRegistration(provider) =>
history.push(ProviderRegistrationStep(provider))
RepositoryReference(repository) =>
history.push(RepositoryReferenceStep(repository))
}
history.append(self.history)
{ ..self, history, }
}
///|
pub fn Provenance::at(self : Provenance, location : Location) -> Provenance {
{ ..self, origin: Physical(location), }
}
///|
pub fn Provenance::to_string(self : Provenance) -> String {
let parts = []
for step in self.history {
match step {
NpmRequestStep(registry, reference) =>
parts.push(
"npm:" + reference.to_string() + " registry=" + registry.to_string(),
)
NpmStep(resolution) => parts.push(resolution.to_string())
MavenRequestStep(repository, coordinates) =>
parts.push(
"maven:" +
coordinates.to_string() +
" repository=" +
repository.to_string(),
)
MavenStep(resolution) => parts.push(resolution.to_string())
GitHubRequestStep(repository, reference) =>
parts.push(
"github:" +
repository.to_string() +
" tag=" +
reference.tag.to_string() +
" asset=" +
reference.asset.to_string(),
)
GitHubStep(resolution) => parts.push(resolution.to_string())
AcquisitionStep(acquisition) => {
parts.push(acquisition.requested.to_string())
parts.push(acquisition.resolved.to_string())
parts.push(acquisition.digest.to_string())
}
ResolutionStep(resolution) => {
parts.push(resolution.reference.to_string())
if resolution.identity is Some(identity) {
parts.push(identity.to_string())
}
}
ContainerStep(location) => parts.push(location.to_string())
ProviderRegistrationStep(provider) => parts.push(provider.to_string())
RepositoryReferenceStep(repository) => parts.push(repository.to_string())
}
}
parts.push(
match self.origin {
Physical(location) => location.to_string()
ProviderRegistration(provider) => provider.to_string()
RepositoryReference(repository) => repository.to_string()
},
)
parts.join(" -> ")
}
///|
pub impl Show for Provenance with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
pub struct ContentDigest {
priv bytes : FixedArray[Byte]
} derive(Eq, Debug)
///|
pub fn ContentDigest::sha256(data : Bytes) -> ContentDigest {
{ bytes: @crypto.sha256(data), }
}
///|
pub fn ContentDigest::parse(text : String) -> ContentDigest? {
if !text.has_prefix("sha256:") || text.length() != 71 {
return None
}
let bytes = FixedArray::make(32, b'\x00')
fn digit(c : UInt16) -> Int32? {
let n = c.to_int()
if n >= 48 && n <= 57 {
Some(n - 48)
} else if n >= 97 && n <= 102 {
Some(n - 87)
} else if n >= 65 && n <= 70 {
Some(n - 55)
} else {
None
}
}
for i in 0..<32 {
guard digit(text.code_unit_at(7 + i * 2)) is Some(high) &&
digit(text.code_unit_at(8 + i * 2)) is Some(low) else {
return None
}
bytes[i] = (high * 16 + low).to_byte()
}
Some({ bytes, })
}
///|
pub fn ContentDigest::to_string(self : ContentDigest) -> String {
"sha256:" + @crypto.bytes_to_hex_string(self.bytes)
}
///|
pub impl Show for ContentDigest with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
/// A physical cache directory in the portable filesystem-path subset. This
/// validates syntax, not existence, permissions or filesystem-specific limits.
pub struct CacheDirectory {
priv value : String
} derive(Eq, Debug)
///|
pub fn CacheDirectory::parse(text : String) -> CacheDirectory? {
if !valid_value(text) {
return None
}
let drive = text.length() >= 3 &&
((text[0] >= 65 && text[0] <= 90) || (text[0] >= 97 && text[0] <= 122)) &&
text[1] == 58 &&
(text[2] == 47 || text[2] == 92)
for i in 0.. String {
self.value
}
///|
pub fn CacheDirectory::location(self : CacheDirectory) -> Location {
FilesystemDirectory(self)
}
///|
pub fn Provenance::github_requested(
self : Provenance,
repository : GitHubRepository,
reference : GitHubAssetRef,
) -> Provenance {
let history = [GitHubRequestStep(repository, reference)]
history.append(self.history)
{ ..self, history, }
}