diff --git a/src/uu/df/src/blocks.rs b/src/uu/df/src/blocks.rs index bb6e06333..9a7e5c580 100644 --- a/src/uu/df/src/blocks.rs +++ b/src/uu/df/src/blocks.rs @@ -167,6 +167,15 @@ pub(crate) enum BlockSize { Bytes(u64), } +impl BlockSize { + /// Returns the associated value + pub(crate) fn as_u64(&self) -> u64 { + match *self { + Self::Bytes(n) => n, + } + } +} + impl Default for BlockSize { fn default() -> Self { if env::var("POSIXLY_CORRECT").is_ok() { diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 192cbcadf..a11dcf7a0 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -12,6 +12,7 @@ mod filesystem; mod table; use blocks::{HumanReadable, SizeFormat}; +use table::HeaderMode; use uucore::display::Quotable; use uucore::error::{UError, UResult, USimpleError}; use uucore::fsext::{read_fs_list, MountInfo}; @@ -72,6 +73,7 @@ struct Options { show_all_fs: bool, size_format: SizeFormat, block_size: BlockSize, + header_mode: HeaderMode, /// Optional list of filesystem types to include in the output table. /// @@ -99,6 +101,7 @@ impl Default for Options { show_all_fs: Default::default(), block_size: Default::default(), size_format: Default::default(), + header_mode: Default::default(), include: Default::default(), exclude: Default::default(), show_total: Default::default(), @@ -176,6 +179,21 @@ impl Options { ), ParseSizeError::ParseFailure(s) => OptionsError::InvalidBlockSize(s), })?, + header_mode: { + if matches.is_present(OPT_HUMAN_READABLE_BINARY) + || matches.is_present(OPT_HUMAN_READABLE_DECIMAL) + { + HeaderMode::HumanReadable + } else if matches.is_present(OPT_PORTABILITY) { + HeaderMode::PosixPortability + // is_present() doesn't work here, it always returns true because OPT_OUTPUT has + // default values and hence is always present + } else if matches.occurrences_of(OPT_OUTPUT) > 0 { + HeaderMode::Output + } else { + HeaderMode::Default + } + }, size_format: { if matches.is_present(OPT_HUMAN_READABLE_BINARY) { SizeFormat::HumanReadable(HumanReadable::Binary) diff --git a/src/uu/df/src/table.rs b/src/uu/df/src/table.rs index 5309da38f..a9a37cfeb 100644 --- a/src/uu/df/src/table.rs +++ b/src/uu/df/src/table.rs @@ -289,6 +289,23 @@ impl<'a> RowFormatter<'a> { } } +/// A HeaderMode defines what header labels should be shown. +pub(crate) enum HeaderMode { + Default, + // the user used -h or -H + HumanReadable, + // the user used -P + PosixPortability, + // the user used --output + Output, +} + +impl Default for HeaderMode { + fn default() -> Self { + Self::Default + } +} + /// The data of the header row. struct Header {} @@ -302,15 +319,22 @@ impl Header { for column in &options.columns { let header = match column { Column::Source => String::from("Filesystem"), - Column::Size => match options.size_format { - SizeFormat::HumanReadable(_) => String::from("Size"), - SizeFormat::StaticBlockSize => { - format!("{}-blocks", options.block_size) + Column::Size => match options.header_mode { + HeaderMode::HumanReadable => String::from("Size"), + HeaderMode::PosixPortability => { + format!("{}-blocks", options.block_size.as_u64()) } + _ => format!("{}-blocks", options.block_size), }, Column::Used => String::from("Used"), - Column::Avail => String::from("Available"), - Column::Pcent => String::from("Use%"), + Column::Avail => match options.header_mode { + HeaderMode::HumanReadable | HeaderMode::Output => String::from("Avail"), + _ => String::from("Available"), + }, + Column::Pcent => match options.header_mode { + HeaderMode::PosixPortability => String::from("Capacity"), + _ => String::from("Use%"), + }, Column::Target => String::from("Mounted on"), Column::Itotal => String::from("Inodes"), Column::Iused => String::from("IUsed"), @@ -428,7 +452,7 @@ mod tests { use crate::blocks::{HumanReadable, SizeFormat}; use crate::columns::Column; - use crate::table::{Header, Row, RowFormatter}; + use crate::table::{Header, HeaderMode, Row, RowFormatter}; use crate::{BlockSize, Options}; const COLUMNS_WITH_FS_TYPE: [Column; 7] = [ @@ -548,37 +572,49 @@ mod tests { } #[test] - fn test_header_with_human_readable_binary() { + fn test_human_readable_header() { let options = Options { - size_format: SizeFormat::HumanReadable(HumanReadable::Binary), + header_mode: HeaderMode::HumanReadable, + ..Default::default() + }; + assert_eq!( + Header::get_headers(&options), + vec!("Filesystem", "Size", "Used", "Avail", "Use%", "Mounted on") + ); + } + + #[test] + fn test_posix_portability_header() { + let options = Options { + header_mode: HeaderMode::PosixPortability, ..Default::default() }; assert_eq!( Header::get_headers(&options), vec!( "Filesystem", - "Size", + "1024-blocks", "Used", "Available", - "Use%", + "Capacity", "Mounted on" ) ); } #[test] - fn test_header_with_human_readable_si() { + fn test_output_header() { let options = Options { - size_format: SizeFormat::HumanReadable(HumanReadable::Decimal), + header_mode: HeaderMode::Output, ..Default::default() }; assert_eq!( Header::get_headers(&options), vec!( "Filesystem", - "Size", + "1K-blocks", "Used", - "Available", + "Avail", "Use%", "Mounted on" ) diff --git a/tests/by-util/test_df.rs b/tests/by-util/test_df.rs index 81a9eef72..3c5bfdeea 100644 --- a/tests/by-util/test_df.rs +++ b/tests/by-util/test_df.rs @@ -73,7 +73,7 @@ fn test_df_output() { "Filesystem", "Size", "Used", - "Available", + "Avail", "Capacity", "Use%", "Mounted", @@ -84,7 +84,7 @@ fn test_df_output() { "Filesystem", "Size", "Used", - "Available", + "Avail", "Use%", "Mounted", "on", @@ -107,7 +107,7 @@ fn test_df_output_overridden() { "Filesystem", "Size", "Used", - "Available", + "Avail", "Capacity", "Use%", "Mounted", @@ -118,7 +118,7 @@ fn test_df_output_overridden() { "Filesystem", "Size", "Used", - "Available", + "Avail", "Use%", "Mounted", "on", @@ -134,6 +134,46 @@ fn test_df_output_overridden() { assert_eq!(actual, expected); } +#[test] +fn test_default_headers() { + let expected = if cfg!(target_os = "macos") { + vec![ + "Filesystem", + "1K-blocks", + "Used", + "Available", + "Capacity", + "Use%", + "Mounted", + "on", + ] + } else { + vec![ + "Filesystem", + "1K-blocks", + "Used", + "Available", + "Use%", + "Mounted", + "on", + ] + }; + let output = new_ucmd!().succeeds().stdout_move_str(); + let actual = output.lines().take(1).collect::>()[0]; + let actual = actual.split_whitespace().collect::>(); + assert_eq!(actual, expected); +} + +#[test] +fn test_precedence_of_human_readable_header_over_output_header() { + let output = new_ucmd!() + .args(&["-H", "--output=size"]) + .succeeds() + .stdout_move_str(); + let header = output.lines().next().unwrap().to_string(); + assert_eq!(header.trim(), "Size"); +} + #[test] fn test_total_option_with_single_dash() { // These should fail because `-total` should have two dashes, @@ -443,6 +483,31 @@ fn test_block_size_with_suffix() { assert_eq!(get_header("1GB"), "1GB-blocks"); } +#[test] +fn test_block_size_in_posix_portability_mode() { + fn get_header(block_size: &str) -> String { + let output = new_ucmd!() + .args(&["-P", "-B", block_size]) + .succeeds() + .stdout_move_str(); + output + .lines() + .next() + .unwrap() + .to_string() + .split_whitespace() + .nth(1) + .unwrap() + .to_string() + } + + assert_eq!(get_header("1024"), "1024-blocks"); + assert_eq!(get_header("1K"), "1024-blocks"); + assert_eq!(get_header("1KB"), "1000-blocks"); + assert_eq!(get_header("1M"), "1048576-blocks"); + assert_eq!(get_header("1MB"), "1000000-blocks"); +} + #[test] fn test_too_large_block_size() { fn run_command(size: &str) {