From ec048b3857bddeca0e14c8ddb5e763191f8e1ac0 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Mon, 21 Feb 2022 19:54:12 -0500 Subject: [PATCH] df: implement the --output command-line argument Implement the `--output` command-line argument, which allows specifying an exact sequence of columns to display in the output table. For example, $ df --output=source,fstype | head -n3 Filesystem Type udev devtmpfs tmpfs tmpfs (The spacing does not exactly match the spacing of GNU `df` yet.) Fixes #3057. --- src/uu/df/src/columns.rs | 67 +++++++++++++++++++++++++++++++++++++--- tests/by-util/test_df.rs | 29 +++++++++++++++++ 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/src/uu/df/src/columns.rs b/src/uu/df/src/columns.rs index f9b4c4f2b..89dd35220 100644 --- a/src/uu/df/src/columns.rs +++ b/src/uu/df/src/columns.rs @@ -3,7 +3,7 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. // spell-checker:ignore itotal iused iavail ipcent pcent squashfs -use crate::{OPT_INODES, OPT_PRINT_TYPE}; +use crate::{OPT_INODES, OPT_OUTPUT, OPT_PRINT_TYPE}; use clap::ArgMatches; /// The columns in the output table produced by `df`. @@ -65,8 +65,9 @@ impl Column { match ( matches.is_present(OPT_PRINT_TYPE), matches.is_present(OPT_INODES), + matches.occurrences_of(OPT_OUTPUT) > 0, ) { - (false, false) => vec![ + (false, false, false) => vec![ Self::Source, Self::Size, Self::Used, @@ -76,7 +77,20 @@ impl Column { Self::Pcent, Self::Target, ], - (false, true) => vec![ + (false, false, true) => { + matches + .values_of(OPT_OUTPUT) + .unwrap() + .map(|s| { + // Unwrapping here should not panic because the + // command-line argument parsing library should be + // responsible for ensuring each comma-separated + // string is a valid column label. + Self::parse(s).unwrap() + }) + .collect() + } + (false, true, false) => vec![ Self::Source, Self::Itotal, Self::Iused, @@ -84,7 +98,7 @@ impl Column { Self::Ipcent, Self::Target, ], - (true, false) => vec![ + (true, false, false) => vec![ Self::Source, Self::Fstype, Self::Size, @@ -95,7 +109,7 @@ impl Column { Self::Pcent, Self::Target, ], - (true, true) => vec![ + (true, true, false) => vec![ Self::Source, Self::Fstype, Self::Itotal, @@ -104,6 +118,49 @@ impl Column { Self::Ipcent, Self::Target, ], + // The command-line arguments -T and -i are each mutually + // exclusive with --output, so the command-line argument + // parser should reject those combinations before we get + // to this point in the code. + _ => unreachable!(), + } + } + + /// Convert a column name to the corresponding enumeration variant. + /// + /// There are twelve valid column names, one for each variant: + /// + /// - "source" + /// - "fstype" + /// - "itotal" + /// - "iused" + /// - "iavail" + /// - "ipcent" + /// - "size" + /// - "used" + /// - "avail" + /// - "pcent" + /// - "file" + /// - "target" + /// + /// # Errors + /// + /// If the string `s` is not one of the valid column names. + fn parse(s: &str) -> Result { + match s { + "source" => Ok(Self::Source), + "fstype" => Ok(Self::Fstype), + "itotal" => Ok(Self::Itotal), + "iused" => Ok(Self::Iused), + "iavail" => Ok(Self::Iavail), + "ipcent" => Ok(Self::Ipcent), + "size" => Ok(Self::Size), + "used" => Ok(Self::Used), + "avail" => Ok(Self::Avail), + "pcent" => Ok(Self::Pcent), + "file" => Ok(Self::File), + "target" => Ok(Self::Target), + _ => Err(()), } } } diff --git a/tests/by-util/test_df.rs b/tests/by-util/test_df.rs index 8f8474901..5d99b648b 100644 --- a/tests/by-util/test_df.rs +++ b/tests/by-util/test_df.rs @@ -183,4 +183,33 @@ fn test_block_size_1024() { assert_eq!(get_header(34 * 1024 * 1024 * 1024), "34G-blocks"); } +// TODO The spacing does not match GNU df. Also we need to remove +// trailing spaces from the heading row. +#[test] +fn test_output_selects_columns() { + let output = new_ucmd!() + .args(&["--output=source"]) + .succeeds() + .stdout_move_str(); + assert_eq!(output.lines().next().unwrap(), "Filesystem "); + + let output = new_ucmd!() + .args(&["--output=source,target"]) + .succeeds() + .stdout_move_str(); + assert_eq!( + output.lines().next().unwrap(), + "Filesystem Mounted on " + ); + + let output = new_ucmd!() + .args(&["--output=source,target,used"]) + .succeeds() + .stdout_move_str(); + assert_eq!( + output.lines().next().unwrap(), + "Filesystem Mounted on Used " + ); +} + // ToDO: more tests...