use std::ffi::OsString;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GlobalOutcome {
Command,
Query,
MissingValue,
InvalidOption,
NoCommand,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct GlobalLayout {
pub prefix_len: usize,
pub outcome: GlobalOutcome,
}
pub(crate) const VALUE_OPTIONS: &[&[u8]] = &[
b"-C",
b"-c",
b"--git-dir",
b"--work-tree",
b"--namespace",
b"--config-env",
b"--shallow-file",
b"--attr-source",
];
const FLAG_OPTIONS: &[&[u8]] = &[
b"-p",
b"--paginate",
b"-P",
b"--no-pager",
b"--no-lazy-fetch",
b"--no-replace-objects",
b"--bare",
b"--literal-pathspecs",
b"--no-literal-pathspecs",
b"--glob-pathspecs",
b"--noglob-pathspecs",
b"--icase-pathspecs",
b"--no-optional-locks",
b"--no-advice",
];
const INLINE_OPTIONS: &[&[u8]] = &[
b"--git-dir=",
b"--work-tree=",
b"--namespace=",
b"--config-env=",
b"--attr-source=",
];
pub fn global_layout(arguments: &[OsString]) -> GlobalLayout {
let mut is_value: bool = false;
for (index, token) in arguments.iter().enumerate() {
if is_value {
is_value = false;
continue;
}
let argument: &[u8] = token.as_encoded_bytes();
if !argument.starts_with(b"-") {
return GlobalLayout {
prefix_len: index,
outcome: GlobalOutcome::Command,
};
}
if [
b"--help".as_slice(),
b"-h",
b"--version",
b"-v",
b"--html-path",
b"--man-path",
b"--info-path",
]
.contains(&argument)
|| argument.starts_with(b"--list-cmds=")
{
return GlobalLayout {
prefix_len: index,
outcome: GlobalOutcome::Query,
};
}
if let Some(remainder) = argument.strip_prefix(b"--exec-path") {
if remainder.first() != Some(&b'=') {
return GlobalLayout {
prefix_len: index,
outcome: GlobalOutcome::Query,
};
}
continue;
}
if VALUE_OPTIONS.contains(&argument) {
if index + 1 == arguments.len() {
return GlobalLayout {
prefix_len: index,
outcome: GlobalOutcome::MissingValue,
};
}
is_value = true;
continue;
}
if FLAG_OPTIONS.contains(&argument) {
continue;
}
let mut inline: bool = false;
for prefix in INLINE_OPTIONS {
if argument.starts_with(prefix) {
inline = true;
break;
}
}
if inline {
continue;
}
return GlobalLayout {
prefix_len: index,
outcome: GlobalOutcome::InvalidOption,
};
}
return GlobalLayout {
prefix_len: arguments.len(),
outcome: GlobalOutcome::NoCommand,
};
}
pub fn command_tokens(arguments: &[OsString]) -> Option<(&OsString, &[OsString])> {
let layout: GlobalLayout = global_layout(arguments);
if layout.outcome != GlobalOutcome::Command {
return None;
}
return arguments[layout.prefix_len..].split_first();
}
#[cfg(test)]
#[path = "global_arguments_tests.rs"]
mod tests;