use std::{ fs, io, path::{Path, PathBuf}, }; use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter}; use crate::{ Parseable, atom::Atom, repo::profile::{LineBasedFileExpr, Profile}, }; 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)] enum Package { Add(Atom), Remove(Atom), } pub(super) fn evaluate>(parents: &[Profile], path: P) -> Result, Error> { let parsed = match fs::read_to_string(path.as_ref().join("packages")) { Ok(contents) => parse(&contents)?, Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => Vec::new(), Err(e) => return Err(Error::Io(path.as_ref().to_path_buf(), e)), }; Ok(inherit(parents, parsed)) } fn inherit(parents: &[Profile], packages: Vec) -> Vec { let mut accumulated = Vec::new(); for parent in parents { for package in parent.packages() { accumulated.push(package.clone()); } } for package in packages { match package { Package::Add(package) => { accumulated.push(package); } Package::Remove(package) => { accumulated.retain(|p| *p != package); } } } 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(package) => Some(package), }) .collect()) }