use super::tables::{FILE_OPERANDS, NO_FILE_OPERANDS, WRITE_ALL_PATHS, WRITE_LAST_PATH};
pub(super) fn file_operand<'w>(
word: &'w str,
program: &str,
options_ended: bool,
) -> Option<&'w str> {
if word.is_empty() || NO_FILE_OPERANDS.contains(&program) {
return None;
}
let value = value_of(word);
if value.is_some() || (!options_ended && word.starts_with('-')) {
return value.filter(|v| v.contains('/') && is_local(v));
}
let file_program = FILE_OPERANDS.contains(&program)
|| WRITE_ALL_PATHS.contains(&program)
|| WRITE_LAST_PATH.contains(&program);
((file_program || word.contains('/')) && is_local(word)).then_some(word)
}
fn value_of(word: &str) -> Option<&str> {
let (key, value) = word.split_once('=')?;
(!key.is_empty() && !key.contains('/')).then_some(value)
}
fn is_local(word: &str) -> bool {
let head = word.split('/').next().unwrap_or(word);
!word.contains("://") && !head.contains(':') && !word.is_empty()
}
#[cfg(test)]
mod tests {
use super::file_operand;
#[test]
fn slash_words_are_files_for_any_program() {
assert_eq!(file_operand("s/id_rsa", "base64", false), Some("s/id_rsa"));
assert_eq!(file_operand("--in=s/k", "openssl", false), Some("s/k"));
assert_eq!(file_operand("-", "cat", true), Some("-"));
for (word, program) in [
("status", "git"),
("left-pad", "npm"),
("--short", "git"),
("-n", "head"),
("https://x.example/a", "curl"),
("host:/tmp", "scp"),
("me@host:dst/x", "rsync"),
("s/id_rsa", "echo"),
("CC=clang", "make"),
("--color=auto", "ls"),
] {
assert_eq!(file_operand(word, program, false), None, "{program} {word}");
}
}
#[test]
fn plain_operands_of_file_programs_are_files() {
for program in [
"cat", "head", "tail", "less", "grep", "rg", "cp", "mv", "rm", "tee", "ls",
] {
assert_eq!(file_operand("s", program, false), Some("s"), "{program}");
}
assert_eq!(file_operand("--", "cat", false), None);
}
}