use crate::command_options::{
Occurrence, OptionError, OptionErrorKind, OptionSpec, OptionValue, ParseMode, ParsedOptions,
};
use crate::command_options_value::{Spelling, take_value};
use std::ffi::OsString;
#[derive(Clone, Copy)]
struct Candidate {
spec: OptionSpec,
unset: bool,
}
fn accept(
arguments: &[OsString],
index: usize,
chosen: Candidate,
attached: Option<usize>,
parsed: &mut ParsedOptions,
) -> Result<bool, OptionError> {
let spelling: Spelling = if chosen.unset {
Spelling::NegatedLong
} else {
Spelling::Long
};
let value: Option<OptionValue> =
take_value(arguments, index, chosen.spec.arity, attached, spelling)?;
parsed.occurrences.push(Occurrence {
id: chosen.spec.id,
negated: chosen.unset,
value,
token: index,
});
return Ok(matches!(value, Some(OptionValue::Detached { .. })));
}
fn name_end(bytes: &[u8]) -> usize {
let mut index: usize = 0;
while index < bytes.len() {
if bytes[index] == b'=' {
return index;
}
index += 1;
}
return bytes.len();
}
pub(crate) fn scan_long_option(
arguments: &[OsString],
index: usize,
table: &[OptionSpec],
mode: ParseMode,
parsed: &mut ParsedOptions,
) -> Result<bool, OptionError> {
let token: &[u8] = arguments[index].as_encoded_bytes();
let argument: &[u8] = &token[2..];
let argument_name_end: usize = name_end(argument);
let mut stem: &[u8] = argument;
let mut negated: bool = false;
let mut double_negated: bool = false;
if let Some(once) = stem.strip_prefix(b"no-") {
stem = once;
if let Some(twice) = stem.strip_prefix(b"no-") {
stem = twice;
double_negated = true;
} else {
negated = true;
}
}
let stem_name: &[u8] = &stem[..argument_name_end - (argument.len() - stem.len())];
let mut candidate: Option<Candidate> = None;
let mut ambiguous: bool = false;
for spec in table {
let long: &str = if let Some(spelling) = spec.long {
spelling
} else {
continue;
};
let mut long_name: &[u8] = long.as_bytes();
let mut spec_negative: bool = false;
if let Some(positive) = long_name.strip_prefix(b"no-") {
long_name = positive;
spec_negative = true;
} else if double_negated {
continue;
}
let unset: bool = negated != spec_negative;
if unset && !spec.negatable {
continue;
}
if let Some(rest) = stem.strip_prefix(long_name) {
if rest.is_empty() {
let exact: Candidate = Candidate { spec: *spec, unset };
return accept(arguments, index, exact, None, parsed);
}
if rest[0] == b'=' {
let exact: Candidate = Candidate { spec: *spec, unset };
let offset: usize = token.len() - rest.len() + 1;
return accept(arguments, index, exact, Some(offset), parsed);
}
continue;
}
if mode.keep_unknown {
continue;
}
if long_name.starts_with(stem_name) {
ambiguous = ambiguous || candidate.is_some();
candidate = Some(Candidate { spec: *spec, unset });
}
if spec.negatable && b"no-".starts_with(argument) {
ambiguous = ambiguous || candidate.is_some();
candidate = Some(Candidate {
spec: *spec,
unset: !spec_negative,
});
}
}
if ambiguous {
return Err(OptionError {
kind: OptionErrorKind::AmbiguousOption,
token: index,
});
}
if let Some(chosen) = candidate {
let attached: Option<usize> = if argument_name_end < argument.len() {
Some(2 + argument_name_end + 1)
} else {
None
};
return accept(arguments, index, chosen, attached, parsed);
}
if mode.keep_unknown {
parsed.unknown.push(index);
return Ok(false);
}
return Err(OptionError {
kind: OptionErrorKind::UnknownOption,
token: index,
});
}
#[cfg(test)]
#[path = "command_options_long_tests.rs"]
mod tests;