1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-27 19:17:43 +00:00

ls: Implement --zero flag. (#2929) (#3746)

* ls: Implement --zero flag. (#2929)

This flag can be used to provide a easy machine parseable output from
ls, as discussed in the GNU bug report
https://debbugs.gnu.org/cgi/bugreport.cgi?bug=49716.

There are some peculiarities with this flag:

 - Current implementation of GNU ls of the `--zero` flag implies some
   other flags. Those can be overridden by setting those flags after
   `--zero` in the command line.
 - This flag is not compatible with `--dired`. This patch is not 100%
   compliant with GNU ls: GNU ls `--zero` will fail if `--dired` and
   `-l` are set, while with this patch only `--dired` is needed for the
   command to fail.

We also add `--dired` flag to the parser, with no additional behaviour
change.

Testing done:
```
$ bash util/build-gnu.sh
 [...]
$ bash util/run-gnu-test.sh tests/ls/zero-option.sh
 [...]
 PASS: tests/ls/zero-option.sh
 ============================================================================
 Testsuite summary for GNU coreutils 9.1.36-8ec11
 ============================================================================
 # TOTAL: 1
 # PASS:  1
 # SKIP:  0
 # XFAIL: 0
 # FAIL:  0
 # XPASS: 0
 # ERROR: 0
 ============================================================================
```

* Use the US way to spell Behavior

* Fix formatting with cargo fmt -- tests/by-util/test_ls.rs

* Simplify --zero flag overriding logic by using index_of

Also, allow multiple --zero flags, as this is possible with GNU ls
command. Only the last one is taken into account.

Co-authored-by: Sylvestre Ledru <sledru@mozilla.com>
This commit is contained in:
Pierre Marsais 2022-07-31 18:02:50 +01:00 committed by GitHub
parent 38679f1c1b
commit 4ad0d5c5a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 228 additions and 14 deletions

View file

@ -5,7 +5,7 @@
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
// spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf tabsize
// spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf tabsize dired
#[macro_use]
extern crate uucore;
@ -137,6 +137,8 @@ pub mod options {
pub static IGNORE: &str = "ignore";
pub static CONTEXT: &str = "context";
pub static GROUP_DIRECTORIES_FIRST: &str = "group-directories-first";
pub static ZERO: &str = "zero";
pub static DIRED: &str = "dired";
}
const DEFAULT_TERM_WIDTH: u16 = 80;
@ -337,6 +339,7 @@ pub struct Config {
context: bool,
selinux_supported: bool,
group_directories_first: bool,
eol: char,
}
// Fields that can be removed or added to the long format
@ -481,7 +484,7 @@ impl Config {
Time::Modification
};
let needs_color = match options.value_of(options::COLOR) {
let mut needs_color = match options.value_of(options::COLOR) {
None => options.is_present(options::COLOR),
Some(val) => match val {
"" | "always" | "yes" | "force" => true,
@ -490,12 +493,6 @@ impl Config {
},
};
let color = if needs_color {
Some(LsColors::from_env().unwrap_or_default())
} else {
None
};
let cmd_line_bs = options.value_of(options::size::BLOCK_SIZE);
let opt_si = cmd_line_bs.is_some()
&& options
@ -607,7 +604,7 @@ impl Config {
};
#[allow(clippy::needless_bool)]
let show_control = if options.is_present(options::HIDE_CONTROL_CHARS) {
let mut show_control = if options.is_present(options::HIDE_CONTROL_CHARS) {
false
} else if options.is_present(options::SHOW_CONTROL_CHARS) {
true
@ -619,7 +616,7 @@ impl Config {
.value_of(options::QUOTING_STYLE)
.map(|cmd_line_qs| cmd_line_qs.to_owned());
let quoting_style = if let Some(style) = opt_quoting_style {
let mut quoting_style = if let Some(style) = opt_quoting_style {
match style.as_str() {
"literal" => QuotingStyle::Literal { show_control },
"shell" => QuotingStyle::Shell {
@ -750,6 +747,80 @@ impl Config {
}
}
// According to ls info page, `--zero` implies the following flags:
// - `--show-control-chars`
// - `--format=single-column`
// - `--color=none`
// - `--quoting-style=literal`
// Current GNU ls implementation allows `--zero` Behavior to be
// overridden by later flags.
let zero_formats_opts = [
options::format::ACROSS,
options::format::COLUMNS,
options::format::COMMAS,
options::format::LONG,
options::format::LONG_NO_GROUP,
options::format::LONG_NO_OWNER,
options::format::LONG_NUMERIC_UID_GID,
options::format::ONE_LINE,
options::FORMAT,
];
let zero_colors_opts = [options::COLOR];
let zero_show_control_opts = [options::HIDE_CONTROL_CHARS, options::SHOW_CONTROL_CHARS];
let zero_quoting_style_opts = [
options::QUOTING_STYLE,
options::quoting::C,
options::quoting::ESCAPE,
options::quoting::LITERAL,
];
let get_last = |flag: &str| -> usize { options.index_of(flag).unwrap_or(0) };
if get_last(options::ZERO)
> zero_formats_opts
.into_iter()
.map(get_last)
.max()
.unwrap_or(0)
{
format = if format == Format::Long {
format
} else {
Format::OneLine
};
}
if get_last(options::ZERO)
> zero_colors_opts
.into_iter()
.map(get_last)
.max()
.unwrap_or(0)
{
needs_color = false;
}
if get_last(options::ZERO)
> zero_show_control_opts
.into_iter()
.map(get_last)
.max()
.unwrap_or(0)
{
show_control = true;
}
if get_last(options::ZERO)
> zero_quoting_style_opts
.into_iter()
.map(get_last)
.max()
.unwrap_or(0)
{
quoting_style = QuotingStyle::Literal { show_control };
}
let color = if needs_color {
Some(LsColors::from_env().unwrap_or_default())
} else {
None
};
let dereference = if options.is_present(options::dereference::ALL) {
Dereference::All
} else if options.is_present(options::dereference::ARGS) {
@ -798,6 +869,11 @@ impl Config {
}
},
group_directories_first: options.is_present(options::GROUP_DIRECTORIES_FIRST),
eol: if options.is_present(options::ZERO) {
'\0'
} else {
'\n'
},
})
}
}
@ -913,6 +989,19 @@ pub fn uu_app<'a>() -> Command<'a> {
options::format::COLUMNS,
]),
)
.arg(
Arg::new(options::ZERO)
.long(options::ZERO)
.conflicts_with(options::DIRED)
.overrides_with(options::ZERO)
.help("List entries separated by ASCII NUL characters."),
)
.arg(
Arg::new(options::DIRED)
.long(options::DIRED)
.short('D')
.hide(true),
)
// The next four arguments do not override with the other format
// options, see the comment in Config::from for the reason.
// Ideally, they would use Arg::override_with, with their own name
@ -1884,7 +1973,12 @@ fn display_total(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
.as_ref()
.map_or(0, |md| get_block_size(md, config));
}
writeln!(out, "total {}", display_size(total_size, config))?;
write!(
out,
"total {}{}",
display_size(total_size, config),
config.eol
)?;
Ok(())
}
@ -1994,12 +2088,12 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
// Current col is never zero again if names have been printed.
// So we print a newline.
if current_col > 0 {
writeln!(out,)?;
write!(out, "{}", config.eol)?;
}
}
_ => {
for name in names {
writeln!(out, "{}", name.contents)?;
write!(out, "{}{}", name.contents, config.eol)?;
}
}
};
@ -2199,7 +2293,7 @@ fn display_item_long(
let dfn = display_file_name(item, config, None, "".to_owned(), out).contents;
writeln!(out, " {} {}", display_date(md, config), dfn)?;
write!(out, " {} {}{}", display_date(md, config), dfn, config.eol)?;
} else {
#[cfg(unix)]
let leading_char = {

View file

@ -796,6 +796,126 @@ fn test_ls_commas() {
}
}
#[test]
fn test_ls_zero() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir("0-test-zero");
at.touch(&at.plus_as_string("2-test-zero"));
at.touch(&at.plus_as_string("3-test-zero"));
let ignored_opts = [
"--quoting-style=c",
"--color=always",
"-m",
"--hide-control-chars",
];
scene
.ucmd()
.arg("--zero")
.succeeds()
.stdout_only("0-test-zero\x002-test-zero\x003-test-zero\x00");
for opt in ignored_opts {
scene
.ucmd()
.args(&[opt, "--zero"])
.succeeds()
.stdout_only("0-test-zero\x002-test-zero\x003-test-zero\x00");
}
scene
.ucmd()
.args(&["--zero", "--quoting-style=c"])
.succeeds()
.stdout_only("\"0-test-zero\"\x00\"2-test-zero\"\x00\"3-test-zero\"\x00");
scene
.ucmd()
.args(&["--zero", "--color=always"])
.succeeds()
.stdout_only("\x1b[1;34m0-test-zero\x1b[0m\x002-test-zero\x003-test-zero\x00");
scene
.ucmd()
.args(&["--zero", "-m"])
.succeeds()
.stdout_only("0-test-zero, 2-test-zero, 3-test-zero\x00");
scene
.ucmd()
.args(&["--zero", "--hide-control-chars"])
.succeeds()
.stdout_only("0-test-zero\x002-test-zero\x003-test-zero\x00");
scene
.ucmd()
.args(&["--zero", "--quoting-style=c", "--zero"])
.succeeds()
.stdout_only("0-test-zero\x002-test-zero\x003-test-zero\x00");
#[cfg(unix)]
{
at.touch(&at.plus_as_string("1\ntest-zero"));
let ignored_opts = [
"--quoting-style=c",
"--color=always",
"-m",
"--hide-control-chars",
];
scene
.ucmd()
.arg("--zero")
.succeeds()
.stdout_only("0-test-zero\x001\ntest-zero\x002-test-zero\x003-test-zero\x00");
for opt in ignored_opts {
scene
.ucmd()
.args(&[opt, "--zero"])
.succeeds()
.stdout_only("0-test-zero\x001\ntest-zero\x002-test-zero\x003-test-zero\x00");
}
scene
.ucmd()
.args(&["--zero", "--quoting-style=c"])
.succeeds()
.stdout_only(
"\"0-test-zero\"\x00\"1\\ntest-zero\"\x00\"2-test-zero\"\x00\"3-test-zero\"\x00",
);
scene
.ucmd()
.args(&["--zero", "--color=always"])
.succeeds()
.stdout_only(
"\x1b[1;34m0-test-zero\x1b[0m\x001\ntest-zero\x002-test-zero\x003-test-zero\x00",
);
scene
.ucmd()
.args(&["--zero", "-m"])
.succeeds()
.stdout_only("0-test-zero, 1\ntest-zero, 2-test-zero, 3-test-zero\x00");
scene
.ucmd()
.args(&["--zero", "--hide-control-chars"])
.succeeds()
.stdout_only("0-test-zero\x001?test-zero\x002-test-zero\x003-test-zero\x00");
}
scene
.ucmd()
.args(&["-l", "--zero"])
.succeeds()
.stdout_contains("total ");
}
#[test]
fn test_ls_commas_trailing() {
let scene = TestScenario::new(util_name!());