1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 19:47:45 +00:00

df: implement POSIX conform header line

It also fixes #3195
This commit is contained in:
Daniel Hofstetter 2022-05-10 07:34:01 +02:00 committed by Sylvestre Ledru
parent 0ebd9c9391
commit e26fed61b3
4 changed files with 147 additions and 19 deletions

View file

@ -167,6 +167,15 @@ pub(crate) enum BlockSize {
Bytes(u64), 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 { impl Default for BlockSize {
fn default() -> Self { fn default() -> Self {
if env::var("POSIXLY_CORRECT").is_ok() { if env::var("POSIXLY_CORRECT").is_ok() {

View file

@ -12,6 +12,7 @@ mod filesystem;
mod table; mod table;
use blocks::{HumanReadable, SizeFormat}; use blocks::{HumanReadable, SizeFormat};
use table::HeaderMode;
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::{UError, UResult, USimpleError}; use uucore::error::{UError, UResult, USimpleError};
use uucore::fsext::{read_fs_list, MountInfo}; use uucore::fsext::{read_fs_list, MountInfo};
@ -72,6 +73,7 @@ struct Options {
show_all_fs: bool, show_all_fs: bool,
size_format: SizeFormat, size_format: SizeFormat,
block_size: BlockSize, block_size: BlockSize,
header_mode: HeaderMode,
/// Optional list of filesystem types to include in the output table. /// Optional list of filesystem types to include in the output table.
/// ///
@ -99,6 +101,7 @@ impl Default for Options {
show_all_fs: Default::default(), show_all_fs: Default::default(),
block_size: Default::default(), block_size: Default::default(),
size_format: Default::default(), size_format: Default::default(),
header_mode: Default::default(),
include: Default::default(), include: Default::default(),
exclude: Default::default(), exclude: Default::default(),
show_total: Default::default(), show_total: Default::default(),
@ -176,6 +179,21 @@ impl Options {
), ),
ParseSizeError::ParseFailure(s) => OptionsError::InvalidBlockSize(s), 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: { size_format: {
if matches.is_present(OPT_HUMAN_READABLE_BINARY) { if matches.is_present(OPT_HUMAN_READABLE_BINARY) {
SizeFormat::HumanReadable(HumanReadable::Binary) SizeFormat::HumanReadable(HumanReadable::Binary)

View file

@ -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. /// The data of the header row.
struct Header {} struct Header {}
@ -302,15 +319,22 @@ impl Header {
for column in &options.columns { for column in &options.columns {
let header = match column { let header = match column {
Column::Source => String::from("Filesystem"), Column::Source => String::from("Filesystem"),
Column::Size => match options.size_format { Column::Size => match options.header_mode {
SizeFormat::HumanReadable(_) => String::from("Size"), HeaderMode::HumanReadable => String::from("Size"),
SizeFormat::StaticBlockSize => { HeaderMode::PosixPortability => {
format!("{}-blocks", options.block_size) format!("{}-blocks", options.block_size.as_u64())
} }
_ => format!("{}-blocks", options.block_size),
}, },
Column::Used => String::from("Used"), Column::Used => String::from("Used"),
Column::Avail => String::from("Available"), Column::Avail => match options.header_mode {
Column::Pcent => String::from("Use%"), 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::Target => String::from("Mounted on"),
Column::Itotal => String::from("Inodes"), Column::Itotal => String::from("Inodes"),
Column::Iused => String::from("IUsed"), Column::Iused => String::from("IUsed"),
@ -428,7 +452,7 @@ mod tests {
use crate::blocks::{HumanReadable, SizeFormat}; use crate::blocks::{HumanReadable, SizeFormat};
use crate::columns::Column; use crate::columns::Column;
use crate::table::{Header, Row, RowFormatter}; use crate::table::{Header, HeaderMode, Row, RowFormatter};
use crate::{BlockSize, Options}; use crate::{BlockSize, Options};
const COLUMNS_WITH_FS_TYPE: [Column; 7] = [ const COLUMNS_WITH_FS_TYPE: [Column; 7] = [
@ -548,37 +572,49 @@ mod tests {
} }
#[test] #[test]
fn test_header_with_human_readable_binary() { fn test_human_readable_header() {
let options = Options { 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() ..Default::default()
}; };
assert_eq!( assert_eq!(
Header::get_headers(&options), Header::get_headers(&options),
vec!( vec!(
"Filesystem", "Filesystem",
"Size", "1024-blocks",
"Used", "Used",
"Available", "Available",
"Use%", "Capacity",
"Mounted on" "Mounted on"
) )
); );
} }
#[test] #[test]
fn test_header_with_human_readable_si() { fn test_output_header() {
let options = Options { let options = Options {
size_format: SizeFormat::HumanReadable(HumanReadable::Decimal), header_mode: HeaderMode::Output,
..Default::default() ..Default::default()
}; };
assert_eq!( assert_eq!(
Header::get_headers(&options), Header::get_headers(&options),
vec!( vec!(
"Filesystem", "Filesystem",
"Size", "1K-blocks",
"Used", "Used",
"Available", "Avail",
"Use%", "Use%",
"Mounted on" "Mounted on"
) )

View file

@ -73,7 +73,7 @@ fn test_df_output() {
"Filesystem", "Filesystem",
"Size", "Size",
"Used", "Used",
"Available", "Avail",
"Capacity", "Capacity",
"Use%", "Use%",
"Mounted", "Mounted",
@ -84,7 +84,7 @@ fn test_df_output() {
"Filesystem", "Filesystem",
"Size", "Size",
"Used", "Used",
"Available", "Avail",
"Use%", "Use%",
"Mounted", "Mounted",
"on", "on",
@ -107,7 +107,7 @@ fn test_df_output_overridden() {
"Filesystem", "Filesystem",
"Size", "Size",
"Used", "Used",
"Available", "Avail",
"Capacity", "Capacity",
"Use%", "Use%",
"Mounted", "Mounted",
@ -118,7 +118,7 @@ fn test_df_output_overridden() {
"Filesystem", "Filesystem",
"Size", "Size",
"Used", "Used",
"Available", "Avail",
"Use%", "Use%",
"Mounted", "Mounted",
"on", "on",
@ -134,6 +134,46 @@ fn test_df_output_overridden() {
assert_eq!(actual, expected); 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::<Vec<&str>>()[0];
let actual = actual.split_whitespace().collect::<Vec<_>>();
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] #[test]
fn test_total_option_with_single_dash() { fn test_total_option_with_single_dash() {
// These should fail because `-total` should have two dashes, // 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"); 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] #[test]
fn test_too_large_block_size() { fn test_too_large_block_size() {
fn run_command(size: &str) { fn run_command(size: &str) {