1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge pull request #3322 from jfinkels/df-multiple-columns-error

df: error on duplicate columns in --output arg
This commit is contained in:
Sylvestre Ledru 2022-03-29 22:30:14 +02:00 committed by GitHub
commit 05ec34eb94
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 85 additions and 24 deletions

View file

@ -55,19 +55,31 @@ pub(crate) enum Column {
Capacity, Capacity,
} }
/// An error while defining which columns to display in the output table.
#[derive(Debug)]
pub(crate) enum ColumnError {
/// If a column appears more than once in the `--output` argument.
MultipleColumns(String),
}
impl Column { impl Column {
/// Convert from command-line arguments to sequence of columns. /// Convert from command-line arguments to sequence of columns.
/// ///
/// The set of columns that will appear in the output table can be /// The set of columns that will appear in the output table can be
/// specified by command-line arguments. This function converts /// specified by command-line arguments. This function converts
/// those arguments to a [`Vec`] of [`Column`] variants. /// those arguments to a [`Vec`] of [`Column`] variants.
pub(crate) fn from_matches(matches: &ArgMatches) -> Vec<Self> { ///
/// # Errors
///
/// This function returns an error if a column is specified more
/// than once in the command-line argument.
pub(crate) fn from_matches(matches: &ArgMatches) -> Result<Vec<Self>, ColumnError> {
match ( match (
matches.is_present(OPT_PRINT_TYPE), matches.is_present(OPT_PRINT_TYPE),
matches.is_present(OPT_INODES), matches.is_present(OPT_INODES),
matches.occurrences_of(OPT_OUTPUT) > 0, matches.occurrences_of(OPT_OUTPUT) > 0,
) { ) {
(false, false, false) => vec![ (false, false, false) => Ok(vec![
Self::Source, Self::Source,
Self::Size, Self::Size,
Self::Used, Self::Used,
@ -76,29 +88,37 @@ impl Column {
Self::Capacity, Self::Capacity,
Self::Pcent, Self::Pcent,
Self::Target, Self::Target,
], ]),
(false, false, true) => { (false, false, true) => {
matches // Unwrapping should not panic because in this arm of
.values_of(OPT_OUTPUT) // the `match` statement, we know that `OPT_OUTPUT`
.unwrap() // is non-empty.
.map(|s| { let names = matches.values_of(OPT_OUTPUT).unwrap();
// Unwrapping here should not panic because the let mut seen: Vec<&str> = vec![];
// command-line argument parsing library should be let mut columns = vec![];
// responsible for ensuring each comma-separated for name in names {
// string is a valid column label. if seen.contains(&name) {
Self::parse(s).unwrap() return Err(ColumnError::MultipleColumns(name.to_string()));
}) }
.collect() seen.push(name);
// 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.
let column = Self::parse(name).unwrap();
columns.push(column);
}
Ok(columns)
} }
(false, true, false) => vec![ (false, true, false) => Ok(vec![
Self::Source, Self::Source,
Self::Itotal, Self::Itotal,
Self::Iused, Self::Iused,
Self::Iavail, Self::Iavail,
Self::Ipcent, Self::Ipcent,
Self::Target, Self::Target,
], ]),
(true, false, false) => vec![ (true, false, false) => Ok(vec![
Self::Source, Self::Source,
Self::Fstype, Self::Fstype,
Self::Size, Self::Size,
@ -108,8 +128,8 @@ impl Column {
Self::Capacity, Self::Capacity,
Self::Pcent, Self::Pcent,
Self::Target, Self::Target,
], ]),
(true, true, false) => vec![ (true, true, false) => Ok(vec![
Self::Source, Self::Source,
Self::Fstype, Self::Fstype,
Self::Itotal, Self::Itotal,
@ -117,7 +137,7 @@ impl Column {
Self::Iavail, Self::Iavail,
Self::Ipcent, Self::Ipcent,
Self::Target, Self::Target,
], ]),
// The command-line arguments -T and -i are each mutually // The command-line arguments -T and -i are each mutually
// exclusive with --output, so the command-line argument // exclusive with --output, so the command-line argument
// parser should reject those combinations before we get // parser should reject those combinations before we get

View file

@ -11,17 +11,19 @@ mod columns;
mod filesystem; mod filesystem;
mod table; mod table;
use uucore::error::{UResult, USimpleError}; use uucore::display::Quotable;
use uucore::error::{UError, UResult};
use uucore::format_usage; use uucore::format_usage;
use uucore::fsext::{read_fs_list, MountInfo}; use uucore::fsext::{read_fs_list, MountInfo};
use clap::{crate_version, Arg, ArgMatches, Command}; use clap::{crate_version, Arg, ArgMatches, Command};
use std::error::Error;
use std::fmt; use std::fmt;
use std::path::Path; use std::path::Path;
use crate::blocks::{block_size_from_matches, BlockSize}; use crate::blocks::{block_size_from_matches, BlockSize};
use crate::columns::Column; use crate::columns::{Column, ColumnError};
use crate::filesystem::Filesystem; use crate::filesystem::Filesystem;
use crate::table::{DisplayRow, Header, Row}; use crate::table::{DisplayRow, Header, Row};
@ -103,8 +105,12 @@ impl Default for Options {
} }
} }
#[derive(Debug)]
enum OptionsError { enum OptionsError {
InvalidBlockSize, InvalidBlockSize,
/// An error getting the columns to display in the output table.
ColumnError(ColumnError),
} }
impl fmt::Display for OptionsError { impl fmt::Display for OptionsError {
@ -115,6 +121,11 @@ impl fmt::Display for OptionsError {
// TODO This needs to vary based on whether `--block-size` // TODO This needs to vary based on whether `--block-size`
// or `-B` were provided. // or `-B` were provided.
Self::InvalidBlockSize => write!(f, "invalid --block-size argument"), Self::InvalidBlockSize => write!(f, "invalid --block-size argument"),
Self::ColumnError(ColumnError::MultipleColumns(s)) => write!(
f,
"option --output: field {} used more than once",
s.quote()
),
} }
} }
} }
@ -131,7 +142,7 @@ impl Options {
include: matches.values_of_lossy(OPT_TYPE), include: matches.values_of_lossy(OPT_TYPE),
exclude: matches.values_of_lossy(OPT_EXCLUDE_TYPE), exclude: matches.values_of_lossy(OPT_EXCLUDE_TYPE),
show_total: matches.is_present(OPT_TOTAL), show_total: matches.is_present(OPT_TOTAL),
columns: Column::from_matches(matches), columns: Column::from_matches(matches).map_err(OptionsError::ColumnError)?,
}) })
} }
} }
@ -273,6 +284,28 @@ where
.collect() .collect()
} }
#[derive(Debug)]
enum DfError {
/// A problem while parsing command-line options.
OptionsError(OptionsError),
}
impl Error for DfError {}
impl UError for DfError {
fn usage(&self) -> bool {
matches!(self, Self::OptionsError(OptionsError::ColumnError(_)))
}
}
impl fmt::Display for DfError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::OptionsError(e) => e.fmt(f),
}
}
}
#[uucore::main] #[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().get_matches_from(args); let matches = uu_app().get_matches_from(args);
@ -284,7 +317,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
} }
let opt = Options::from(&matches).map_err(|e| USimpleError::new(1, format!("{}", e)))?; let opt = Options::from(&matches).map_err(DfError::OptionsError)?;
// Get the list of filesystems to display in the output table. // Get the list of filesystems to display in the output table.
let filesystems: Vec<Filesystem> = match matches.values_of(OPT_PATHS) { let filesystems: Vec<Filesystem> = match matches.values_of(OPT_PATHS) {

View file

@ -272,3 +272,11 @@ fn test_output_file_specific_files() {
] ]
); );
} }
#[test]
fn test_output_field_no_more_than_once() {
new_ucmd!()
.arg("--output=target,source,target")
.fails()
.usage_error("option --output: field 'target' used more than once");
}