put version and version operator in the same Option in the Atom struct

All atoms must either have a version with a version operator, or have
no version and no version operator. Putting these in the same Option
helps encode that into the type system.
This commit is contained in:
John Turner
2025-11-07 20:57:48 +00:00
parent 5fd26e7c81
commit b147d967d4
2 changed files with 46 additions and 21 deletions

View File

@@ -106,15 +106,23 @@ pub struct UseDep {
#[derive(Clone, Debug, Get)] #[derive(Clone, Debug, Get)]
pub struct Atom { pub struct Atom {
blocker: Option<Blocker>, blocker: Option<Blocker>,
version_operator: Option<VersionOperator>,
category: Category, category: Category,
name: Name, name: Name,
version: Option<Version>, version: Option<(VersionOperator, Version)>,
slot: Option<Slot>, slot: Option<Slot>,
#[get(kind = "deref")] #[get(kind = "deref")]
usedeps: Vec<UseDep>, usedeps: Vec<UseDep>,
} }
impl Atom {
pub fn version_operator(&self) -> Option<VersionOperator> {
match self.version {
Some((operator, _)) => Some(operator),
None => None,
}
}
}
impl fmt::Display for Blocker { impl fmt::Display for Blocker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
@@ -299,7 +307,7 @@ impl fmt::Display for Atom {
write!(f, "{blocker}")?; write!(f, "{blocker}")?;
} }
if let Some(version_operator) = self.version_operator.as_ref() { if let Some(version_operator) = self.version_operator().as_ref() {
write!(f, "{version_operator}")?; write!(f, "{version_operator}")?;
} }
@@ -307,7 +315,7 @@ impl fmt::Display for Atom {
write!(f, "/")?; write!(f, "/")?;
write!(f, "{}", self.name)?; write!(f, "{}", self.name)?;
if let Some(version) = self.version.as_ref() { if let Some((_, version)) = self.version.as_ref() {
write!(f, "-{version}")?; write!(f, "-{version}")?;
} }

View File

@@ -253,36 +253,52 @@ impl<'a> Parseable<'a, &'a str> for Atom {
type Parser = impl Parser<&'a str, Output = Self>; type Parser = impl Parser<&'a str, Output = Self>;
fn parser() -> Self::Parser { fn parser() -> Self::Parser {
Blocker::parser() let usedeps = || {
.opt()
.and(VersionOperator::parser().opt())
.and(Category::parser())
.and(Name::parser().preceded_by(tag("/")))
.and(Version::parser().preceded_by(tag("-")).opt())
.and(Slot::parser().preceded_by(tag(":")).opt())
.and(
UseDep::parser() UseDep::parser()
.separated_by(tag(",")) .separated_by(tag(","))
.many() .many()
.delimited_by(tag("["), tag("]")) .delimited_by(tag("["), tag("]"))
.opt(), .opt()
) };
let without_version = Blocker::parser()
.opt()
.and(Category::parser())
.and(Name::parser().preceded_by(tag("/")))
.and(Slot::parser().preceded_by(tag(":")).opt())
.and(usedeps())
.map(|((((blocker, category), name), slot), usedeps)| Atom {
blocker,
category,
name,
version: None,
slot,
usedeps: usedeps.unwrap_or(Vec::new()),
});
let with_version = Blocker::parser()
.opt()
.and(VersionOperator::parser())
.and(Category::parser())
.and(Name::parser().preceded_by(tag("/")))
.and(Version::parser().preceded_by(tag("-")))
.and(Slot::parser().preceded_by(tag(":")).opt())
.and(usedeps())
.map( .map(
|((((((blocker, version_operator), category), name), version), slot), usedeps)| { |((((((blocker, version_operator), category), name), version), slot), usedeps)| {
Atom { Atom {
blocker, blocker,
version_operator,
category, category,
name, name,
version, version: Some((version_operator, version)),
slot, slot,
usedeps: usedeps.unwrap_or(Vec::new()), usedeps: usedeps.unwrap_or(Vec::new()),
} }
}, },
) )
.verify_output(|atom| match (&atom.version_operator, &atom.version) { .verify_output(|atom| match &atom.version {
(Some(VersionOperator::Eq), Some(_)) => true, Some((VersionOperator::Eq, _)) => true,
(Some(_), Some(version)) Some((_, version))
if !version if !version
.numbers() .numbers()
.iter() .iter()
@@ -290,9 +306,10 @@ impl<'a> Parseable<'a, &'a str> for Atom {
{ {
true true
} }
(None, None) => true,
_ => false, _ => false,
}) });
with_version.or(without_version)
} }
} }