Skip to main content

use_arg/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, slice};
5
6/// Commonly used argument primitives.
7pub mod prelude {
8    pub use crate::{Arg, RawArgs, is_flag_like, is_option_separator, is_positional};
9}
10
11/// An owned command-line argument token.
12#[derive(Clone, Debug, PartialEq, Eq, Hash)]
13pub struct Arg {
14    value: String,
15}
16
17impl Arg {
18    /// Creates an owned argument token.
19    #[must_use]
20    pub fn new(value: impl Into<String>) -> Self {
21        Self {
22            value: value.into(),
23        }
24    }
25
26    /// Returns the borrowed token string.
27    #[must_use]
28    pub fn as_str(&self) -> &str {
29        &self.value
30    }
31
32    /// Returns the owned token string.
33    #[must_use]
34    pub fn into_string(self) -> String {
35        self.value
36    }
37
38    /// Returns whether this token is the `--` option separator.
39    #[must_use]
40    pub fn is_option_separator(&self) -> bool {
41        is_option_separator(&self.value)
42    }
43
44    /// Returns whether this token looks like a flag or option token.
45    #[must_use]
46    pub fn is_flag_like(&self) -> bool {
47        is_flag_like(&self.value)
48    }
49
50    /// Returns whether this token looks positional.
51    #[must_use]
52    pub fn is_positional(&self) -> bool {
53        is_positional(&self.value)
54    }
55}
56
57impl AsRef<str> for Arg {
58    fn as_ref(&self) -> &str {
59        self.as_str()
60    }
61}
62
63impl From<String> for Arg {
64    fn from(value: String) -> Self {
65        Self::new(value)
66    }
67}
68
69impl From<&str> for Arg {
70    fn from(value: &str) -> Self {
71        Self::new(value)
72    }
73}
74
75impl fmt::Display for Arg {
76    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
77        formatter.write_str(&self.value)
78    }
79}
80
81/// A raw owned argument collection.
82#[derive(Clone, Debug, Default, PartialEq, Eq)]
83pub struct RawArgs {
84    args: Vec<Arg>,
85}
86
87impl RawArgs {
88    /// Creates a raw argument collection from owned tokens.
89    #[must_use]
90    pub const fn new(args: Vec<Arg>) -> Self {
91        Self { args }
92    }
93
94    /// Creates an empty raw argument collection.
95    #[must_use]
96    pub const fn empty() -> Self {
97        Self { args: Vec::new() }
98    }
99
100    /// Adds an argument token to the end of the collection.
101    pub fn push(&mut self, arg: Arg) {
102        self.args.push(arg);
103    }
104
105    /// Returns the number of stored tokens.
106    #[must_use]
107    pub const fn len(&self) -> usize {
108        self.args.len()
109    }
110
111    /// Returns whether the collection has no tokens.
112    #[must_use]
113    pub const fn is_empty(&self) -> bool {
114        self.args.is_empty()
115    }
116
117    /// Returns a borrowed iterator over stored tokens.
118    pub fn iter(&self) -> slice::Iter<'_, Arg> {
119        self.args.iter()
120    }
121
122    /// Returns the underlying owned vector.
123    #[must_use]
124    pub fn into_vec(self) -> Vec<Arg> {
125        self.args
126    }
127}
128
129impl FromIterator<Arg> for RawArgs {
130    fn from_iter<T: IntoIterator<Item = Arg>>(iter: T) -> Self {
131        Self::new(iter.into_iter().collect())
132    }
133}
134
135impl FromIterator<String> for RawArgs {
136    fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
137        iter.into_iter().map(Arg::from).collect()
138    }
139}
140
141impl IntoIterator for RawArgs {
142    type Item = Arg;
143    type IntoIter = std::vec::IntoIter<Arg>;
144
145    fn into_iter(self) -> Self::IntoIter {
146        self.args.into_iter()
147    }
148}
149
150impl<'a> IntoIterator for &'a RawArgs {
151    type Item = &'a Arg;
152    type IntoIter = slice::Iter<'a, Arg>;
153
154    fn into_iter(self) -> Self::IntoIter {
155        self.iter()
156    }
157}
158
159/// Returns whether a token is the `--` option separator.
160#[must_use]
161pub fn is_option_separator(token: &str) -> bool {
162    token == "--"
163}
164
165/// Returns whether a token looks like a flag or option token.
166#[must_use]
167pub fn is_flag_like(token: &str) -> bool {
168    token.starts_with('-') && token != "-" && !is_option_separator(token)
169}
170
171/// Returns whether a token looks positional.
172#[must_use]
173pub fn is_positional(token: &str) -> bool {
174    !is_option_separator(token) && !is_flag_like(token)
175}
176
177#[cfg(test)]
178mod tests {
179    use super::{Arg, RawArgs, is_flag_like, is_option_separator, is_positional};
180
181    #[test]
182    fn classifies_argument_tokens() {
183        assert!(is_positional("file.txt"));
184        assert!(is_positional("-"));
185        assert!(is_option_separator("--"));
186        assert!(is_flag_like("-v"));
187        assert!(is_flag_like("--verbose"));
188        assert!(!is_flag_like("--"));
189    }
190
191    #[test]
192    fn stores_owned_arguments() {
193        let mut args = RawArgs::empty();
194        args.push(Arg::from("tool"));
195        args.push(Arg::from("README.md"));
196
197        let tokens: Vec<_> = args.iter().map(Arg::as_str).collect();
198
199        assert_eq!(args.len(), 2);
200        assert_eq!(tokens, vec!["tool", "README.md"]);
201        assert!(args.into_vec()[1].is_positional());
202    }
203}