use std::convert::AsRef;
use super::options::*;
use super::analysis::*;
const SINGLE_DASH_PREFIX: &str = "-";
const DOUBLE_DASH_PREFIX: &str = "--";
const EARLY_TERMINATOR: &str = "--";
enum ArgTypeBasic<'a> {
NonOption,
EarlyTerminator,
LongOption(&'a str),
ShortOptionSet(&'a str),
}
pub(crate) fn process<'o, 'r, 'a, A>(args: &'a [A], options: &'o OptionSet<'r, 'a>) -> Analysis<'a>
where A: 'a + AsRef<str>,
'r: 'o, 'a: 'r
{
let get_basic_arg_type_fn = match options.mode {
OptionsMode::Standard => get_basic_arg_type_standard,
OptionsMode::Alternate => get_basic_arg_type_alternate,
};
let mut results = Analysis::new(args.len());
let mut early_terminator_encountered = false;
let mut arg_iter = args.iter().enumerate();
while let Some((arg_index, arg_non_ref)) = arg_iter.next() {
let arg_ref = arg_non_ref.as_ref();
let arg_type = match early_terminator_encountered {
true => ArgTypeBasic::NonOption,
false => get_basic_arg_type_fn(arg_ref),
};
match arg_type {
ArgTypeBasic::NonOption => {
results.add(ItemClass::Ok(Item::NonOption(arg_index, arg_ref)));
},
ArgTypeBasic::EarlyTerminator => {
early_terminator_encountered = true;
results.add(ItemClass::Ok(Item::EarlyTerminator(arg_index)));
},
ArgTypeBasic::LongOption(opt_string) => {
let (name, data_included) = match opt_string.find('=') {
None => (opt_string, None),
Some(i) => {
let split = opt_string.split_at(i);
let data_included = unsafe { split.1.get_unchecked(1..) };
(split.0, Some(data_included))
},
};
if name.is_empty() {
results.add(ItemClass::Warn(ItemW::LongWithNoName(arg_index)));
results.warn = true;
continue;
}
let mut matched: Option<&LongOption> = None;
let mut ambiguity = false;
for candidate in options.long {
if candidate.name == name {
matched = Some(candidate);
ambiguity = false;
break;
}
else if options.allow_abbreviations && !ambiguity
&& name.len() < candidate.name.len()
&& candidate.name.starts_with(name)
{
match matched {
Some(_) => { ambiguity = true; },
None => { matched = Some(candidate); },
}
}
}
if ambiguity {
results.add(ItemClass::Err(ItemE::AmbiguousLong(arg_index, name)));
results.error = true;
}
else if let Some(matched) = matched {
let opt_name = &matched.name;
if matched.expects_data {
if let Some(data) = data_included {
results.add(ItemClass::Ok(Item::LongWithData {
i: arg_index, n: opt_name, d: data, l: DataLocation::SameArg }));
}
else if let Some((_, next_arg)) = arg_iter.next() {
results.add(ItemClass::Ok(Item::LongWithData {
i: arg_index, n: opt_name, d: next_arg.as_ref(),
l: DataLocation::NextArg }));
}
else {
results.add(ItemClass::Err(ItemE::LongMissingData(arg_index, opt_name)));
results.error = true;
}
}
else {
match data_included {
None |
Some("") => {
results.add(ItemClass::Ok(Item::Long(arg_index, opt_name)));
},
Some(data) => {
results.add(ItemClass::Warn(ItemW::LongWithUnexpectedData {
i: arg_index, n: opt_name, d: data }));
results.warn = true;
},
}
}
}
else {
results.add(ItemClass::Warn(ItemW::UnknownLong(arg_index, name)));
results.warn = true;
}
},
ArgTypeBasic::ShortOptionSet(optset_string) => {
let last_char_index = optset_string.chars().count() - 1;
for (i, (byte_pos, ch)) in optset_string.char_indices().enumerate() {
let mut match_found = false;
let mut expects_data = false;
for candidate in options.short {
if candidate.ch == ch {
match_found = true;
expects_data = candidate.expects_data;
break;
}
}
if !match_found {
results.add(ItemClass::Warn(ItemW::UnknownShort(arg_index, ch)));
results.warn = true;
}
else if !expects_data {
results.add(ItemClass::Ok(Item::Short(arg_index, ch)));
}
else {
if i < last_char_index {
let next_char_byte_pos = byte_pos + ch.len_utf8();
let data = optset_string.split_at(next_char_byte_pos).1;
results.add(ItemClass::Ok(Item::ShortWithData {
i: arg_index, c: ch, d: data, l: DataLocation::SameArg }));
break;
}
else if let Some((_, next_arg)) = arg_iter.next() {
results.add(ItemClass::Ok(Item::ShortWithData {
i: arg_index, c: ch, d: next_arg.as_ref(),
l: DataLocation::NextArg }));
}
else {
results.add(ItemClass::Err(ItemE::ShortMissingData(arg_index, ch)));
results.error = true;
}
}
}
},
}
}
results
}
#[inline(always)]
fn has_prefix(arg: &str, prefix: &str) -> bool {
let prefix_len = prefix.len();
arg.len() > prefix_len && &prefix.as_bytes()[..] == &arg.as_bytes()[..prefix_len]
}
fn get_basic_arg_type_standard<'a>(arg: &'a str) -> ArgTypeBasic<'a> {
if arg == EARLY_TERMINATOR {
ArgTypeBasic::EarlyTerminator
}
else if has_prefix(arg, DOUBLE_DASH_PREFIX) {
ArgTypeBasic::LongOption(unsafe { arg.get_unchecked(DOUBLE_DASH_PREFIX.len()..) })
}
else if has_prefix(arg, SINGLE_DASH_PREFIX) {
ArgTypeBasic::ShortOptionSet(unsafe { arg.get_unchecked(SINGLE_DASH_PREFIX.len()..) })
}
else {
ArgTypeBasic::NonOption
}
}
fn get_basic_arg_type_alternate<'a>(arg: &'a str) -> ArgTypeBasic<'a> {
if arg == EARLY_TERMINATOR {
ArgTypeBasic::EarlyTerminator
}
else if has_prefix(arg, SINGLE_DASH_PREFIX) {
ArgTypeBasic::LongOption(unsafe { arg.get_unchecked(SINGLE_DASH_PREFIX.len()..) })
}
else {
ArgTypeBasic::NonOption
}
}