mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +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:
parent
4acc96fee3
commit
30eb77ac79
3 changed files with 150 additions and 9 deletions
|
@ -65,6 +65,7 @@ mod options {
|
|||
pub const INODES: &str = "inodes";
|
||||
pub const EXCLUDE: &str = "exclude";
|
||||
pub const EXCLUDE_FROM: &str = "exclude-from";
|
||||
pub const FILES0_FROM: &str = "files0-from";
|
||||
pub const VERBOSE: &str = "verbose";
|
||||
pub const FILE: &str = "FILE";
|
||||
}
|
||||
|
@ -587,6 +588,49 @@ pub fn div_ceil(a: u64, b: u64) -> u64 {
|
|||
(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]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
|
@ -601,13 +645,28 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
summarize,
|
||||
)?;
|
||||
|
||||
let files = match matches.get_one::<String>(options::FILE) {
|
||||
Some(_) => matches
|
||||
.get_many::<String>(options::FILE)
|
||||
.unwrap()
|
||||
.map(PathBuf::from)
|
||||
.collect(),
|
||||
None => vec![PathBuf::from(".")],
|
||||
let files = if let Some(file_from) = matches.get_one::<String>(options::FILES0_FROM) {
|
||||
if file_from == "-" && matches.get_one::<String>(options::FILE).is_some() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!(
|
||||
"extra operand {}\nfile operands cannot be combined with --files0-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(|| {
|
||||
|
@ -954,6 +1013,14 @@ pub fn uu_app() -> Command {
|
|||
.help("exclude files that match any pattern in FILE")
|
||||
.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::new(options::TIME)
|
||||
.long(options::TIME)
|
||||
|
|
|
@ -3,10 +3,9 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// 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))]
|
||||
use regex::Regex;
|
||||
#[cfg(not(windows))]
|
||||
use std::io::Write;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
|
@ -991,3 +990,74 @@ fn test_du_symlink_multiple_fail() {
|
|||
assert_eq!(result.code(), 1);
|
||||
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"));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
# 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
|
||||
|
||||
# with ls --dired, in case of error, we have a slightly different error position
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue