makers 0.8.0

a POSIX-compatible make implemented in Rust
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<Rc<RefCell<Target>>>,
    },
}

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<Rc<RefCell<Target>>>,
    ) -> Self {
        Self::Complete { target, get_target }
    }

    pub fn lookup(&self, macro_name: &str) -> Result<String> {
        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::<Result<_, _>>()?
        } 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::<Result<_, _>>()?
        } 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<Vec<String>> {
        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<Vec<String>> {
        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<Vec<String>> {
        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<Vec<String>> {
        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<Vec<String>> {
        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")
            }
        }
    }
}