diff options
Diffstat (limited to 'src/repo/profile/package_use')
| -rw-r--r-- | src/repo/profile/package_use/mod.rs | 125 | ||||
| -rw-r--r-- | src/repo/profile/package_use/parsers.rs | 36 |
2 files changed, 161 insertions, 0 deletions
diff --git a/src/repo/profile/package_use/mod.rs b/src/repo/profile/package_use/mod.rs new file mode 100644 index 0000000..ddad8b3 --- /dev/null +++ b/src/repo/profile/package_use/mod.rs @@ -0,0 +1,125 @@ +use std::{ + collections::HashMap, + io, + path::{Path, PathBuf}, +}; + +use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter}; + +use crate::{ + Parseable, + atom::Atom, + repo::profile::{FlagOperation, LineBasedFileExpr, Profile, read_config_files}, + useflag::UseFlag, +}; + +mod parsers; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}: io error: {1}")] + Io(PathBuf, io::Error), + #[error("parser error: {0}")] + Parser(String), +} + +#[derive(Debug, Clone)] +struct Expr(Atom, Vec<FlagOperation>); + +#[derive(Debug, Clone, Copy)] +pub(super) enum Kind { + Use, + Force, + Mask, + StableForce, + StableMask, +} + +pub(super) fn evaluate<P: AsRef<Path>>( + parents: &[Profile], + kind: Kind, + path: P, +) -> Result<HashMap<Atom, Vec<UseFlag>>, Error> { + let file_path = match kind { + Kind::Use => "package.use", + Kind::Force => "package.use.force", + Kind::Mask => "package.use.mask", + Kind::StableForce => "package.use.stable.force", + Kind::StableMask => "package.use.stable.mask", + }; + + let parsed = match read_config_files(path.as_ref().join(file_path)) { + Ok(contents) => parse(&contents)?, + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => HashMap::new(), + Err(e) => return Err(Error::Io(path.as_ref().to_path_buf(), e)), + }; + + Ok(inherit(parents, kind, parsed)) +} + +fn inherit( + parents: &[Profile], + kind: Kind, + vars: HashMap<Atom, Vec<FlagOperation>>, +) -> HashMap<Atom, Vec<UseFlag>> { + let mut accumulated: HashMap<Atom, Vec<UseFlag>> = HashMap::new(); + + for parent in parents { + let source = match kind { + Kind::Use => parent.package_use(), + Kind::Force => parent.package_use_force(), + Kind::Mask => parent.package_use_mask(), + Kind::StableForce => parent.package_use_stable_force(), + Kind::StableMask => parent.package_use_stable_mask(), + }; + + for (atom, flags) in source { + accumulated + .entry(atom.clone()) + .and_modify(|f| f.extend(flags.iter().cloned())) + .or_insert(flags.clone()); + } + } + + for (atom, flags) in vars { + match accumulated.get_mut(&atom) { + Some(accumulated) => { + for flag in flags { + match flag { + FlagOperation::Add(flag) => accumulated.push(flag), + FlagOperation::Remove(flag) => accumulated.retain(|v| *v != flag), + } + } + } + None => { + accumulated.insert( + atom.clone(), + flags + .iter() + .filter_map(|flag| match flag { + FlagOperation::Add(flag) => Some(flag), + FlagOperation::Remove(_) => None, + }) + .cloned() + .collect(), + ); + } + } + } + + accumulated +} + +fn parse(contents: &str) -> Result<HashMap<Atom, Vec<FlagOperation>>, Error> { + Ok(LineBasedFileExpr::<Expr>::parser() + .separated_by_with_opt_trailing(ascii_whitespace1()) + .many() + .parse_finished(InputIter::new(contents)) + .map_err(|e| Error::Parser(e.rest().to_string()))? + .into_iter() + .filter_map(|expr| match expr { + LineBasedFileExpr::Comment => None, + LineBasedFileExpr::Expr(Expr(atom, operations)) => Some((atom, operations)), + }) + .collect()) +} diff --git a/src/repo/profile/package_use/parsers.rs b/src/repo/profile/package_use/parsers.rs new file mode 100644 index 0000000..f7bc801 --- /dev/null +++ b/src/repo/profile/package_use/parsers.rs @@ -0,0 +1,36 @@ +use mon::{Parser, ParserIter, ascii_whitespace, ascii_whitespace1, tag}; + +use crate::{ + Parseable, + atom::Atom, + repo::profile::{FlagOperation, package_use::Expr}, +}; + +impl<'a> Parseable<'a, &'a str> for Expr { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + Atom::parser() + .followed_by(ascii_whitespace1()) + .and( + FlagOperation::parser() + .separated_by(ascii_whitespace().and_not(tag("\n")).repeated().at_least(1)) + .at_least(1), + ) + .map(|(atom, operations)| Expr(atom, operations)) + } +} + +#[cfg(test)] +mod test { + use mon::input::InputIter; + + use super::*; + + #[test] + fn test_parse_expr() { + let it = InputIter::new("foo/bar a -b"); + + Expr::parser().check_finished(it).unwrap(); + } +} |
