1use std::env::VarError;
2use thiserror::Error;
3
4#[derive(Debug, Default, PartialEq)]
5pub struct Arg {
6 sections: Vec<(String, Var)>,
7 buffer: String,
8}
9
10impl Arg {
11 pub fn new() -> Self {
12 Default::default()
13 }
14
15 pub fn expand(self) -> Result<String, VarError> {
16 let mut builder = Vec::with_capacity(self.sections.len() * 2 + 1);
17
18 for (text, var) in self.sections {
19 builder.push(text);
20 builder.push(var.expand()?);
21 }
22
23 builder.push(self.buffer);
24
25 Ok(builder.join(""))
26 }
27
28 pub fn push(&mut self, section: &str) {
29 self.buffer.push_str(section);
30 }
31
32 pub fn push_char(&mut self, c: char) {
33 self.buffer.push(c);
34 }
35
36 pub fn push_var(&mut self, var: Var) {
37 self.sections.push((self.buffer.clone(), var));
38 self.buffer.clear();
39 }
40}
41
42#[derive(Debug, Default, Clone, PartialEq)]
43pub struct Var {
44 name: String,
45 option: VarOption,
46}
47
48#[derive(Debug, Clone, PartialEq)]
51pub enum VarOption {
52 None, Default(String), DefaultIfEmpty(String), IfNotEmpty(String), IfSet(String), Offset(isize), OffsetLength(isize, isize), LengthOf, }
61
62impl Default for VarOption {
63 fn default() -> Self {
64 VarOption::None
65 }
66}
67
68impl Var {
69 pub fn expand(self) -> Result<String, VarError> {
70 match (std::env::var(self.name), self.option) {
71 (Err(VarError::NotPresent), VarOption::Default(d)) => Ok(d),
72 (Err(VarError::NotPresent), VarOption::DefaultIfEmpty(d)) => Ok(d),
73 (Ok(v), VarOption::DefaultIfEmpty(d)) => if v.is_empty() { Ok(d) } else { Ok(v) },
74 (Ok(v), VarOption::IfNotEmpty(u)) => if v.is_empty() { Ok("".to_string()) } else { Ok(u) },
75 (Ok(_), VarOption::IfSet(v)) => Ok(v),
76 (Ok(v), VarOption::Offset(offset)) => {
77 let l = v.len() as isize;
78 let offset = if offset < 0 { l + offset } else { offset };
79 let offset = if offset < 0 { 0 } else { offset };
80 if offset > l {
81 Ok("".to_string())
82 } else {
83 Ok(v[offset as usize..].to_string())
84 }
85 },
86 (Ok(v), VarOption::OffsetLength(offset, length)) => {
87 let l = v.len() as isize;
88 let offset = if offset < 0 { l + offset } else { offset };
89 let offset = if offset < 0 { 0 } else { offset };
90 let end = if length < 0 { l + length } else { offset + length };
91 let end = if end > l { l } else { end };
92 if offset > l || end > l || end < offset {
93 Ok("".to_string())
94 } else {
95 Ok(v[offset as usize..end as usize].to_string())
96 }
97 },
98 (Ok(v), VarOption::LengthOf) => Ok(format!("{}", v.len())),
99 (Err(VarError::NotPresent), VarOption::LengthOf) => Ok("0".to_string()),
100 (Ok(v), _) => Ok(v),
101 (Err(VarError::NotPresent), _) => Ok("".to_string()),
102 (Err(VarError::NotUnicode(os)), _) => Err(VarError::NotUnicode(os)),
103 }
104 }
105}
106
107#[derive(Error, Debug, PartialEq)]
108pub enum ParseError {
109 #[error("unexpected end of file")]
110 EOF,
111 #[error("invalid variable name")]
112 InvalidVar,
113}
114
115use std::iter::Peekable;
116
117pub fn into_arg(s: String) -> Result<Arg, ParseError> {
118 let mut chars = s.chars().peekable();
119
120 let mut arg = Arg::new();
121
122 loop {
123 match chars.next() {
124 Some('$') => {
125 arg.push_var(parse_var(&mut chars)?);
126 },
127 Some('"') => {
128 parse_string(&mut chars, &mut arg)?;
129 },
130 Some('\'') => {
131 parse_single(&mut chars, &mut arg)?;
132 },
133 Some('\\') => {
134 match chars.next() {
135 Some('n') => arg.push_char('\n'),
136 Some('$') => arg.push_char('$'),
137 Some('"') => arg.push_char('"'),
138 Some('\'') => arg.push_char('\''),
139 Some('\\') => arg.push_char('\\'),
140 Some(c) => arg.push_char(c),
141 None => return Err(ParseError::EOF),
142 }
143 }
144 Some(c) => arg.push_char(c),
145 None => { break; }
146 }
147 }
148
149 Ok(arg)
150}
151
152fn parse_string<I>(s: &mut Peekable<I>, arg: &mut Arg) -> Result<(), ParseError>
153where I: Iterator<Item=char> {
154 loop {
155 match s.next() {
156 Some('$') => arg.push_var(parse_var(s)?),
157 Some('"') => break,
158 Some('\\') => {
159 match s.next() {
160 Some('n') => arg.push_char('\n'),
161 Some('$') => arg.push_char('$'),
162 Some('"') => arg.push_char('"'),
163 Some('\\') => arg.push_char('\\'),
164 Some(c) => arg.push_char(c),
165 None => return Err(ParseError::EOF),
166 }
167 }
168 Some(c) => arg.push_char(c),
169 _ => return Err(ParseError::EOF),
170 }
171 }
172 Ok(())
173}
174
175fn parse_single<I>(s: &mut Peekable<I>, arg: &mut Arg) -> Result<(), ParseError>
176where I: Iterator<Item=char> {
177 loop {
178 match s.next() {
179 Some('\'') => break,
180 Some('\\') => {
181 match s.next() {
182 Some('n') => arg.push_char('\n'),
183 Some('$') => arg.push_char('$'),
184 Some('\'') => arg.push_char('\''),
185 Some('\\') => arg.push_char('\\'),
186 Some(c) => arg.push_char(c),
187 None => return Err(ParseError::EOF),
188 }
189 }
190 Some(c) => arg.push_char(c),
191 _ => return Err(ParseError::EOF),
192 }
193 }
194 Ok(())
195}
196
197const VALID_VAR_CHARS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789";
198
199fn parse_var<I>(s: &mut Peekable<I>) -> Result<Var, ParseError>
200where I: Iterator<Item=char> {
201 if let Some(c) = s.peek() {
202 if *c == '{' {
203 s.next();
204
205 let mut buffer = vec![];
206
207 loop {
208 match s.next() {
209 Some(c) => if c == '}' { break } else { buffer.push(c) },
210 None => return Err(ParseError::EOF),
211 }
212 }
213
214 return Ok(parse_var_options(&mut buffer.into_iter().peekable())?);
215 }
216 }
217
218 if s.peek().is_none() {
219 return Err(ParseError::EOF);
220 }
221
222 let first = s.next().unwrap();
223 if !VALID_VAR_CHARS.contains(first) {
224 return Err(ParseError::InvalidVar);
225 }
226
227 let mut name = String::new();
228 name.push(first);
229
230 while let Some(c) = s.peek() {
231 if !VALID_VAR_CHARS.contains(*c) {
232 break;
233 }
234 name.push(s.next().unwrap());
235 }
236
237 Ok(Var{
238 name,
239 option: VarOption::None,
240 })
241}
242
243fn parse_var_options<I>(s: &mut Peekable<I>) -> Result<Var, ParseError>
244where I: Iterator<Item=char> {
245 if s.peek().is_none() {
246 return Err(ParseError::EOF);
247 }
248
249 let mut name = String::new();
250 let mut length = false;
251
252 let first = s.next().unwrap();
253 if first == '#' {
254 length = true;
255 } else if !VALID_VAR_CHARS.contains(first) {
256 return Err(ParseError::InvalidVar);
257 } else {
258 name.push(first);
259 }
260
261 while let Some(c) = s.peek() {
262 if !VALID_VAR_CHARS.contains(*c) {
263 break;
264 }
265 name.push(s.next().unwrap());
266 }
267
268 if length && !s.peek().is_none() {
269 return Err(ParseError::InvalidVar);
270 } else if length && name.len() == 0 {
271 return Err(ParseError::EOF);
272 } else if length {
273 return Ok(Var{
274 name,
275 option: VarOption::LengthOf,
276 });
277 }
278
279 let c = match s.next() {
289 None => return Ok(Var{
290 name,
291 option: VarOption::None,
292 }), Some(c) => c,
294 };
295
296 match c {
297 '-' => {
298 Ok(Var{
299 name,
300 option: VarOption::Default(s.collect()),
301 })
302 }, '+' => {
304 Ok(Var{
305 name,
306 option: VarOption::IfSet(s.collect()),
307 })
308 }, ':' => {
310 match s.peek().map(|&c| c) {
311 Some('-') => {
312 s.next();
313 Ok(Var{
314 name,
315 option: VarOption::DefaultIfEmpty(s.collect()),
316 })
317 }, Some('+') => {
319 s.next();
320 Ok(Var{
321 name,
322 option: VarOption::IfNotEmpty(s.collect()),
323 })
324 } Some(_) => {
326 let s: String = s.collect();
327 let sections: Vec<isize> = s.split(':')
328 .flat_map(|n| n
329 .trim()
330 .to_string()
331 .parse::<isize>()
332 ).collect();
333 match sections[..] {
334 [offset] => Ok(Var{
335 name,
336 option: VarOption::Offset(offset),
337 }),
338 [offset, length] => Ok(Var{
339 name,
340 option: VarOption::OffsetLength(offset, length),
341 }),
342 _ => Err(ParseError::InvalidVar),
343 }
344 } _ => {
346 Err(ParseError::InvalidVar)
347 },
348 }
349 }
350 _ => {
351 Err(ParseError::InvalidVar)
352 },
353 }
354}
355#[derive(Error, Debug)]
356pub enum ParseExpandError {
357 #[error("expanding args")]
358 ExpandError(#[from] VarError),
359 #[error("parsing args")]
360 ParseError(#[from] ParseError),
361}
362
363pub fn parse_expand(s: String) -> Result<String, ParseExpandError> {
364 Ok(into_arg(s)?.expand()?)
365}
366
367#[cfg(test)]
368mod tests {
369 use std::env::{set_var, remove_var, var, VarError};
370 use crate::env::*;
371
372 const VAR_NAME: &str = "TEST_FOO_BAR";
373 const VAR_NAME_EMPTY: &str = "TEST_FOO_BAR_EMPTY";
374 const VAR_NAME_NULL: &str = "TEST_FOO_BAR_NULL";
375 const VAR_VAL: &str = "test_foo_bar";
376 const VAR_DEFAULT: &str = "default_value";
377
378 fn setup() {
379 set_var(VAR_NAME, VAR_VAL);
380 assert_eq!(var(VAR_NAME).expect("could not get var"), VAR_VAL);
381
382 set_var(VAR_NAME_EMPTY, "");
383 assert_eq!(var(VAR_NAME_EMPTY).expect("could not get var"), "");
384
385 remove_var(VAR_NAME_NULL);
386 assert_eq!(var(VAR_NAME_NULL), Err(VarError::NotPresent));
387 }
388
389 use VarOption::*;
390
391 #[test]
392 fn expand_arg() {
393
394 let a = 'a' as u8;
395 let sections: Vec<(String, Var)> = (0u8..4).map(|i| {
396 let name = format!("{}{}", VAR_NAME, i);
397 let val = format!("{}{}", VAR_VAL, i);
398 set_var(&name, &val);
399 assert_eq!(var(&name).expect("could not get var"), val);
400
401 (
402 format!("{}", (a + i) as char),
403 Var {
404 name: name,
405 option: None,
406 },
407 )
408 }).collect();
409
410 let arg = Arg{
411 sections: sections.clone(),
412 buffer: "".to_string(),
413 };
414 assert_eq!(arg.expand(), Ok(format!("a{v}0b{v}1c{v}2d{v}3", v=VAR_VAL)));
415
416 let arg = Arg{
417 sections: sections.clone(),
418 buffer: "e".to_string(),
419 };
420 assert_eq!(arg.expand(), Ok(format!("a{v}0b{v}1c{v}2d{v}3e", v=VAR_VAL)));
421 }
422
423 macro_rules! test_expand {
424 ($context:expr, $option:expr, $val:expr) => {
425 test_expand!(VAR_NAME, $context, $option, $val);
426 };
427
428 (empty, $context:expr, $option:expr, $val:expr) => {
429 test_expand!(VAR_NAME_EMPTY, $context, $option, $val);
430 };
431
432 (null, $context:expr, $option:expr, $val:expr) => {
433 test_expand!(VAR_NAME_NULL, $context, $option, $val);
434 };
435
436 ($var:expr, $context:expr, $option:expr, $val:expr) => {
437 let v = Var {
438 name: $var.to_string(),
439 option: $option,
440 };
441 let expanded = v.expand();
442 let expected = Ok($val.to_string());
443 assert_eq!(expanded, expected, "\nfor test: {}", $context);
444 };
445
446 ($context:expr, $option:expr, err $err:expr) => {
447 let v = Var {
448 name: VAR_NAME.to_string(),
449 option: $option,
450 };
451 let expanded = v.expand();
452 let expected = Err($err);
453 assert_eq!(expanded, expected, "\nfor test: {}", $context);
454 };
455 }
456
457 #[test]
458 #[should_panic]
459 fn test_expand_ok() {
460 setup();
461 test_expand!("panic", None, "");
462 }
463
464 #[test]
465 #[should_panic]
466 fn test_expand_err() {
467 setup();
468 test_expand!("panic", None, err VarError::NotPresent);
469 }
470
471 #[test]
472 fn expand_var_not_empty() {
473 setup();
474
475 let len = VAR_VAL.len();
476 let l = len as isize;
477
478 test_expand!("none", None, VAR_VAL);
479 test_expand!("default", Default(VAR_DEFAULT.to_string()), VAR_VAL);
480 test_expand!("default_if_empty", DefaultIfEmpty(VAR_DEFAULT.to_string()), VAR_VAL);
481 test_expand!("if_not_empty", IfNotEmpty(VAR_DEFAULT.to_string()), VAR_DEFAULT);
482 test_expand!("if_set", IfSet(VAR_DEFAULT.to_string()), VAR_DEFAULT);
483
484 test_expand!("offset", Offset(5), &VAR_VAL[5..]);
485 test_expand!("offset_outofrange", Offset(l), "");
486 test_expand!("offset_negative", Offset(-3), &VAR_VAL[len-3..]);
487 test_expand!("offset_negative_outofrange", Offset(-l-1), VAR_VAL);
488
489 test_expand!("offset_length", OffsetLength(5, 3), &VAR_VAL[5..8]);
490 test_expand!("offset_negative_length", OffsetLength(-7, 3), &VAR_VAL[len-7..len-4]);
491 test_expand!("offset_length_negative", OffsetLength(5, -4), &VAR_VAL[5..len-4]);
492 test_expand!("offset_negative_length_negative", OffsetLength(-7, -4), &VAR_VAL[len-7..len-4]);
493 test_expand!("offset_outofrange_length", OffsetLength(l, 3), "");
494 test_expand!("offset_length_extra", OffsetLength(5, 8), &VAR_VAL[5..]);
495 test_expand!("offset_negative_outofrange_length", OffsetLength(-l-1, 3), &VAR_VAL[..3]);
496 test_expand!("offset_length_zero", OffsetLength(5, 0), "");
497 test_expand!("offset_negative_length_zero", OffsetLength(-7, 0), "");
498 test_expand!("offset_outofrange_length_zero", OffsetLength(l, 0), "");
499 test_expand!("offset_negative_outofrange_length_zero", OffsetLength(-l-1, 0), "");
500
501 test_expand!("length_of", LengthOf, format!("{}", l));
502
503 test_expand!(empty, "none", None, "");
505 test_expand!(empty, "default", Default(VAR_DEFAULT.to_string()), "");
506 test_expand!(empty, "default_if_empty", DefaultIfEmpty(VAR_DEFAULT.to_string()), VAR_DEFAULT);
507 test_expand!(empty, "if_not_empty", IfNotEmpty(VAR_DEFAULT.to_string()), "");
508 test_expand!(empty, "if_set", IfSet(VAR_DEFAULT.to_string()), VAR_DEFAULT);
509
510 test_expand!(empty, "offset", Offset(5), "");
511 test_expand!(empty, "offset_negative", Offset(-5), "");
512
513 test_expand!(empty, "offset_length", OffsetLength(5, 3), "");
514 test_expand!(empty, "offset_negative_length", OffsetLength(-7, 3), "");
515 test_expand!(empty, "offset_length_negative", OffsetLength(5, -4), "");
516 test_expand!(empty, "offset_negative_length_negative", OffsetLength(-7, -4), "");
517
518 test_expand!(empty, "length_of", LengthOf, "0");
519
520 test_expand!(null, "none", None, "");
522 test_expand!(null, "default", Default(VAR_DEFAULT.to_string()), VAR_DEFAULT);
523 test_expand!(null, "default_if_empty", DefaultIfEmpty(VAR_DEFAULT.to_string()), VAR_DEFAULT);
524 test_expand!(null, "if_not_empty", IfNotEmpty(VAR_DEFAULT.to_string()), "");
525 test_expand!(null, "if_set", IfSet(VAR_DEFAULT.to_string()), "");
526
527 test_expand!(null, "offset", Offset(5), "");
528 test_expand!(null, "offset_negative", Offset(-5), "");
529
530 test_expand!(null, "offset_length", OffsetLength(5, 3), "");
531 test_expand!(null, "offset_negative_length", OffsetLength(-7, 3), "");
532 test_expand!(null, "offset_length_negative", OffsetLength(5, -4), "");
533 test_expand!(null, "offset_negative_length_negative", OffsetLength(-7, -4), "");
534
535 test_expand!(null, "length_of", LengthOf, "0");
536 }
537
538 macro_rules! test_parse_var_options{
539 ($context:expr, $case:expr, $name:expr, $option:expr) => {
540 let got = parse_var_options(&mut $case.to_string().chars().peekable());
541 let expected = Ok(Var{
542 name: $name.to_string(),
543 option: $option,
544 });
545 assert_eq!(got, expected, "\nfor test: {}", $context);
546 };
547
548 ($context:expr, $case:expr, err $err:expr) => {
549 let got = parse_var_options(&mut $case.to_string().chars().peekable());
550 let expected = Err($err);
551 assert_eq!(got, expected, "\nfor test: {}", $context);
552 };
553 }
554
555 #[test]
556 #[should_panic]
557 fn test_parse_var_options_ok_panic() {
558 test_parse_var_options!("panic", "$", "$", None);
559 }
560
561 #[test]
562 #[should_panic]
563 fn test_parse_var_options_err_panic() {
564 test_parse_var_options!("panic", "FOO", err ParseError::InvalidVar);
565 }
566
567 #[test]
568 fn parse_var_options_ok() {
569 test_parse_var_options!("basic", "FOO", "FOO", None);
570 test_parse_var_options!("default", "FOO-default", "FOO", Default("default".to_string()));
571 test_parse_var_options!("default if empty", "FOO:-default", "FOO", DefaultIfEmpty("default".to_string()));
572 test_parse_var_options!("if set", "FOO+value", "FOO", IfSet("value".to_string()));
573 test_parse_var_options!("if not empty", "FOO:+value", "FOO", IfNotEmpty("value".to_string()));
574 test_parse_var_options!("if not empty", "FOO:+value", "FOO", IfNotEmpty("value".to_string()));
575
576 test_parse_var_options!("offset", "FOO:5", "FOO", Offset(5));
577 test_parse_var_options!("offset negative", "FOO: -5", "FOO", Offset(-5));
578
579 test_parse_var_options!("offset length", "FOO:5:3", "FOO", OffsetLength(5, 3));
580 test_parse_var_options!("offset negative length", "FOO: -5:3", "FOO", OffsetLength(-5, 3));
581 test_parse_var_options!("offset length negative", "FOO:5:-3", "FOO", OffsetLength(5, -3));
582 test_parse_var_options!("offset negative length negative", "FOO: -5:-3", "FOO", OffsetLength(-5, -3));
583
584 test_parse_var_options!("length of", "#FOO", "FOO", LengthOf);
585 }
586
587 #[test]
588 fn parse_var_options_err() {
589 test_parse_var_options!("empty", "", err ParseError::EOF);
590 test_parse_var_options!("bad_first", "$", err ParseError::InvalidVar);
591 test_parse_var_options!("empty length of", "#", err ParseError::EOF);
592 test_parse_var_options!("bad length of", "#$", err ParseError::InvalidVar);
593 test_parse_var_options!("empty after :", "FOO:", err ParseError::InvalidVar);
594 test_parse_var_options!("invalid after :", "FOO:_", err ParseError::InvalidVar);
595 test_parse_var_options!("too many numbers after :", "FOO:4:4:4", err ParseError::InvalidVar);
596 test_parse_var_options!("unknown after var", "FOO$", err ParseError::InvalidVar);
597 }
598
599 macro_rules! test_parse_var{
600 ($context:expr, $case:expr, $name:expr) => {
601 let case = $case.to_string();
602 let mut chars = case.chars().peekable();
603 let got = parse_var(&mut chars);
604 let expected = Ok(Var{
605 name: $name.to_string(),
606 option: None,
607 });
608 assert_eq!(got, expected, "\nfor test: {}", $context);
609 let excess: String = chars.collect();
610 assert_eq!(excess, "", "\nfor excess test: {}", $context);
611 };
612
613 ($context:expr, $case:expr, $name:expr, excess $excess:expr) => {
614 let case = $case.to_string();
615 let mut chars = case.chars().peekable();
616 let got = parse_var(&mut chars);
617 let expected = Ok(Var{
618 name: $name.to_string(),
619 option: None,
620 });
621 assert_eq!(got, expected, "\nfor test: {}", $context);
622 let excess: String = chars.collect();
623 assert_eq!(excess, $excess, "\nfor excess test: {}", $context);
624 };
625
626 ($context:expr, $case:expr, $name:expr, $option:expr) => {
627 let got = parse_var(&mut $case.to_string().chars().peekable());
628 let expected = Ok(Var{
629 name: $name.to_string(),
630 option: $option,
631 });
632 assert_eq!(got, expected, "\nfor test: {}", $context);
633 };
634
635 ($context:expr, $case:expr, err $err:expr) => {
636 let got = parse_var(&mut $case.to_string().chars().peekable());
637 let expected = Err($err);
638 assert_eq!(got, expected, "\nfor test: {}", $context);
639 };
640 }
641
642 #[test]
643 #[should_panic]
644 fn test_parse_var_with_option_ok_panic() {
645 test_parse_var!("panic", "{$}", "$", None);
646 }
647
648 #[test]
649 #[should_panic]
650 fn test_parse_var_ok_panic() {
651 test_parse_var!("panic", "{$}", "$");
652 }
653
654 #[test]
655 #[should_panic]
656 fn test_parse_var_err_panic() {
657 test_parse_var!("panic", "{FOO}-", err ParseError::InvalidVar);
658 }
659
660 #[test]
661 fn parse_var_with_option_ok() {
662 test_parse_var!("basic", "{FOO}", "FOO", None);
663 test_parse_var!("default", "{FOO-default}", "FOO", Default("default".to_string()));
664 test_parse_var!("default if empty", "{FOO:-default}", "FOO", DefaultIfEmpty("default".to_string()));
665 test_parse_var!("if set", "{FOO+value}", "FOO", IfSet("value".to_string()));
666 test_parse_var!("if not empty", "{FOO:+value}", "FOO", IfNotEmpty("value".to_string()));
667 test_parse_var!("if not empty", "{FOO:+value}", "FOO", IfNotEmpty("value".to_string()));
668
669 test_parse_var!("offset", "{FOO:5}", "FOO", Offset(5));
670 test_parse_var!("offset negative", "{FOO: -5}", "FOO", Offset(-5));
671
672 test_parse_var!("offset length", "{FOO:5:3}", "FOO", OffsetLength(5, 3));
673 test_parse_var!("offset negative length", "{FOO: -5:3}", "FOO", OffsetLength(-5, 3));
674 test_parse_var!("offset length negative", "{FOO:5:-3}", "FOO", OffsetLength(5, -3));
675 test_parse_var!("offset negative length negative", "{FOO: -5:-3}", "FOO", OffsetLength(-5, -3));
676
677 test_parse_var!("length of", "{#FOO}", "FOO", LengthOf);
678 }
679
680 #[test]
681 fn parse_var_with_option_err() {
682 test_parse_var!("empty", "{}", err ParseError::EOF);
683 test_parse_var!("bad_first", "{$}", err ParseError::InvalidVar);
684 test_parse_var!("empty length of", "{#}", err ParseError::EOF);
685 test_parse_var!("bad length of", "{#$}", err ParseError::InvalidVar);
686 test_parse_var!("empty after :", "{FOO:}", err ParseError::InvalidVar);
687 test_parse_var!("invalid after :", "{FOO:_}", err ParseError::InvalidVar);
688 test_parse_var!("too many numbers after :", "{FOO:4:4:4}", err ParseError::InvalidVar);
689 test_parse_var!("unknown after var", "{FOO$}", err ParseError::InvalidVar);
690 }
691
692 #[test]
693 fn parse_var_ok() {
694 test_parse_var!("simple", "FOO", "FOO");
695 test_parse_var!("extra", "FOO-", "FOO", excess "-");
696 }
697
698 #[test]
699 fn into_arg_ok() {
700 let arg = into_arg("foo bar \"baz $var\"\\nfoo bar 'baz $var'\\n".to_string());
701 let mut expected = Arg::new();
702 expected.push("foo bar baz ");
703 expected.push_var(Var{name: "var".to_string(), option: None});
704 expected.push("\nfoo bar baz $var\n");
705 assert_eq!(arg, Ok(expected));
706 }
707}