use crate::api::{CliArgument, CliOption, GenericCapturable, Scalar};
use crate::matcher::{ArgumentConfig, Bound, OptionConfig};
use crate::model::Nargs;
use crate::parser::{
AnonymousCapturable, ArgumentCapture, ArgumentParameter, OptionCapture, OptionParameter,
ParseError,
};
use crate::prelude::Choices;
use std::collections::HashMap;
pub(crate) struct AnonymousCapture<'a, T: 'a> {
field: Box<dyn GenericCapturable<'a, T> + 'a>,
}
impl<'a, T> AnonymousCapture<'a, T> {
pub(crate) fn bind(field: impl GenericCapturable<'a, T> + 'a) -> Self {
Self {
field: Box::new(field),
}
}
}
impl<'a, T> AnonymousCapturable for AnonymousCapture<'a, T> {
fn matched(&mut self) {
self.field.matched();
}
fn capture(&mut self, value: &str) -> Result<(), ParseError> {
self.field.capture(value).map_err(ParseError::from)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum ParameterClass {
Opt,
Arg,
}
pub(super) struct ParameterInner<'a, T> {
class: ParameterClass,
field: AnonymousCapture<'a, T>,
nargs: Nargs,
name: String,
short: Option<char>,
help: Option<String>,
meta: Option<Vec<String>>,
choices: HashMap<String, String>,
}
impl<'a, T> ParameterInner<'a, T> {
pub(super) fn class(&self) -> ParameterClass {
self.class
}
}
impl<'a, T> std::fmt::Debug for ParameterInner<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let class = match &self.class {
ParameterClass::Opt => "Opt",
ParameterClass::Arg => "Arg",
};
let name = match &self.class {
ParameterClass::Opt => format!("--{n}", n = self.name),
ParameterClass::Arg => format!("{n}", n = self.name),
};
let short = match &self.class {
ParameterClass::Opt => match &self.short {
Some(s) => format!(" -{s},"),
None => "".to_string(),
},
ParameterClass::Arg => "".to_string(),
};
let help = if let Some(d) = &self.help {
format!(", {d}")
} else {
"".to_string()
};
write!(
f,
"{class}[{t}, {nargs}, {name},{short} {help}]",
t = std::any::type_name::<T>(),
nargs = self.nargs,
)
}
}
impl<'a, T> From<&ParameterInner<'a, T>> for OptionConfig {
fn from(value: &ParameterInner<'a, T>) -> Self {
OptionConfig::new(
value.name.clone(),
value.short.clone(),
Bound::from(value.nargs),
)
}
}
impl<'a, T> From<ParameterInner<'a, T>> for OptionCapture<'a> {
fn from(value: ParameterInner<'a, T>) -> Self {
let config = OptionConfig::from(&value);
let ParameterInner { field, .. } = value;
(config, Box::new(field))
}
}
impl<'a, T> From<&ParameterInner<'a, T>> for OptionParameter {
fn from(value: &ParameterInner<'a, T>) -> Self {
OptionParameter::new(
value.name.clone(),
value.short.clone(),
value.nargs,
value.help.clone(),
value.meta.clone(),
value.choices.clone(),
)
}
}
impl<'a, T> From<&ParameterInner<'a, T>> for ArgumentConfig {
fn from(value: &ParameterInner<'a, T>) -> Self {
ArgumentConfig::new(value.name.clone(), Bound::from(value.nargs))
}
}
impl<'a, T> From<ParameterInner<'a, T>> for ArgumentCapture<'a> {
fn from(value: ParameterInner<'a, T>) -> Self {
let config = ArgumentConfig::from(&value);
let ParameterInner { field, .. } = value;
(config, Box::new(field))
}
}
impl<'a, T> From<&ParameterInner<'a, T>> for ArgumentParameter {
fn from(value: &ParameterInner<'a, T>) -> Self {
ArgumentParameter::new(
value.name.clone(),
value.nargs,
value.help.clone(),
value.meta.clone(),
value.choices.clone(),
)
}
}
pub struct Condition<'a, T>(Parameter<'a, T>);
impl<'a, T: std::str::FromStr + std::fmt::Display> Condition<'a, T> {
pub fn new(value: Scalar<'a, T>, name: &'static str) -> Self {
Condition(Parameter::argument(value, name))
}
pub fn help(self, description: impl Into<String>) -> Self {
let inner = self.0;
Self(inner.help(description))
}
pub fn meta(self, description: Vec<impl Into<String>>) -> Self {
let inner = self.0;
Self(inner.meta(description))
}
pub(super) fn consume(self) -> Parameter<'a, T> {
self.0
}
}
impl<'a, T: std::str::FromStr + std::fmt::Display> Choices<T> for Condition<'a, T> {
fn choice(self, variant: T, description: impl Into<String>) -> Self {
let inner = self.0;
Self(inner.choice(variant, description))
}
}
pub struct Parameter<'a, T>(ParameterInner<'a, T>);
impl<'a, T> Parameter<'a, T> {
pub fn option(
field: impl GenericCapturable<'a, T> + CliOption + 'a,
name: impl Into<String>,
short: Option<char>,
) -> Self {
let nargs = field.nargs();
Self(ParameterInner {
class: ParameterClass::Opt,
field: AnonymousCapture::bind(field),
nargs,
name: name.into(),
short,
help: None,
meta: None,
choices: HashMap::default(),
})
}
pub fn argument(
field: impl GenericCapturable<'a, T> + CliArgument + 'a,
name: impl Into<String>,
) -> Self {
let nargs = field.nargs();
Self(ParameterInner {
class: ParameterClass::Arg,
field: AnonymousCapture::bind(field),
nargs,
name: name.into(),
short: None,
help: None,
meta: None,
choices: HashMap::default(),
})
}
pub fn help(self, description: impl Into<String>) -> Self {
let mut inner = self.0;
inner.help = Some(description.into());
Self(inner)
}
pub fn meta(self, descriptions: Vec<impl Into<String>>) -> Self {
let mut inner = self.0;
inner.meta = Some(descriptions.into_iter().map(|s| s.into()).collect());
Self(inner)
}
pub(super) fn name(&self) -> String {
self.0.name.clone()
}
pub(super) fn consume(self) -> ParameterInner<'a, T> {
self.0
}
}
impl<'a, T: std::fmt::Display> Choices<T> for Parameter<'a, T> {
fn choice(self, variant: T, description: impl Into<String>) -> Self {
let mut inner = self.0;
inner
.choices
.insert(variant.to_string(), description.into());
Self(inner)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::{Parameter, Switch};
#[test]
fn option() {
let mut flag: bool = false;
let option = Parameter::option(Switch::new(&mut flag, true), "flag", None).consume();
assert_eq!(option.class, ParameterClass::Opt);
assert_eq!(option.name, "flag");
assert_eq!(option.short, None);
assert_eq!(option.help, None);
assert_eq!(option.meta, None);
assert_eq!(option.choices, HashMap::default());
}
#[test]
fn option_short() {
let mut flag: bool = false;
let option = Parameter::option(Switch::new(&mut flag, true), "flag", Some('f')).consume();
assert_eq!(option.class, ParameterClass::Opt);
assert_eq!(option.name, "flag");
assert_eq!(option.short, Some('f'));
assert_eq!(option.help, None);
assert_eq!(option.meta, None);
assert_eq!(option.choices, HashMap::default());
}
#[test]
fn option_help() {
let mut flag: bool = false;
let option = Parameter::option(Switch::new(&mut flag, true), "flag", None)
.help("help message")
.consume();
assert_eq!(option.class, ParameterClass::Opt);
assert_eq!(option.name, "flag".to_string());
assert_eq!(option.short, None);
assert_eq!(option.help, Some("help message".to_string()));
assert_eq!(option.meta, None);
assert_eq!(option.choices, HashMap::default());
}
#[test]
fn option_meta() {
let mut flag: bool = false;
let option = Parameter::option(Switch::new(&mut flag, true), "flag", None)
.meta(vec!["meta message"])
.consume();
assert_eq!(option.class, ParameterClass::Opt);
assert_eq!(option.name, "flag".to_string());
assert_eq!(option.short, None);
assert_eq!(option.help, None);
assert_eq!(option.meta, Some(vec!["meta message".to_string()]));
assert_eq!(option.choices, HashMap::default());
}
#[test]
fn option_choice() {
let mut flag: bool = false;
let option = Parameter::option(Switch::new(&mut flag, true), "flag", None)
.choice(true, "b")
.choice(false, "d")
.choice(true, "e")
.consume();
assert_eq!(option.class, ParameterClass::Opt);
assert_eq!(option.name, "flag".to_string());
assert_eq!(option.short, None);
assert_eq!(option.help, None);
assert_eq!(option.meta, None);
assert_eq!(
option.choices,
HashMap::from([
("true".to_string(), "e".to_string()),
("false".to_string(), "d".to_string())
])
);
}
#[test]
fn argument() {
let mut item: bool = false;
let argument = Parameter::argument(Scalar::new(&mut item), "item").consume();
assert_eq!(argument.class, ParameterClass::Arg);
assert_eq!(argument.name, "item".to_string());
assert_eq!(argument.short, None);
assert_eq!(argument.help, None);
assert_eq!(argument.meta, None);
assert_eq!(argument.choices, HashMap::default());
}
#[test]
fn argument_help() {
let mut item: bool = false;
let argument = Parameter::argument(Scalar::new(&mut item), "item")
.help("help message")
.consume();
assert_eq!(argument.class, ParameterClass::Arg);
assert_eq!(argument.name, "item".to_string());
assert_eq!(argument.short, None);
assert_eq!(argument.help, Some("help message".to_string()));
assert_eq!(argument.meta, None);
assert_eq!(argument.choices, HashMap::default());
}
#[test]
fn argument_meta() {
let mut item: bool = false;
let argument = Parameter::argument(Scalar::new(&mut item), "item")
.meta(vec!["meta message"])
.consume();
assert_eq!(argument.class, ParameterClass::Arg);
assert_eq!(argument.name, "item".to_string());
assert_eq!(argument.short, None);
assert_eq!(argument.help, None);
assert_eq!(argument.meta, Some(vec!["meta message".to_string()]));
assert_eq!(argument.choices, HashMap::default());
}
#[test]
fn argument_choice() {
let mut item: bool = false;
let argument = Parameter::argument(Scalar::new(&mut item), "item")
.choice(true, "b")
.choice(false, "d")
.choice(true, "e")
.help("help")
.meta(vec!["meta"])
.consume();
assert_eq!(argument.class, ParameterClass::Arg);
assert_eq!(argument.name, "item".to_string());
assert_eq!(argument.short, None);
assert_eq!(argument.help, Some("help".to_string()));
assert_eq!(argument.meta, Some(vec!["meta".to_string()]));
assert_eq!(
argument.choices,
HashMap::from([
("true".to_string(), "e".to_string()),
("false".to_string(), "d".to_string())
])
);
}
#[test]
fn condition() {
let mut item: bool = false;
let condition = Condition::new(Scalar::new(&mut item), "item")
.choice(true, "b")
.choice(false, "d")
.choice(true, "e")
.help("help")
.meta(vec!["meta"])
.consume();
let argument = condition.consume();
assert_eq!(argument.class, ParameterClass::Arg);
assert_eq!(argument.name, "item".to_string());
assert_eq!(argument.short, None);
assert_eq!(argument.help, Some("help".to_string()));
assert_eq!(argument.meta, Some(vec!["meta".to_string()]));
assert_eq!(
argument.choices,
HashMap::from([
("true".to_string(), "e".to_string()),
("false".to_string(), "d".to_string())
])
);
}
}