Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 108 additions & 14 deletions src/uu/ls/src/ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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'
},
})
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(())
}

Expand Down Expand Up @@ -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)?;
}
}
};
Expand Down Expand Up @@ -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 = {
Expand Down
120 changes: 120 additions & 0 deletions tests/by-util/test_ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!());
Expand Down