1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

du: implement files0-from (#5721)

* du: implement files0-from

Should make tests/du/files0-from-dir pass

* du: prepare tests/du/files0-from.pl

* fix the build on Windows

* add testfile to the ignore list

* remove useless comment

Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>

* mkdir is enough

Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>

* address review comments

---------

Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>
This commit is contained in:
Sylvestre Ledru 2023-12-26 14:40:31 +01:00 committed by GitHub
parent 4acc96fee3
commit 30eb77ac79
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 150 additions and 9 deletions

View file

@ -65,6 +65,7 @@ mod options {
pub const INODES: &str = "inodes"; pub const INODES: &str = "inodes";
pub const EXCLUDE: &str = "exclude"; pub const EXCLUDE: &str = "exclude";
pub const EXCLUDE_FROM: &str = "exclude-from"; pub const EXCLUDE_FROM: &str = "exclude-from";
pub const FILES0_FROM: &str = "files0-from";
pub const VERBOSE: &str = "verbose"; pub const VERBOSE: &str = "verbose";
pub const FILE: &str = "FILE"; pub const FILE: &str = "FILE";
} }
@ -587,6 +588,49 @@ pub fn div_ceil(a: u64, b: u64) -> u64 {
(a + b - 1) / b (a + b - 1) / b
} }
// Read file paths from the specified file, separated by null characters
fn read_files_from(file_name: &str) -> Result<Vec<PathBuf>, std::io::Error> {
let reader: Box<dyn BufRead> = if file_name == "-" {
// Read from standard input
Box::new(BufReader::new(std::io::stdin()))
} else {
// First, check if the file_name is a directory
let path = PathBuf::from(file_name);
if path.is_dir() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("{}: read error: Is a directory", file_name),
));
}
// Attempt to open the file and handle the error if it does not exist
match File::open(file_name) {
Ok(file) => Box::new(BufReader::new(file)),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"cannot open '{}' for reading: No such file or directory",
file_name
),
))
}
Err(e) => return Err(e),
}
};
let mut paths = Vec::new();
for line in reader.split(b'\0') {
let path = line?;
if !path.is_empty() {
paths.push(PathBuf::from(String::from_utf8_lossy(&path).to_string()));
}
}
Ok(paths)
}
#[uucore::main] #[uucore::main]
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
@ -601,13 +645,28 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
summarize, summarize,
)?; )?;
let files = match matches.get_one::<String>(options::FILE) { let files = if let Some(file_from) = matches.get_one::<String>(options::FILES0_FROM) {
Some(_) => matches if file_from == "-" && matches.get_one::<String>(options::FILE).is_some() {
.get_many::<String>(options::FILE) return Err(std::io::Error::new(
.unwrap() std::io::ErrorKind::Other,
.map(PathBuf::from) format!(
.collect(), "extra operand {}\nfile operands cannot be combined with --files0-from",
None => vec![PathBuf::from(".")], matches.get_one::<String>(options::FILE).unwrap().quote()
),
)
.into());
}
read_files_from(file_from)?
} else {
match matches.get_one::<String>(options::FILE) {
Some(_) => matches
.get_many::<String>(options::FILE)
.unwrap()
.map(PathBuf::from)
.collect(),
None => vec![PathBuf::from(".")],
}
}; };
let time = matches.contains_id(options::TIME).then(|| { let time = matches.contains_id(options::TIME).then(|| {
@ -954,6 +1013,14 @@ pub fn uu_app() -> Command {
.help("exclude files that match any pattern in FILE") .help("exclude files that match any pattern in FILE")
.action(ArgAction::Append) .action(ArgAction::Append)
) )
.arg(
Arg::new(options::FILES0_FROM)
.long("files0-from")
.value_name("FILE")
.value_hint(clap::ValueHint::FilePath)
.help("summarize device usage of the NUL-terminated file names specified in file F; if F is -, then read names from standard input")
.action(ArgAction::Append)
)
.arg( .arg(
Arg::new(options::TIME) Arg::new(options::TIME)
.long(options::TIME) .long(options::TIME)

View file

@ -3,10 +3,9 @@
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore (paths) sublink subwords azerty azeaze xcwww azeaz amaz azea qzerty tazerty tsublink // spell-checker:ignore (paths) sublink subwords azerty azeaze xcwww azeaz amaz azea qzerty tazerty tsublink testfile1 testfile2 filelist testdir testfile
#[cfg(not(windows))] #[cfg(not(windows))]
use regex::Regex; use regex::Regex;
#[cfg(not(windows))]
use std::io::Write; use std::io::Write;
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
@ -991,3 +990,74 @@ fn test_du_symlink_multiple_fail() {
assert_eq!(result.code(), 1); assert_eq!(result.code(), 1);
result.stdout_contains("4\tfile1\n"); result.stdout_contains("4\tfile1\n");
} }
#[test]
// Disable on Windows because of different path separators and handling of null characters
#[cfg(not(target_os = "windows"))]
fn test_du_files0_from() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
let mut file1 = at.make_file("testfile1");
file1.write_all(b"content1").unwrap();
let mut file2 = at.make_file("testfile2");
file2.write_all(b"content2").unwrap();
at.mkdir("testdir");
let mut file3 = at.make_file("testdir/testfile3");
file3.write_all(b"content3").unwrap();
let mut file_list = at.make_file("filelist");
write!(file_list, "testfile1\0testfile2\0testdir\0").unwrap();
ts.ucmd()
.arg("--files0-from=filelist")
.succeeds()
.stdout_contains("testfile1")
.stdout_contains("testfile2")
.stdout_contains("testdir");
}
#[test]
fn test_du_files0_from_stdin() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
let mut file1 = at.make_file("testfile1");
file1.write_all(b"content1").unwrap();
let mut file2 = at.make_file("testfile2");
file2.write_all(b"content2").unwrap();
let input = "testfile1\0testfile2\0";
ts.ucmd()
.arg("--files0-from=-")
.pipe_in(input)
.succeeds()
.stdout_contains("testfile1")
.stdout_contains("testfile2");
}
#[test]
fn test_du_files0_from_dir() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.mkdir("dir");
let result = ts.ucmd().arg("--files0-from=dir").fails();
assert_eq!(result.stderr_str(), "du: dir: read error: Is a directory\n");
}
#[test]
fn test_du_files0_from_combined() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.mkdir("dir");
let result = ts.ucmd().arg("--files0-from=-").arg("foo").fails();
let stderr = result.stderr_str();
assert!(stderr.contains("file operands cannot be combined with --files0-from"));
}

