103 lines
3.0 KiB
Rust
103 lines
3.0 KiB
Rust
//! Extensions to clap.
|
|
|
|
use std::{collections::HashMap, str::FromStr};
|
|
|
|
/// An error that occurred while parsing a base value or its
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum ValueParseError {
|
|
/// No value was given; the required type could not be parsed.
|
|
#[error("No value was given")]
|
|
NoValue,
|
|
|
|
/// The first value could not properly be parsed.
|
|
#[error("Could not parse first value: {0}")]
|
|
BadParse(String),
|
|
|
|
/// Additional values were added, but not in a key=value format.
|
|
#[error("A key-value pair was not given")]
|
|
BadKeyValue,
|
|
}
|
|
|
|
/// A helper struct to parse key-value arguments, without any prior argument.
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct Options {
|
|
/// The values provided.
|
|
pub values: HashMap<String, String>,
|
|
}
|
|
|
|
impl std::fmt::Display for Options {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let mut iter = self.values.iter().peekable();
|
|
while let Some((key, value)) = iter.next() {
|
|
write!(f, "{key}={value}")?;
|
|
if iter.peek().is_some() {
|
|
write!(f, ",")?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl FromStr for Options {
|
|
type Err = ValueParseError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
if s.is_empty() {
|
|
return Ok(Default::default())
|
|
}
|
|
let values = s
|
|
.split(',')
|
|
.map(|value| {
|
|
let [k, v] = value
|
|
.splitn(2, '=')
|
|
.collect::<Vec<_>>()
|
|
.try_into()
|
|
.map_err(|_| ValueParseError::BadKeyValue)?;
|
|
Ok((k.to_string(), v.to_string()))
|
|
})
|
|
.collect::<Result<HashMap<String, String>, ValueParseError>>()?;
|
|
Ok(Self { values })
|
|
}
|
|
}
|
|
|
|
/// A helper struct for clap arguments that can contain additional arguments. For example:
|
|
/// `keyfork mnemonic generate --encrypt-to cert.asc,output=encrypted.asc`.
|
|
#[derive(Clone, Debug)]
|
|
pub struct ValueWithOptions<T: FromStr>
|
|
where
|
|
T::Err: std::error::Error,
|
|
{
|
|
/// A mapping between keys and values.
|
|
pub values: HashMap<String, String>,
|
|
|
|
/// The first variable for the argument, such as a [`PathBuf`].
|
|
pub inner: T,
|
|
}
|
|
|
|
impl<T: std::str::FromStr> FromStr for ValueWithOptions<T>
|
|
where
|
|
<T as FromStr>::Err: std::error::Error,
|
|
{
|
|
type Err = ValueParseError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
let mut values = s.split(',');
|
|
let first = values.next().ok_or(ValueParseError::NoValue)?;
|
|
let mut others = HashMap::new();
|
|
for value in values {
|
|
let [lhs, rhs] = value
|
|
.splitn(2, '=')
|
|
.collect::<Vec<_>>()
|
|
.try_into()
|
|
.map_err(|_| ValueParseError::BadKeyValue)?;
|
|
others.insert(lhs.to_string(), rhs.to_string());
|
|
}
|
|
Ok(Self {
|
|
inner: first
|
|
.parse()
|
|
.map_err(|e: <T as FromStr>::Err| ValueParseError::BadParse(e.to_string()))?,
|
|
values: others,
|
|
})
|
|
}
|
|
}
|