use super::{Classification, Handler, HandlerContext, has_flag};
pub static ENV_HANDLER: EnvHandler = EnvHandler;
pub struct EnvHandler;
impl Handler for EnvHandler {
fn commands(&self) -> &[&str] {
&["env"]
}
fn classify(&self, ctx: &HandlerContext) -> Classification {
let positionals: Vec<&str> = ctx
.args
.iter()
.filter(|a| !a.starts_with('-') && !a.contains('='))
.map(String::as_str)
.collect();
if positionals.is_empty() {
return Classification::Allow("env (print environment)".into());
}
Classification::Recurse(positionals.join(" "))
}
}
pub static XARGS_HANDLER: XargsHandler = XargsHandler;
pub struct XargsHandler;
const XARGS_VALUE_FLAGS: &[&str] = &[
"-I",
"-n",
"-P",
"-L",
"-s",
"-E",
"-d",
"--max-args",
"--max-procs",
"--max-lines",
"--max-chars",
"--delimiter",
"--eof",
"--replace",
];
impl Handler for XargsHandler {
fn commands(&self) -> &[&str] {
&["xargs"]
}
fn classify(&self, ctx: &HandlerContext) -> Classification {
if has_flag(ctx.args, &["-p", "--interactive"]) {
return Classification::Ask("xargs (interactive)".into());
}
let inner_start = find_xargs_inner_command(ctx.args);
let inner: Vec<&str> = ctx.args[inner_start..].iter().map(String::as_str).collect();
if inner.is_empty() {
return Classification::Ask("xargs (no command)".into());
}
Classification::Recurse(inner.join(" "))
}
}
fn find_xargs_inner_command(args: &[String]) -> usize {
let mut i = 0;
while i < args.len() {
let arg = args[i].as_str();
if XARGS_VALUE_FLAGS.contains(&arg) {
i += 2; } else if XARGS_VALUE_FLAGS.iter().any(|f| arg.starts_with(f)) {
i += 1; } else if arg.starts_with('-') {
i += 1; } else {
return i; }
}
args.len()
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use std::path::Path;
use super::*;
fn ctx<'a>(args: &'a [String], cmd: &'a str) -> HandlerContext<'a> {
HandlerContext {
command_name: cmd,
args,
working_directory: Path::new("/tmp"),
remote: false,
receives_piped_input: false,
cd_allowed_dirs: &[],
}
}
#[test]
fn xargs_simple_inner_command() {
let args: Vec<String> = vec!["rm".into()];
let result = XARGS_HANDLER.classify(&ctx(&args, "xargs"));
assert!(matches!(result, Classification::Recurse(cmd) if cmd == "rm"));
}
#[test]
fn xargs_skips_value_flags() {
let args: Vec<String> = vec!["-n".into(), "5".into(), "grep".into(), "pattern".into()];
let result = XARGS_HANDLER.classify(&ctx(&args, "xargs"));
assert!(
matches!(result, Classification::Recurse(cmd) if cmd == "grep pattern"),
"expected 'grep pattern'"
);
}
#[test]
fn xargs_skips_attached_value_flags() {
let args: Vec<String> = vec!["-n5".into(), "grep".into(), "pattern".into()];
let result = XARGS_HANDLER.classify(&ctx(&args, "xargs"));
assert!(matches!(result, Classification::Recurse(cmd) if cmd == "grep pattern"));
}
#[test]
fn xargs_multiple_flags_with_values() {
let args: Vec<String> = vec![
"-P".into(),
"4".into(),
"-n".into(),
"1".into(),
"echo".into(),
];
let result = XARGS_HANDLER.classify(&ctx(&args, "xargs"));
assert!(matches!(result, Classification::Recurse(cmd) if cmd == "echo"));
}
#[test]
fn xargs_interactive_asks() {
let args: Vec<String> = vec!["-p".into(), "rm".into()];
let result = XARGS_HANDLER.classify(&ctx(&args, "xargs"));
assert!(matches!(result, Classification::Ask(_)));
}
#[test]
fn xargs_no_inner_command() {
let args: Vec<String> = vec!["-0".into(), "-n".into(), "5".into()];
let result = XARGS_HANDLER.classify(&ctx(&args, "xargs"));
assert!(matches!(result, Classification::Ask(reason) if reason.contains("no command")));
}
#[test]
fn xargs_replace_flag() {
let args: Vec<String> = vec!["-I".into(), "{}".into(), "echo".into(), "{}".into()];
let result = XARGS_HANDLER.classify(&ctx(&args, "xargs"));
assert!(matches!(result, Classification::Recurse(cmd) if cmd.starts_with("echo")));
}
#[test]
fn env_bare_allows() {
let args: Vec<String> = vec![];
let result = ENV_HANDLER.classify(&ctx(&args, "env"));
assert!(matches!(result, Classification::Allow(_)));
}
#[test]
fn env_with_command_recurses() {
let args: Vec<String> = vec!["FOO=bar".into(), "git".into(), "status".into()];
let result = ENV_HANDLER.classify(&ctx(&args, "env"));
assert!(matches!(result, Classification::Recurse(_)));
}
}