mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 19:47:45 +00:00
Implement Total size feature (#2170)
* ls: Implement total size feature - Implement total size reporting that was missing - Fix minor formatting / readability nits * tests: Add tests for ls total sizes feature * ls: Fix MSRV build errors due to unsupported attributes for if blocks * ls: Add windows support for total sizes feature - Add windows support (defaults to file size as block sizes related infromation is not avialable on windows) - Renamed some functions
This commit is contained in:
parent
231bb7be93
commit
7d2b051866
3 changed files with 100 additions and 21 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1692,6 +1692,7 @@ dependencies = [
|
||||||
name = "uu_basename"
|
name = "uu_basename"
|
||||||
version = "0.0.6"
|
version = "0.0.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"clap",
|
||||||
"uucore",
|
"uucore",
|
||||||
"uucore_procs",
|
"uucore_procs",
|
||||||
]
|
]
|
||||||
|
@ -2645,6 +2646,7 @@ dependencies = [
|
||||||
name = "uu_who"
|
name = "uu_who"
|
||||||
version = "0.0.6"
|
version = "0.0.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"clap",
|
||||||
"uucore",
|
"uucore",
|
||||||
"uucore_procs",
|
"uucore_procs",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1179,31 +1179,32 @@ impl PathData {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list(locs: Vec<String>, config: Config) -> i32 {
|
fn list(locs: Vec<String>, config: Config) -> i32 {
|
||||||
let number_of_locs = locs.len();
|
|
||||||
|
|
||||||
let mut files = Vec::<PathData>::new();
|
let mut files = Vec::<PathData>::new();
|
||||||
let mut dirs = Vec::<PathData>::new();
|
let mut dirs = Vec::<PathData>::new();
|
||||||
let mut has_failed = false;
|
let mut has_failed = false;
|
||||||
|
|
||||||
let mut out = BufWriter::new(stdout());
|
let mut out = BufWriter::new(stdout());
|
||||||
|
|
||||||
for loc in locs {
|
for loc in &locs {
|
||||||
let p = PathBuf::from(&loc);
|
let p = PathBuf::from(&loc);
|
||||||
if !p.exists() {
|
if !p.exists() {
|
||||||
show_error!("'{}': {}", &loc, "No such file or directory");
|
show_error!("'{}': {}", &loc, "No such file or directory");
|
||||||
// We found an error, the return code of ls should not be 0
|
/*
|
||||||
// And no need to continue the execution
|
We found an error, the return code of ls should not be 0
|
||||||
|
And no need to continue the execution
|
||||||
|
*/
|
||||||
has_failed = true;
|
has_failed = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let path_data = PathData::new(p, None, None, &config, true);
|
let path_data = PathData::new(p, None, None, &config, true);
|
||||||
|
|
||||||
let show_dir_contents = if let Some(ft) = path_data.file_type() {
|
let show_dir_contents = match path_data.file_type() {
|
||||||
!config.directory && ft.is_dir()
|
Some(ft) => !config.directory && ft.is_dir(),
|
||||||
} else {
|
None => {
|
||||||
has_failed = true;
|
has_failed = true;
|
||||||
false
|
false
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if show_dir_contents {
|
if show_dir_contents {
|
||||||
|
@ -1217,7 +1218,7 @@ fn list(locs: Vec<String>, config: Config) -> i32 {
|
||||||
|
|
||||||
sort_entries(&mut dirs, &config);
|
sort_entries(&mut dirs, &config);
|
||||||
for dir in dirs {
|
for dir in dirs {
|
||||||
if number_of_locs > 1 {
|
if locs.len() > 1 {
|
||||||
let _ = writeln!(out, "\n{}:", dir.p_buf.display());
|
let _ = writeln!(out, "\n{}:", dir.p_buf.display());
|
||||||
}
|
}
|
||||||
enter_directory(&dir, &config, &mut out);
|
enter_directory(&dir, &config, &mut out);
|
||||||
|
@ -1331,7 +1332,7 @@ fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize) {
|
||||||
if let Some(md) = entry.md() {
|
if let Some(md) = entry.md() {
|
||||||
(
|
(
|
||||||
display_symlink_count(&md).len(),
|
display_symlink_count(&md).len(),
|
||||||
display_file_size(&md, config).len(),
|
display_size(md.len(), config).len(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(0, 0)
|
(0, 0)
|
||||||
|
@ -1344,14 +1345,22 @@ fn pad_left(string: String, count: usize) -> String {
|
||||||
|
|
||||||
fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout>) {
|
fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout>) {
|
||||||
if config.format == Format::Long {
|
if config.format == Format::Long {
|
||||||
let (mut max_links, mut max_size) = (1, 1);
|
let (mut max_links, mut max_width) = (1, 1);
|
||||||
|
let mut total_size = 0;
|
||||||
|
|
||||||
for item in items {
|
for item in items {
|
||||||
let (links, size) = display_dir_entry_size(item, config);
|
let (links, width) = display_dir_entry_size(item, config);
|
||||||
max_links = links.max(max_links);
|
max_links = links.max(max_links);
|
||||||
max_size = size.max(max_size);
|
max_width = width.max(max_width);
|
||||||
|
total_size += item.md().map_or(0, |md| get_block_size(md, config));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if total_size > 0 {
|
||||||
|
let _ = writeln!(out, "total {}", display_size(total_size, config));
|
||||||
|
}
|
||||||
|
|
||||||
for item in items {
|
for item in items {
|
||||||
display_item_long(item, max_links, max_size, config, out);
|
display_item_long(item, max_links, max_width, config, out);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let names = items.iter().filter_map(|i| display_file_name(&i, config));
|
let names = items.iter().filter_map(|i| display_file_name(&i, config));
|
||||||
|
@ -1396,6 +1405,29 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_block_size(md: &Metadata, config: &Config) -> u64 {
|
||||||
|
/* GNU ls will display sizes in terms of block size
|
||||||
|
md.len() will differ from this value when the file has some holes
|
||||||
|
*/
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
// hard-coded for now - enabling setting this remains a TODO
|
||||||
|
let ls_block_size = 1024;
|
||||||
|
return match config.size_format {
|
||||||
|
SizeFormat::Binary => md.blocks() * 512,
|
||||||
|
SizeFormat::Decimal => md.blocks() * 512,
|
||||||
|
SizeFormat::Bytes => md.blocks() * 512 / ls_block_size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
{
|
||||||
|
let _ = config;
|
||||||
|
// no way to get block size for windows, fall-back to file size
|
||||||
|
md.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn display_grid(
|
fn display_grid(
|
||||||
names: impl Iterator<Item = Cell>,
|
names: impl Iterator<Item = Cell>,
|
||||||
width: u16,
|
width: u16,
|
||||||
|
@ -1471,7 +1503,7 @@ fn display_item_long(
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
out,
|
out,
|
||||||
" {} {} {}",
|
" {} {} {}",
|
||||||
pad_left(display_file_size(&md, config), max_size),
|
pad_left(display_size(md.len(), config), max_size),
|
||||||
display_date(&md, config),
|
display_date(&md, config),
|
||||||
// unwrap is fine because it fails when metadata is not available
|
// unwrap is fine because it fails when metadata is not available
|
||||||
// but we already know that it is because it's checked at the
|
// but we already know that it is because it's checked at the
|
||||||
|
@ -1626,13 +1658,13 @@ fn format_prefixed(prefixed: NumberPrefix<f64>) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_file_size(metadata: &Metadata, config: &Config) -> String {
|
fn display_size(len: u64, config: &Config) -> String {
|
||||||
// NOTE: The human-readable behaviour deviates from the GNU ls.
|
// NOTE: The human-readable behaviour deviates from the GNU ls.
|
||||||
// The GNU ls uses binary prefixes by default.
|
// The GNU ls uses binary prefixes by default.
|
||||||
match config.size_format {
|
match config.size_format {
|
||||||
SizeFormat::Binary => format_prefixed(NumberPrefix::binary(metadata.len() as f64)),
|
SizeFormat::Binary => format_prefixed(NumberPrefix::binary(len as f64)),
|
||||||
SizeFormat::Decimal => format_prefixed(NumberPrefix::decimal(metadata.len() as f64)),
|
SizeFormat::Decimal => format_prefixed(NumberPrefix::decimal(len as f64)),
|
||||||
SizeFormat::Bytes => metadata.len().to_string(),
|
SizeFormat::Bytes => len.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::common::util::*;
|
||||||
extern crate regex;
|
extern crate regex;
|
||||||
use self::regex::Regex;
|
use self::regex::Regex;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
@ -308,6 +309,50 @@ fn test_ls_long() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ls_long_total_size() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
at.touch(&at.plus_as_string("test-long"));
|
||||||
|
at.append("test-long", "1");
|
||||||
|
at.touch(&at.plus_as_string("test-long2"));
|
||||||
|
at.append("test-long2", "2");
|
||||||
|
|
||||||
|
let expected_prints: HashMap<_, _> = if cfg!(unix) {
|
||||||
|
[
|
||||||
|
("long_vanilla", "total 8"),
|
||||||
|
("long_human_readable", "total 8.0K"),
|
||||||
|
("long_si", "total 8.2k"),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
[
|
||||||
|
("long_vanilla", "total 2"),
|
||||||
|
("long_human_readable", "total 2"),
|
||||||
|
("long_si", "total 2"),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
for arg in &["-l", "--long", "--format=long", "--format=verbose"] {
|
||||||
|
let result = scene.ucmd().arg(arg).succeeds();
|
||||||
|
result.stdout_contains(expected_prints["long_vanilla"]);
|
||||||
|
|
||||||
|
for arg2 in &["-h", "--human-readable", "--si"] {
|
||||||
|
let result = scene.ucmd().arg(arg).arg(arg2).succeeds();
|
||||||
|
result.stdout_contains(if *arg2 == "--si" {
|
||||||
|
expected_prints["long_si"]
|
||||||
|
} else {
|
||||||
|
expected_prints["long_human_readable"]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ls_long_formats() {
|
fn test_ls_long_formats() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue