mirror of
https://github.com/uutils/coreutils
synced 2024-10-06 16:09:08 +00:00
ls: across & commas formats and width parameter (#1869)
This commit is contained in:
parent
d86ee34bc6
commit
de3f9b8186
|
@ -15,7 +15,6 @@ extern crate uucore;
|
|||
|
||||
use clap::{App, Arg};
|
||||
use number_prefix::NumberPrefix;
|
||||
use std::cmp::Reverse;
|
||||
#[cfg(unix)]
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
|
@ -30,6 +29,7 @@ use std::path::{Path, PathBuf};
|
|||
#[cfg(unix)]
|
||||
use std::time::Duration;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::{cmp::Reverse, process::exit};
|
||||
|
||||
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
|
||||
use time::{strftime, Timespec};
|
||||
|
@ -78,6 +78,8 @@ pub mod options {
|
|||
pub static ONELINE: &str = "1";
|
||||
pub static LONG: &str = "long";
|
||||
pub static COLUMNS: &str = "C";
|
||||
pub static ACROSS: &str = "x";
|
||||
pub static COMMAS: &str = "m";
|
||||
pub static LONG_NO_OWNER: &str = "g";
|
||||
pub static LONG_NO_GROUP: &str = "o";
|
||||
}
|
||||
|
@ -98,6 +100,7 @@ pub mod options {
|
|||
pub static HUMAN_READABLE: &str = "human-readable";
|
||||
pub static SI: &str = "si";
|
||||
}
|
||||
pub static WIDTH: &str = "width";
|
||||
pub static AUTHOR: &str = "author";
|
||||
pub static NO_GROUP: &str = "no-group";
|
||||
pub static FORMAT: &str = "format";
|
||||
|
@ -120,6 +123,8 @@ enum Format {
|
|||
Columns,
|
||||
Long,
|
||||
OneLine,
|
||||
Across,
|
||||
Commas,
|
||||
}
|
||||
|
||||
enum Sort {
|
||||
|
@ -166,6 +171,7 @@ struct Config {
|
|||
#[cfg(unix)]
|
||||
color: bool,
|
||||
long: LongFormat,
|
||||
width: Option<u16>,
|
||||
}
|
||||
|
||||
// Fields that can be removed or added to the long format
|
||||
|
@ -183,6 +189,8 @@ impl Config {
|
|||
"long" | "verbose" => Format::Long,
|
||||
"single-column" => Format::OneLine,
|
||||
"columns" | "vertical" => Format::Columns,
|
||||
"across" | "horizontal" => Format::Across,
|
||||
"commas" => Format::Commas,
|
||||
// below should never happen as clap already restricts the values.
|
||||
_ => unreachable!("Invalid field for --format"),
|
||||
},
|
||||
|
@ -190,6 +198,10 @@ impl Config {
|
|||
)
|
||||
} else if options.is_present(options::format::LONG) {
|
||||
(Format::Long, options::format::LONG)
|
||||
} else if options.is_present(options::format::ACROSS) {
|
||||
(Format::Across, options::format::ACROSS)
|
||||
} else if options.is_present(options::format::COMMAS) {
|
||||
(Format::Commas, options::format::COMMAS)
|
||||
} else {
|
||||
(Format::Columns, options::format::COLUMNS)
|
||||
};
|
||||
|
@ -316,6 +328,16 @@ impl Config {
|
|||
}
|
||||
};
|
||||
|
||||
let width = options
|
||||
.value_of(options::WIDTH)
|
||||
.map(|x| {
|
||||
x.parse::<u16>().unwrap_or_else(|_e| {
|
||||
show_error!("invalid line width: ‘{}’", x);
|
||||
exit(2);
|
||||
})
|
||||
})
|
||||
.or_else(|| termsize::get().map(|s| s.cols));
|
||||
|
||||
Config {
|
||||
format,
|
||||
files,
|
||||
|
@ -334,6 +356,7 @@ impl Config {
|
|||
#[cfg(unix)]
|
||||
inode: options.is_present(options::INODE),
|
||||
long,
|
||||
width,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -354,13 +377,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.long(options::FORMAT)
|
||||
.help("Set the display format.")
|
||||
.takes_value(true)
|
||||
.possible_values(&["long", "verbose", "single-column", "columns"])
|
||||
.possible_values(&["long", "verbose", "single-column", "columns", "vertical", "across", "horizontal", "commas"])
|
||||
.hide_possible_values(true)
|
||||
.require_equals(true)
|
||||
.overrides_with_all(&[
|
||||
options::FORMAT,
|
||||
options::format::COLUMNS,
|
||||
options::format::LONG,
|
||||
options::format::ACROSS,
|
||||
options::format::COLUMNS,
|
||||
]),
|
||||
)
|
||||
.arg(
|
||||
|
@ -371,6 +396,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
options::FORMAT,
|
||||
options::format::COLUMNS,
|
||||
options::format::LONG,
|
||||
options::format::ACROSS,
|
||||
options::format::COLUMNS,
|
||||
]),
|
||||
)
|
||||
.arg(
|
||||
|
@ -381,8 +408,33 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.overrides_with_all(&[
|
||||
options::FORMAT,
|
||||
options::format::COLUMNS,
|
||||
options::format::ONELINE,
|
||||
options::format::LONG,
|
||||
options::format::ACROSS,
|
||||
options::format::COLUMNS,
|
||||
]),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::format::ACROSS)
|
||||
.short(options::format::ACROSS)
|
||||
.help("List entries in rows instead of in columns.")
|
||||
.overrides_with_all(&[
|
||||
options::FORMAT,
|
||||
options::format::COLUMNS,
|
||||
options::format::LONG,
|
||||
options::format::ACROSS,
|
||||
options::format::COLUMNS,
|
||||
]),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::format::COMMAS)
|
||||
.short(options::format::COMMAS)
|
||||
.help("List entries separated by commas.")
|
||||
.overrides_with_all(&[
|
||||
options::FORMAT,
|
||||
options::format::COLUMNS,
|
||||
options::format::LONG,
|
||||
options::format::ACROSS,
|
||||
options::format::COLUMNS,
|
||||
]),
|
||||
)
|
||||
// The next three arguments do not override with the other format
|
||||
|
@ -601,6 +653,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.long(options::RECURSIVE)
|
||||
.help("List the contents of all directories recursively."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::WIDTH)
|
||||
.long(options::WIDTH)
|
||||
.short("w")
|
||||
.help("Assume that the terminal is COLS columns wide.")
|
||||
.value_name("COLS")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::COLOR)
|
||||
.long(options::COLOR)
|
||||
|
@ -779,47 +839,71 @@ fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config) {
|
|||
display_item_long(item, strip, max_links, max_size, config);
|
||||
}
|
||||
} else {
|
||||
if config.format != Format::OneLine {
|
||||
let names = items.iter().filter_map(|i| {
|
||||
let md = get_metadata(i, config);
|
||||
match md {
|
||||
Err(e) => {
|
||||
let filename = get_file_name(i, strip);
|
||||
show_error!("'{}': {}", filename, e);
|
||||
None
|
||||
}
|
||||
Ok(md) => Some(display_file_name(&i, strip, &md, config)),
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(size) = termsize::get() {
|
||||
let mut grid = Grid::new(GridOptions {
|
||||
filling: Filling::Spaces(2),
|
||||
direction: Direction::TopToBottom,
|
||||
});
|
||||
|
||||
for name in names {
|
||||
grid.add(name);
|
||||
}
|
||||
|
||||
if let Some(output) = grid.fit_into_width(size.cols as usize) {
|
||||
print!("{}", output);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't display a grid, either because we don't know
|
||||
// the terminal width or because fit_into_width failed
|
||||
for i in items {
|
||||
let names = items.iter().filter_map(|i| {
|
||||
let md = get_metadata(i, config);
|
||||
if let Ok(md) = md {
|
||||
println!("{}", display_file_name(&i, strip, &md, config).contents);
|
||||
match md {
|
||||
Err(e) => {
|
||||
let filename = get_file_name(i, strip);
|
||||
show_error!("'{}': {}", filename, e);
|
||||
None
|
||||
}
|
||||
Ok(md) => Some(display_file_name(&i, strip, &md, config)),
|
||||
}
|
||||
});
|
||||
|
||||
match (&config.format, config.width) {
|
||||
(Format::Columns, Some(width)) => display_grid(names, width, Direction::TopToBottom),
|
||||
(Format::Across, Some(width)) => display_grid(names, width, Direction::LeftToRight),
|
||||
(Format::Commas, width_opt) => {
|
||||
let term_width = width_opt.unwrap_or(1);
|
||||
let mut current_col = 0;
|
||||
let mut names = names;
|
||||
if let Some(name) = names.next() {
|
||||
print!("{}", name.contents);
|
||||
current_col = name.width as u16 + 2;
|
||||
}
|
||||
for name in names {
|
||||
let name_width = name.width as u16;
|
||||
if current_col + name_width + 1 > term_width {
|
||||
current_col = name_width + 2;
|
||||
print!(",\n{}", name.contents);
|
||||
} else {
|
||||
current_col += name_width + 2;
|
||||
print!(", {}", name.contents);
|
||||
}
|
||||
}
|
||||
// Current col is never zero again if names have been printed.
|
||||
// So we print a newline.
|
||||
if current_col > 0 {
|
||||
println!();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
for name in names {
|
||||
println!("{}", name.contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn display_grid(names: impl Iterator<Item = Cell>, width: u16, direction: Direction) {
|
||||
let mut grid = Grid::new(GridOptions {
|
||||
filling: Filling::Spaces(2),
|
||||
direction,
|
||||
});
|
||||
|
||||
for name in names {
|
||||
grid.add(name);
|
||||
}
|
||||
|
||||
match grid.fit_into_width(width as usize) {
|
||||
Some(output) => print!("{}", output),
|
||||
// Width is too small for the grid, so we fit it in one column
|
||||
None => print!("{}", grid.fit_into_columns(1)),
|
||||
}
|
||||
}
|
||||
|
||||
use uucore::fs::display_permissions;
|
||||
|
||||
fn display_item_long(
|
||||
|
|
|
@ -57,12 +57,72 @@ fn test_ls_a() {
|
|||
assert!(!result.stdout.contains(".."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ls_width() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
at.touch(&at.plus_as_string("test-width-1"));
|
||||
at.touch(&at.plus_as_string("test-width-2"));
|
||||
at.touch(&at.plus_as_string("test-width-3"));
|
||||
at.touch(&at.plus_as_string("test-width-4"));
|
||||
|
||||
for option in &["-w 100", "-w=100", "--width=100", "--width 100"] {
|
||||
let result = scene
|
||||
.ucmd()
|
||||
.args(&option.split(" ").collect::<Vec<_>>())
|
||||
.run();
|
||||
println!("stderr = {:?}", result.stderr);
|
||||
println!("stdout = {:?}", result.stdout);
|
||||
assert_eq!(
|
||||
result.stdout,
|
||||
"test-width-1 test-width-2 test-width-3 test-width-4\n",
|
||||
)
|
||||
}
|
||||
|
||||
for option in &["-w 50", "-w=50", "--width=50", "--width 50"] {
|
||||
let result = scene
|
||||
.ucmd()
|
||||
.args(&option.split(" ").collect::<Vec<_>>())
|
||||
.run();
|
||||
println!("stderr = {:?}", result.stderr);
|
||||
println!("stdout = {:?}", result.stdout);
|
||||
assert_eq!(
|
||||
result.stdout,
|
||||
"test-width-1 test-width-3\ntest-width-2 test-width-4\n",
|
||||
)
|
||||
}
|
||||
|
||||
for option in &[
|
||||
"-w 25",
|
||||
"-w=25",
|
||||
"--width=25",
|
||||
"--width 25",
|
||||
"-w 0",
|
||||
"-w=0",
|
||||
"--width=0",
|
||||
"--width 0",
|
||||
] {
|
||||
let result = scene
|
||||
.ucmd()
|
||||
.args(&option.split(" ").collect::<Vec<_>>())
|
||||
.run();
|
||||
println!("stderr = {:?}", result.stderr);
|
||||
println!("stdout = {:?}", result.stdout);
|
||||
assert_eq!(
|
||||
result.stdout,
|
||||
"test-width-1\ntest-width-2\ntest-width-3\ntest-width-4\n",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ls_columns() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
at.touch(&at.plus_as_string("test-columns-1"));
|
||||
at.touch(&at.plus_as_string("test-columns-2"));
|
||||
at.touch(&at.plus_as_string("test-columns-3"));
|
||||
at.touch(&at.plus_as_string("test-columns-4"));
|
||||
|
||||
// Columns is the default
|
||||
let result = scene.ucmd().run();
|
||||
|
@ -71,9 +131,15 @@ fn test_ls_columns() {
|
|||
assert!(result.success);
|
||||
|
||||
#[cfg(not(windows))]
|
||||
assert_eq!(result.stdout, "test-columns-1\ntest-columns-2\n");
|
||||
assert_eq!(
|
||||
result.stdout,
|
||||
"test-columns-1\ntest-columns-2\ntest-columns-3\ntest-columns-4\n"
|
||||
);
|
||||
#[cfg(windows)]
|
||||
assert_eq!(result.stdout, "test-columns-1 test-columns-2\n");
|
||||
assert_eq!(
|
||||
result.stdout,
|
||||
"test-columns-1 test-columns-2 test-columns-3 test-columns-4\n"
|
||||
);
|
||||
|
||||
for option in &["-C", "--format=columns"] {
|
||||
let result = scene.ucmd().arg(option).run();
|
||||
|
@ -81,9 +147,107 @@ fn test_ls_columns() {
|
|||
println!("stdout = {:?}", result.stdout);
|
||||
assert!(result.success);
|
||||
#[cfg(not(windows))]
|
||||
assert_eq!(result.stdout, "test-columns-1\ntest-columns-2\n");
|
||||
assert_eq!(
|
||||
result.stdout,
|
||||
"test-columns-1\ntest-columns-2\ntest-columns-3\ntest-columns-4\n"
|
||||
);
|
||||
#[cfg(windows)]
|
||||
assert_eq!(result.stdout, "test-columns-1 test-columns-2\n");
|
||||
assert_eq!(
|
||||
result.stdout,
|
||||
"test-columns-1 test-columns-2 test-columns-3 test-columns-4\n"
|
||||
);
|
||||
}
|
||||
|
||||
for option in &["-C", "--format=columns"] {
|
||||
let result = scene.ucmd().arg("-w=40").arg(option).run();
|
||||
println!("stderr = {:?}", result.stderr);
|
||||
println!("stdout = {:?}", result.stdout);
|
||||
assert!(result.success);
|
||||
assert_eq!(
|
||||
result.stdout,
|
||||
"test-columns-1 test-columns-3\ntest-columns-2 test-columns-4\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ls_across() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
at.touch(&at.plus_as_string("test-across-1"));
|
||||
at.touch(&at.plus_as_string("test-across-2"));
|
||||
at.touch(&at.plus_as_string("test-across-3"));
|
||||
at.touch(&at.plus_as_string("test-across-4"));
|
||||
|
||||
for option in &["-x", "--format=across"] {
|
||||
let result = scene.ucmd().arg(option).succeeds();
|
||||
// Because the test terminal has width 0, this is the same output as
|
||||
// the columns option.
|
||||
println!("stderr = {:?}", result.stderr);
|
||||
println!("stdout = {:?}", result.stdout);
|
||||
if cfg!(unix) {
|
||||
assert_eq!(
|
||||
result.stdout,
|
||||
"test-across-1\ntest-across-2\ntest-across-3\ntest-across-4\n"
|
||||
);
|
||||
} else {
|
||||
assert_eq!(
|
||||
result.stdout,
|
||||
"test-across-1 test-across-2 test-across-3 test-across-4\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for option in &["-x", "--format=across"] {
|
||||
let result = scene.ucmd().arg("-w=30").arg(option).run();
|
||||
// Because the test terminal has width 0, this is the same output as
|
||||
// the columns option.
|
||||
println!("stderr = {:?}", result.stderr);
|
||||
println!("stdout = {:?}", result.stdout);
|
||||
assert_eq!(
|
||||
result.stdout,
|
||||
"test-across-1 test-across-2\ntest-across-3 test-across-4\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ls_commas() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
at.touch(&at.plus_as_string("test-commas-1"));
|
||||
at.touch(&at.plus_as_string("test-commas-2"));
|
||||
at.touch(&at.plus_as_string("test-commas-3"));
|
||||
at.touch(&at.plus_as_string("test-commas-4"));
|
||||
|
||||
for option in &["-m", "--format=commas"] {
|
||||
let result = scene.ucmd().arg(option).succeeds();
|
||||
if cfg!(unix) {
|
||||
assert_eq!(
|
||||
result.stdout,
|
||||
"test-commas-1,\ntest-commas-2,\ntest-commas-3,\ntest-commas-4\n"
|
||||
);
|
||||
} else {
|
||||
assert_eq!(
|
||||
result.stdout,
|
||||
"test-commas-1, test-commas-2, test-commas-3, test-commas-4\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for option in &["-m", "--format=commas"] {
|
||||
let result = scene.ucmd().arg("-w=30").arg(option).succeeds();
|
||||
assert_eq!(
|
||||
result.stdout,
|
||||
"test-commas-1, test-commas-2,\ntest-commas-3, test-commas-4\n"
|
||||
);
|
||||
}
|
||||
for option in &["-m", "--format=commas"] {
|
||||
let result = scene.ucmd().arg("-w=45").arg(option).succeeds();
|
||||
assert_eq!(
|
||||
result.stdout,
|
||||
"test-commas-1, test-commas-2, test-commas-3,\ntest-commas-4\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue