use eyre::{bail, eyre, Result}; use std::cell::RefCell; use std::path::Path; use std::rc::Rc; use super::target::Target; #[derive(Clone)] pub enum LookupInternal<'a> { Partial { targets: &'a Vec<&'a str>, }, Complete { target: Option<&'a Target>, get_target: &'a dyn Fn(&str) -> Result>>, }, } impl<'a> LookupInternal<'a> { pub const fn new_partial(targets: &'a Vec<&str>) -> Self { Self::Partial { targets } } pub const fn new( target: Option<&'a Target>, get_target: &'a dyn Fn(&str) -> Result>>, ) -> Self { Self::Complete { target, get_target } } pub fn lookup(&self, macro_name: &str) -> Result { let macro_pieces = match macro_name.chars().next() { Some('@') => self.target_name()?, Some('?') => self.newer_prerequisites()?, Some('<') => self.inference_prerequisite()?, Some('*') => self.target_stem()?, #[cfg(feature = "full")] Some('^') => self.all_prerequisites()?, _ => bail!("unknown internal macro {}", macro_name), }; let macro_pieces = if macro_name.ends_with('D') { macro_pieces .into_iter() .map(|x| { Path::new(&x) .parent() .ok_or_else(|| eyre!("no parent")) .map(|x| x.to_string_lossy().into()) }) .collect::>()? } else if macro_name.ends_with('F') { macro_pieces .into_iter() .map(|x| { Path::new(&x) .file_name() .ok_or_else(|| eyre!("no filename")) .map(|x| x.to_string_lossy().into()) }) .collect::>()? } else { macro_pieces }; Ok(macro_pieces.join(" ")) } /// POSIX: The $@ shall evaluate to the full target name of the current target. fn target_name(&self) -> Result> { match self { Self::Partial { targets } => { Ok(targets.iter().map(|target| target.to_string()).collect()) } Self::Complete { target: Some(target), .. } => Ok(vec![target.name.clone()]), Self::Complete { target: None, .. } => { bail!("tried to expand internal macro with no target") } } } /// POSIX: The $? macro shall evaluate to the list of prerequisites that are newer than the current target. fn newer_prerequisites(&self) -> Result> { match self { Self::Partial { .. } => bail!("can’t expand $? when target not defined"), Self::Complete { target: Some(target), get_target, } => Ok(target .prerequisites .iter() .filter(|prereq| { get_target(prereq) .ok() .and_then(|prereq| prereq.borrow().newer_than(target)) .unwrap_or(false) }) .cloned() .collect()), Self::Complete { target: None, .. } => { bail!("tried to expand internal macro with no target") } } } /// POSIX: In an inference rule, the $< macro shall evaluate to the filename whose existence allowed the inference rule to be chosen for the target. In the .DEFAULT rule, the $< macro shall evaluate to the current target name. /// /// GNU: The name of the first prerequisite. fn inference_prerequisite(&self) -> Result> { match self { Self::Partial { .. } => bail!("can’t expand $< when target not defined"), Self::Complete { target: Some(target), .. } => { // TODO check that exists_but_inferring_anyway won’t break this Ok(vec![target .prerequisites .first() .cloned() .unwrap_or_default()]) } Self::Complete { target: None, .. } => { bail!("tried to expand internal macro with no target") } } } /// POSIX: The $* macro shall evaluate to the current target name with its suffix deleted. fn target_stem(&self) -> Result> { match self { Self::Partial { .. } => bail!("can’t expand $* when target not defined"), Self::Complete { target: Some(target), .. } => Ok(vec![target .stem .as_ref() .unwrap_or(&target.name) .to_owned()]), Self::Complete { target: None, .. } => { bail!("tried to expand internal macro with no target") } } } /// GNU: The names of all the prerequisites. #[cfg(feature = "full")] fn all_prerequisites(&self) -> Result> { match self { Self::Partial { .. } => bail!("can’t expand $^ when target not defined"), Self::Complete { target: Some(target), .. } => Ok(target.prerequisites.clone()), Self::Complete { target: None, .. } => { bail!("tried to expand internal macro with no target") } } } }