diff --git a/src/find/matchers/mod.rs b/src/find/matchers/mod.rs index 82c47ee..03fbef9 100644 --- a/src/find/matchers/mod.rs +++ b/src/find/matchers/mod.rs @@ -453,7 +453,7 @@ fn build_matcher_tree( return Err(From::from(format!("missing argument to {}", args[i]))); } i += 1; - Some(Printf::new(args[i])?.into_box()) + Some(Printf::new(args[i], None)?.into_box()) } "-fprint" => { if i >= args.len() - 1 { @@ -464,6 +464,19 @@ fn build_matcher_tree( let file = get_or_create_file(args[i])?; Some(Printer::new(PrintDelimiter::Newline, Some(file)).into_box()) } + "-fprintf" => { + if i >= args.len() - 2 { + return Err(From::from(format!("missing argument to {}", args[i]))); + } + + // Action: -fprintf file format + // Args + 1: output file path + // Args + 2: format string + i += 1; + let file = get_or_create_file(args[i])?; + i += 1; + Some(Printf::new(args[i], Some(file))?.into_box()) + } "-fprint0" => { if i >= args.len() - 1 { return Err(From::from(format!("missing argument to {}", args[i]))); diff --git a/src/find/matchers/printf.rs b/src/find/matchers/printf.rs index 23949d3..451a91d 100644 --- a/src/find/matchers/printf.rs +++ b/src/find/matchers/printf.rs @@ -4,11 +4,11 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use std::borrow::Cow; use std::error::Error; -use std::fs; +use std::fs::{self, File}; use std::path::Path; use std::time::SystemTime; +use std::{borrow::Cow, io::Write}; use chrono::{format::StrftimeItems, DateTime, Local}; @@ -571,20 +571,18 @@ fn format_directive<'entry>( /// find's printf syntax. pub struct Printf { format: FormatString, + output_file: Option, } impl Printf { - pub fn new(format: &str) -> Result> { + pub fn new(format: &str, output_file: Option) -> Result> { Ok(Self { format: FormatString::parse(format)?, + output_file, }) } -} - -impl Matcher for Printf { - fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool { - let mut out = matcher_io.deps.get_output().borrow_mut(); + fn print(&self, file_info: &WalkEntry, mut out: impl Write) { for component in &self.format.components { match component { FormatComponent::Literal(literal) => write!(out, "{literal}").unwrap(), @@ -619,6 +617,16 @@ impl Matcher for Printf { }, } } + } +} + +impl Matcher for Printf { + fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool { + if let Some(file) = &self.output_file { + self.print(file_info, file); + } else { + self.print(file_info, &mut *matcher_io.deps.get_output().borrow_mut()); + } true } @@ -804,7 +812,7 @@ mod tests { let file_info = get_dir_entry_for("test_data/simple", "abbbc"); let deps = FakeDependencies::new(); - let matcher = Printf::new("%f,%7f,%-7f").unwrap(); + let matcher = Printf::new("%f,%7f,%-7f", None).unwrap(); assert!(matcher.matches(&file_info, &mut deps.new_matcher_io())); assert_eq!("abbbc, abbbc,abbbc ", deps.get_output_as_string()); } @@ -814,7 +822,7 @@ mod tests { let file_info = get_dir_entry_for("test_data/simple", "abbbc"); let deps = FakeDependencies::new(); - let matcher = Printf::new("%h %H %p %P").unwrap(); + let matcher = Printf::new("%h %H %p %P", None).unwrap(); assert!(matcher.matches(&file_info, &mut deps.new_matcher_io())); assert_eq!( format!( @@ -833,7 +841,7 @@ mod tests { let file_info = get_dir_entry_for("test_data/simple", "subdir/ABBBC"); let deps = FakeDependencies::new(); - let matcher = Printf::new("%h %H %p %P").unwrap(); + let matcher = Printf::new("%h %H %p %P", None).unwrap(); assert!(matcher.matches(&file_info, &mut deps.new_matcher_io())); assert_eq!( format!( @@ -853,7 +861,7 @@ mod tests { let file_info_2 = get_dir_entry_for("test_data/depth/1", "2/f2"); let deps = FakeDependencies::new(); - let matcher = Printf::new("%d.").unwrap(); + let matcher = Printf::new("%d.", None).unwrap(); assert!(matcher.matches(&file_info_1, &mut deps.new_matcher_io())); assert!(matcher.matches(&file_info_2, &mut deps.new_matcher_io())); assert_eq!("1.2.", deps.get_output_as_string()); @@ -865,7 +873,7 @@ mod tests { let file_info_d = get_dir_entry_for("test_data/simple", "subdir"); let deps = FakeDependencies::new(); - let matcher = Printf::new("%y").unwrap(); + let matcher = Printf::new("%y", None).unwrap(); assert!(matcher.matches(&file_info_f, &mut deps.new_matcher_io())); assert!(matcher.matches(&file_info_d, &mut deps.new_matcher_io())); assert_eq!("fd", deps.get_output_as_string()); @@ -893,7 +901,7 @@ mod tests { let socket_info = get_dir_entry_for(&temp_dir_path, socket_name); let deps = FakeDependencies::new(); - let matcher = Printf::new("%y").unwrap(); + let matcher = Printf::new("%y", None).unwrap(); assert!(matcher.matches(&fifo_info, &mut deps.new_matcher_io())); assert!(matcher.matches(&socket_info, &mut deps.new_matcher_io())); assert_eq!("ps", deps.get_output_as_string()); @@ -904,7 +912,7 @@ mod tests { let file_info = get_dir_entry_for("test_data/size", "512bytes"); let deps = FakeDependencies::new(); - let matcher = Printf::new("%s").unwrap(); + let matcher = Printf::new("%s", None).unwrap(); assert!(matcher.matches(&file_info, &mut deps.new_matcher_io())); assert_eq!("512", deps.get_output_as_string()); } @@ -986,7 +994,7 @@ mod tests { let deps = FakeDependencies::new(); - let matcher = Printf::new("%y %Y %l\n").unwrap(); + let matcher = Printf::new("%y %Y %l\n", None).unwrap(); assert!(matcher.matches(®ular_file, &mut deps.new_matcher_io())); assert!(matcher.matches(&link_f, &mut deps.new_matcher_io())); assert!(matcher.matches(&link_d, &mut deps.new_matcher_io())); @@ -1033,7 +1041,7 @@ mod tests { let file_info = get_dir_entry_for(&temp_dir_path, new_file_name); let deps = FakeDependencies::new(); - let matcher = Printf::new("%t,%T@,%TF").unwrap(); + let matcher = Printf::new("%t,%T@,%TF", None).unwrap(); assert!(matcher.matches(&file_info, &mut deps.new_matcher_io())); assert_eq!( format!( @@ -1061,7 +1069,7 @@ mod tests { let file_info = get_dir_entry_for(&temp_dir_path, new_file_name); let deps = FakeDependencies::new(); - let matcher = Printf::new("%u %U %g %G").unwrap(); + let matcher = Printf::new("%u %U %g %G", None).unwrap(); assert!(matcher.matches(&file_info, &mut deps.new_matcher_io())); assert_eq!( format!("{user} {uid} {group} {gid}"), @@ -1086,7 +1094,7 @@ mod tests { let file_info = get_dir_entry_for(&temp_dir_path, new_file_name); let deps = FakeDependencies::new(); - let matcher = Printf::new("%m %M").unwrap(); + let matcher = Printf::new("%m %M", None).unwrap(); assert!(matcher.matches(&file_info, &mut deps.new_matcher_io())); assert_eq!("755 -rwxr-xr-x", deps.get_output_as_string()); } diff --git a/src/find/mod.rs b/src/find/mod.rs index bb9cb91..a94d508 100644 --- a/src/find/mod.rs +++ b/src/find/mod.rs @@ -1434,6 +1434,24 @@ mod tests { ); } + #[test] + fn find_fprintf() { + let deps = FakeDependencies::new(); + let rc = find_main( + &[ + "find", + "./test_data/simple", + "-fprintf", + "test_data/find_fprintf", + "%h %H %p %P", + ], + &deps, + ); + assert_eq!(rc, 0); + + let _ = fs::remove_file("test_data/find_fprintf"); + } + #[test] #[cfg(unix)] fn test_ls() { diff --git a/tests/find_cmd_tests.rs b/tests/find_cmd_tests.rs index 1510260..ad2a34f 100644 --- a/tests/find_cmd_tests.rs +++ b/tests/find_cmd_tests.rs @@ -992,6 +992,33 @@ fn find_follow() { .stderr(predicate::str::is_empty()); } +#[test] +#[serial(working_dir)] +fn find_fprintf() { + let _ = fs::remove_file("test_data/find_fprintf"); + + Command::cargo_bin("find") + .expect("found binary") + .args([ + "test_data/simple", + "-fprintf", + "test_data/find_fprintf", + "%h %H %p %P", + ]) + .assert() + .success() + .stdout(predicate::str::is_empty()) + .stderr(predicate::str::is_empty()); + + // read test_data/find_fprintf + let mut f = File::open("test_data/find_fprintf").unwrap(); + let mut contents = String::new(); + f.read_to_string(&mut contents).unwrap(); + assert!(contents.contains("test_data/simple")); + + let _ = fs::remove_file("test_data/find_fprintf"); +} + #[test] #[serial(working_dir)] fn find_ls() {