1use std::ffi::{OsStr, OsString};
4
5pub trait ArgString: Sized {
7 fn parse_arg(self) -> Result<ParsedArg<Self>, Self>;
11
12 fn to_str(&self) -> Option<&str>;
14
15 fn to_osstr(&self) -> &OsStr;
17}
18
19fn is_arg_name(c: char) -> bool {
20 match c {
21 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' => true,
22 _ => false,
23 }
24}
25
26impl ArgString for String {
27 fn parse_arg(self) -> Result<ParsedArg<String>, String> {
28 let mut chars = self.chars();
29 match chars.next() {
30 Some('-') => (),
31 _ => return Ok(ParsedArg::Positional(self)),
32 }
33 let cur = chars.clone();
34 match chars.next() {
35 Some('-') => {
36 if chars.as_str().is_empty() {
37 return Ok(ParsedArg::EndOfFlags);
38 }
39 }
40 Some(_) => chars = cur,
41 None => return Ok(ParsedArg::Positional(self)),
42 }
43 let body = chars.as_str();
44 let (name, value) = match body.find('=') {
45 Some(idx) => (&body[..idx], Some(&body[idx + 1..])),
46 None => (body, None),
47 };
48 if name.is_empty() || !name.chars().all(is_arg_name) {
49 return Err(self);
50 }
51 Ok(ParsedArg::Named(name.to_owned(), value.map(str::to_owned)))
52 }
53
54 fn to_str(&self) -> Option<&str> {
55 Some(self)
56 }
57
58 fn to_osstr(&self) -> &OsStr {
59 self.as_ref()
60 }
61}
62
63impl ArgString for OsString {
64 fn parse_arg(self) -> Result<ParsedArg<OsString>, OsString> {
65 use std::os::unix::ffi::{OsStrExt, OsStringExt};
66 let bytes = self.as_bytes();
67 if bytes.len() < 2 || bytes[0] != b'-' {
68 return Ok(ParsedArg::Positional(self));
69 }
70 let body = if bytes[1] != b'-' {
71 &bytes[1..]
72 } else if bytes.len() == 2 {
73 return Ok(ParsedArg::EndOfFlags);
74 } else {
75 &bytes[2..]
76 };
77 let (name, value) = match body.iter().position(|&c| c == b'=') {
78 None => (body, None),
79 Some(idx) => (&body[..idx], Some(&body[idx + 1..])),
80 };
81 if name.len() == 0
82 || name[0] == b'-'
83 || name[name.len() - 1] == b'-'
84 || !name.iter().all(|&c| is_arg_name(c as char))
85 {
86 return Err(self);
87 }
88 let name = Vec::from(name);
89 let name = unsafe { String::from_utf8_unchecked(name) };
90 let value = value.map(|v| OsString::from_vec(Vec::from(v)));
91 Ok(ParsedArg::Named(name, value))
92 }
93
94 fn to_str(&self) -> Option<&str> {
95 OsStr::to_str(self)
96 }
97
98 fn to_osstr(&self) -> &OsStr {
99 self
100 }
101}
102
103#[derive(Debug, Clone, PartialEq, Eq)]
105pub enum ParsedArg<T> {
106 Positional(T),
108 EndOfFlags,
110 Named(String, Option<T>),
114}
115
116impl<T> ParsedArg<T> {
117 pub fn map<U, F>(self, f: F) -> ParsedArg<U>
119 where
120 F: FnOnce(T) -> U,
121 {
122 match self {
123 ParsedArg::Positional(x) => ParsedArg::Positional(f(x)),
124 ParsedArg::EndOfFlags => ParsedArg::EndOfFlags,
125 ParsedArg::Named(x, y) => ParsedArg::Named(x, y.map(f)),
126 }
127 }
128}
129
130#[cfg(test)]
131mod test {
132 use super::*;
133 use std::ffi::OsStr;
134 use std::fmt::Debug;
135 use std::os::unix::ffi::OsStrExt;
136
137 fn osstr(s: &[u8]) -> OsString {
138 OsString::from(OsStr::from_bytes(s))
139 }
140
141 struct Case<T>(T, ParsedArg<T>);
142
143 impl<T> Case<T> {
144 fn map<F, U>(self, f: F) -> Case<U>
145 where
146 F: Fn(T) -> U,
147 {
148 let Case(input, output) = self;
149 Case(f(input), output.map(f))
150 }
151 }
152
153 impl<T: Debug + Clone + ArgString + PartialEq<T>> Case<T> {
154 fn test(&self) -> bool {
155 let Case(input, expected) = self;
156 match input.clone().parse_arg() {
157 Ok(arg) => {
158 if &arg != expected {
159 eprintln!(
160 "{:?}.parse_arg(): got {:?}, expect {:?}",
161 input, expected, arg
162 );
163 false
164 } else {
165 true
166 }
167 }
168 Err(_) => {
169 eprintln!("{:?}.parse_arg(): got error, expect {:?}", input, expected);
170 false
171 }
172 }
173 }
174 }
175
176 fn success_cases() -> Vec<Case<String>> {
177 let mut cases = vec![
178 Case("abc", ParsedArg::Positional("abc")),
179 Case("", ParsedArg::Positional("")),
180 Case("-", ParsedArg::Positional("-")),
181 Case("--", ParsedArg::EndOfFlags),
182 Case("-a", ParsedArg::Named("a".to_owned(), None)),
183 Case("--a", ParsedArg::Named("a".to_owned(), None)),
184 Case("-a=", ParsedArg::Named("a".to_owned(), Some(""))),
185 Case("--a=", ParsedArg::Named("a".to_owned(), Some(""))),
186 Case("--arg-name", ParsedArg::Named("arg-name".to_owned(), None)),
187 Case("--ARG_NAME", ParsedArg::Named("ARG_NAME".to_owned(), None)),
188 Case(
189 "--opt=value",
190 ParsedArg::Named("opt".to_owned(), Some("value")),
191 ),
192 ];
193 cases.drain(..).map(|c| c.map(str::to_owned)).collect()
194 }
195
196 struct Fail<T>(T);
197
198 impl<T: Debug + Clone + ArgString + PartialEq<T>> Fail<T> {
199 fn test(&self) -> bool {
200 let Fail(input) = self;
201 match input.clone().parse_arg() {
202 Ok(arg) => {
203 eprintln!("{:?}.parse_arg(): got {:?}, expect error", input, arg);
204 false
205 }
206 Err(e) => {
207 if &e != input {
208 eprintln!(
209 "{:?}.parse_arg(): got error {:?}, expect error {:?}",
210 input, e, input
211 );
212 false
213 } else {
214 true
215 }
216 }
217 }
218 }
219 }
220
221 const FAIL_CASES: &'static [&'static str] =
222 &["-\0", "--\n", "--\0=", "-=", "--=", "-=value", "--=xyz"];
223
224 #[test]
225 fn parse_string_success() {
226 let mut success = true;
227 for case in success_cases().drain(..) {
228 if !case.test() {
229 success = false;
230 }
231 }
232 if !success {
233 panic!("failed");
234 }
235 }
236
237 #[test]
238 fn parse_osstring_success() {
239 let mut success = true;
240 let mut cases: Vec<Case<OsString>> = success_cases()
241 .drain(..)
242 .map(|c| c.map(OsString::from))
243 .collect();
244 cases.push(Case(
245 osstr(b"\x80\xff"),
246 ParsedArg::Positional(osstr(b"\x80\xff")),
247 ));
248 cases.push(Case(
249 osstr(b"--opt=\xff"),
250 ParsedArg::Named("opt".to_owned(), Some(osstr(b"\xff"))),
251 ));
252 for case in cases.drain(..) {
253 if !case.test() {
254 success = false;
255 }
256 }
257 if !success {
258 panic!("failed");
259 }
260 }
261
262 #[test]
263 fn parse_string_failure() {
264 let mut success = true;
265 for &input in FAIL_CASES.iter() {
266 if !Fail(input.to_owned()).test() {
267 success = false;
268 }
269 }
270 if !success {
271 panic!("failed");
272 }
273 }
274
275 #[test]
276 fn parse_osstring_failure() {
277 let mut success = true;
278 let mut cases: Vec<OsString> = FAIL_CASES
279 .iter()
280 .map(|&s| OsString::from(s.to_owned()))
281 .collect();
282 for input in cases.drain(..) {
283 if !Fail(input).test() {
284 success = false;
285 }
286 }
287 if !success {
288 panic!("failed");
289 }
290 }
291}