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); pub(super) fn evaluate>( parents: &[Profile], path: P, ) -> Result, 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) { 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>) -> HashMap { let parent_vars = parents .iter() .flat_map(|parent| parent.make_defaults().clone().into_iter()) .collect::>(); 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::>(); let joined = interpolated.join(""); (key.0, joined) }) .collect::>() } fn parse(contents: &str) -> Result>, Error> { Ok(LineBasedFileExpr::::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()) }