summaryrefslogtreecommitdiff
path: root/src/repo/profile/make_defaults
diff options
context:
space:
mode:
authorJohn Turner <jturner.usa@gmail.com>2025-11-29 20:43:28 +0000
committerJohn Turner <jturner.usa@gmail.com>2025-11-29 20:50:59 +0000
commitd1127df296aa7871555293e324d125e6d8a843e1 (patch)
treebaddfab365df586207fa9c1dadf270ee94c3f46f /src/repo/profile/make_defaults
parent94f3397d197e47eb58a7391acd9c63c5565fa26e (diff)
downloadgentoo-utils-profiles.tar.gz
impl profile evaluationprofiles
Diffstat (limited to 'src/repo/profile/make_defaults')
-rw-r--r--src/repo/profile/make_defaults/mod.rs135
-rw-r--r--src/repo/profile/make_defaults/parsers.rs88
2 files changed, 223 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();
+ }
+}