summaryrefslogtreecommitdiff
path: root/src/repo/profile/package_use
diff options
context:
space:
mode:
Diffstat (limited to 'src/repo/profile/package_use')
-rw-r--r--src/repo/profile/package_use/mod.rs125
-rw-r--r--src/repo/profile/package_use/parsers.rs36
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();
+ }
+}