Skip to content

Commit

Permalink
Implement -fprint (#421)
Browse files Browse the repository at this point in the history
  • Loading branch information
hanbings authored Aug 11, 2024
1 parent 79986a2 commit 423453e
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 16 deletions.
49 changes: 46 additions & 3 deletions src/find/matchers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ mod user;
use ::regex::Regex;
use chrono::{DateTime, Datelike, NaiveDateTime, Utc};
use fs::FileSystemMatcher;
use std::fs::File;
use std::path::Path;
use std::time::SystemTime;
use std::{error::Error, str::FromStr};
Expand Down Expand Up @@ -202,7 +203,7 @@ pub fn build_top_level_matcher(
if !top_level_matcher.has_side_effects() {
let mut new_and_matcher = AndMatcherBuilder::new();
new_and_matcher.new_and_condition(top_level_matcher);
new_and_matcher.new_and_condition(Printer::new(PrintDelimiter::Newline));
new_and_matcher.new_and_condition(Printer::new(PrintDelimiter::Newline, None));
return Ok(new_and_matcher.build());
}
Ok(top_level_matcher)
Expand Down Expand Up @@ -339,6 +340,13 @@ fn parse_str_to_newer_args(input: &str) -> Option<(String, String)> {
}
}

/// Creates a file if it doesn't exist.
/// If it does exist, it will be overwritten.
fn get_or_create_file(path: &str) -> Result<File, Box<dyn Error>> {
let file = File::create(path)?;
Ok(file)
}

/// The main "translate command-line args into a matcher" function. Will call
/// itself recursively if it encounters an opening bracket. A successful return
/// consists of a tuple containing the new index into the args array to use (if
Expand All @@ -361,15 +369,24 @@ fn build_matcher_tree(
let mut invert_next_matcher = false;
while i < args.len() {
let possible_submatcher = match args[i] {
"-print" => Some(Printer::new(PrintDelimiter::Newline).into_box()),
"-print0" => Some(Printer::new(PrintDelimiter::Null).into_box()),
"-print" => Some(Printer::new(PrintDelimiter::Newline, None).into_box()),
"-print0" => Some(Printer::new(PrintDelimiter::Null, None).into_box()),
"-printf" => {
if i >= args.len() - 1 {
return Err(From::from(format!("missing argument to {}", args[i])));
}
i += 1;
Some(Printf::new(args[i])?.into_box())
}
"-fprint" => {
if i >= args.len() - 1 {
return Err(From::from(format!("missing argument to {}", args[i])));
}
i += 1;

let file = get_or_create_file(args[i])?;
Some(Printer::new(PrintDelimiter::Newline, Some(file)).into_box())
}
"-true" => Some(TrueMatcher.into_box()),
"-false" => Some(FalseMatcher.into_box()),
"-lname" | "-ilname" => {
Expand Down Expand Up @@ -1518,4 +1535,30 @@ mod tests {
.expect("-version should stop parsing");
assert!(config.version_requested);
}

#[test]
fn get_or_create_file_test() {
use std::fs;

// remove file if hard link file exist.
// But you can't delete a file that doesn't exist,
// so ignore the error returned here.
let _ = fs::remove_file("test_data/get_or_create_file_test");

// test create file
let file = get_or_create_file("test_data/get_or_create_file_test");
assert!(file.is_ok());

let file = get_or_create_file("test_data/get_or_create_file_test");
assert!(file.is_ok());

// test error when file no permission
#[cfg(unix)]
{
let result = get_or_create_file("/etc/shadow");
assert!(result.is_err());
}

let _ = fs::remove_file("test_data/get_or_create_file_test");
}
}
73 changes: 62 additions & 11 deletions src/find/matchers/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

use std::{
fs::File,
io::{stderr, Write},
};

use walkdir::DirEntry;

use super::{Matcher, MatcherIO};
Expand All @@ -25,25 +30,53 @@ impl std::fmt::Display for PrintDelimiter {
/// This matcher just prints the name of the file to stdout.
pub struct Printer {
delimiter: PrintDelimiter,
output_file: Option<File>,
}

impl Printer {
pub fn new(delimiter: PrintDelimiter) -> Self {
Self { delimiter }
pub fn new(delimiter: PrintDelimiter, output_file: Option<File>) -> Self {
Self {
delimiter,
output_file,
}
}
}

impl Matcher for Printer {
fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
let mut out = matcher_io.deps.get_output().borrow_mut();
write!(
fn print(&self, file_info: &DirEntry, mut out: impl Write, print_error_message: bool) {
match write!(
out,
"{}{}",
file_info.path().to_string_lossy(),
self.delimiter
)
.unwrap();
) {
Ok(_) => {}
Err(e) => {
if print_error_message {
writeln!(
&mut stderr(),
"Error writing {:?} for {}",
file_info.path().to_string_lossy(),
e
)
.unwrap();
uucore::error::set_exit_code(1);
}
}
}
out.flush().unwrap();
}
}

impl Matcher for Printer {
fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
if let Some(file) = &self.output_file {
self.print(file_info, file, true);
} else {
self.print(
file_info,
&mut *matcher_io.deps.get_output().borrow_mut(),
false,
);
}
true
}

Expand All @@ -64,7 +97,7 @@ mod tests {
fn prints_newline() {
let abbbc = get_dir_entry_for("./test_data/simple", "abbbc");

let matcher = Printer::new(PrintDelimiter::Newline);
let matcher = Printer::new(PrintDelimiter::Newline, None);
let deps = FakeDependencies::new();
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
assert_eq!(
Expand All @@ -77,12 +110,30 @@ mod tests {
fn prints_null() {
let abbbc = get_dir_entry_for("./test_data/simple", "abbbc");

let matcher = Printer::new(PrintDelimiter::Null);
let matcher = Printer::new(PrintDelimiter::Null, None);
let deps = FakeDependencies::new();
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
assert_eq!(
fix_up_slashes("./test_data/simple/abbbc\0"),
deps.get_output_as_string()
);
}

#[test]
#[cfg(target_os = "linux")]
fn prints_error_message() {
let dev_full = File::open("/dev/full").unwrap();
let abbbc = get_dir_entry_for("./test_data/simple", "abbbc");

let matcher = Printer::new(PrintDelimiter::Newline, Some(dev_full));
let deps = FakeDependencies::new();

assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));

// Reset the exit code global variable in case we run another test after this one
// See https://github.com/uutils/coreutils/issues/5777
uucore::error::set_exit_code(0);

assert!(deps.get_output_as_string().is_empty());
}
}
17 changes: 17 additions & 0 deletions src/find/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1300,4 +1300,21 @@ mod tests {

assert_eq!(rc, 0);
}

#[test]
fn find_fprint() {
let deps = FakeDependencies::new();
let rc = find_main(
&[
"find",
"./test_data/simple",
"-fprint",
"test_data/find_fprint",
],
&deps,
);
assert_eq!(rc, 0);

let _ = fs::remove_file("test_data/find_fprint");
}
}
26 changes: 24 additions & 2 deletions tests/find_cmd_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
use assert_cmd::Command;
use predicates::prelude::*;
use serial_test::serial;
use std::fs::File;
use std::io::Write;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::{env, io::ErrorKind};
use tempfile::Builder;

Expand Down Expand Up @@ -946,3 +946,25 @@ fn find_daystart() {
.success()
.stderr(predicate::str::is_empty());
}

#[test]
#[serial(working_dir)]
fn find_fprint() {
let _ = fs::remove_file("test_data/find_fprint");

Command::cargo_bin("find")
.expect("found binary")
.args(["test_data/simple", "-fprint", "test_data/find_fprint"])
.assert()
.success()
.stdout(predicate::str::is_empty())
.stderr(predicate::str::is_empty());

// read test_data/find_fprint
let mut f = File::open("test_data/find_fprint").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_fprint");
}

0 comments on commit 423453e

Please sign in to comment.