Skip to content

Commit 8e3ab5c

Browse files
committed
Add split_iter to split into an Iterator
It allows the caller to store the words in the collection of their choosing. The behavior of "split" is not modified to preserve backwards compatibility.
1 parent 8f264f1 commit 8e3ab5c

File tree

1 file changed

+135
-100
lines changed

1 file changed

+135
-100
lines changed

src/lib.rs

Lines changed: 135 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
use std::borrow::Cow;
1414
use std::error;
1515
use std::fmt;
16-
use std::mem;
16+
use std::iter;
1717
use std::result;
18+
use std::str::Chars;
1819

1920
/// An error returned when shell parsing fails.
2021
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -28,6 +29,7 @@ impl fmt::Display for ParseError {
2829

2930
impl error::Error for ParseError {}
3031

32+
#[derive(Debug)]
3133
enum State {
3234
/// Within a delimiter.
3335
Delimiter,
@@ -47,6 +49,136 @@ enum State {
4749
Comment,
4850
}
4951

52+
pub struct Split<'a> {
53+
chars: Chars<'a>,
54+
state: State,
55+
}
56+
57+
pub fn split_iter(s: &str) -> Split<'_> {
58+
Split {
59+
chars: s.chars(),
60+
state: State::Delimiter,
61+
}
62+
}
63+
64+
impl Iterator for Split<'_> {
65+
type Item = Result<String, ParseError>;
66+
67+
fn next(&mut self) -> Option<Result<String, ParseError>> {
68+
use State::*;
69+
70+
let mut word = String::new();
71+
72+
loop {
73+
let c = self.chars.next();
74+
self.state = match self.state {
75+
Delimiter => match c {
76+
None => return None,
77+
Some('\'') => SingleQuoted,
78+
Some('\"') => DoubleQuoted,
79+
Some('\\') => Backslash,
80+
Some('\t') | Some(' ') | Some('\n') => Delimiter,
81+
Some('#') => Comment,
82+
Some(c) => {
83+
word.push(c);
84+
Unquoted
85+
}
86+
},
87+
Backslash => match c {
88+
None => {
89+
word.push('\\');
90+
self.state = Delimiter;
91+
return Some(Ok(word));
92+
}
93+
Some('\n') => Delimiter,
94+
Some(c) => {
95+
word.push(c);
96+
Unquoted
97+
}
98+
},
99+
Unquoted => match c {
100+
None => {
101+
self.state = Delimiter;
102+
return Some(Ok(word));
103+
}
104+
Some('\'') => SingleQuoted,
105+
Some('\"') => DoubleQuoted,
106+
Some('\\') => UnquotedBackslash,
107+
Some('\t') | Some(' ') | Some('\n') => {
108+
self.state = Delimiter;
109+
return Some(Ok(word));
110+
}
111+
Some(c) => {
112+
word.push(c);
113+
Unquoted
114+
}
115+
},
116+
UnquotedBackslash => match c {
117+
None => {
118+
word.push('\\');
119+
self.state = Delimiter;
120+
return Some(Ok(word));
121+
}
122+
Some('\n') => Unquoted,
123+
Some(c) => {
124+
word.push(c);
125+
Unquoted
126+
}
127+
},
128+
SingleQuoted => match c {
129+
None => {
130+
self.state = Delimiter;
131+
return Some(Err(ParseError));
132+
}
133+
Some('\'') => Unquoted,
134+
Some(c) => {
135+
word.push(c);
136+
SingleQuoted
137+
}
138+
},
139+
DoubleQuoted => match c {
140+
None => {
141+
self.state = Delimiter;
142+
return Some(Err(ParseError));
143+
}
144+
Some('\"') => Unquoted,
145+
Some('\\') => DoubleQuotedBackslash,
146+
Some(c) => {
147+
word.push(c);
148+
DoubleQuoted
149+
}
150+
},
151+
DoubleQuotedBackslash => match c {
152+
None => {
153+
self.state = Delimiter;
154+
return Some(Err(ParseError));
155+
}
156+
Some('\n') => DoubleQuoted,
157+
Some(c @ '$') | Some(c @ '`') | Some(c @ '"') | Some(c @ '\\') => {
158+
word.push(c);
159+
DoubleQuoted
160+
}
161+
Some(c) => {
162+
word.push('\\');
163+
word.push(c);
164+
DoubleQuoted
165+
}
166+
},
167+
Comment => match c {
168+
None => {
169+
self.state = Delimiter;
170+
return None;
171+
}
172+
Some('\n') => Delimiter,
173+
Some(_) => Comment,
174+
},
175+
}
176+
}
177+
}
178+
}
179+
180+
impl iter::FusedIterator for Split<'_> {}
181+
50182
/// Splits command line into separate arguments, in much the same way Unix shell would, but without
51183
/// many of expansion the shell would perform.
52184
///
@@ -106,107 +238,10 @@ enum State {
106238
/// .expect("failed to wait for subprocess");
107239
/// ```
108240
pub fn split(s: &str) -> result::Result<Vec<String>, ParseError> {
109-
use State::*;
110-
111241
let mut words = Vec::new();
112-
let mut word = String::new();
113-
let mut chars = s.chars();
114-
let mut state = Delimiter;
115-
116-
loop {
117-
let c = chars.next();
118-
state = match state {
119-
Delimiter => match c {
120-
None => break,
121-
Some('\'') => SingleQuoted,
122-
Some('\"') => DoubleQuoted,
123-
Some('\\') => Backslash,
124-
Some('\t') | Some(' ') | Some('\n') => Delimiter,
125-
Some('#') => Comment,
126-
Some(c) => {
127-
word.push(c);
128-
Unquoted
129-
}
130-
},
131-
Backslash => match c {
132-
None => {
133-
word.push('\\');
134-
words.push(mem::replace(&mut word, String::new()));
135-
break;
136-
}
137-
Some('\n') => Delimiter,
138-
Some(c) => {
139-
word.push(c);
140-
Unquoted
141-
}
142-
},
143-
Unquoted => match c {
144-
None => {
145-
words.push(mem::replace(&mut word, String::new()));
146-
break;
147-
}
148-
Some('\'') => SingleQuoted,
149-
Some('\"') => DoubleQuoted,
150-
Some('\\') => UnquotedBackslash,
151-
Some('\t') | Some(' ') | Some('\n') => {
152-
words.push(mem::replace(&mut word, String::new()));
153-
Delimiter
154-
}
155-
Some(c) => {
156-
word.push(c);
157-
Unquoted
158-
}
159-
},
160-
UnquotedBackslash => match c {
161-
None => {
162-
word.push('\\');
163-
words.push(mem::replace(&mut word, String::new()));
164-
break;
165-
}
166-
Some('\n') => Unquoted,
167-
Some(c) => {
168-
word.push(c);
169-
Unquoted
170-
}
171-
},
172-
SingleQuoted => match c {
173-
None => return Err(ParseError),
174-
Some('\'') => Unquoted,
175-
Some(c) => {
176-
word.push(c);
177-
SingleQuoted
178-
}
179-
},
180-
DoubleQuoted => match c {
181-
None => return Err(ParseError),
182-
Some('\"') => Unquoted,
183-
Some('\\') => DoubleQuotedBackslash,
184-
Some(c) => {
185-
word.push(c);
186-
DoubleQuoted
187-
}
188-
},
189-
DoubleQuotedBackslash => match c {
190-
None => return Err(ParseError),
191-
Some('\n') => DoubleQuoted,
192-
Some(c @ '$') | Some(c @ '`') | Some(c @ '"') | Some(c @ '\\') => {
193-
word.push(c);
194-
DoubleQuoted
195-
}
196-
Some(c) => {
197-
word.push('\\');
198-
word.push(c);
199-
DoubleQuoted
200-
}
201-
},
202-
Comment => match c {
203-
None => break,
204-
Some('\n') => Delimiter,
205-
Some(_) => Comment,
206-
},
207-
}
242+
for word in split_iter(s) {
243+
words.push(word?);
208244
}
209-
210245
Ok(words)
211246
}
212247

0 commit comments

Comments
 (0)