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