use crate::manifest::{Command, MatchMode};
#[must_use]
pub fn first_match<'a>(commands: &'a [Command], argv: &[String]) -> Option<&'a Command> {
commands.iter().find(|command| matches(command, argv))
}
pub(crate) fn matches(command: &Command, argv: &[String]) -> bool {
let tokens: Vec<&str> = command.name.split_whitespace().collect();
if tokens.is_empty() {
return false;
}
match command.match_mode {
MatchMode::Prefix => leads(&tokens, argv),
MatchMode::Exact => argv.len() == tokens.len() && leads(&tokens, argv),
}
}
fn leads(tokens: &[&str], argv: &[String]) -> bool {
match argv.get(..tokens.len()) {
Some(prefix) => prefix.iter().map(String::as_str).eq(tokens.iter().copied()),
None => false,
}
}
#[cfg(test)]
mod tests {
use super::first_match;
use crate::manifest::{Command, MatchMode};
fn rule(name: &str) -> Command {
Command {
name: name.to_owned(),
inputs: Vec::new(),
exclude: Vec::new(),
match_mode: MatchMode::Prefix,
outputs: Vec::new(),
tags: Vec::new(),
on_hit: None,
ttl: None,
}
}
fn exact_rule(name: &str) -> Command {
Command {
name: name.to_owned(),
inputs: Vec::new(),
exclude: Vec::new(),
match_mode: MatchMode::Exact,
outputs: Vec::new(),
tags: Vec::new(),
on_hit: None,
ttl: None,
}
}
fn argv(parts: &[&str]) -> Vec<String> {
parts.iter().map(|part| (*part).to_owned()).collect()
}
#[test]
fn matches_token_prefix() {
let rules = [rule("cargo test")];
assert!(first_match(&rules, &argv(&["cargo", "test", "--workspace"])).is_some());
assert!(first_match(&rules, &argv(&["cargo", "build"])).is_none());
assert!(
first_match(&rules, &argv(&["cargo"])).is_none(),
"shorter than matcher"
);
}
#[test]
fn first_rule_in_order_wins() {
let rules = [rule("cargo"), rule("cargo test")];
let hit = first_match(&rules, &argv(&["cargo", "test"])).expect("match");
assert_eq!(hit.name, "cargo", "earlier rule wins on a tie");
}
#[test]
fn token_boundary_is_respected() {
let rules = [rule("car")];
assert!(
first_match(&rules, &argv(&["cargo"])).is_none(),
"no partial-token match"
);
}
#[test]
fn exact_rejects_trailing_args() {
let rules = [exact_rule("cargo test")];
assert!(
first_match(&rules, &argv(&["cargo", "test"])).is_some(),
"exact matches the bare command"
);
assert!(
first_match(&rules, &argv(&["cargo", "test", "--workspace"])).is_none(),
"exact rejects extra args a prefix rule would accept"
);
}
}