#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::{fmt, slice};
pub mod prelude {
pub use crate::{Arg, RawArgs, is_flag_like, is_option_separator, is_positional};
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Arg {
value: String,
}
impl Arg {
#[must_use]
pub fn new(value: impl Into<String>) -> Self {
Self {
value: value.into(),
}
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.value
}
#[must_use]
pub fn into_string(self) -> String {
self.value
}
#[must_use]
pub fn is_option_separator(&self) -> bool {
is_option_separator(&self.value)
}
#[must_use]
pub fn is_flag_like(&self) -> bool {
is_flag_like(&self.value)
}
#[must_use]
pub fn is_positional(&self) -> bool {
is_positional(&self.value)
}
}
impl AsRef<str> for Arg {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl From<String> for Arg {
fn from(value: String) -> Self {
Self::new(value)
}
}
impl From<&str> for Arg {
fn from(value: &str) -> Self {
Self::new(value)
}
}
impl fmt::Display for Arg {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.value)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct RawArgs {
args: Vec<Arg>,
}
impl RawArgs {
#[must_use]
pub const fn new(args: Vec<Arg>) -> Self {
Self { args }
}
#[must_use]
pub const fn empty() -> Self {
Self { args: Vec::new() }
}
pub fn push(&mut self, arg: Arg) {
self.args.push(arg);
}
#[must_use]
pub const fn len(&self) -> usize {
self.args.len()
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.args.is_empty()
}
pub fn iter(&self) -> slice::Iter<'_, Arg> {
self.args.iter()
}
#[must_use]
pub fn into_vec(self) -> Vec<Arg> {
self.args
}
}
impl FromIterator<Arg> for RawArgs {
fn from_iter<T: IntoIterator<Item = Arg>>(iter: T) -> Self {
Self::new(iter.into_iter().collect())
}
}
impl FromIterator<String> for RawArgs {
fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
iter.into_iter().map(Arg::from).collect()
}
}
impl IntoIterator for RawArgs {
type Item = Arg;
type IntoIter = std::vec::IntoIter<Arg>;
fn into_iter(self) -> Self::IntoIter {
self.args.into_iter()
}
}
impl<'a> IntoIterator for &'a RawArgs {
type Item = &'a Arg;
type IntoIter = slice::Iter<'a, Arg>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[must_use]
pub fn is_option_separator(token: &str) -> bool {
token == "--"
}
#[must_use]
pub fn is_flag_like(token: &str) -> bool {
token.starts_with('-') && token != "-" && !is_option_separator(token)
}
#[must_use]
pub fn is_positional(token: &str) -> bool {
!is_option_separator(token) && !is_flag_like(token)
}
#[cfg(test)]
mod tests {
use super::{Arg, RawArgs, is_flag_like, is_option_separator, is_positional};
#[test]
fn classifies_argument_tokens() {
assert!(is_positional("file.txt"));
assert!(is_positional("-"));
assert!(is_option_separator("--"));
assert!(is_flag_like("-v"));
assert!(is_flag_like("--verbose"));
assert!(!is_flag_like("--"));
}
#[test]
fn stores_owned_arguments() {
let mut args = RawArgs::empty();
args.push(Arg::from("tool"));
args.push(Arg::from("README.md"));
let tokens: Vec<_> = args.iter().map(Arg::as_str).collect();
assert_eq!(args.len(), 2);
assert_eq!(tokens, vec!["tool", "README.md"]);
assert!(args.into_vec()[1].is_positional());
}
}