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); #[derive(Debug, Clone, Copy)] pub(super) enum Kind { Use, Force, Mask, StableForce, StableMask, } pub(super) fn evaluate>( parents: &[Profile], kind: Kind, path: P, ) -> Result>, 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>, ) -> HashMap> { let mut accumulated: HashMap> = 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>, Error> { Ok(LineBasedFileExpr::::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()) }