diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 01bc2705521..dbdb383b2f2 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -45,19 +45,18 @@ use unicode_width::UnicodeWidthStr; #[cfg(unix)] use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; -static ABOUT: &str = " - By default, ls will list the files and contents of any directories on - the command line, expect that it will ignore files and directories - whose names start with '.' -"; -static AFTER_HELP: &str = "The TIME_STYLE argument can be full-iso, long-iso, iso. -Also the TIME_STYLE environment variable sets the default style to use."; - fn get_usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) } +#[cfg(not(target_os = "freebsd"))] +static LS_BLOCK_SIZE: u64 = 1024; + +#[cfg(target_os = "freebsd")] +static LS_BLOCK_SIZE: u64 = 512; + pub mod options { + pub mod format { pub static ONE_LINE: &str = "1"; pub static LONG: &str = "long"; @@ -68,10 +67,12 @@ pub mod options { pub static LONG_NO_GROUP: &str = "o"; pub static LONG_NUMERIC_UID_GID: &str = "numeric-uid-gid"; } + pub mod files { pub static ALL: &str = "all"; pub static ALMOST_ALL: &str = "almost-all"; } + pub mod sort { pub static SIZE: &str = "S"; pub static TIME: &str = "t"; @@ -79,30 +80,38 @@ pub mod options { pub static VERSION: &str = "v"; pub static EXTENSION: &str = "X"; } + pub mod time { pub static ACCESS: &str = "u"; pub static CHANGE: &str = "c"; } + pub mod size { pub static HUMAN_READABLE: &str = "human-readable"; pub static SI: &str = "si"; + pub static S: &str = "s"; + pub static SIZE: &str = "size"; } + pub mod quoting { pub static ESCAPE: &str = "escape"; pub static LITERAL: &str = "literal"; pub static C: &str = "quote-name"; } - pub static QUOTING_STYLE: &str = "quoting-style"; + pub mod indicator_style { pub static SLASH: &str = "p"; pub static FILE_TYPE: &str = "file-type"; pub static CLASSIFY: &str = "classify"; } + pub mod dereference { pub static ALL: &str = "dereference"; pub static ARGS: &str = "dereference-command-line"; pub static DIR_ARGS: &str = "dereference-command-line-symlink-to-dir"; } + + pub static QUOTING_STYLE: &str = "quoting-style"; pub static HIDE_CONTROL_CHARS: &str = "hide-control-chars"; pub static SHOW_CONTROL_CHARS: &str = "show-control-chars"; pub static WIDTH: &str = "width"; @@ -205,6 +214,7 @@ struct Config { quoting_style: QuotingStyle, indicator_style: IndicatorStyle, time_style: TimeStyle, + show_size: bool, } // Fields that can be removed or added to the long format @@ -528,6 +538,8 @@ impl Config { Dereference::DirArgs }; + let show_size = options.is_present(options::size::S); + Config { format, files, @@ -547,6 +559,7 @@ impl Config { quoting_style, indicator_style, time_style, + show_size, } } } @@ -560,16 +573,28 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let app = App::new(executable!()) .version(crate_version!()) - .about(ABOUT) + .about( + "By default, ls will list the files and contents of any directories on \ + the command line, expect that it will ignore files and directories \ + whose names start with '.'.", + ) .usage(&usage[..]) - // Format arguments .arg( Arg::with_name(options::FORMAT) .long(options::FORMAT) .help("Set the display format.") .takes_value(true) - .possible_values(&["long", "verbose", "single-column", "columns", "vertical", "across", "horizontal", "commas"]) + .possible_values(&[ + "long", + "verbose", + "single-column", + "columns", + "vertical", + "across", + "horizontal", + "commas", + ]) .hide_possible_values(true) .require_equals(true) .overrides_with_all(&[ @@ -639,41 +664,51 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(options::format::ONE_LINE) .short(options::format::ONE_LINE) .help("List one file per line.") - .multiple(true) + .multiple(true), ) .arg( Arg::with_name(options::format::LONG_NO_GROUP) .short(options::format::LONG_NO_GROUP) - .help("Long format without group information. Identical to --format=long with --no-group.") - .multiple(true) + .help( + "Long format without group information. \ + Identical to --format=long with --no-group.", + ) + .multiple(true), ) .arg( Arg::with_name(options::format::LONG_NO_OWNER) .short(options::format::LONG_NO_OWNER) .help("Long format without owner information.") - .multiple(true) + .multiple(true), ) .arg( Arg::with_name(options::format::LONG_NUMERIC_UID_GID) .short("n") .long(options::format::LONG_NUMERIC_UID_GID) .help("-l with numeric UIDs and GIDs.") - .multiple(true) + .multiple(true), ) - // Quoting style .arg( Arg::with_name(options::QUOTING_STYLE) .long(options::QUOTING_STYLE) .takes_value(true) .help("Set quoting style.") - .possible_values(&["literal", "shell", "shell-always", "shell-escape", "shell-escape-always", "c", "escape"]) + .possible_values(&[ + "literal", + "shell", + "shell-always", + "shell-escape", + "shell-escape-always", + "c", + "escape", + ]) .overrides_with_all(&[ options::QUOTING_STYLE, options::quoting::LITERAL, options::quoting::ESCAPE, options::quoting::C, - ]) + ]), ) .arg( Arg::with_name(options::quoting::LITERAL) @@ -685,7 +720,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::quoting::LITERAL, options::quoting::ESCAPE, options::quoting::C, - ]) + ]), ) .arg( Arg::with_name(options::quoting::ESCAPE) @@ -697,7 +732,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::quoting::LITERAL, options::quoting::ESCAPE, options::quoting::C, - ]) + ]), ) .arg( Arg::with_name(options::quoting::C) @@ -709,76 +744,63 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::quoting::LITERAL, options::quoting::ESCAPE, options::quoting::C, - ]) + ]), ) - // Control characters .arg( Arg::with_name(options::HIDE_CONTROL_CHARS) .short("q") .long(options::HIDE_CONTROL_CHARS) .help("Replace control characters with '?' if they are not escaped.") - .overrides_with_all(&[ - options::HIDE_CONTROL_CHARS, - options::SHOW_CONTROL_CHARS, - ]) + .overrides_with_all(&[options::HIDE_CONTROL_CHARS, options::SHOW_CONTROL_CHARS]), ) .arg( Arg::with_name(options::SHOW_CONTROL_CHARS) .long(options::SHOW_CONTROL_CHARS) .help("Show control characters 'as is' if they are not escaped.") - .overrides_with_all(&[ - options::HIDE_CONTROL_CHARS, - options::SHOW_CONTROL_CHARS, - ]) + .overrides_with_all(&[options::HIDE_CONTROL_CHARS, options::SHOW_CONTROL_CHARS]), ) - // Time arguments .arg( Arg::with_name(options::TIME) .long(options::TIME) - .help("Show time in :\n\ + .help( + "Show time in :\n\ \taccess time (-u): atime, access, use;\n\ \tchange time (-t): ctime, status.\n\ - \tbirth time: birth, creation;") + \tbirth time: birth, creation;", + ) .value_name("field") .takes_value(true) - .possible_values(&["atime", "access", "use", "ctime", "status", "birth", "creation"]) + .possible_values(&[ + "atime", "access", "use", "ctime", "status", "birth", "creation", + ]) .hide_possible_values(true) .require_equals(true) - .overrides_with_all(&[ - options::TIME, - options::time::ACCESS, - options::time::CHANGE, - ]) + .overrides_with_all(&[options::TIME, options::time::ACCESS, options::time::CHANGE]), ) .arg( Arg::with_name(options::time::CHANGE) .short(options::time::CHANGE) - .help("If the long listing format (e.g., -l, -o) is being used, print the status \ + .help( + "If the long listing format (e.g., -l, -o) is being used, print the status \ change time (the 'ctime' in the inode) instead of the modification time. When \ explicitly sorting by time (--sort=time or -t) or when not using a long listing \ - format, sort according to the status change time.") - .overrides_with_all(&[ - options::TIME, - options::time::ACCESS, - options::time::CHANGE, - ]) + format, sort according to the status change time.", + ) + .overrides_with_all(&[options::TIME, options::time::ACCESS, options::time::CHANGE]), ) .arg( Arg::with_name(options::time::ACCESS) .short(options::time::ACCESS) - .help("If the long listing format (e.g., -l, -o) is being used, print the status \ + .help( + "If the long listing format (e.g., -l, -o) is being used, print the status \ access time instead of the modification time. When explicitly sorting by time \ (--sort=time or -t) or when not using a long listing format, sort according to the \ - access time.") - .overrides_with_all(&[ - options::TIME, - options::time::ACCESS, - options::time::CHANGE, - ]) + access time.", + ) + .overrides_with_all(&[options::TIME, options::time::ACCESS, options::time::CHANGE]), ) - // Hide and ignore .arg( Arg::with_name(options::HIDE) @@ -786,7 +808,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) .multiple(true) .value_name("PATTERN") - .help("do not list implied entries matching shell PATTERN (overridden by -a or -A)") + .help( + "do not list implied entries matching shell PATTERN (overridden by -a or -A)", + ), ) .arg( Arg::with_name(options::IGNORE) @@ -795,7 +819,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) .multiple(true) .value_name("PATTERN") - .help("do not list implied entries matching shell PATTERN") + .help("do not list implied entries matching shell PATTERN"), ) .arg( Arg::with_name(options::IGNORE_BACKUPS) @@ -803,7 +827,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::IGNORE_BACKUPS) .help("Ignore entries which end with ~."), ) - // Sort arguments .arg( Arg::with_name(options::SORT) @@ -820,7 +843,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::sort::NONE, options::sort::VERSION, options::sort::EXTENSION, - ]) + ]), ) .arg( Arg::with_name(options::sort::SIZE) @@ -833,7 +856,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::sort::NONE, options::sort::VERSION, options::sort::EXTENSION, - ]) + ]), ) .arg( Arg::with_name(options::sort::TIME) @@ -846,7 +869,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::sort::NONE, options::sort::VERSION, options::sort::EXTENSION, - ]) + ]), ) .arg( Arg::with_name(options::sort::VERSION) @@ -859,7 +882,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::sort::NONE, options::sort::VERSION, options::sort::EXTENSION, - ]) + ]), ) .arg( Arg::with_name(options::sort::EXTENSION) @@ -872,14 +895,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::sort::NONE, options::sort::VERSION, options::sort::EXTENSION, - ]) + ]), ) .arg( Arg::with_name(options::sort::NONE) .short(options::sort::NONE) - .help("Do not sort; list the files in whatever order they are stored in the \ - directory. This is especially useful when listing very large directories, \ - since not doing any sorting can be noticeably faster.") + .help( + "Do not sort; list the files in whatever order they are stored in the \ + directory. This is especially useful when listing very large directories, \ + since not doing any sorting can be noticeably faster.", + ) .overrides_with_all(&[ options::SORT, options::sort::SIZE, @@ -887,9 +912,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::sort::NONE, options::sort::VERSION, options::sort::EXTENSION, - ]) + ]), ) - // Dereferencing .arg( Arg::with_name(options::dereference::ALL) @@ -903,48 +927,43 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::dereference::ALL, options::dereference::DIR_ARGS, options::dereference::ARGS, - ]) + ]), ) .arg( Arg::with_name(options::dereference::DIR_ARGS) .long(options::dereference::DIR_ARGS) .help( "Do not dereference symlinks except when they link to directories and are \ - given as command line arguments.", + given as command line arguments.", ) .overrides_with_all(&[ options::dereference::ALL, options::dereference::DIR_ARGS, options::dereference::ARGS, - ]) + ]), ) .arg( Arg::with_name(options::dereference::ARGS) .short("H") .long(options::dereference::ARGS) - .help( - "Do not dereference symlinks except when given as command line arguments.", - ) + .help("Do not dereference symlinks except when given as command line arguments.") .overrides_with_all(&[ options::dereference::ALL, options::dereference::DIR_ARGS, options::dereference::ARGS, - ]) + ]), ) - // Long format options .arg( Arg::with_name(options::NO_GROUP) .long(options::NO_GROUP) .short("-G") - .help("Do not show group in long format.") - ) - .arg( - Arg::with_name(options::AUTHOR) - .long(options::AUTHOR) - .help("Show author in long format. On the supported platforms, the author \ - always matches the file owner.") + .help("Do not show group in long format."), ) + .arg(Arg::with_name(options::AUTHOR).long(options::AUTHOR).help( + "Show author in long format. \ + On the supported platforms, the author always matches the file owner.", + )) // Other Flags .arg( Arg::with_name(options::files::ALL) @@ -957,9 +976,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .short("A") .long(options::files::ALMOST_ALL) .help( - "In a directory, do not ignore all file names that start with '.', only ignore \ - '.' and '..'.", - ), + "In a directory, do not ignore all file names that start with '.', \ +only ignore '.' and '..'.", + ), ) .arg( Arg::with_name(options::DIRECTORY) @@ -982,21 +1001,29 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg( Arg::with_name(options::size::SI) .long(options::size::SI) - .help("Print human readable file sizes using powers of 1000 instead of 1024.") + .help("Print human readable file sizes using powers of 1000 instead of 1024."), + ) + .arg( + Arg::with_name(options::size::S) + .short(options::size::S) + .long(options::size::SIZE) + .help("Print the allocated size of each file, in blocks."), ) .arg( Arg::with_name(options::INODE) .short("i") .long(options::INODE) - .help("print the index number of each file"), + .help("Print the index number of each file."), ) .arg( Arg::with_name(options::REVERSE) .short("r") .long(options::REVERSE) - .help("Reverse whatever the sorting method is--e.g., list files in reverse \ + .help( + "Reverse whatever the sorting method is e.g., list files in reverse \ alphabetical order, youngest first, smallest first, or whatever.", - )) + ), + ) .arg( Arg::with_name(options::RECURSIVE) .short("R") @@ -1009,7 +1036,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .short("w") .help("Assume that the terminal is COLS columns wide.") .value_name("COLS") - .takes_value(true) + .takes_value(true), ) .arg( Arg::with_name(options::COLOR) @@ -1022,8 +1049,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg( Arg::with_name(options::INDICATOR_STYLE) .long(options::INDICATOR_STYLE) - .help(" append indicator with style WORD to entry names: none (default), slash\ - (-p), file-type (--file-type), classify (-F)") + .help( + "Append indicator with style WORD to entry names: \ + none (default), slash (-p), file-type (--file-type), classify (-F)", + ) .takes_value(true) .possible_values(&["none", "slash", "file-type", "classify"]) .overrides_with_all(&[ @@ -1031,21 +1060,24 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::indicator_style::SLASH, options::indicator_style::CLASSIFY, options::INDICATOR_STYLE, - ])) - .arg( + ]), + ) + .arg( Arg::with_name(options::indicator_style::CLASSIFY) .short("F") .long(options::indicator_style::CLASSIFY) - .help("Append a character to each file name indicating the file type. Also, for \ - regular files that are executable, append '*'. The file type indicators are \ - '/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \ - '>' for doors, and nothing for regular files.") + .help( + "Append a character to each file name indicating the file type. Also, for \ + regular files that are executable, append '*'. The file type indicators are \ + '/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \ + '>' for doors, and nothing for regular files.", + ) .overrides_with_all(&[ options::indicator_style::FILE_TYPE, options::indicator_style::SLASH, options::indicator_style::CLASSIFY, options::INDICATOR_STYLE, - ]) + ]), ) .arg( Arg::with_name(options::indicator_style::FILE_TYPE) @@ -1056,18 +1088,19 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::indicator_style::SLASH, options::indicator_style::CLASSIFY, options::INDICATOR_STYLE, - ])) + ]), + ) .arg( Arg::with_name(options::indicator_style::SLASH) .short(options::indicator_style::SLASH) - .help("Append / indicator to directories." - ) + .help("Append / indicator to directories.") .overrides_with_all(&[ options::indicator_style::FILE_TYPE, options::indicator_style::SLASH, options::indicator_style::CLASSIFY, options::INDICATOR_STYLE, - ])) + ]), + ) .arg( //This still needs support for posix-*, +FORMAT Arg::with_name(options::TIME_STYLE) @@ -1075,27 +1108,25 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("time/date format with -l; see TIME_STYLE below") .value_name("TIME_STYLE") .env("TIME_STYLE") - .possible_values(&[ - "full-iso", - "long-iso", - "iso", - "locale", - ]) - .overrides_with_all(&[ - options::TIME_STYLE - ]) + .possible_values(&["full-iso", "long-iso", "iso", "locale"]) + .overrides_with_all(&[options::TIME_STYLE]), ) .arg( Arg::with_name(options::FULL_TIME) - .long(options::FULL_TIME) - .overrides_with(options::FULL_TIME) - .help("like -l --time-style=full-iso") + .long(options::FULL_TIME) + .overrides_with(options::FULL_TIME) + .help("like -l --time-style=full-iso"), ) - - // Positional arguments - .arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true)) - - .after_help(AFTER_HELP); + // Positional arguments + .arg( + Arg::with_name(options::PATHS) + .multiple(true) + .takes_value(true), + ) + .after_help( + "The TIME_STYLE argument can be full-iso, long-iso, iso. \ + Also the TIME_STYLE environment variable sets the default style to use.", + ); let matches = app.get_matches_from(args); @@ -1338,40 +1369,13 @@ fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result { } } -fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize) { - if let Some(md) = entry.md() { - ( - display_symlink_count(md).len(), - display_size_or_rdev(md, config).len(), - ) - } else { - (0, 0) - } -} - fn pad_left(string: String, count: usize) -> String { format!("{:>width$}", string, width = count) } fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter) { if config.format == Format::Long { - let (mut max_links, mut max_width) = (1, 1); - let mut total_size = 0; - - for item in items { - let (links, width) = display_dir_entry_size(item, config); - max_links = links.max(max_links); - 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 { - display_item_long(item, max_links, max_width, config, out); - } + display_item_long(items, config, out); } else { let names = items.iter().filter_map(|i| display_file_name(i, config)); @@ -1383,32 +1387,11 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter { - let term_width = width_opt.unwrap_or(1); - let mut current_col = 0; - let mut names = names; - if let Some(name) = names.next() { - let _ = write!(out, "{}", 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; - let _ = write!(out, ",\n{}", name.contents); - } else { - current_col += name_width + 2; - let _ = write!(out, ", {}", name.contents); - } - } - // Current col is never zero again if names have been printed. - // So we print a newline. - if current_col > 0 { - let _ = writeln!(out,); - } + display_comma(names, width_opt.unwrap_or(1).into(), out); } _ => { for name in names { - let _ = writeln!(out, "{}", name.contents); + writeln!(out, "{}", name.contents).unwrap(); } } } @@ -1422,11 +1405,10 @@ fn get_block_size(md: &Metadata, config: &Config) -> u64 { #[cfg(unix)] { // hard-coded for now - enabling setting this remains a TODO - let ls_block_size = 1024; match config.size_format { SizeFormat::Binary => md.blocks() * 512, SizeFormat::Decimal => md.blocks() * 512, - SizeFormat::Bytes => md.blocks() * 512 / ls_block_size, + SizeFormat::Bytes => md.blocks() * 512 / LS_BLOCK_SIZE, } } @@ -1466,59 +1448,64 @@ fn display_grid( use uucore::fs::display_permissions; -fn display_item_long( - item: &PathData, - max_links: usize, - max_size: usize, - config: &Config, - out: &mut BufWriter, -) { - let md = match item.md() { - None => { - show_error!("could not show file: {}", &item.p_buf.display()); - return; +fn display_item_long(paths: &[PathData], config: &Config, out: &mut BufWriter) { + let metadatas = paths + .iter() + // FIXME: For now it just unwraps. Perhaps introduce better error messages? + .map(|path| path.md().unwrap()) + .collect::>(); + let (max_links, max_width, total_size) = metadatas + .iter() + .map(|md| { + ( + display_symlink_count(md).len(), + display_size_or_rdev(md, config).len(), + get_block_size(md, config), + ) + }) + .fold((1, 1, 0u64), |(a, b, c), (d, e, f)| { + (a.max(d), b.max(e), c.saturating_add(f)) + }); + if total_size > 0 { + writeln!(out, "total {}", display_size(total_size, config)).unwrap(); + } + for (item, md) in paths.iter().zip(metadatas.iter()) { + #[cfg(unix)] + { + if config.inode { + write!(out, "{} ", get_inode(md)).unwrap(); + } } - Some(md) => md, - }; - - #[cfg(unix)] - { - if config.inode { - let _ = write!(out, "{} ", get_inode(md)); + write!( + out, + "{} {}", + display_permissions(md, true), + pad_left(display_symlink_count(md), max_links), + ) + .unwrap(); + if config.long.owner { + write!(out, " {}", display_uname(md, config)).unwrap(); } + if config.long.group { + write!(out, " {}", display_group(md, config)).unwrap(); + } + // Author is only different from owner on GNU/Hurd, so we reuse + // the owner, since GNU/Hurd is not currently supported by Rust. + if config.long.author { + write!(out, " {}", display_uname(md, config)).unwrap(); + } + writeln!( + out, + " {} {} {}", + pad_left(display_size_or_rdev(md, config), max_width), + display_date(md, config), + // unwrap is fine because it fails when metadata is not available + // but we already know that it is because it's checked at the + // start of the function. + display_file_name(item, config).unwrap().contents, + ) + .unwrap(); } - - let _ = write!( - out, - "{} {}", - display_permissions(md, true), - pad_left(display_symlink_count(md), max_links), - ); - - if config.long.owner { - let _ = write!(out, " {}", display_uname(md, config)); - } - - if config.long.group { - let _ = write!(out, " {}", display_group(md, config)); - } - - // Author is only different from owner on GNU/Hurd, so we reuse - // the owner, since GNU/Hurd is not currently supported by Rust. - if config.long.author { - let _ = write!(out, " {}", display_uname(md, config)); - } - - let _ = writeln!( - out, - " {} {} {}", - pad_left(display_size_or_rdev(md, config), max_size), - display_date(md, config), - // unwrap is fine because it fails when metadata is not available - // but we already know that it is because it's checked at the - // start of the function. - display_file_name(item, config).unwrap().contents, - ); } #[cfg(unix)] @@ -1742,9 +1729,15 @@ fn display_file_name(path: &PathData, config: &Config) -> Option { } } + let file_size = if config.show_size { + format!("{} ", get_block_size(path.md().unwrap(), config)) + } else { + String::new() + }; + // We need to keep track of the width ourselves instead of letting term_grid // infer it because the color codes mess up term_grid's width calculation. - let mut width = name.width(); + let mut width = name.width() + file_size.width(); if let Some(ls_colors) = &config.color { name = color_name(ls_colors, &path.p_buf, name, path.md()?); @@ -1785,10 +1778,9 @@ fn display_file_name(path: &PathData, config: &Config) -> Option { } } - Some(Cell { - contents: name, - width, - }) + let contents = format!("{}{}", file_size, name); + + Some(Cell { contents, width }) } fn color_name(ls_colors: &LsColors, path: &Path, name: String, md: &Metadata) -> String { @@ -1809,3 +1801,31 @@ fn display_symlink_count(_metadata: &Metadata) -> String { fn display_symlink_count(metadata: &Metadata) -> String { metadata.nlink().to_string() } + +fn display_comma( + names: impl Iterator, + term_width: usize, + out: &mut BufWriter, +) { + let mut current_col = 0; + let mut names = names; + if let Some(name) = names.next() { + let _ = write!(out, "{}", name.contents); + current_col = name.width + 2; + } + for name in names { + let name_width = name.width; + if current_col + name_width + 1 > term_width { + current_col = name_width + 2; + let _ = write!(out, ",\n{}", name.contents); + } else { + current_col += name_width + 2; + let _ = write!(out, ", {}", name.contents); + } + } + // Current col is never zero again if names have been printed. + // So we print a newline. + if current_col > 0 { + let _ = writeln!(out,); + } +} diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 44d14c30416..7e12c3efbcf 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -2022,6 +2022,32 @@ fn test_ls_path() { .stdout_is(expected_stdout); } +#[test] +fn test_ls_size() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("test-1"); + at.append( + "test-1", + &std::iter::repeat("a\n").take(123456).collect::(), + ); + at.touch("test-2"); + at.append("test-2", &"a\n".repeat(234567)); + at.touch("test-3"); + at.append("test-3", &"a\n".repeat(345678)); + at.touch("test-4"); + at.append("test-4", &"a\n".repeat(456789)); + + scene.ucmd().arg("-s").succeeds(); + + let result = scene.ucmd().arg("-s").succeeds(); + #[cfg(not(windows))] + result.stdout_only("244 test-1\n460 test-2\n676 test-3\n896 test-4\n"); + #[cfg(windows)] + result.stdout_only("246912 test-1 469134 test-2 691356 test-3 913578 test-4\n"); +} + #[test] fn test_ls_dangling_symlinks() { let scene = TestScenario::new(util_name!());