use crate::spec::{Spec, Specs, Type};
use flaggy_values::error::{ValueError, ValueResult};
use flaggy_values::value::{Value, Values};
use std::collections::HashMap;
use std::iter::Peekable;
fn get_default_values<'a>(specs: &Specs) -> HashMap<String, Value> {
specs
.iter()
.filter(|s| s.has_default_value())
.map(|s| -> (String, Value) {
match s.get_flag_type() {
Type::Required { ref default_value } => (
s.get_name().to_owned(),
Value::Single(default_value.as_ref().unwrap().clone()),
),
Type::Boolean => (s.get_name().to_owned(), Value::Boolean(false.to_string())),
Type::Positional {
ref default_value, ..
} => (
s.get_name().to_owned(),
Value::Repeated(default_value.as_ref().unwrap().clone()),
),
_ => panic!(
"Default value lookup for {:?} not implemented",
s.get_flag_type()
),
}
})
.collect()
}
fn new_named_flag_value(spec: &Spec, value: Option<String>) -> ValueResult<Value> {
Ok(match spec.is_boolean() {
false => Value::Single(match value {
None => return Err(ValueError::MissingValue(spec.get_name().to_owned())),
Some(value) => value,
}),
true => Value::Boolean(match value {
None => true.to_string(),
Some(value) => {
if value.parse::<bool>().is_err() {
return Err(ValueError::BadBoolean(value.clone()));
}
value
}
}),
})
}
struct ParsedNamedFlag {
name: String,
value: Value,
}
struct NamedFlagSpec<'a> {
value: Option<String>,
spec: &'a Spec,
}
impl<'a> NamedFlagSpec<'a> {
fn new<'b>(specs: &'a Specs, flag: &'b str) -> ValueResult<NamedFlagSpec<'a>> {
let trimmed = if flag.starts_with("--") {
&flag[2..]
} else {
&flag[1..]
};
let equals_idx = trimmed.rfind('=');
let name = equals_idx.map_or(trimmed, |ei| &trimmed[0..ei]);
let value = equals_idx.map_or(None, |ei| Some((&trimmed[ei + 1..]).to_owned()));
let spec: &'a Spec = match specs.find_named_spec(name) {
Some(s) => s,
None => return Err(ValueError::UnknownFlag(name.to_owned())),
};
Ok(NamedFlagSpec {
value: value,
spec: spec,
})
}
}
struct PositionalFlagSpec {
name: String,
is_variadic: bool,
}
fn parse_next_named_flag<'a, 'b, I: Iterator<Item = &'b String>>(
specs: &'a Specs,
args: &mut Peekable<I>,
) -> ValueResult<Option<ParsedNamedFlag>> {
let flag: &'b String = match args.peek() {
Some(p) => {
if p.starts_with('-') {
p
} else {
return Ok(None);
}
}
None => return Ok(None),
};
args.next();
let mut spec = NamedFlagSpec::new(specs, flag)?;
if !spec.spec.is_boolean() {
let next_arg_is_value = args.peek().map_or(false, |v| !v.starts_with('-'));
if next_arg_is_value && spec.value.is_none() {
spec.value = Some(args.next().unwrap().clone());
}
}
Ok(Some(ParsedNamedFlag {
name: spec.spec.get_name().to_owned(),
value: new_named_flag_value(&spec.spec, spec.value)?,
}))
}
struct ValueIterator<'a, 'b, I: Iterator<Item = &'b String>> {
specs: &'a Specs,
args: Peekable<I>,
finished_named_flags: bool,
positional_specs: Vec<PositionalFlagSpec>,
}
impl<'a, 'b, I: Iterator<Item = &'b String>> ValueIterator<'a, 'b, I> {
fn new(specs: &'a Specs, args: Peekable<I>) -> ValueIterator<'a, 'b, I> {
ValueIterator {
specs: specs,
args: args,
finished_named_flags: false,
positional_specs: specs
.iter()
.filter_map(|s| match s.is_positional() {
false => None,
true => Some(PositionalFlagSpec {
name: s.get_name().to_owned(),
is_variadic: s.is_variadic(),
}),
})
.rev()
.collect(),
}
}
}
impl<'a, 'b, I: Iterator<Item = &'b String>> Iterator for ValueIterator<'a, 'b, I> {
type Item = ValueResult<(String, Value)>;
fn next(&mut self) -> Option<Self::Item> {
if !self.finished_named_flags {
match parse_next_named_flag(self.specs, &mut self.args) {
Ok(parsed_flag) => match parsed_flag {
Some(parsed_flag) => return Some(Ok((parsed_flag.name, parsed_flag.value))),
None => self.finished_named_flags = true,
},
Err(e) => return Some(Err(e)),
}
}
match self.positional_specs.pop() {
None => None,
Some(spec) => match spec.is_variadic {
false => match self.args.next() {
None => None,
Some(value) => Some(Ok((spec.name, Value::Repeated(vec![value.clone()])))),
},
true => {
let mut values = vec![];
while let Some(value) = self.args.next() {
values.push(value.clone());
}
Some(Ok((spec.name, Value::Repeated(values))))
}
},
}
}
}
pub(crate) fn build_values<'a, 'b, I: Iterator<Item = &'b String>>(
specs: &'a Specs,
args: Peekable<I>,
) -> ValueResult<Values> {
let default_values = get_default_values(specs);
let values: ValueResult<HashMap<String, Value>> = ValueIterator::new(specs, args).collect();
let values = Values::new(default_values, values?);
for s in specs.iter() {
if s.is_required() && !values.contains_key(s.get_name()) {
return Err(ValueError::MissingFlag(s.get_name().to_owned()));
}
}
Ok(values)
}