Implement -fprint (#421)

This commit is contained in:
hanbings
2024-08-11 15:38:05 +08:00
committed by GitHub
parent 79986a26f2
commit 423453eaf8
4 changed files with 151 additions and 18 deletions
+46 -3
View File
@@ -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};
@@ -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)
@@ -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
@@ -361,8 +369,8 @@ 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])));
@@ -370,6 +378,15 @@ fn build_matcher_tree(
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" => {
@@ -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");
}
}
+64 -13
View File
@@ -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};
@@ -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,
}
}
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
) {
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 {
let mut out = matcher_io.deps.get_output().borrow_mut();
write!(
out,
"{}{}",
file_info.path().to_string_lossy(),
self.delimiter
)
.unwrap();
out.flush().unwrap();
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
}
@@ -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!(
@@ -77,7 +110,7 @@ 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!(
@@ -85,4 +118,22 @@ mod tests {
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
View File
@@ -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");
}
}
+24 -2
View File
@@ -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;
@@ -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");
}