use std::ffi::{OsStr, OsString};
#[derive(Debug, Clone, Eq, PartialEq)]
#[allow(clippy::enum_variant_names)]
pub(crate) enum Arg {
Short(char, bool, OsString),
Long(String, bool, OsString),
ArgWord(OsString),
Word(OsString),
PosWord(OsString),
}
impl Arg {
pub(crate) fn os_str(&self) -> &OsStr {
match self {
Arg::Short(_, _, s)
| Arg::Long(_, _, s)
| Arg::ArgWord(s)
| Arg::Word(s)
| Arg::PosWord(s) => s.as_ref(),
}
}
pub(crate) fn match_short(&self, val: char) -> bool {
match self {
Arg::Short(s, _, _) => *s == val,
Arg::ArgWord(_) | Arg::Long(_, _, _) | Arg::Word(_) | Arg::PosWord(_) => false,
}
}
pub(crate) fn match_long(&self, val: &str) -> bool {
match self {
Arg::Long(s, _, _) => *s == val,
Arg::Short(_, _, _) | Arg::ArgWord(_) | Arg::Word(_) | Arg::PosWord(_) => false,
}
}
}
impl std::fmt::Display for Arg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Arg::Short(s, _, _) => write!(f, "-{}", s),
Arg::Long(l, _, _) => write!(f, "--{}", l),
Arg::ArgWord(w) | Arg::Word(w) | Arg::PosWord(w) => {
write!(f, "{}", w.to_string_lossy())
}
}
}
}
#[derive(Eq, PartialEq, Debug)]
pub(crate) enum ArgType {
Short,
Long,
}
pub(crate) fn split_os_argument(input: &std::ffi::OsStr) -> Option<(ArgType, String, Option<Arg>)> {
#[cfg(any(unix, windows))]
{
#[cfg(unix)]
type Elt = u8;
#[cfg(windows)]
type Elt = u16;
fn os_from_vec(vec: Vec<Elt>) -> OsString {
#[cfg(unix)]
{
<OsString as std::os::unix::ffi::OsStringExt>::from_vec(vec)
}
#[cfg(windows)]
{
<OsString as std::os::windows::ffi::OsStringExt>::from_wide(&vec)
}
}
fn str_from_vec(vec: Vec<Elt>) -> Option<String> {
Some(os_from_vec(vec).to_str()?.to_owned())
}
const DASH: Elt = b'-' as Elt;
const EQUALS: Elt = b'=' as Elt;
let mut name = Vec::with_capacity(input.len());
let mut items;
#[cfg(unix)]
{
items = std::os::unix::ffi::OsStrExt::as_bytes(input)
.iter()
.copied();
}
#[cfg(windows)]
{
items = std::os::windows::ffi::OsStrExt::encode_wide(input);
}
if items.next()? != DASH {
return None;
}
let ty;
match items.next()? {
DASH => ty = ArgType::Long,
val => {
ty = ArgType::Short;
name.push(val);
}
}
loop {
match items.next() {
Some(EQUALS) => {
if ty == ArgType::Short && name.len() > 1 {
let mut body = name.drain(1..).collect::<Vec<_>>();
body.push(EQUALS);
body.extend(items);
name.truncate(1);
let os = Arg::ArgWord(os_from_vec(body));
return Some((ty, str_from_vec(name)?, Some(os)));
}
break;
}
Some(val) => name.push(val),
None => {
if name.is_empty() {
return None;
}
return Some((ty, str_from_vec(name)?, None));
}
}
}
let name = str_from_vec(name)?;
let word = {
let os = os_from_vec(items.collect());
Arg::ArgWord(os)
};
Some((ty, name, Some(word)))
}
#[cfg(not(any(unix, windows)))]
{
split_os_argument_fallback(input)
}
}
#[cfg(any(all(not(windows), not(unix)), test))]
pub(crate) fn split_os_argument_fallback(
input: &std::ffi::OsStr,
) -> Option<(ArgType, String, Option<Arg>)> {
let string = input.to_str()?;
let mut chars = string.chars();
let mut name = String::with_capacity(string.len());
if chars.next()? != '-' {
return None;
}
let ty;
match chars.next()? {
'-' => ty = ArgType::Long,
val => {
ty = ArgType::Short;
name.push(val);
}
}
loop {
match chars.next() {
Some('=') => {
if ty == ArgType::Short && name.len() > 1 {
let mut body = name.drain(1..).collect::<String>();
body.push('=');
body.extend(chars);
name.truncate(1);
let os = Arg::ArgWord(OsString::from(body));
return Some((ty, name, Some(os)));
}
break;
}
Some(val) => name.push(val),
None => {
if name.is_empty() {
return None;
}
return Some((ty, name, None));
}
}
}
Some((
ty,
name,
Some(Arg::ArgWord(OsString::from(chars.collect::<String>()))),
))
}