mirror of
https://github.com/uutils/coreutils
synced 2024-12-13 23:02:38 +00:00
uucore: introduce ShortcutValueParser
This commit is contained in:
parent
468af7dc16
commit
24b979c821
3 changed files with 143 additions and 0 deletions
|
@ -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")]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod parse_glob;
|
||||
pub mod parse_size;
|
||||
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…
Reference in a new issue