windows_args/
lib.rs

1#![doc(html_root_url = "https://docs.rs/windows-args/0.2.0")]
2
3//! # `windows-args`
4//!
5//! A command-line argument parser for Windows, copied almost wholesale from the rust standard library.
6//!
7//! Offerings include:
8//!
9//! * [`Args`] and [`ArgsOs`], iterators that produce `String` and `OsString` values respectively.
10//! * Two parsing functions, [`Args::parse_cmd`] and [`Args::parse_args`].
11//!     * These differ in how they parse the first argument, and in how they treat empty input.
12//!
13//! Due to limitations of the current implementation, this crate currently can only be used
14//! on Windows.
15//!
16//! ```rust
17//! use windows_args::Args;
18//!
19//! // to parse a complete command (beginning with an executable name)
20//! # #[allow(unused)]
21//! let mut args = Args::parse_cmd(r#"foobar.exe to "C:\Program Files\Hi.txt" now"#);
22//!
23//! // to parse arguments to a command (NOT beginning with an executable name)
24//! let mut args = Args::parse_args(r#"foobar to "C:\Program Files\Hi.txt" now"#);
25//!
26//! assert_eq!(args.next(), Some("foobar".to_string()));
27//! assert_eq!(args.next(), Some("to".to_string()));
28//! assert_eq!(args.next(), Some("C:\\Program Files\\Hi.txt".to_string()));
29//! assert_eq!(args.next(), Some("now".to_string()));
30//! assert_eq!(args.next(), None);
31//! ```
32
33#[cfg(windows)]
34use std::ffi::{OsStr, OsString};
35use std::fmt;
36use crate::args::ArgsWtf8;
37use wtf8::{Wtf8, Wtf8Buf};
38
39mod wtf8like;
40mod args;
41
42/// An iterator over the arguments of a process, yielding a [`String`] value for
43/// each argument.
44///
45/// [`String`]: ../string/struct.String.html
46pub struct Args { inner: ArgsWtf8<Wtf8Buf> }
47
48/// An iterator over the arguments of a process, yielding an [`OsString`] value
49/// for each argument.
50///
51/// [`OsString`]: ../ffi/struct.OsString.html
52#[cfg(windows)]
53pub struct ArgsOs { inner: ArgsWtf8<OsString> }
54
55#[cfg(windows)]
56impl ArgsOs {
57    /// Parse an [`OsStr`] containing the complete command line.
58    ///
59    /// The output will always contain at least one argument (representing the executable name).
60    /// If the input was empty, a placeholder name is given.
61    ///
62    /// ```rust
63    /// use std::ffi::OsString;
64    ///
65    /// let args = windows_args::ArgsOs::parse_cmd("test  \" \"".as_ref());
66    /// assert_eq!(
67    ///     args.collect::<Vec<_>>(),
68    ///     vec!["test".into(), " ".into()] as Vec<OsString>,
69    /// );
70    /// ```
71    pub fn parse_cmd(input: &OsStr) -> Self {
72        ArgsOs { inner: ArgsWtf8::parse_cmd(input) }
73    }
74
75    /// Parse an [`OsStr`] containing whitespace-separated arguments to an executable.
76    ///
77    /// This function is intended to be used for strings which **do not** begin with
78    /// the executable name.
79    ///
80    /// ```rust
81    /// use std::ffi::OsString;
82    ///
83    /// let args = windows_args::ArgsOs::parse_args("test  \" \"".as_ref());
84    /// assert_eq!(
85    ///     args.collect::<Vec<_>>(),
86    ///     vec!["test".into(), " ".into()] as Vec<OsString>,
87    /// );
88    /// ```
89    pub fn parse_args(input: &OsStr) -> Self {
90        parse_args_via_parse_cmd(
91            input,
92            ArgsOs::parse_cmd,
93            OsString::with_capacity,
94            |buf, s| buf.push(s),
95            OsStr::len,
96        )
97    }
98}
99
100impl Args {
101    /// Parse a string containing the complete command line.
102    ///
103    /// The output will always contain at least one argument (representing the executable name).
104    /// If the input was empty, a placeholder name is given.
105    ///
106    /// ```
107    /// let args = windows_args::Args::parse_cmd(r#"me.exe  \\\"#);
108    /// assert_eq!(
109    ///     args.collect::<Vec<_>>(),
110    ///     vec!["me.exe".to_string(), r#"\\\"#.to_string()],
111    /// );
112    /// ```
113    pub fn parse_cmd(input: &str) -> Self {
114        Args { inner: ArgsWtf8::parse_cmd(Wtf8::from_str(input)) }
115    }
116
117    /// Parse a string containing whitespace-separated arguments to an executable.
118    ///
119    /// This function is intended to be used for strings which **do not** begin with
120    /// the executable name.
121    ///
122    /// ```
123    /// let args = windows_args::Args::parse_args(r#"file.txt  \\\"#);
124    /// assert_eq!(
125    ///     args.collect::<Vec<_>>(),
126    ///     vec!["file.txt".to_string(), r#"\\\"#.to_string()],
127    /// );
128    /// ```
129    pub fn parse_args(input: &str) -> Self {
130        parse_args_via_parse_cmd(
131            input,
132            Args::parse_cmd,
133            String::with_capacity,
134            String::push_str,
135            str::len,
136        )
137    }
138}
139
140fn expect_still_utf8(arg: Wtf8Buf) -> String {
141    arg.into_string().unwrap_or_else(|arg| {
142        panic!("\
143valid UTF-8 became invalid after arg splitting?!
144BadArg: {:?}\
145", arg);
146    })
147}
148
149impl Iterator for Args {
150    type Item = String;
151    fn next(&mut self) -> Option<String> { self.inner.next().map(expect_still_utf8) }
152    fn size_hint(&self) -> (usize, Option<usize>) { self.inner.size_hint() }
153}
154
155impl ExactSizeIterator for Args {
156    fn len(&self) -> usize { self.inner.len() }
157}
158
159impl DoubleEndedIterator for Args {
160    fn next_back(&mut self) -> Option<String> { self.inner.next_back().map(expect_still_utf8) }
161}
162
163impl fmt::Debug for Args {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        f.debug_struct("Args")
166            .field("inner", &self.inner.inner_debug())
167            .finish()
168    }
169}
170
171#[cfg(windows)]
172impl Iterator for ArgsOs {
173    type Item = OsString;
174    fn next(&mut self) -> Option<OsString> { self.inner.next() }
175    fn size_hint(&self) -> (usize, Option<usize>) { self.inner.size_hint() }
176}
177
178#[cfg(windows)]
179impl ExactSizeIterator for ArgsOs {
180    fn len(&self) -> usize { self.inner.len() }
181}
182
183#[cfg(windows)]
184impl DoubleEndedIterator for ArgsOs {
185    fn next_back(&mut self) -> Option<OsString> { self.inner.next_back() }
186}
187
188#[cfg(windows)]
189impl fmt::Debug for ArgsOs {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        f.debug_struct("ArgsOs")
192            .field("inner", &self.inner.inner_debug())
193            .finish()
194    }
195}
196
197fn parse_args_via_parse_cmd<A, OwnS, RefS: ?Sized>(
198    input: &RefS,
199    parse_cmd: impl FnOnce(&RefS) -> A,
200    with_capacity: impl FnOnce(usize) -> OwnS,
201    push_str: impl Fn(&mut OwnS, &RefS),
202    len: impl Fn(&RefS) -> usize,
203) -> A
204where
205    A: Iterator,
206    OwnS: std::ops::Deref<Target=RefS>,
207    str: AsRef<RefS>,
208{
209    // Prepend a command name
210    let mut modified_input = with_capacity(len(input) + 2);
211    push_str(&mut modified_input, "a ".as_ref());
212    push_str(&mut modified_input, input);
213
214    // Skip the command name in the output
215    let mut out = parse_cmd(&modified_input);
216    out.next();
217
218    out
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224
225    #[test]
226    fn special_traits() {
227        assert_eq!(Args::parse_cmd("a b").next_back(), Some("b".into()));
228        assert_eq!(Args::parse_cmd("a b").len(), 2);
229    }
230
231    #[cfg(windows)]
232    #[test]
233    fn special_traits_windows() {
234        assert_eq!(ArgsOs::parse_cmd("a b".as_ref()).next_back(), Some("b".into()));
235        assert_eq!(ArgsOs::parse_cmd("a b".as_ref()).len(), 2);
236    }
237
238    #[test]
239    fn args_cmd_differences() {
240        assert_eq!(Args::parse_cmd("").collect::<Vec<_>>(), vec![String::new()]);
241        assert_eq!(Args::parse_args("").collect::<Vec<_>>(), Vec::<String>::new());
242
243        assert_eq!(
244            Args::parse_cmd(r#""abc\"def""#).collect::<Vec<_>>(),
245            vec!["abc\\".to_string(), "def".to_string(),
246        ]);
247        assert_eq!(
248            Args::parse_args(r#""abc\"def""#).collect::<Vec<_>>(),
249            vec!["abc\"def".to_string()],
250        );
251
252        assert_eq!(
253            Args::parse_cmd(r#"a "abc\"def""#).collect::<Vec<_>>(),
254            vec!["a".to_string(), "abc\"def".to_string()],
255        );
256        assert_eq!(
257            Args::parse_cmd(r#"a "abc\"def""#).collect::<Vec<_>>(),
258            vec!["a".to_string(), "abc\"def".to_string()],
259        );
260    }
261}