use crate::{
ArgyleError,
ArgsOsStr,
KeyKind,
Options,
OptionsOsStr,
};
use std::{
cell::Cell,
ffi::{
OsStr,
OsString,
},
ops::{
BitOr,
Deref,
Index,
},
os::unix::ffi::{
OsStrExt,
OsStringExt,
},
};
pub const FLAG_REQUIRED: u8 = 0b0000_0001;
pub const FLAG_SUBCOMMAND: u8 = 0b0000_0010;
#[cfg(feature = "dynamic-help")]
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "dynamic-help")))]
pub const FLAG_DYNAMIC_HELP: u8 = 0b0000_0100;
pub const FLAG_HELP: u8 = 0b0000_1000;
pub const FLAG_VERSION: u8 = 0b0001_0000;
const FLAG_HAS_HELP: u8 = 0b0010_0000;
const FLAG_HAS_VERSION: u8 = 0b0100_0000;
const FLAG_DO_VERSION: u8 = FLAG_VERSION | FLAG_HAS_VERSION;
#[cfg(feature = "dynamic-help")]
const FLAG_ANY_HELP: u8 = FLAG_HELP | FLAG_DYNAMIC_HELP;
#[cfg(not(feature = "dynamic-help"))]
const FLAG_ANY_HELP: u8 = FLAG_HELP;
#[derive(Debug, Clone, Default)]
pub struct Argue {
args: Vec<Vec<u8>>,
last: Cell<usize>,
flags: u8,
}
impl Deref for Argue {
type Target = [Vec<u8>];
#[inline]
fn deref(&self) -> &Self::Target { &self.args }
}
impl<'a> FromIterator<&'a [u8]> for Argue {
fn from_iter<I: IntoIterator<Item = &'a [u8]>>(src: I) -> Self {
src.into_iter().map(<[u8]>::to_vec).collect()
}
}
impl FromIterator<Vec<u8>> for Argue {
fn from_iter<I: IntoIterator<Item = Vec<u8>>>(src: I) -> Self {
src.into_iter().map(OsString::from_vec).collect()
}
}
impl FromIterator<OsString> for Argue {
fn from_iter<I: IntoIterator<Item = OsString>>(src: I) -> Self {
let mut args: Vec<Vec<u8>> = Vec::with_capacity(16);
let mut last = 0_usize;
let mut flags = 0_u8;
let mut idx = 0_usize;
for a in src {
let mut a = a.into_vec();
let key: &[u8] = a.as_slice();
if 0 == idx && (key.is_empty() || key.iter().all(u8::is_ascii_whitespace)) {
continue;
}
match KeyKind::from(key) {
KeyKind::None => {
if key == b"--" { break; }
args.push(a);
idx += 1;
},
KeyKind::Short => {
if key == b"-V" { flags |= FLAG_HAS_VERSION; }
else if key == b"-h" { flags |= FLAG_HAS_HELP; }
args.push(a);
last = idx;
idx += 1;
},
KeyKind::Long => {
if key == b"--version" { flags |= FLAG_HAS_VERSION; }
else if key == b"--help" { flags |= FLAG_HAS_HELP; }
args.push(a);
last = idx;
idx += 1;
},
KeyKind::ShortV => {
let b = a.split_off(2);
args.push(a);
args.push(b);
last = idx + 1;
idx += 2;
},
KeyKind::LongV(end) => {
let b =
if end + 1 < key.len() { a.split_off(end + 1) }
else { Vec::new() };
a.truncate(end); args.push(a);
args.push(b);
last = idx + 1;
idx += 2;
},
}
}
Self {
args,
last: Cell::new(last),
flags,
}
}
}
impl Index<usize> for Argue {
type Output = [u8];
#[inline]
fn index(&self, idx: usize) -> &Self::Output { &self.args[idx] }
}
impl Argue {
#[inline]
pub fn new(chk_flags: u8) -> Result<Self, ArgyleError> {
let mut out: Self = std::env::args_os().skip(1).collect();
out.check_flags(chk_flags)?;
Ok(out)
}
fn check_flags(&mut self, flags: u8) -> Result<(), ArgyleError> {
if 0 < flags {
self.flags |= flags;
if self.args.is_empty() {
if FLAG_REQUIRED == self.flags & FLAG_REQUIRED {
return Err(ArgyleError::Empty);
}
}
else if FLAG_DO_VERSION == self.flags & FLAG_DO_VERSION {
return Err(ArgyleError::WantsVersion);
}
else if
0 != self.flags & FLAG_ANY_HELP &&
(FLAG_HAS_HELP == self.flags & FLAG_HAS_HELP || self.args[0] == b"help")
{
#[cfg(feature = "dynamic-help")]
if FLAG_DYNAMIC_HELP == self.flags & FLAG_DYNAMIC_HELP {
return Err(ArgyleError::WantsDynamicHelp(
if self.args[0][0] != b'-' && self.args[0] != b"help" {
Some(Box::from(self.args[0].as_slice()))
}
else { None }
));
}
return Err(ArgyleError::WantsHelp);
}
}
Ok(())
}
#[must_use]
pub fn with_list(mut self) -> Self {
if let Some(raw) = self.option2_os(b"-l", b"--list").and_then(|p| std::fs::read_to_string(p).ok()) {
for line in raw.lines() {
let bytes = line.trim().as_bytes();
if ! bytes.is_empty() {
self.args.push(bytes.to_vec());
}
}
}
self
}
}
impl Argue {
#[allow(clippy::missing_const_for_fn)] #[must_use]
#[inline]
pub fn take(self) -> Vec<Vec<u8>> { self.args }
}
impl Argue {
#[must_use]
#[inline]
pub fn switch(&self, key: &[u8]) -> bool { self.args.iter().any(|x| x == key) }
#[must_use]
#[inline]
pub fn switch2(&self, short: &[u8], long: &[u8]) -> bool {
self.args.iter().any(|x| x == short || x == long)
}
#[must_use]
pub fn switch_count(&self, key: &[u8]) -> usize {
self.args.iter().filter(|&x| x == key).count()
}
#[must_use]
pub fn switch2_count(&self, short: &[u8], long: &[u8]) -> usize {
self.args.iter().filter(|&x| x == short || x == long).count()
}
#[must_use]
pub fn switch_by_prefix(&self, prefix: &[u8]) -> Option<&[u8]> {
if prefix.is_empty() { None }
else {
self.args.iter().find_map(|x| {
let key = x.strip_prefix(prefix)?;
if key.is_empty() { None }
else { Some(key) }
})
}
}
#[must_use]
pub fn bitflags<'a, N, I>(&self, pairs: I) -> N
where
N: BitOr<Output = N> + Default,
I: IntoIterator<Item=(&'a [u8], N)>
{
pairs.into_iter()
.fold(N::default(), |flags, (switch, flag)|
if self.switch(switch) { flags | flag }
else { flags }
)
}
pub fn option(&self, key: &[u8]) -> Option<&[u8]> {
let idx = self.args.iter().rposition(|x| x == key)? + 1;
self._option(idx)
}
pub fn option2(&self, short: &[u8], long: &[u8]) -> Option<&[u8]> {
let idx = self.args.iter().rposition(|x| x == short || x == long)? + 1;
self._option(idx)
}
pub fn option_values<'a>(&'a self, key: &'a [u8], delimiter: Option<u8>)
-> Options<'a> {
if let Some(idx) = self.args.iter().rposition(|x| x == key) {
let idx = idx + 1;
if idx < self.args.len() {
if self.last.get() < idx { self.last.set(idx); }
return Options::new(&self.args[..=idx], key, None, delimiter);
}
}
Options::default()
}
pub fn option2_iter<'a>(&'a self, short: &'a [u8], long: &'a [u8], delimiter: Option<u8>)
-> Options<'a> {
if let Some(idx) = self.args.iter().rposition(|x| x == short || x == long) {
let idx = idx + 1;
if idx < self.args.len() {
if self.last.get() < idx { self.last.set(idx); }
return Options::new(&self.args[..=idx], short, Some(long), delimiter);
}
}
Options::default()
}
#[must_use]
pub fn option_by_prefix(&self, prefix: &[u8]) -> Option<(&[u8], &[u8])> {
if prefix.is_empty() { None }
else {
let (idx, key) = self.args.iter()
.enumerate()
.find_map(|(idx, x)| {
let key = x.strip_prefix(prefix)?;
if key.is_empty() { None }
else { Some((idx, key)) }
})?;
let val = self._option(idx + 1)?;
Some((key, val))
}
}
fn _option(&self, idx: usize) -> Option<&[u8]> {
let arg = self.args.get(idx)?;
if self.last.get() < idx { self.last.set(idx); }
Some(arg.as_slice())
}
#[must_use]
pub fn args(&self) -> &[Vec<u8>] {
let idx = self.arg_idx();
if idx < self.args.len() { &self.args[idx..] }
else { &[] }
}
#[must_use]
pub fn arg(&self, idx: usize) -> Option<&[u8]> {
let start_idx = self.arg_idx();
self.args.get(start_idx + idx).map(Vec::as_slice)
}
}
impl Argue {
#[must_use]
pub fn get(&self, idx: usize) -> Option<&[u8]> {
self.args.get(idx).map(Vec::as_slice)
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool { self.args.is_empty() }
#[inline]
#[must_use]
pub fn len(&self) -> usize { self.args.len() }
}
impl Argue {
#[must_use]
pub fn switch_by_prefix_os(&self, prefix: &[u8]) -> Option<&OsStr> {
self.switch_by_prefix(prefix).map(OsStr::from_bytes)
}
#[must_use]
pub fn option_os(&self, key: &[u8]) -> Option<&OsStr> {
self.option(key).map(OsStr::from_bytes)
}
#[must_use]
pub fn option2_os(&self, short: &[u8], long: &[u8]) -> Option<&OsStr> {
self.option2(short, long).map(OsStr::from_bytes)
}
pub fn option_values_os<'a>(&'a self, key: &'a [u8], delimiter: Option<u8>)
-> OptionsOsStr<'a> {
OptionsOsStr(self.option_values(key, delimiter))
}
pub fn option2_iter_os<'a>(&'a self, short: &'a [u8], long: &'a [u8], delimiter: Option<u8>)
-> OptionsOsStr<'a> {
OptionsOsStr(self.option2_iter(short, long, delimiter))
}
#[must_use]
pub fn option_by_prefix_os(&self, prefix: &[u8]) -> Option<(&OsStr, &OsStr)> {
self.option_by_prefix(prefix)
.map(|(k, v)| (OsStr::from_bytes(k), OsStr::from_bytes(v)))
}
#[must_use]
pub fn args_os(&self) -> ArgsOsStr { ArgsOsStr::new(self.args()) }
#[must_use]
pub fn arg_os(&self, idx: usize) -> Option<&OsStr> {
self.arg(idx).map(OsStr::from_bytes)
}
}
impl Argue {
fn arg_idx(&self) -> usize {
let last = self.last.get();
if 0 == last && 0 == self.flags & FLAG_SUBCOMMAND { 0 }
else { last + 1 }
}
}
#[cfg(test)]
mod tests {
use super::*;
use brunch as _;
#[test]
fn t_parse_args() {
let mut base: Vec<&[u8]> = vec![
b"hey",
b"-kVal",
b"--empty=",
b"--key=Val",
];
let mut args: Argue = base.iter().copied().collect();
assert_eq!(
*args,
[
b"hey".to_vec(),
b"-k".to_vec(),
b"Val".to_vec(),
b"--empty".to_vec(),
vec![],
b"--key".to_vec(),
b"Val".to_vec(),
]
);
assert_eq!(args.get(0), Some(&b"hey"[..]));
assert_eq!(&args[1], b"-k");
assert!(args.switch(b"-k"));
assert!(args.switch(b"--key"));
assert!(args.switch2(b"-k", b"--key"));
assert_eq!(args.option(b"--key"), Some(&b"Val"[..]));
assert_eq!(args.option2(b"-k", b"--key"), Some(&b"Val"[..]));
assert!(args.args().is_empty());
assert!(! args.switch(b"-c"));
assert!(! args.switch2(b"-c", b"--copy"));
assert!(args.option(b"-c").is_none());
assert!(args.option2(b"-c", b"--copy").is_none());
assert!(args.get(100).is_none());
base.insert(0, b"--prefix");
args = base.iter().copied().collect();
assert_eq!(
*args,
[
b"--prefix".to_vec(),
b"hey".to_vec(),
b"-k".to_vec(),
b"Val".to_vec(),
b"--empty".to_vec(),
vec![],
b"--key".to_vec(),
b"Val".to_vec(),
]
);
assert_eq!(args.get(0), Some(&b"--prefix"[..]));
assert!(args.switch(b"--prefix"));
assert_eq!(args.option(b"--prefix"), Some(&b"hey"[..]));
let hey = OsStr::new("hey");
assert_eq!(args.option_os(b"--prefix"), Some(hey));
assert_eq!(args.arg(0), None);
let trailing: &[&[u8]] = &[b"Hello", b"World"];
base.extend_from_slice(trailing);
args = base.iter().copied().collect();
assert_eq!(args.arg(0), Some(&b"Hello"[..]));
assert_eq!(args.arg(1), Some(&b"World"[..]));
assert_eq!(args.arg(2), None);
assert_eq!(args.args(), trailing);
args = [b"hello".to_vec()].into_iter().collect();
assert_eq!(args.arg(0), Some(&b"hello"[..]));
args.flags |= FLAG_SUBCOMMAND;
assert!(args.arg(0).is_none());
}
#[test]
fn t_version() {
let mut base: Vec<&[u8]> = vec![
b"hey",
b"-V",
];
let mut args: Argue = base.iter().copied().collect();
assert!(matches!(
args.check_flags(FLAG_VERSION),
Err(ArgyleError::WantsVersion)
));
args = base.iter().copied().collect();
assert!(args.check_flags(FLAG_HELP).is_ok());
base[1] = b"--version";
args = base.iter().copied().collect();
assert!(matches!(
args.check_flags(FLAG_VERSION),
Err(ArgyleError::WantsVersion)
));
args = base.iter().copied().collect();
assert!(args.check_flags(FLAG_HELP).is_ok());
base[1] = b"--ok";
args = base.iter().copied().collect();
assert!(args.check_flags(FLAG_VERSION).is_ok());
args = base.iter().copied().collect();
assert!(args.check_flags(FLAG_HELP).is_ok());
}
#[test]
fn t_help() {
let mut base: Vec<&[u8]> = vec![
b"hey",
b"-h",
];
let mut args: Argue = base.iter().copied().collect();
assert!(matches!(
args.check_flags(FLAG_HELP),
Err(ArgyleError::WantsHelp)
));
#[cfg(feature = "dynamic-help")]
{
args = base.iter().copied().collect();
match args.check_flags(FLAG_DYNAMIC_HELP) {
Err(ArgyleError::WantsDynamicHelp(e)) => {
let expected: Option<Box<[u8]>> = Some(Box::from(&b"hey"[..]));
assert_eq!(e, expected);
},
_ => panic!("Test should have produced an error with Some(Box(hey))."),
}
}
args = base.iter().copied().collect();
assert!(args.check_flags(FLAG_VERSION).is_ok());
base[0] = b"--help";
args = base.iter().copied().collect();
assert!(matches!(
args.check_flags(FLAG_HELP),
Err(ArgyleError::WantsHelp)
));
#[cfg(feature = "dynamic-help")]
{
args = base.iter().copied().collect();
assert!(matches!(
args.check_flags(FLAG_DYNAMIC_HELP),
Err(ArgyleError::WantsDynamicHelp(None))
));
}
args = base.iter().copied().collect();
assert!(args.check_flags(FLAG_VERSION).is_ok());
base[0] = b"help";
base[1] = b"--foo";
args = base.iter().copied().collect();
assert!(matches!(
args.check_flags(FLAG_HELP),
Err(ArgyleError::WantsHelp)
));
#[cfg(feature = "dynamic-help")]
{
args = base.iter().copied().collect();
assert!(matches!(
args.check_flags(FLAG_DYNAMIC_HELP),
Err(ArgyleError::WantsDynamicHelp(None))
));
}
args = base.iter().copied().collect();
assert!(args.check_flags(FLAG_VERSION).is_ok());
base[0] = b"hey";
args = base.iter().copied().collect();
assert!(args.check_flags(FLAG_HELP).is_ok());
#[cfg(feature = "dynamic-help")]
{
args = base.iter().copied().collect();
assert!(args.check_flags(FLAG_DYNAMIC_HELP).is_ok());
}
args = base.iter().copied().collect();
assert!(args.check_flags(FLAG_VERSION).is_ok());
}
#[test]
fn t_with_list() {
let list = std::path::Path::new("skel/list.txt");
assert!(list.exists(), "Missing list.txt");
let mut base: Vec<Vec<u8>> = vec![
b"print".to_vec(),
b"-l".to_vec(),
b"skel/list.txt".to_vec(),
];
let mut args = base.iter().cloned().collect::<Argue>().with_list();
assert_eq!(
*args,
[
b"print".to_vec(),
b"-l".to_vec(),
b"skel/list.txt".to_vec(),
b"/foo/bar/one".to_vec(),
b"/foo/bar/two".to_vec(),
b"/foo/bar/three".to_vec(),
]
);
assert_eq!(args.arg(0), Some(&b"/foo/bar/one"[..]));
assert_eq!(args.arg(1), Some(&b"/foo/bar/two"[..]));
assert_eq!(args.arg(2), Some(&b"/foo/bar/three"[..]));
base[2] = b"skel/not-list.txt".to_vec();
args = base.iter().cloned().collect::<Argue>().with_list();
assert_eq!(
*args,
[
b"print".to_vec(),
b"-l".to_vec(),
b"skel/not-list.txt".to_vec(),
]
);
}
#[test]
fn t_bitflags() {
const FLAG_EMPTY: u8 = 0b0000_0001;
const FLAG_HELLO: u8 = 0b0000_0010;
const FLAG_K: u8 = 0b0000_0100;
const FLAG_ONE_MORE: u8 = 0b0000_1000;
const FLAG_OTHER: u8 = 0b0001_0000;
let base: Vec<&[u8]> = vec![
b"hey",
b"-k",
b"--empty",
b"--key=Val",
b"--hello",
b"--one-more",
];
let args: Argue = base.iter().copied().collect();
let flags: u8 = args.bitflags([
(&b"-k"[..], FLAG_K),
(&b"--empty"[..], FLAG_EMPTY),
(&b"--hello"[..], FLAG_HELLO),
(&b"--one-more"[..], FLAG_ONE_MORE),
(&b"--other"[..], FLAG_OTHER),
]);
assert_eq!(flags & FLAG_K, FLAG_K);
assert_eq!(flags & FLAG_EMPTY, FLAG_EMPTY);
assert_eq!(flags & FLAG_HELLO, FLAG_HELLO);
assert_eq!(flags & FLAG_ONE_MORE, FLAG_ONE_MORE);
assert_eq!(flags & FLAG_OTHER, 0);
}
#[test]
fn t_by_prefix() {
let base: Vec<&[u8]> = vec![
b"hey",
b"-k",
b"--dump-three",
b"--key-1=Val",
b"--dump-four",
b"--one-more",
];
let args = base.iter().cloned().collect::<Argue>();
assert_eq!(args.switch_by_prefix(b"--dump"), Some(&b"-three"[..]));
assert_eq!(args.switch_by_prefix(b"--dump-"), Some(&b"three"[..]));
assert_eq!(args.switch_by_prefix(b"--with"), None);
assert_eq!(args.switch_by_prefix(b"-k"), None);
assert_eq!(
args.option_by_prefix(b"--key-"),
Some((&b"1"[..], &b"Val"[..]))
);
assert_eq!(args.option_by_prefix(b"--foo"), None);
assert_eq!(args.option_by_prefix(b"--key-1"), None); }
}