use std::collections::BTreeMap;
use std::path::Path;
use crate::clock::Clock;
use crate::error::{Error, Result};
use crate::manifest::Manifest;
use crate::status::{self, Assessment, Records, Shared, expansion_state};
use crate::{parametric, probe, selection};
type Expanded<'a> = (Vec<parametric::Match<'a>>, BTreeMap<String, Shared>);
pub struct Verdict {
pub rule: String,
assessed: Assessment,
}
impl Verdict {
#[must_use]
pub const fn is_fresh(&self) -> bool {
self.assessed.state.is_fresh()
}
#[must_use]
pub const fn state(&self) -> &'static str {
self.assessed.state.label()
}
#[must_use]
pub fn reason(&self) -> Option<String> {
self.assessed.reason()
}
#[must_use]
pub const fn is_remediable(&self) -> bool {
self.assessed.state.is_remediable()
}
}
pub fn evaluate(cwd: &Path, argv: Option<&[String]>, tags: &[String]) -> Result<Vec<Verdict>> {
let now = Clock::resolve()?;
let located = Manifest::locate(cwd)?;
let manifest = &located.manifest;
let base = located.root.as_path();
let cache_dir = base.join(&manifest.cache_dir);
let records = Records {
dir: &cache_dir,
now,
};
if !tags.is_empty() && argv.is_some() {
return Err(Error::TagWithCommand);
}
if let Some(argv) = argv {
let matches = parametric::resolve_matches(manifest, base, argv)?;
parametric::detect_collision(&matches)?;
let hit = matches.first().ok_or_else(|| Error::NoMatch {
command: argv.join(" "),
})?;
let mut probes = probe::Resolver::new(manifest, base);
let shared = status::shared_inputs(manifest, hit.rule, base, &mut probes)?;
return Ok(vec![verdict_for(hit, &shared, base, records)?]);
}
let (matches, shared) = expand_matching(manifest, base, tags)?;
let kept: Vec<&str> = shared.keys().map(String::as_str).collect();
selection::ensure_gateable(manifest, &located.path, tags, &kept, matches.len())?;
verdicts_for(&matches, &shared, base, records)
}
fn expand_matching<'a>(
manifest: &'a Manifest,
base: &Path,
tags: &[String],
) -> Result<Expanded<'a>> {
let mut matches = Vec::new();
let mut shared = BTreeMap::new();
let mut probes = probe::Resolver::new(manifest, base);
let kept = manifest
.commands
.iter()
.filter(|rule| tags.iter().all(|tag| rule.tags.contains(tag)));
for rule in kept {
shared.insert(
rule.name.clone(),
status::shared_inputs(manifest, rule, base, &mut probes)?,
);
matches.extend(parametric::expand_rule(manifest, base, rule)?);
}
parametric::detect_collision(&matches)?;
Ok((matches, shared))
}
fn verdicts_for(
matches: &[parametric::Match],
shared: &BTreeMap<String, Shared>,
base: &Path,
records: Records,
) -> Result<Vec<Verdict>> {
matches
.iter()
.map(|hit| {
let inputs = shared
.get(hit.rule.name.as_str())
.expect("shared inputs resolved for every kept rule");
verdict_for(hit, inputs, base, records)
})
.collect()
}
fn verdict_for(
hit: ¶metric::Match,
shared: &Shared,
base: &Path,
records: Records,
) -> Result<Verdict> {
Ok(Verdict {
rule: hit.exp.identity.clone(),
assessed: expansion_state(hit, shared, base, records)?,
})
}
#[cfg(test)]
#[path = "freshness_tests.rs"]
mod tests;