Files
gentoo-utils/src/atom/mod.rs
2025-11-13 19:57:28 +00:00

742 lines
20 KiB
Rust

use core::{
fmt::{self},
option::Option,
};
use std::cmp::Ordering;
use crate::useflag::UseFlag;
use get::Get;
use itertools::Itertools;
pub mod parsers;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Blocker {
Weak,
Strong,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VersionOperator {
Lt,
Gt,
Eq,
LtEq,
GtEq,
Roughly,
}
#[derive(Clone, Debug, PartialEq, Eq, Get)]
pub struct Category(#[get(method = "get", kind = "deref")] String);
#[derive(Clone, Debug, PartialEq, Eq, Get)]
pub struct Name(#[get(method = "get", kind = "deref")] String);
#[derive(Clone, Debug, Get)]
pub struct VersionNumber(#[get(method = "get", kind = "deref")] String);
#[derive(Debug, Clone, Get)]
struct VersionNumbers(#[get(method = "get", kind = "deref")] Vec<VersionNumber>);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum VersionSuffixKind {
Alpha,
Beta,
Pre,
Rc,
P,
}
#[derive(Clone, Debug, Get)]
pub struct VersionSuffix {
kind: VersionSuffixKind,
number: Option<VersionNumber>,
}
#[derive(Debug, Clone, Get)]
pub struct VersionSuffixes(#[get(method = "get", kind = "deref")] Vec<VersionSuffix>);
#[derive(Clone, Debug, Get)]
pub struct Version {
numbers: VersionNumbers,
letter: Option<char>,
suffixes: VersionSuffixes,
rev: Option<VersionNumber>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SlotOperator {
Eq,
Star,
}
#[derive(Clone, Debug, PartialEq, Eq, Get)]
pub struct SlotName(#[get(method = "name", kind = "deref")] String);
#[derive(Clone, Debug, PartialEq, Eq, Get)]
pub struct Slot {
slot: Option<SlotName>,
sub: Option<SlotName>,
operator: Option<SlotOperator>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UseDepNegate {
Minus,
Exclamation,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UseDepSign {
Enabled,
Disabled,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UseDepCondition {
Eq,
Question,
}
#[derive(Clone, Debug, PartialEq, Eq, Get)]
pub struct UseDep {
negate: Option<UseDepNegate>,
flag: UseFlag,
sign: Option<UseDepSign>,
condition: Option<UseDepCondition>,
}
#[derive(Clone, Debug, PartialEq, Eq, Get)]
pub struct Cp {
category: Category,
name: Name,
}
#[derive(Clone, Debug, PartialEq, Eq, Get)]
pub struct Cpv {
category: Category,
name: Name,
version: Version,
slot: Option<Slot>,
}
#[derive(Clone, Debug, Get, PartialEq, Eq)]
pub struct Atom {
blocker: Option<Blocker>,
category: Category,
name: Name,
version: Option<(VersionOperator, Version)>,
slot: Option<Slot>,
#[get(kind = "deref")]
usedeps: Vec<UseDep>,
}
impl Atom {
pub fn version_operator(&self) -> Option<VersionOperator> {
match self.version {
Some((operator, _)) => Some(operator),
None => None,
}
}
}
impl PartialEq for VersionSuffix {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind
&& match dbg!((&self.number, &other.number)) {
(Some(a), Some(b)) => a.0 == b.0,
(Some(a), None) if a.get().chars().all(|c| c == '0') => true,
(None, Some(b)) if b.get().chars().all(|c| c == '0') => true,
(Some(_), None) => false,
(None, Some(_)) => false,
(None, None) => true,
}
}
}
impl Eq for VersionSuffix {}
impl PartialOrd for VersionSuffix {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VersionSuffix {
fn cmp(&self, other: &Self) -> Ordering {
match &self.kind.cmp(&other.kind) {
Ordering::Less => Ordering::Less,
Ordering::Greater => Ordering::Greater,
Ordering::Equal => match (&self.number, &other.number) {
(Some(a), Some(b)) => {
a.0.parse::<u64>()
.unwrap()
.cmp(&b.0.parse::<u64>().unwrap())
}
(Some(a), None) if a.get().chars().all(|c| c == '0') => Ordering::Equal,
(None, Some(b)) if b.get().chars().all(|c| c == '0') => Ordering::Equal,
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
(None, None) => Ordering::Equal,
},
}
}
}
impl PartialEq for VersionSuffixes {
fn eq(&self, other: &Self) -> bool {
let mut a = self.get().iter();
let mut b = other.get().iter();
loop {
match (a.next(), b.next()) {
(Some(a), Some(b)) if a == b => continue,
(Some(_), Some(_)) => break false,
(None, None) => break true,
_ => break false,
}
}
}
}
impl Eq for VersionSuffixes {}
impl PartialOrd for VersionSuffixes {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VersionSuffixes {
fn cmp(&self, other: &Self) -> Ordering {
let mut a = self.get().iter();
let mut b = other.get().iter();
loop {
match dbg!((a.next(), b.next())) {
(Some(a), Some(b)) => match a.cmp(b) {
Ordering::Less => break Ordering::Less,
Ordering::Greater => break Ordering::Greater,
Ordering::Equal => continue,
},
(Some(a), None) if matches!(a.kind, VersionSuffixKind::P) => {
break Ordering::Greater;
}
(Some(_), None) => break Ordering::Less,
(None, Some(b)) if matches!(b.kind, VersionSuffixKind::P) => break Ordering::Less,
(None, Some(_)) => break Ordering::Greater,
(None, None) => break Ordering::Equal,
}
}
}
}
impl PartialEq for VersionNumbers {
fn eq(&self, other: &Self) -> bool {
self.get().first().unwrap().get().parse::<u64>().unwrap()
== other.get().first().unwrap().get().parse().unwrap()
&& {
let mut a = self.get().iter().skip(1);
let mut b = other.get().iter().skip(1);
loop {
match (a.next(), b.next()) {
(Some(a), Some(b)) if a.get().starts_with("0") => {
let a = a.get().trim_end_matches("0");
let b = b.get().trim_end_matches("0");
if a != b {
break false;
}
}
(Some(a), Some(b)) => {
if a.get().parse::<u64>().unwrap() != b.get().parse::<u64>().unwrap() {
break false;
}
}
(Some(a), None) if a.get().chars().all(|c| c == '0') => continue,
(None, Some(b)) if b.get().chars().all(|c| c == '0') => continue,
(None, None) => break true,
_ => break false,
}
}
}
}
}
impl Eq for VersionNumbers {}
impl PartialOrd for VersionNumbers {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VersionNumbers {
fn cmp(&self, other: &Self) -> Ordering {
match self
.get()
.first()
.unwrap()
.get()
.parse::<u64>()
.unwrap()
.cmp(&other.get().first().unwrap().get().parse::<u64>().unwrap())
{
Ordering::Less => return Ordering::Less,
Ordering::Greater => return Ordering::Greater,
Ordering::Equal => {
let mut a = self.get().iter().skip(1);
let mut b = other.get().iter().skip(1);
loop {
match (a.next(), b.next()) {
(Some(a), Some(b)) if a.get().starts_with("0") => {
let a = a.get().trim_end_matches("0");
let b = b.get().trim_end_matches("0");
match a.cmp(b) {
Ordering::Less => break Ordering::Less,
Ordering::Greater => break Ordering::Greater,
Ordering::Equal => continue,
}
}
(Some(a), Some(b)) => match a
.get()
.parse::<u64>()
.unwrap()
.cmp(&b.get().parse::<u64>().unwrap())
{
Ordering::Less => break Ordering::Less,
Ordering::Greater => break Ordering::Greater,
Ordering::Equal => continue,
},
(Some(a), None) if a.get().chars().all(|c| c == '0') => continue,
(None, Some(b)) if b.get().chars().all(|c| c == '0') => continue,
(Some(_), None) => break Ordering::Greater,
(None, Some(_)) => break Ordering::Less,
(None, None) => break Ordering::Equal,
}
}
}
}
}
}
impl PartialEq for Version {
fn eq(&self, other: &Self) -> bool {
self.numbers == other.numbers
&& self.suffixes == other.suffixes
&& match (&self.rev, &other.rev) {
(Some(a), Some(b)) => {
a.get().parse::<u64>().unwrap() == b.get().parse::<u64>().unwrap()
}
(Some(a), None) if a.get().chars().all(|c| c == '0') => true,
(Some(_), None) => false,
(None, Some(b)) if b.get().chars().all(|c| c == '0') => true,
(None, Some(_)) => false,
(None, None) => true,
}
}
}
impl Eq for Version {}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
match self.numbers.cmp(&other.numbers) {
Ordering::Less => Ordering::Less,
Ordering::Greater => Ordering::Greater,
Ordering::Equal => match self.suffixes.cmp(&other.suffixes) {
Ordering::Less => Ordering::Less,
Ordering::Greater => Ordering::Greater,
Ordering::Equal => match (&self.rev, &other.rev) {
(Some(a), Some(b)) => a
.get()
.parse::<u64>()
.unwrap()
.cmp(&b.get().parse().unwrap()),
(Some(a), None) if a.get().chars().all(|c| c == '0') => Ordering::Equal,
(Some(_), None) => Ordering::Greater,
(None, Some(b)) if b.get().chars().all(|c| c == '0') => Ordering::Equal,
(None, Some(_)) => Ordering::Less,
(None, None) => Ordering::Equal,
},
},
}
}
}
impl PartialOrd for Cpv {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.category == other.category && self.name == other.name {
Some(self.version.cmp(&other.version))
} else {
None
}
}
}
impl fmt::Display for Blocker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Weak => write!(f, "!"),
Self::Strong => write!(f, "!!"),
}
}
}
impl fmt::Display for VersionOperator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Lt => write!(f, "<"),
Self::Gt => write!(f, ">"),
Self::Eq => write!(f, "="),
Self::LtEq => write!(f, "<="),
Self::GtEq => write!(f, ">="),
Self::Roughly => write!(f, "~"),
}
}
}
impl fmt::Display for Category {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl fmt::Display for VersionNumber {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl fmt::Display for VersionSuffixKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Alpha => write!(f, "alpha"),
Self::Beta => write!(f, "beta"),
Self::Pre => write!(f, "pre"),
Self::Rc => write!(f, "rc"),
Self::P => write!(f, "p"),
}
}
}
impl fmt::Display for VersionSuffix {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)?;
if let Some(number) = self.number.as_ref() {
write!(f, "{number}")?;
}
Ok(())
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let numbers = self
.numbers
.get()
.iter()
.map(|n| n.get())
.intersperse(".")
.collect::<String>();
let suffixes = self
.suffixes
.get()
.iter()
.map(|s| s.to_string())
.intersperse("_".to_string())
.collect::<String>();
write!(f, "{}", numbers)?;
if let Some(letter) = self.letter {
write!(f, "{letter}")?;
}
if suffixes.len() > 0 {
write!(f, "_{}", suffixes)?;
}
if let Some(rev) = self.rev.as_ref() {
write!(f, "-r{rev}")?;
}
Ok(())
}
}
impl fmt::Display for SlotOperator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Eq => write!(f, "="),
Self::Star => write!(f, "*"),
}
}
}
impl fmt::Display for SlotName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl fmt::Display for Slot {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(slot) = self.slot.as_ref() {
write!(f, "{slot}")?;
}
if let Some(sub) = self.sub.as_ref() {
write!(f, "/{sub}")?;
}
if let Some(operator) = self.operator.as_ref() {
write!(f, "{operator}")?;
}
Ok(())
}
}
impl fmt::Display for UseDepNegate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Minus => write!(f, "-"),
Self::Exclamation => write!(f, "!"),
}
}
}
impl fmt::Display for UseDepSign {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Enabled => write!(f, "(+)"),
Self::Disabled => write!(f, "(-)"),
}
}
}
impl fmt::Display for UseDepCondition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Eq => write!(f, "="),
Self::Question => write!(f, "?"),
}
}
}
impl fmt::Display for UseDep {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(negate) = self.negate.as_ref() {
write!(f, "{negate}")?;
}
write!(f, "{}", self.flag)?;
if let Some(sign) = self.sign.as_ref() {
write!(f, "{sign}")?;
}
if let Some(condition) = self.condition.as_ref() {
write!(f, "{condition}")?;
}
Ok(())
}
}
impl fmt::Display for Cp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}/{}", &self.category, &self.name)
}
}
impl fmt::Display for Cpv {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}/{}-{}", &self.category, &self.name, &self.version)?;
if let Some(slot) = self.slot.as_ref() {
write!(f, ":{slot}")?;
}
Ok(())
}
}
impl fmt::Display for Atom {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(blocker) = self.blocker.as_ref() {
write!(f, "{blocker}")?;
}
if let Some(version_operator) = self.version_operator().as_ref() {
write!(f, "{version_operator}")?;
}
write!(f, "{}", self.category)?;
write!(f, "/")?;
write!(f, "{}", self.name)?;
if let Some((_, version)) = self.version.as_ref() {
write!(f, "-{version}")?;
}
if let Some(slot) = self.slot.as_ref() {
write!(f, ":{slot}")?;
}
let usedeps = self
.usedeps
.iter()
.map(|u| u.to_string())
.intersperse(",".to_string())
.collect::<String>();
if !usedeps.is_empty() {
write!(f, "[{usedeps}]")?;
}
Ok(())
}
}
#[cfg(test)]
mod test {
use mon::{Parser, input::InputIter};
use super::*;
use crate::Parseable;
macro_rules! assert_eq_display {
($a:expr, $b:expr) => {
if $a != $b {
panic!("{} != {}", $a, $b);
}
};
}
macro_rules! assert_cmp_display {
($a:expr, $b:expr, $ordering:expr) => {
if $a.cmp(&$b) != $ordering {
panic!("{} ~ {} != {:?}", $a, $b, $ordering)
}
};
}
macro_rules! assert_partial_cmp_display {
($a:expr, $b:expr, $ordering:expr) => {
if $a.partial_cmp(&$b) != $ordering {
panic!("{} ~ {} != {:?}", $a, $b, $ordering)
}
};
}
#[test]
fn test_version_display() {
let s = "1.0.0_alpha1_beta1-r1";
let version = Version::parser().parse_finished(InputIter::new(s)).unwrap();
assert_eq!(version.to_string().as_str(), s);
}
#[test]
fn test_display_atom() {
let s = "!!>=foo/bar-1.0.0v_alpha1_beta1-r1:slot/sub=[a,b,c]";
let atom = Atom::parser().parse_finished(InputIter::new(s)).unwrap();
assert_eq!(atom.to_string().as_str(), s);
}
#[test]
fn test_version_suffix_eq() {
let a = VersionSuffix::parser()
.parse_finished(InputIter::new("alpha0"))
.unwrap();
let b = VersionSuffix::parser()
.parse_finished(InputIter::new("alpha"))
.unwrap();
assert_eq_display!(a, b);
}
#[test]
fn test_version_eq() {
let versions = [
("1", "1"),
("1", "1.0.0"),
("1.0", "1.0.0"),
("1.0.0_alpha0", "1.0.0_alpha"),
("1.0.0", "1.0.0-r0"),
];
for (a, b) in versions.map(|(a, b)| {
(
Version::parser().parse_finished(InputIter::new(a)).unwrap(),
Version::parser().parse_finished(InputIter::new(b)).unwrap(),
)
}) {
assert_eq_display!(a, b);
}
}
#[test]
fn test_version_cmp() {
let versions = [
("1.0.1", "1.0", Ordering::Greater),
("1.0.0", "1.0.0_alpha", Ordering::Greater),
("1.0.0_alpha", "1.0.0_alpha_p", Ordering::Less),
("1.0.0-r0", "1.0.0", Ordering::Equal),
("1.0.0-r0000", "1.0.0", Ordering::Equal),
];
for (a, b, ordering) in versions.iter().map(|(a, b, ordering)| {
(
Version::parser().parse_finished(InputIter::new(a)).unwrap(),
Version::parser().parse_finished(InputIter::new(b)).unwrap(),
ordering,
)
}) {
assert_cmp_display!(a, b, *ordering);
}
}
#[test]
fn test_cpv_eq() {
let cpvs = [
("foo/bar-1", "foo/bar-1", Some(Ordering::Equal)),
("foo/baz-1", "foo/bar-1", None),
];
for (a, b, ordering) in cpvs.iter().copied().map(|(a, b, ordering)| {
(
Cpv::parser().parse_finished(InputIter::new(a)).unwrap(),
Cpv::parser().parse_finished(InputIter::new(b)).unwrap(),
ordering,
)
}) {
assert_partial_cmp_display!(a, b, ordering);
}
}
}