diff options
Diffstat (limited to 'src/repo/profile/make_defaults/mod.rs')
| -rw-r--r-- | src/repo/profile/make_defaults/mod.rs | 135 |
1 files changed, 135 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()) +} |
