repodb_parser 0.3.1

Parser for Arch Linux repository DB's
Documentation
// SPDX-FileCopyrightText: 2022-2024 Michael Picht <mipi@fsfe.org>
//
// SPDX-License-Identifier: GPL-3.0-or-later

use crate::pkg::{Op, Rstr};
use anyhow::{anyhow, Context};
use lazy_static::lazy_static;
use regex::Regex;
use std::{
    cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd},
    fmt::{Display, Formatter, Result},
    str::FromStr,
};

/// Package dependency
pub struct Dep {
    pub pkg_name: String,
    pub rstr: Option<Rstr>,
}

/// Make package dependency printable
impl Display for Dep {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        match &self.rstr {
            Some(rstr) => write!(f, "{} {}", self.pkg_name, rstr),
            None => write!(f, "{}", self.pkg_name),
        }
    }
}

/// Make package dependency sortable
impl Ord for Dep {
    fn cmp(&self, other: &Self) -> Ordering {
        self.pkg_name.cmp(&other.pkg_name)
    }
}
impl PartialOrd for Dep {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}
impl PartialEq for Dep {
    fn eq(&self, other: &Self) -> bool {
        self.pkg_name == other.pkg_name
    }
}
impl Eq for Dep {}

/// Support creation of dependency from string
impl FromStr for Dep {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> anyhow::Result<Self> {
        // Regular expression to describe a valid dependency
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^([^<>=]+)(=|<|>|<=|>=)?([^<>=]*)$").unwrap();
        }

        let err_msg = format!("'{s}' is not a valid dependency");

        if !RE.is_match(s) {
            return Err(anyhow!(err_msg));
        }

        // Since there was a match, captures must exist and we can unwrap
        let captures = RE.captures(s).unwrap();

        // Either one or three groups must have matched - i.e., two or four
        // captures in total
        if captures.get(2).is_none() {
            Ok(Dep {
                pkg_name: s.trim().to_string(),
                rstr: None,
            })
        } else {
            Ok(Dep {
                pkg_name: captures.get(1).unwrap().as_str().trim().to_string(),
                rstr: Some(Rstr {
                    op: Op::from_str(captures.get(2).unwrap().as_str().trim())
                        .with_context(|| err_msg)?,
                    version: captures.get(3).unwrap().as_str().trim().to_string(),
                }),
            })
        }
    }
}

/// Optional package dependency
pub struct OptDep {
    pub pkg_name: String,
    pub comment: Option<String>,
}

/// Make optional dependency printable
impl Display for OptDep {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        match &self.comment {
            Some(comment) => write!(f, "{} // {}", self.pkg_name, comment),
            None => write!(f, "{}", self.pkg_name),
        }
    }
}

/// Make optional dependency sortable
impl Ord for OptDep {
    fn cmp(&self, other: &Self) -> Ordering {
        self.pkg_name.cmp(&other.pkg_name)
    }
}
impl PartialOrd for OptDep {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}
impl PartialEq for OptDep {
    fn eq(&self, other: &Self) -> bool {
        self.pkg_name == other.pkg_name
    }
}
impl Eq for OptDep {}

/// Support creation of optional dependency from string
impl FromStr for OptDep {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> anyhow::Result<Self> {
        // Regular expression to describe a valid optional dependency
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^([^:]+)(: *)?(.*)$").unwrap();
        }

        let err_msg = format!("'{s}' is not a valid optional dependency");

        if !RE.is_match(s) {
            return Err(anyhow!(err_msg));
        }

        // Since there was a match, captures must exist and we can unwrap
        let captures = RE.captures(s).unwrap();

        // Either one or three groups must have matched - i.e., two or four
        // captures in total
        if captures.get(2).is_none() {
            Ok(OptDep {
                pkg_name: s.trim().to_string(),
                comment: None,
            })
        } else {
            Ok(OptDep {
                pkg_name: captures.get(1).unwrap().as_str().trim().to_string(),
                comment: Some(captures.get(3).unwrap().as_str().trim().to_string()),
            })
        }
    }
}