use std::collections::HashMap;
use std::{fmt, mem};
use super::pattern::r#match;
use super::{CommandLine, ItemSource, MacroSet};
use eyre::{eyre, OptionExt, Result};
use regex::Captures;
#[derive(Clone, Debug)]
pub struct InferenceRule {
pub source: ItemSource,
pub products: Vec<String>,
pub prerequisites: Vec<String>,
pub commands: Vec<CommandLine>,
pub macros: MacroSet,
}
impl InferenceRule {
pub fn new_suffix(
source: ItemSource,
s1: String,
s2: String,
commands: Vec<CommandLine>,
macros: MacroSet,
) -> Self {
Self {
source,
products: vec![format!("%{}", s1)],
prerequisites: vec![format!("%{}", s2)],
commands,
macros,
}
}
pub fn first_match<'s, 't: 's>(&'s self, target_name: &'t str) -> Result<Option<Captures<'t>>> {
self.products
.iter()
.map(|pattern| {
r#match(pattern.strip_prefix("./").unwrap_or(pattern), target_name)
})
.try_fold(None, |x, y| y.map(|y| x.or(y)))
}
pub fn matches(&self, target_name: &str) -> Result<bool> {
self.first_match(target_name).map(|x| x.is_some())
}
pub fn prereqs<'s>(
&'s self,
target_name: &'s str,
) -> Result<impl Iterator<Item = String> + 's> {
let capture = self
.first_match(target_name)?
.ok_or_else(|| eyre!("asked non-matching inference rule for prerequisites"))?;
let percent_expansion = capture
.get(1)
.ok_or_eyre("should've matched the %")?
.as_str();
Ok(self
.prerequisites
.iter()
.map(move |p| p.replace('%', percent_expansion)))
}
fn extend(&mut self, other: Self) {
assert_eq!(&self.products, &other.products);
match (self.commands.is_empty(), other.commands.is_empty()) {
(false, false) => {
*self = other;
}
(true, false) => {
let mut other = other;
mem::swap(self, &mut other);
self.extend(other);
}
(false, true) | (true, true) => {
self.prerequisites.extend(other.prerequisites);
self.macros.extend(other.macros);
}
}
}
}
impl fmt::Display for InferenceRule {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
f,
"{}: {}",
self.products.join(" "),
self.prerequisites.join(" ")
)?;
for command in &self.commands {
writeln!(f, "\t{}", command)?;
}
Ok(())
}
}
#[derive(Clone, Default)]
pub struct InferenceRuleSet {
data: HashMap<Vec<String>, HashMap<Vec<String>, InferenceRule>>,
}
impl InferenceRuleSet {
pub fn get(&self, products: &[String], prerequisites: &[String]) -> Option<&InferenceRule> {
self.data.get(products).and_then(|x| x.get(prerequisites))
}
fn get_mut(
&mut self,
products: &[String],
prerequisites: &[String],
) -> Option<&mut InferenceRule> {
self.data
.get_mut(products)
.and_then(|x| x.get_mut(prerequisites))
}
pub fn put(&mut self, rule: InferenceRule) {
if let Some(existing_rule) = self.get_mut(&rule.products, &rule.prerequisites) {
existing_rule.extend(rule);
} else {
self.data
.entry(rule.products.clone())
.or_default()
.insert(rule.prerequisites.clone(), rule);
}
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn extend(&mut self, other: Self) {
for other in other.data.into_values().flat_map(HashMap::into_values) {
self.put(other);
}
}
pub fn iter(&self) -> impl Iterator<Item = &InferenceRule> {
self.data.values().flat_map(HashMap::values)
}
}
impl From<Vec<InferenceRule>> for InferenceRuleSet {
fn from(value: Vec<InferenceRule>) -> Self {
let mut result = Self::default();
for rule in value {
result.put(rule);
}
result
}
}
#[cfg(test)]
mod test {
use super::*;
type R = Result<()>;
#[test]
fn suffix_match() -> R {
let rule = InferenceRule::new_suffix(
ItemSource::Builtin,
".o".to_owned(),
".c".to_owned(),
vec![],
MacroSet::new(),
);
assert!(rule.matches("foo.o")?);
assert!(rule.matches("dir/foo.o")?);
Ok(())
}
#[cfg(feature = "full")]
#[test]
fn percent_match() -> R {
let rule = InferenceRule {
source: ItemSource::Builtin,
products: vec!["licenseListPublisher-%.jar-valid".to_owned()],
prerequisites: vec![
"licenseListPublisher-%.jar.asc".to_owned(),
"licenseListPublisher-%.jar".to_owned(),
"goneall.gpg".to_owned(),
],
commands: vec![],
macros: MacroSet::new(),
};
assert!(rule.matches("licenseListPublisher-2.2.1.jar-valid")?);
Ok(())
}
#[cfg(feature = "full")]
#[test]
fn subdir_match() -> R {
let rule = InferenceRule {
source: ItemSource::Builtin,
products: vec!["a/%.o".to_owned()],
prerequisites: vec!["a/%.c".to_owned()],
commands: vec![],
macros: MacroSet::new(),
};
assert!(rule.matches("a/foo.o")?);
Ok(())
}
#[cfg(feature = "full")]
#[test]
fn self_subdir_match() -> R {
let rule = InferenceRule {
source: ItemSource::Builtin,
products: vec!["./%.o".to_owned()],
prerequisites: vec!["./%.c".to_owned()],
commands: vec![],
macros: MacroSet::new(),
};
assert!(rule.matches("foo.o")?);
Ok(())
}
}