aopt_core/
args.rs

1use std::borrow::Cow;
2use std::ffi::OsStr;
3use std::ffi::OsString;
4use std::fmt::Display;
5use std::ops::Deref;
6
7use crate::str::CowOsStrUtils;
8use crate::ARef;
9use crate::Error;
10
11const EQUAL: char = '=';
12
13#[derive(Debug, Clone, Default)]
14pub struct ArgInfo<'a> {
15    pub name: Cow<'a, str>,
16
17    pub value: Option<Cow<'a, OsStr>>,
18}
19
20impl<'a> ArgInfo<'a> {
21    /// Parse the input command line item with given regexs, return an [`ArgInfo`].
22    ///
23    /// The struct of the input option string are:
24    ///
25    /// ```plaintext
26    /// [--/option][=][value]
27    ///        |    |    |
28    ///        |    |    |
29    ///        |    |    The value part, it is optional.
30    ///        |    |
31    ///        |    The delimiter of option name and value.
32    ///        |    
33    ///        The option name part, it must be provide by user.
34    /// ```
35    ///
36    /// # Example
37    ///
38    /// ```rust
39    /// # use aopt_core::args::Args;
40    /// # use aopt_core::Error;
41    /// # use aopt_core::args::ArgInfo;
42    /// # use std::ffi::OsStr;
43    /// #
44    /// # fn main() -> Result<(), Error> {
45    ///     {// parse option with value
46    ///         let output = ArgInfo::parse(OsStr::new("--foo=32"))?;
47    ///
48    ///         assert_eq!(output.name, "--foo");
49    ///         assert_eq!(output.value.as_deref(), Some(OsStr::new("32")));
50    ///     }
51    ///     {// parse boolean option
52    ///         let output = ArgInfo::parse(OsStr::new("--/bar"))?;
53    ///
54    ///         assert_eq!(output.name, "--/bar");
55    ///         assert_eq!(output.value, None);
56    ///     }
57    ///     {// parse other string
58    ///         let output = ArgInfo::parse(OsStr::new("-=bar"))?;
59    ///
60    ///         assert_eq!(output.name, "-");
61    ///         assert_eq!(output.value.as_deref(), Some(OsStr::new("bar")));
62    ///     }
63    /// # Ok(())
64    /// # }
65    /// ```
66    pub fn parse(val: &'a OsStr) -> Result<Self, Error> {
67        let arg_display = format!("{}", std::path::Path::new(val).display());
68
69        crate::trace!("parsing command line argument {val:?}");
70        if let Some((name, value)) = crate::str::split_once(val, EQUAL) {
71            // - convert the name to &str, the name must be valid utf8
72            let name = name
73                .to_str(|v| v.trim())
74                .ok_or_else(|| Error::arg(&arg_display, "failed convert OsStr to str"))?;
75
76            if name.is_empty() {
77                return Err(Error::arg(arg_display, "can not be empty"));
78            }
79            Ok(Self {
80                name,
81                value: Some(value),
82            })
83        } else {
84            let name = val
85                .to_str()
86                .ok_or_else(|| Error::arg(arg_display, "failed convert OsStr to str"))?;
87
88            Ok(Self {
89                name: Cow::Borrowed(name),
90                value: None,
91            })
92        }
93    }
94}
95
96#[derive(Debug, Clone, Default)]
97pub struct Args {
98    inner: ARef<Vec<OsString>>,
99}
100
101impl Args {
102    pub fn new<S: Into<OsString>>(inner: impl Iterator<Item = S>) -> Self {
103        Self {
104            inner: ARef::new(inner.map(|v| v.into()).collect()),
105        }
106    }
107
108    /// Create from [`args_os`](std::env::args_os()).
109    pub fn from_env() -> Self {
110        Self::new(std::env::args_os())
111    }
112
113    pub fn unwrap_or_clone(self) -> Vec<OsString> {
114        ARef::unwrap_or_clone(self.inner)
115    }
116}
117
118impl<T: Into<OsString>, I: IntoIterator<Item = T>> From<I> for Args {
119    fn from(value: I) -> Self {
120        Self::new(value.into_iter())
121    }
122}
123
124impl From<Args> for Vec<OsString> {
125    fn from(value: Args) -> Self {
126        value.unwrap_or_clone()
127    }
128}
129
130impl Deref for Args {
131    type Target = Vec<OsString>;
132
133    fn deref(&self) -> &Self::Target {
134        &self.inner
135    }
136}
137
138pub fn iter2<'a, 'b>(
139    args: &'a [&'b OsStr],
140) -> impl Iterator<Item = (&'a &'b OsStr, Option<&'a &'b OsStr>)> {
141    args.iter()
142        .scan(args.iter().skip(1), |i, e| Some((e, i.next())))
143}
144
145impl Display for Args {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        write!(
148            f,
149            "Args {{[{}]}}",
150            self.inner
151                .iter()
152                .map(|v| format!("{:?}", v))
153                .collect::<Vec<String>>()
154                .join(", ")
155        )
156    }
157}
158
159#[cfg(test)]
160mod test {
161
162    use std::ffi::OsStr;
163
164    use super::Args;
165
166    #[test]
167    fn test_args() {
168        let args = Args::from(["--opt", "value", "--bool", "pos"]);
169        let mut iter = args
170            .iter()
171            .zip(args.iter().skip(1).map(Some).chain(None))
172            .enumerate();
173
174        if let Some((idx, (opt, arg))) = iter.next() {
175            assert_eq!(idx, 0);
176            assert_eq!(opt, OsStr::new("--opt"));
177            assert_eq!(arg.map(|v| v.as_ref()), Some(OsStr::new("value")));
178        }
179
180        if let Some((idx, (opt, arg))) = iter.next() {
181            assert_eq!(idx, 1);
182            assert_eq!(opt, OsStr::new("value"));
183            assert_eq!(arg.map(|v| v.as_ref()), Some(OsStr::new("--bool")));
184        }
185
186        if let Some((idx, (opt, arg))) = iter.next() {
187            assert_eq!(idx, 2);
188            assert_eq!(opt, OsStr::new("--bool"));
189            assert_eq!(arg.map(|v| v.as_ref()), Some(OsStr::new("pos")));
190        }
191
192        if let Some((idx, (opt, arg))) = iter.next() {
193            assert_eq!(idx, 3);
194            assert_eq!(opt, OsStr::new("pos"));
195            assert_eq!(arg, None);
196        }
197
198        assert_eq!(iter.next(), None);
199    }
200}