mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
* 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:
parent
38679f1c1b
commit
4ad0d5c5a3
2 changed files with 228 additions and 14 deletions
|
@ -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 = {
|
||||
|
|
|
@ -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!());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue