mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 11:07:44 +00:00
Merge pull request #4977 from cakebaker/date_shortcut_value_parser
date: use custom value parser
This commit is contained in:
commit
9d44d8b71d
4 changed files with 153 additions and 9 deletions
|
@ -26,14 +26,13 @@ use uucore::{format_usage, help_about, help_usage, show};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use windows_sys::Win32::{Foundation::SYSTEMTIME, System::SystemInformation::SetSystemTime};
|
use windows_sys::Win32::{Foundation::SYSTEMTIME, System::SystemInformation::SetSystemTime};
|
||||||
|
|
||||||
|
use uucore::shortcut_value_parser::ShortcutValueParser;
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
const DATE: &str = "date";
|
const DATE: &str = "date";
|
||||||
const HOURS: &str = "hours";
|
const HOURS: &str = "hours";
|
||||||
const MINUTES: &str = "minutes";
|
const MINUTES: &str = "minutes";
|
||||||
const SECONDS: &str = "seconds";
|
const SECONDS: &str = "seconds";
|
||||||
const HOUR: &str = "hour";
|
|
||||||
const MINUTE: &str = "minute";
|
|
||||||
const SECOND: &str = "second";
|
|
||||||
const NS: &str = "ns";
|
const NS: &str = "ns";
|
||||||
|
|
||||||
const ABOUT: &str = help_about!("date.md");
|
const ABOUT: &str = help_about!("date.md");
|
||||||
|
@ -110,9 +109,9 @@ enum Iso8601Format {
|
||||||
impl<'a> From<&'a str> for Iso8601Format {
|
impl<'a> From<&'a str> for Iso8601Format {
|
||||||
fn from(s: &str) -> Self {
|
fn from(s: &str) -> Self {
|
||||||
match s {
|
match s {
|
||||||
HOURS | HOUR => Self::Hours,
|
HOURS => Self::Hours,
|
||||||
MINUTES | MINUTE => Self::Minutes,
|
MINUTES => Self::Minutes,
|
||||||
SECONDS | SECOND => Self::Seconds,
|
SECONDS => Self::Seconds,
|
||||||
NS => Self::Ns,
|
NS => Self::Ns,
|
||||||
DATE => Self::Date,
|
DATE => Self::Date,
|
||||||
// Note: This is caught by clap via `possible_values`
|
// Note: This is caught by clap via `possible_values`
|
||||||
|
@ -131,7 +130,7 @@ impl<'a> From<&'a str> for Rfc3339Format {
|
||||||
fn from(s: &str) -> Self {
|
fn from(s: &str) -> Self {
|
||||||
match s {
|
match s {
|
||||||
DATE => Self::Date,
|
DATE => Self::Date,
|
||||||
SECONDS | SECOND => Self::Seconds,
|
SECONDS => Self::Seconds,
|
||||||
NS => Self::Ns,
|
NS => Self::Ns,
|
||||||
// Should be caught by clap
|
// Should be caught by clap
|
||||||
_ => panic!("Invalid format: {s}"),
|
_ => panic!("Invalid format: {s}"),
|
||||||
|
@ -317,7 +316,9 @@ pub fn uu_app() -> Command {
|
||||||
.short('I')
|
.short('I')
|
||||||
.long(OPT_ISO_8601)
|
.long(OPT_ISO_8601)
|
||||||
.value_name("FMT")
|
.value_name("FMT")
|
||||||
.value_parser([DATE, HOUR, HOURS, MINUTE, MINUTES, SECOND, SECONDS, NS])
|
.value_parser(ShortcutValueParser::new([
|
||||||
|
DATE, HOURS, MINUTES, SECONDS, NS,
|
||||||
|
]))
|
||||||
.num_args(0..=1)
|
.num_args(0..=1)
|
||||||
.default_missing_value(OPT_DATE)
|
.default_missing_value(OPT_DATE)
|
||||||
.help(ISO_8601_HELP_STRING),
|
.help(ISO_8601_HELP_STRING),
|
||||||
|
@ -333,7 +334,7 @@ pub fn uu_app() -> Command {
|
||||||
Arg::new(OPT_RFC_3339)
|
Arg::new(OPT_RFC_3339)
|
||||||
.long(OPT_RFC_3339)
|
.long(OPT_RFC_3339)
|
||||||
.value_name("FMT")
|
.value_name("FMT")
|
||||||
.value_parser([DATE, SECOND, SECONDS, NS])
|
.value_parser(ShortcutValueParser::new([DATE, SECONDS, NS]))
|
||||||
.help(RFC_3339_HELP_STRING),
|
.help(RFC_3339_HELP_STRING),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
|
|
|
@ -33,6 +33,7 @@ pub use crate::mods::version_cmp;
|
||||||
pub use crate::parser::parse_glob;
|
pub use crate::parser::parse_glob;
|
||||||
pub use crate::parser::parse_size;
|
pub use crate::parser::parse_size;
|
||||||
pub use crate::parser::parse_time;
|
pub use crate::parser::parse_time;
|
||||||
|
pub use crate::parser::shortcut_value_parser;
|
||||||
|
|
||||||
// * feature-gated modules
|
// * feature-gated modules
|
||||||
#[cfg(feature = "encoding")]
|
#[cfg(feature = "encoding")]
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
pub mod parse_glob;
|
pub mod parse_glob;
|
||||||
pub mod parse_size;
|
pub mod parse_size;
|
||||||
pub mod parse_time;
|
pub mod parse_time;
|
||||||
|
pub mod shortcut_value_parser;
|
||||||
|
|
141
src/uucore/src/lib/parser/shortcut_value_parser.rs
Normal file
141
src/uucore/src/lib/parser/shortcut_value_parser.rs
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
use clap::{
|
||||||
|
builder::{PossibleValue, TypedValueParser},
|
||||||
|
error::{ContextKind, ContextValue, ErrorKind},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ShortcutValueParser(Vec<PossibleValue>);
|
||||||
|
|
||||||
|
/// `ShortcutValueParser` is similar to clap's `PossibleValuesParser`: it verifies that the value is
|
||||||
|
/// from an enumerated set of `PossibleValue`.
|
||||||
|
///
|
||||||
|
/// Whereas `PossibleValuesParser` only accepts exact matches, `ShortcutValueParser` also accepts
|
||||||
|
/// shortcuts as long as they are unambiguous.
|
||||||
|
impl ShortcutValueParser {
|
||||||
|
pub fn new(values: impl Into<Self>) -> Self {
|
||||||
|
values.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypedValueParser for ShortcutValueParser {
|
||||||
|
type Value = String;
|
||||||
|
|
||||||
|
fn parse_ref(
|
||||||
|
&self,
|
||||||
|
cmd: &clap::Command,
|
||||||
|
arg: Option<&clap::Arg>,
|
||||||
|
value: &std::ffi::OsStr,
|
||||||
|
) -> Result<Self::Value, clap::Error> {
|
||||||
|
let value = value
|
||||||
|
.to_str()
|
||||||
|
.ok_or(clap::Error::new(ErrorKind::InvalidUtf8))?;
|
||||||
|
|
||||||
|
let matched_values: Vec<_> = self
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.filter(|x| x.get_name().starts_with(value))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if matched_values.len() == 1 {
|
||||||
|
Ok(matched_values[0].get_name().to_string())
|
||||||
|
} else {
|
||||||
|
let mut err = clap::Error::new(ErrorKind::InvalidValue).with_cmd(cmd);
|
||||||
|
|
||||||
|
if let Some(arg) = arg {
|
||||||
|
err.insert(
|
||||||
|
ContextKind::InvalidArg,
|
||||||
|
ContextValue::String(arg.to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
err.insert(
|
||||||
|
ContextKind::InvalidValue,
|
||||||
|
ContextValue::String(value.to_string()),
|
||||||
|
);
|
||||||
|
|
||||||
|
err.insert(
|
||||||
|
ContextKind::ValidValue,
|
||||||
|
ContextValue::Strings(self.0.iter().map(|x| x.get_name().to_string()).collect()),
|
||||||
|
);
|
||||||
|
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
|
||||||
|
Some(Box::new(self.0.iter().cloned()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, T> From<I> for ShortcutValueParser
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = T>,
|
||||||
|
T: Into<PossibleValue>,
|
||||||
|
{
|
||||||
|
fn from(values: I) -> Self {
|
||||||
|
Self(values.into_iter().map(|t| t.into()).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
|
||||||
|
use clap::{builder::TypedValueParser, error::ErrorKind, Command};
|
||||||
|
|
||||||
|
use super::ShortcutValueParser;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_ref() {
|
||||||
|
let cmd = Command::new("cmd");
|
||||||
|
let parser = ShortcutValueParser::new(["abcd"]);
|
||||||
|
let values = ["a", "ab", "abc", "abcd"];
|
||||||
|
|
||||||
|
for value in values {
|
||||||
|
let result = parser.parse_ref(&cmd, None, OsStr::new(value));
|
||||||
|
assert_eq!("abcd", result.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_ref_with_invalid_value() {
|
||||||
|
let cmd = Command::new("cmd");
|
||||||
|
let parser = ShortcutValueParser::new(["abcd"]);
|
||||||
|
let invalid_values = ["e", "abe", "abcde"];
|
||||||
|
|
||||||
|
for invalid_value in invalid_values {
|
||||||
|
let result = parser.parse_ref(&cmd, None, OsStr::new(invalid_value));
|
||||||
|
assert_eq!(ErrorKind::InvalidValue, result.unwrap_err().kind());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_ref_with_ambiguous_value() {
|
||||||
|
let cmd = Command::new("cmd");
|
||||||
|
let parser = ShortcutValueParser::new(["abcd", "abef"]);
|
||||||
|
let ambiguous_values = ["a", "ab"];
|
||||||
|
|
||||||
|
for ambiguous_value in ambiguous_values {
|
||||||
|
let result = parser.parse_ref(&cmd, None, OsStr::new(ambiguous_value));
|
||||||
|
assert_eq!(ErrorKind::InvalidValue, result.unwrap_err().kind());
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = parser.parse_ref(&cmd, None, OsStr::new("abc"));
|
||||||
|
assert_eq!("abcd", result.unwrap());
|
||||||
|
|
||||||
|
let result = parser.parse_ref(&cmd, None, OsStr::new("abe"));
|
||||||
|
assert_eq!("abef", result.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn test_parse_ref_with_invalid_utf8() {
|
||||||
|
use std::os::unix::prelude::OsStrExt;
|
||||||
|
|
||||||
|
let parser = ShortcutValueParser::new(["abcd"]);
|
||||||
|
let cmd = Command::new("cmd");
|
||||||
|
|
||||||
|
let result = parser.parse_ref(&cmd, None, OsStr::from_bytes(&[0xc3 as u8, 0x28 as u8]));
|
||||||
|
assert_eq!(ErrorKind::InvalidUtf8, result.unwrap_err().kind());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue