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::{
    dep::{Dep, OptDep},
    parser::parse_pkg,
};
use anyhow::anyhow;
use chrono::{DateTime, Utc};
use hex;
use std::{
    cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd},
    collections::BTreeMap,
    fmt::{Display, Formatter, Result},
    io::BufRead,
    str::FromStr,
};
use url::Url;

/// Packages of a packages repository stored in a binary tree map for fast search
/// by package name
pub struct Pkgs(BTreeMap<String, Pkg>);

impl Default for Pkgs {
    fn default() -> Self {
        Self::new()
    }
}

impl Pkgs {
    /// Creates a new, empty packages map
    pub fn new() -> Pkgs {
        Pkgs(BTreeMap::<String, Pkg>::new())
    }

    /// Inserts a package name / package pair into the packages map
    pub fn add(&mut self, name: String, pkg: Pkg) {
        _ = self.0.insert(name.to_string(), pkg);
    }

    /// Returns true if the packages map contains a packages of the submitted
    /// name
    pub fn contains(&self, name: &str) -> bool {
        self.0.contains_key(name)
    }

    /// Returns a reference to the package corresponding to the submitted
    /// package name
    pub fn get(&self, name: &str) -> Option<&Pkg> {
        self.0.get(name)
    }

    /// Returns true if the packages map contains no elements
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    /// Returns the number of packages in the packages map
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// Returns an iterator over the names of the packages of the map, in sorted
    /// order
    pub fn names(&self) -> std::collections::btree_map::Keys<'_, String, Pkg> {
        self.0.keys()
    }

    /// Returns an iterator over the packages of the map, in sorted order by
    /// package name
    pub fn packages(&self) -> std::collections::btree_map::Values<'_, String, Pkg> {
        self.0.values()
    }
}

/// Represents meta data of a package
#[derive(Default)]
pub struct Pkg {
    pub name: String,
    pub file_name: String,
    pub base: String,
    pub version: String,
    pub desc: String,
    pub groups: Vec<String>,
    pub c_size: usize,
    pub i_size: usize,
    pub md5_sum: Vec<u8>,
    pub sha256_sum: Vec<u8>,
    pub pgp_sig: Option<String>,
    pub url: Option<Url>,
    pub license: Vec<String>,
    pub arch: String,
    pub build_date: DateTime<Utc>,
    pub packager: String,
    pub replaces: Vec<String>,
    pub conflicts: Vec<String>,
    pub provides: Vec<String>,
    pub deps: Vec<Dep>,
    pub opt_deps: Vec<OptDep>,
    pub check_deps: Vec<Dep>,
    pub make_deps: Vec<Dep>,
}

/// Makes Pkg sortable
impl Ord for Pkg {
    fn cmp(&self, other: &Self) -> Ordering {
        self.name.cmp(&other.name)
    }
}
impl PartialOrd for Pkg {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}
impl PartialEq for Pkg {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
    }
}
impl Eq for Pkg {}

//
// Helper macros for printing a package
//
macro_rules! writeln_array {
    ($f:expr , $array:expr) => {
        if $array.is_empty() {
            writeln!($f, "()")
        } else {
            writeln!($f, "(")?;
            for item in &$array {
                writeln!($f, "\t{}", item)?;
            }
            writeln!($f, ")")
        }
    };
}
macro_rules! writeln_option {
    ($f:expr , $option:expr) => {
        if let Some(value) = &$option {
            writeln!($f, "{}", value)
        } else {
            writeln!($f, "n/a")
        }
    };
}

/// Makes Pkg printable
impl Display for Pkg {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        writeln!(f, "[{}]", self.name)?;
        writeln!(f, "file_name = {}", self.file_name)?;
        writeln!(f, "base = {}", self.base)?;
        writeln!(f, "version = {}", self.version)?;
        writeln!(f, "desc = {}", self.desc)?;
        write!(f, "groups = ")?;
        writeln_array!(f, self.groups)?;
        writeln!(f, "c_size = {}", self.c_size)?;
        writeln!(f, "i_size = {}", self.i_size)?;
        writeln!(f, "md5_sum = {}", hex::encode(&self.md5_sum))?;
        writeln!(f, "sha256_sum = {}", hex::encode(&self.sha256_sum))?;
        write!(f, "pgp_sig = ")?;
        writeln_option!(f, self.pgp_sig)?;
        write!(f, "url = ")?;
        writeln_option!(f, self.url)?;
        write!(f, "license = ")?;
        writeln_array!(f, self.license)?;
        writeln!(f, "arch = {}", self.arch)?;
        writeln!(f, "build_date = {}", self.build_date)?;
        writeln!(f, "packager = {}", self.packager)?;
        write!(f, "replaces = ")?;
        writeln_array!(f, self.replaces)?;
        write!(f, "conflicts = ")?;
        writeln_array!(f, self.conflicts)?;
        write!(f, "provides = ")?;
        writeln_array!(f, self.provides)?;
        write!(f, "depends = ")?;
        writeln_array!(f, self.deps)?;
        write!(f, "opt_depends = ")?;
        writeln_array!(f, self.opt_deps)?;
        write!(f, "check_depends = ")?;
        writeln_array!(f, self.check_deps)?;
        write!(f, "make_depends = ")?;
        writeln_array!(f, self.make_deps)
    }
}

impl Pkg {
    /// Retrieve meta data from a package from reader and return is as Pkg
    /// structure
    pub fn parse<R: BufRead>(reader: R) -> anyhow::Result<Pkg> {
        parse_pkg(reader)
    }
}

/// String representation for version comparison operators for dependencies
const EQ: &str = "=";
const GE: &str = ">=";
const GT: &str = ">";
const LE: &str = "<=";
const LT: &str = "<";

/// Version comparison operators for dependencies
pub enum Op {
    Eq,
    Ge,
    Gt,
    Le,
    Lt,
}

/// Make version comparison operators printable
impl Display for Op {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        write!(
            f,
            "{}",
            match self {
                Op::Eq => EQ,
                Op::Ge => GE,
                Op::Gt => GT,
                Op::Le => LE,
                Op::Lt => LT,
            }
        )
    }
}

// Support creation of version comparison operators from string
impl FromStr for Op {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> anyhow::Result<Self> {
        match s {
            EQ => Ok(Op::Eq),
            GE => Ok(Op::Ge),
            GT => Ok(Op::Gt),
            LE => Ok(Op::Le),
            LT => Ok(Op::Lt),
            _ => Err(anyhow!(format!(
                "cannot convert '{s}' to dependency operand"
            ))),
        }
    }
}

/// Version restriction for dependencies
pub struct Rstr {
    pub op: Op,
    pub version: String,
}

/// Make version restriction printable
impl Display for Rstr {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        write!(f, "{} {}", self.op, self.version)
    }
}