use crate::error::*;
#[derive(Clone, Debug)]
pub(crate) enum Type {
Required {
default_value: Option<String>,
},
Optional,
Boolean,
Positional {
default_value: Option<Vec<String>>,
is_variadic: bool,
},
}
#[derive(Clone, Debug)]
pub struct Spec {
name: String,
help: String,
short_name: Option<char>,
flag_type: Type,
}
impl Spec {
pub fn required(
name: &str,
help: &str,
short_name: Option<char>,
default_value: Option<&str>,
) -> Spec {
Spec {
name: name.to_owned(),
help: help.to_owned(),
short_name: short_name,
flag_type: Type::Required {
default_value: default_value.map(|dv| dv.to_owned()),
},
}
}
pub fn optional(name: &str, help: &str, short_name: Option<char>) -> Spec {
Spec {
name: name.to_owned(),
help: help.to_owned(),
short_name: short_name,
flag_type: Type::Optional,
}
}
pub fn boolean(name: &str, help: &str, short_name: Option<char>) -> Spec {
Spec {
name: name.to_owned(),
help: help.to_owned(),
short_name: short_name,
flag_type: Type::Boolean,
}
}
pub fn positional(
name: &str,
help: &str,
mut default_value: Option<&[&str]>,
is_variadic: bool,
) -> Result<Spec> {
if let Some(dvs) = default_value {
if dvs.len() > 1 && !is_variadic {
return Err(Error::InvalidArgument(format!(
"Only variadic positional arguments can have multiple default values"
)));
}
if dvs.is_empty() {
default_value = None;
}
}
Ok(Spec {
name: name.to_owned(),
help: help.to_owned(),
short_name: None,
flag_type: Type::Positional {
default_value: default_value.map(|vs| vs.iter().map(|&v| v.to_owned()).collect()),
is_variadic: is_variadic,
},
})
}
pub(crate) fn is_boolean(&self) -> bool {
match self.flag_type {
Type::Boolean => true,
_ => false,
}
}
pub(crate) fn is_positional(&self) -> bool {
match self.flag_type {
Type::Positional { .. } => true,
_ => false,
}
}
pub(crate) fn is_named(&self) -> bool {
!self.is_positional()
}
pub(crate) fn has_default_value(&self) -> bool {
match self.flag_type {
Type::Required { ref default_value } => default_value.is_some(),
Type::Boolean => true,
Type::Positional {
ref default_value, ..
} => default_value.is_some(),
_ => false,
}
}
fn default_value_len(&self) -> usize {
match self.flag_type {
Type::Positional {
ref default_value, ..
} => match *default_value {
None => 0,
Some(ref dvs) => dvs.len(),
},
_ => match self.has_default_value() {
false => 0,
true => 1,
},
}
}
pub(crate) fn is_variadic(&self) -> bool {
match self.flag_type {
Type::Positional { is_variadic, .. } => is_variadic,
_ => false,
}
}
pub(crate) fn is_required(&self) -> bool {
if self.has_default_value() {
return true;
}
match self.flag_type {
Type::Required { .. } => true,
_ => false,
}
}
pub fn get_name(&self) -> &str {
self.name.as_str()
}
pub fn get_help(&self) -> &str {
self.help.as_str()
}
pub fn get_short_name(&self) -> Option<char> {
self.short_name.clone()
}
pub(crate) fn get_flag_type(&self) -> &Type {
&self.flag_type
}
pub(crate) fn get_required_default_value(&self) -> Option<&str> {
match self.flag_type {
Type::Required { ref default_value } => default_value.as_ref().map(|dv| dv.as_str()),
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct Specs {
specs: Vec<Spec>,
}
impl Specs {
pub fn new(specs: Vec<Spec>) -> Result<Specs> {
if !specs
.iter()
.filter(|s| s.is_positional())
.skip_while(|s| !s.has_default_value())
.all(|s| s.has_default_value())
{
return Err(Error::InvalidArgument(format!(
"Positional flags after the first one with a default must also have defaults"
)));
}
if !specs
.iter()
.rev()
.skip_while(|s| !s.is_positional())
.next()
.map_or(true, |s| s.is_variadic() || s.default_value_len() <= 1)
{
return Err(Error::InvalidArgument(format!(
"The last positional flag can only have multiple default values if it is variadic"
)));
}
Ok(Specs { specs: specs })
}
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &Spec> {
self.specs.iter()
}
pub(crate) fn find_named_spec(&self, name: &str) -> Option<&Spec> {
let mut result: Option<&Spec> = None;
for s in &self.specs {
if s.is_named() {
if s.name == name {
result = Some(s);
break;
} else if let Some(sn) = s.short_name {
if name.len() == 1 && name.starts_with(sn) {
result = result.or_else(|| Some(s));
}
}
}
}
result
}
}