diff options
| author | John Turner <jturner.usa@gmail.com> | 2025-11-29 20:43:28 +0000 |
|---|---|---|
| committer | John Turner <jturner.usa@gmail.com> | 2025-11-29 20:50:59 +0000 |
| commit | d1127df296aa7871555293e324d125e6d8a843e1 (patch) | |
| tree | baddfab365df586207fa9c1dadf270ee94c3f46f /src/repo/profile | |
| parent | 94f3397d197e47eb58a7391acd9c63c5565fa26e (diff) | |
| download | gentoo-utils-profiles.tar.gz | |
impl profile evaluationprofiles
Diffstat (limited to 'src/repo/profile')
| -rw-r--r-- | src/repo/profile/make_defaults/mod.rs | 135 | ||||
| -rw-r--r-- | src/repo/profile/make_defaults/parsers.rs | 88 | ||||
| -rw-r--r-- | src/repo/profile/mod.rs | 157 | ||||
| -rw-r--r-- | src/repo/profile/package/mod.rs | 95 | ||||
| -rw-r--r-- | src/repo/profile/package/parsers.rs | 13 | ||||
| -rw-r--r-- | src/repo/profile/package_use/mod.rs | 125 | ||||
| -rw-r--r-- | src/repo/profile/package_use/parsers.rs | 36 | ||||
| -rw-r--r-- | src/repo/profile/packages/mod.rs | 75 | ||||
| -rw-r--r-- | src/repo/profile/packages/parsers.rs | 14 | ||||
| -rw-r--r-- | src/repo/profile/parsers.rs | 35 | ||||
| -rw-r--r-- | src/repo/profile/useflags/mod.rs | 94 |
11 files changed, 867 insertions, 0 deletions
diff --git a/src/repo/profile/make_defaults/mod.rs b/src/repo/profile/make_defaults/mod.rs new file mode 100644 index 0000000..334f165 --- /dev/null +++ b/src/repo/profile/make_defaults/mod.rs @@ -0,0 +1,135 @@ +use std::{ + collections::{HashMap, HashSet}, + fs, io, + path::{Path, PathBuf}, +}; + +use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter}; + +use crate::{ + Parseable, + 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, PartialEq, Eq, Hash)] +struct Key(String); + +#[derive(Debug, Clone)] +struct Literal(String); + +#[derive(Debug, Clone)] +struct Interpolation(String); + +#[derive(Debug, Clone)] +enum Segment { + Literal(Literal), + Interpolation(Interpolation), +} + +#[derive(Debug, Clone)] +struct Assignment(Key, Vec<Segment>); + +pub(super) fn evaluate<P: AsRef<Path>>( + parents: &[Profile], + path: P, +) -> Result<HashMap<String, String>, Error> { + let parsed = match fs::read_to_string(path.as_ref().join("make.defaults")) { + 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)), + }; + + let mut vars = interpolate(parents, parsed); + + incremental(parents, &mut vars); + + Ok(vars) +} + +fn incremental(parents: &[Profile], vars: &mut HashMap<String, String>) { + for key in [ + "USE", + "USE_EXPAND", + "USE_EXPAND_HIDDEN", + "CONFIG_PROTECT", + "CONFIG_PROTECT_MASK", + ] { + let mut accumulated = Vec::new(); + + for parent in parents { + if let Some(values) = parent.make_defaults().get(key) { + accumulated.extend(values.split_ascii_whitespace()); + } + } + + if let Some(values) = vars.get(key) { + accumulated.extend(values.split_ascii_whitespace()); + } + + let mut final_values = Vec::new(); + + for var in accumulated { + if var == "-*" { + final_values.clear(); + } else if let Some(stripped) = var.strip_prefix("-") { + final_values.retain(|v| *v != stripped); + } else { + final_values.push(var); + } + } + + let mut seen = HashSet::new(); + final_values.retain(|v| seen.insert(*v)); + + if !final_values.is_empty() { + vars.insert(key.to_string(), final_values.join(" ")); + } + } +} + +fn interpolate(parents: &[Profile], vars: HashMap<Key, Vec<Segment>>) -> HashMap<String, String> { + let parent_vars = parents + .iter() + .flat_map(|parent| parent.make_defaults().clone().into_iter()) + .collect::<HashMap<_, _>>(); + + vars.into_iter() + .map(|(key, segments)| { + let interpolated = segments + .into_iter() + .map(|segment| match segment { + Segment::Interpolation(i) => parent_vars.get(&i.0).cloned().unwrap_or_default(), + Segment::Literal(literal) => literal.0.trim().to_string(), + }) + .collect::<Vec<_>>(); + + let joined = interpolated.join(""); + + (key.0, joined) + }) + .collect::<HashMap<String, String>>() +} + +fn parse(contents: &str) -> Result<HashMap<Key, Vec<Segment>>, Error> { + Ok(LineBasedFileExpr::<Assignment>::parser() + .separated_by_with_opt_trailing(ascii_whitespace1()) + .many() + .parse_finished(InputIter::new(contents)) + .map_err(|e| Error::Parser(dbg!(e).rest().to_string()))? + .into_iter() + .filter_map(|expr| match expr { + LineBasedFileExpr::Comment => None, + LineBasedFileExpr::Expr(Assignment(key, value)) => Some((key, value)), + }) + .collect()) +} diff --git a/src/repo/profile/make_defaults/parsers.rs b/src/repo/profile/make_defaults/parsers.rs new file mode 100644 index 0000000..4d27791 --- /dev/null +++ b/src/repo/profile/make_defaults/parsers.rs @@ -0,0 +1,88 @@ +use mon::{Parser, ParserIter, ascii_alpha, ascii_alphanumeric, r#if, one_of, tag}; + +use crate::{ + Parseable, + repo::profile::make_defaults::{Assignment, Interpolation, Key, Literal, Segment}, +}; + +impl<'a> Parseable<'a, &'a str> for Key { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + let start = ascii_alpha(); + let rest = ascii_alphanumeric() + .or(one_of("_".chars())) + .repeated() + .many(); + + start + .followed_by(rest) + .recognize() + .map(|output: &str| Key(output.to_string())) + } +} + +impl<'a> Parseable<'a, &'a str> for Literal { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + r#if(|c: &char| *c != '"') + .and_not(Interpolation::parser()) + .repeated() + .at_least(1) + .recognize() + .map(|output: &str| Literal(output.to_string())) + } +} + +impl<'a> Parseable<'a, &'a str> for Interpolation { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + Key::parser() + .recognize() + .delimited_by(tag("{"), tag("}")) + .preceded_by(tag("$")) + .map(|output: &str| Interpolation(output.to_string())) + } +} + +impl<'a> Parseable<'a, &'a str> for Segment { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + Literal::parser() + .map(Segment::Literal) + .or(Interpolation::parser().map(Segment::Interpolation)) + } +} + +impl<'a> Parseable<'a, &'a str> for Assignment { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + Key::parser() + .followed_by(tag("=")) + .and( + Segment::parser() + .repeated() + .many() + .delimited_by(tag("\""), tag("\"")), + ) + .map(|(key, value)| Assignment(key, value)) + } +} + +#[cfg(test)] +mod test { + use mon::input::InputIter; + + use super::*; + + #[test] + fn test_parse_value() { + let it = InputIter::new(r#"KEY="foo ${bar}""#); + + Assignment::parser().check_finished(it).unwrap(); + } +} diff --git a/src/repo/profile/mod.rs b/src/repo/profile/mod.rs new file mode 100644 index 0000000..3edd170 --- /dev/null +++ b/src/repo/profile/mod.rs @@ -0,0 +1,157 @@ +use std::{ + collections::HashMap, + fs::{self, File}, + io::{self, Read}, + path::{Path, PathBuf}, +}; + +use get::Get; +use itertools::Itertools; + +use crate::{atom::Atom, useflag::UseFlag}; + +mod make_defaults; +mod package; +mod package_use; +mod packages; +mod parsers; +mod useflags; + +#[derive(Debug, Clone)] +enum LineBasedFileExpr<T> { + Comment, + Expr(T), +} + +#[derive(Debug, Clone)] +enum FlagOperation { + Add(UseFlag), + Remove(UseFlag), +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}: io error: {1}")] + Io(PathBuf, io::Error), + #[error("error evaluating make.defaults settings: {0}")] + MakeDefaults(#[from] make_defaults::Error), + #[error("error evaluating packages settings: {0}")] + Packages(#[from] packages::Error), + #[error("error evaluating package settings: {0}")] + Package(#[from] package::Error), + #[error("error evaluating package.use settings: {0}")] + PackageUse(#[from] package_use::Error), + #[error("error evaluating use settings: {0}")] + Use(#[from] useflags::Error), +} + +#[derive(Debug, Clone, Get)] +pub struct Profile { + #[get(kind = "deref")] + path: PathBuf, + #[get(kind = "deref")] + parents: Vec<Profile>, + make_defaults: HashMap<String, String>, + #[get(kind = "deref")] + packages: Vec<Atom>, + #[get(kind = "deref")] + package_mask: Vec<Atom>, + #[get(kind = "deref")] + package_provided: Vec<Atom>, + package_use: HashMap<Atom, Vec<UseFlag>>, + package_use_force: HashMap<Atom, Vec<UseFlag>>, + package_use_mask: HashMap<Atom, Vec<UseFlag>>, + package_use_stable_force: HashMap<Atom, Vec<UseFlag>>, + package_use_stable_mask: HashMap<Atom, Vec<UseFlag>>, + #[get(kind = "deref")] + use_force: Vec<UseFlag>, + #[get(kind = "deref")] + use_mask: Vec<UseFlag>, + #[get(kind = "deref")] + use_stable_force: Vec<UseFlag>, + #[get(kind = "deref")] + use_stable_mask: Vec<UseFlag>, +} + +impl Profile { + pub(super) fn evaluate<P: AsRef<Path>>(path: P) -> Result<Self, Error> { + let parents_path = path.as_ref().join("parent"); + + let parents = match fs::read_to_string(&parents_path) { + Ok(parents) => parents + .lines() + .map(|line| path.as_ref().join(line)) + .map(Profile::evaluate) + .collect::<Result<_, _>>()?, + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => Vec::new(), + Err(e) => return Err(Error::Io(parents_path, e)), + }; + + let make_defaults = make_defaults::evaluate(&parents, &path)?; + + let packages = packages::evaluate(&parents, &path)?; + + let package_mask = package::evaluate(&parents, package::Kind::Mask, &path)?; + let package_provided = package::evaluate(&parents, package::Kind::Provided, &path)?; + + let package_use = package_use::evaluate(&parents, package_use::Kind::Use, &path)?; + let package_use_force = package_use::evaluate(&parents, package_use::Kind::Force, &path)?; + let package_use_mask = package_use::evaluate(&parents, package_use::Kind::Mask, &path)?; + let package_use_stable_force = + package_use::evaluate(&parents, package_use::Kind::StableForce, &path)?; + let package_use_stable_mask = + package_use::evaluate(&parents, package_use::Kind::StableMask, &path)?; + + let use_force = useflags::evaluate(&parents, useflags::Kind::Force, &path)?; + let use_mask = useflags::evaluate(&parents, useflags::Kind::Mask, &path)?; + let use_stable_force = useflags::evaluate(&parents, useflags::Kind::StableForce, &path)?; + let use_stable_mask = useflags::evaluate(&parents, useflags::Kind::StableMask, &path)?; + + Ok(Self { + path: path.as_ref().to_path_buf(), + parents, + make_defaults, + packages, + package_mask, + package_provided, + package_use, + package_use_force, + package_use_mask, + package_use_stable_force, + package_use_stable_mask, + use_force, + use_mask, + use_stable_force, + use_stable_mask, + }) + } +} + +fn read_config_files<P: AsRef<Path>>(path: P) -> Result<String, io::Error> { + let metadata = fs::metadata(&path)?; + + if metadata.is_file() { + fs::read_to_string(&path) + } else if metadata.is_dir() { + let mut buffer = String::new(); + let paths = fs::read_dir(&path)? + .collect::<Result<Vec<_>, _>>()? + .into_iter() + .map(|entry| entry.path()) + .filter(|path| path.starts_with(".")) + .sorted() + .collect::<Vec<_>>(); + + for path in &paths { + let mut file = File::open(path)?; + + file.read_to_string(&mut buffer)?; + } + + Ok(buffer) + } else { + let path = fs::canonicalize(&path)?; + + read_config_files(path) + } +} diff --git a/src/repo/profile/package/mod.rs b/src/repo/profile/package/mod.rs new file mode 100644 index 0000000..facf9ea --- /dev/null +++ b/src/repo/profile/package/mod.rs @@ -0,0 +1,95 @@ +use std::{ + io, + path::{Path, PathBuf}, +}; + +use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter}; + +use crate::{ + Parseable, + atom::Atom, + repo::profile::{LineBasedFileExpr, Profile, read_config_files}, +}; + +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, Copy)] +pub(super) enum Kind { + Mask, + Provided, +} + +#[derive(Debug, Clone)] +enum Package { + Add(Atom), + Remove(Atom), +} + +pub(super) fn evaluate<P: AsRef<Path>>( + parents: &[Profile], + kind: Kind, + path: P, +) -> Result<Vec<Atom>, Error> { + let file_path = match kind { + Kind::Mask => "package.mask", + Kind::Provided => "package.provided", + }; + + 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) => Vec::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, packages: Vec<Package>) -> Vec<Atom> { + let mut accumulated = Vec::new(); + + for parent in parents { + let source = match kind { + Kind::Mask => parent.package_mask(), + Kind::Provided => parent.package_provided(), + }; + + for package in source { + 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<Vec<Package>, Error> { + Ok(LineBasedFileExpr::<Package>::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()) +} diff --git a/src/repo/profile/package/parsers.rs b/src/repo/profile/package/parsers.rs new file mode 100644 index 0000000..74d9acc --- /dev/null +++ b/src/repo/profile/package/parsers.rs @@ -0,0 +1,13 @@ +use mon::{Parser, tag}; + +use crate::{Parseable, atom::Atom, repo::profile::package::Package}; + +impl<'a> Parseable<'a, &'a str> for Package { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + Atom::parser() + .map(Package::Add) + .or(Atom::parser().preceded_by(tag("-")).map(Package::Remove)) + } +} 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(); + } +} diff --git a/src/repo/profile/packages/mod.rs b/src/repo/profile/packages/mod.rs new file mode 100644 index 0000000..4fd6559 --- /dev/null +++ b/src/repo/profile/packages/mod.rs @@ -0,0 +1,75 @@ +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<P: AsRef<Path>>(parents: &[Profile], path: P) -> Result<Vec<Atom>, 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<Package>) -> Vec<Atom> { + 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<Vec<Package>, Error> { + Ok(LineBasedFileExpr::<Package>::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()) +} diff --git a/src/repo/profile/packages/parsers.rs b/src/repo/profile/packages/parsers.rs new file mode 100644 index 0000000..e5dc727 --- /dev/null +++ b/src/repo/profile/packages/parsers.rs @@ -0,0 +1,14 @@ +use mon::{Parser, tag}; + +use crate::{Parseable, atom::Atom, repo::profile::packages::Package}; + +impl<'a> Parseable<'a, &'a str> for Package { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + Atom::parser() + .preceded_by(tag("*")) + .map(Package::Add) + .or(Atom::parser().preceded_by(tag("-*")).map(Package::Remove)) + } +} diff --git a/src/repo/profile/parsers.rs b/src/repo/profile/parsers.rs new file mode 100644 index 0000000..44e767e --- /dev/null +++ b/src/repo/profile/parsers.rs @@ -0,0 +1,35 @@ +use mon::{Parser, ParserIter, any, ascii_whitespace1, tag}; + +use crate::{ + Parseable, + repo::profile::{FlagOperation, LineBasedFileExpr}, + useflag::UseFlag, +}; + +impl<'a, T> Parseable<'a, &'a str> for LineBasedFileExpr<T> +where + T: Parseable<'a, &'a str>, +{ + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + let comment = tag("#") + .preceded_by(ascii_whitespace1().opt()) + .followed_by(any().and_not(tag("\n")).repeated().many()) + .map(|_| LineBasedFileExpr::Comment); + let expr = T::parser().map(|expr| LineBasedFileExpr::Expr(expr)); + + comment.or(expr) + } +} + +impl<'a> Parseable<'a, &'a str> for FlagOperation { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + UseFlag::parser() + .preceded_by(tag("-")) + .map(FlagOperation::Remove) + .or(UseFlag::parser().map(FlagOperation::Add)) + } +} diff --git a/src/repo/profile/useflags/mod.rs b/src/repo/profile/useflags/mod.rs new file mode 100644 index 0000000..d7fb96c --- /dev/null +++ b/src/repo/profile/useflags/mod.rs @@ -0,0 +1,94 @@ +use std::{ + io, + path::{Path, PathBuf}, +}; + +use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter}; + +use crate::{ + Parseable, + repo::profile::{FlagOperation, LineBasedFileExpr, Profile, read_config_files}, + useflag::UseFlag, +}; + +#[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, Copy)] +pub(super) enum Kind { + Force, + Mask, + StableForce, + StableMask, +} + +#[allow(clippy::unnecessary_wraps)] +pub(super) fn evaluate<P: AsRef<Path>>( + parents: &[Profile], + kind: Kind, + path: P, +) -> Result<Vec<UseFlag>, Error> { + let file_path = match kind { + Kind::Force => "use.force", + Kind::Mask => "use.mask", + Kind::StableForce => "use.stable.force", + Kind::StableMask => "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) => Vec::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, operations: Vec<FlagOperation>) -> Vec<UseFlag> { + let mut accumulated = Vec::new(); + + for parent in parents { + let source = match kind { + Kind::Force => parent.use_force(), + Kind::Mask => parent.use_mask(), + Kind::StableForce => parent.use_stable_force(), + Kind::StableMask => parent.use_stable_mask(), + }; + + for flag in source { + accumulated.push(flag.clone()); + } + } + + for operation in operations { + match operation { + FlagOperation::Add(flag) => { + accumulated.push(flag); + } + FlagOperation::Remove(flag) => { + accumulated.retain(|v| *v != flag); + } + } + } + + accumulated +} + +fn parse(contents: &str) -> Result<Vec<FlagOperation>, Error> { + Ok(LineBasedFileExpr::<FlagOperation>::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(flag_operation) => Some(flag_operation), + }) + .collect()) +} |
