use crate::parse::Token;
pub(super) fn in_place_backups(tokens: &[Token], files: &[&str], bsd_style: bool) -> Vec<String> {
let words: Vec<&str> = tokens.iter().skip(1).map(Token::as_str).collect();
let mut suffixes = Vec::new();
for (idx, word) in words.iter().enumerate() {
if *word == "--" {
break;
}
if let Some(s) = word.strip_prefix("--in-place=") {
suffixes.push(s);
} else if let Some(cluster) = word.strip_prefix('-').filter(|c| !c.starts_with('-'))
&& let Some(at) = cluster.find('i')
{
let glued = &cluster[at + 1..];
if !glued.is_empty() {
suffixes.push(glued);
} else if bsd_style
&& let Some(next) = words.get(idx + 1)
&& !next.contains('/')
{
suffixes.push(next);
}
}
}
let mut out = Vec::new();
for suffix in suffixes.into_iter().filter(|s| !s.is_empty()) {
for file in files {
out.push(backup_of(file, suffix));
}
}
out
}
fn backup_of(file: &str, suffix: &str) -> String {
if !suffix.contains('*') {
return format!("{file}{suffix}");
}
let (dir, name) = file.rsplit_once('/').map_or(("", file), |(d, n)| (d, n));
let named = suffix.replace('*', name);
if named.contains('/') || dir.is_empty() { named } else { format!("{dir}/{named}") }
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
fn toks(line: &str) -> Vec<Token> {
line.split(' ').map(|w| Token::from_raw(w.to_string())).collect()
}
#[test]
fn each_suffix_spelling_names_its_backup() {
let cases: &[(&str, &[&str], bool, &[&str])] = &[
("sed -i.bak s/x/x/ notes", &["notes"], false, &["notes.bak"]),
("sed -i.git/hooks/* s/x/x/ pre-commit", &["pre-commit"], false, &[".git/hooks/pre-commit"]),
("sed --in-place=.profil* s/x/x/ e", &["e"], false, &[".profile"]),
("sed -i keys s/x/x/ authorized_", &["authorized_"], true, &["authorized_keys"]),
("perl -pi.orig -e 1 src/a.rs", &["src/a.rs"], false, &["src/a.rs.orig"]),
("perl -pibak_* -e 1 src/a.rs", &["src/a.rs"], false, &["src/bak_a.rs"]),
("sed -i s/x/x/ notes", &["notes"], false, &[]),
];
for (line, files, bsd, want) in cases {
assert_eq!(in_place_backups(&toks(line), files, *bsd), want.iter().map(|s| (*s).to_string()).collect::<Vec<_>>(), "{line}");
}
}
proptest! {
#[test]
fn a_backup_is_the_file_renamed_by_its_suffix(file in "[a-z]{1,6}(/[a-z]{1,6}){0,2}", suffix in "[a-z.