View file

@ -283,6 +283,10 @@ sed -i -E "s|^([^#]*2_31.*)$|#\1|g" tests/printf/printf-cov.pl
sed -i -e "s/du: invalid -t argument/du: invalid --threshold argument/" -e "s/du: option requires an argument/error: a value is required for '--threshold <SIZE>' but none was supplied/" -e "/Try 'du --help' for more information./d" tests/du/threshold.sh sed -i -e "s/du: invalid -t argument/du: invalid --threshold argument/" -e "s/du: option requires an argument/error: a value is required for '--threshold <SIZE>' but none was supplied/" -e "/Try 'du --help' for more information./d" tests/du/threshold.sh
# Remove the extra output check
sed -i -e "s|Try '\$prog --help' for more information.\\\n||" tests/du/files0-from.pl
sed -i -e "s|when reading file names from stdin, no file name of\"|-: No such file or directory\n\"|" -e "s| '-' allowed\\\n||" tests/du/files0-from.pl
awk 'BEGIN {count=0} /compare exp out2/ && count < 6 {sub(/compare exp out2/, "grep -q \"cannot be used with\" out2"); count++} 1' tests/df/df-output.sh > tests/df/df-output.sh.tmp && mv tests/df/df-output.sh.tmp tests/df/df-output.sh awk 'BEGIN {count=0} /compare exp out2/ && count < 6 {sub(/compare exp out2/, "grep -q \"cannot be used with\" out2"); count++} 1' tests/df/df-output.sh > tests/df/df-output.sh.tmp && mv tests/df/df-output.sh.tmp tests/df/df-output.sh
# with ls --dired, in case of error, we have a slightly different error position # with ls --dired, in case of error, we have a slightly different error position