1use anyhow::{Context, Result};
2use std::ffi::{OsStr, OsString};
3
4pub fn expand() -> Result<Vec<OsString>> {
5 let mut expander: Expander = Expander::default();
6 for arg: OsString in std::env::args_os() {
7 expander.push(arg)?;
8 }
9 Ok(expander.args)
10}
11
12#[derive(Default)]
13struct Expander {
14 args: Vec<OsString>,
15}
16
17impl Expander {
18 fn push(&mut self, arg: OsString) -> Result<()> {
19 let bytes: &[u8] = arg.as_encoded_bytes();
20 match bytes.split_first() {
21 Some((b'@', rest: &[u8])) => {
22 self.push_file(unsafe { OsStr::from_encoded_bytes_unchecked(bytes:rest) })
23 }
24 _ => {
25 self.args.push(arg);
26 Ok(())
27 }
28 }
29 }
30
31 fn push_file(&mut self, file: &OsStr) -> Result<()> {
32 let contents: String =
33 std::fs::read_to_string(path:file).with_context(|| format!("failed to read {file:?}"))?;
34
35 for part: String in imp::split(&contents) {
36 self.push(arg:part.into())?;
37 }
38 Ok(())
39 }
40}
41
42#[cfg(not(windows))]
43use gnu as imp;
44#[cfg(not(windows))]
45mod gnu {
46 pub fn split(s: &str) -> impl Iterator<Item = String> + '_ {
47 Split { iter: s.chars() }
48 }
49
50 struct Split<'a> {
51 iter: std::str::Chars<'a>,
52 }
53
54 impl<'a> Iterator for Split<'a> {
55 type Item = String;
56
57 fn next(&mut self) -> Option<String> {
58 loop {
59 match self.iter.next()? {
60 c if c.is_whitespace() => {}
61 '"' => break Some(self.quoted('"')),
62 '\'' => break Some(self.quoted('\'')),
63 c => {
64 let mut ret = String::new();
65 self.push(&mut ret, c);
66 while let Some(next) = self.iter.next() {
67 if next.is_whitespace() {
68 break;
69 }
70 self.push(&mut ret, next);
71 }
72 break Some(ret);
73 }
74 }
75 }
76 }
77 }
78
79 impl Split<'_> {
80 fn quoted(&mut self, end: char) -> String {
81 let mut part = String::new();
82 while let Some(next) = self.iter.next() {
83 if next == end {
84 break;
85 }
86 self.push(&mut part, next);
87 }
88 part
89 }
90
91 fn push(&mut self, dst: &mut String, ch: char) {
92 if ch == '\\' {
93 if let Some(ch) = self.iter.next() {
94 dst.push(ch);
95 return;
96 }
97 }
98 dst.push(ch);
99 }
100 }
101
102 #[test]
103 fn tests() {
104 assert_eq!(split("x").collect::<Vec<_>>(), ["x"]);
105 assert_eq!(split("\\x").collect::<Vec<_>>(), ["x"]);
106 assert_eq!(split("'x'").collect::<Vec<_>>(), ["x"]);
107 assert_eq!(split("\"x\"").collect::<Vec<_>>(), ["x"]);
108
109 assert_eq!(split("x y").collect::<Vec<_>>(), ["x", "y"]);
110 assert_eq!(split("x\ny").collect::<Vec<_>>(), ["x", "y"]);
111 assert_eq!(split("\\x y").collect::<Vec<_>>(), ["x", "y"]);
112 assert_eq!(split("'x y'").collect::<Vec<_>>(), ["x y"]);
113 assert_eq!(split("\"x y\"").collect::<Vec<_>>(), ["x y"]);
114 assert_eq!(split("\"x 'y'\"\n'y'").collect::<Vec<_>>(), ["x 'y'", "y"]);
115 assert_eq!(
116 split(
117 r#"
118 a\ \\b
119 z
120 "x y \\z"
121 "#
122 )
123 .collect::<Vec<_>>(),
124 ["a \\b", "z", "x y \\z"]
125 );
126 }
127}
128
129#[cfg(windows)]
130use windows as imp;
131#[cfg(windows)]
132mod windows {
133 pub fn split(s: &str) -> impl Iterator<Item = String> {
134 winsplit::split(s).into_iter()
135 }
136}
137