uucore: introduce ShortcutValueParser

This commit is contained in:
Daniel Hofstetter 2023-06-14 10:35:13 +02:00
parent 468af7dc16
commit 24b979c821
3 changed files with 143 additions and 0 deletions

View file

@ -33,6 +33,7 @@ pub use crate::mods::version_cmp;
pub use crate::parser::parse_glob;
pub use crate::parser::parse_size;
pub use crate::parser::parse_time;
pub use crate::parser::shortcut_value_parser;
// * feature-gated modules
#[cfg(feature = "encoding")]

View file

@ -1,3 +1,4 @@
pub mod parse_glob;
pub mod parse_size;
pub mod parse_time;
pub mod shortcut_value_parser;

View 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());
}
}