cmd_macro/
env.rs

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// Variable expansion options, a subset of what's listed here:
49// https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
50#[derive(Debug, Clone, PartialEq)]
51pub enum VarOption {
52    None, // $VAR or ${VAR}
53    Default(String), // ${VAR-default}
54    DefaultIfEmpty(String), // ${VAR:-default}
55    IfNotEmpty(String), // ${VAR:+value}
56    IfSet(String), // ${VAR+value}
57    Offset(isize), // ${VAR:o}
58    OffsetLength(isize, isize), // ${VAR:o:l}
59    LengthOf, // ${#VAR}
60}
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    // None, // $VAR or ${VAR}
280    // Default(String), // ${VAR-default}
281    // DefaultIfEmpty(String), // ${VAR:-default}
282    // IfNotEmpty(String), // ${VAR:+value}
283    // IfSet(String), // ${VAR+value}
284    // Offset(isize), // ${VAR:o}
285    // OffsetLength(isize, isize), // ${VAR:o:l}
286    // LengthOf, // ${#VAR}
287
288    let c = match s.next() {
289        None => return Ok(Var{
290            name,
291            option: VarOption::None,
292        }), // ${VAR}
293        Some(c) => c,
294    };
295
296    match c {
297        '-' => {
298            Ok(Var{
299                name,
300                option: VarOption::Default(s.collect()),
301            })
302        }, // "${VAR-default}"
303        '+' => {
304            Ok(Var{
305                name,
306                option: VarOption::IfSet(s.collect()),
307            })
308        }, // "${VAR+value}"
309        ':' => {
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                }, // "${VAR:-default}"
318                Some('+') => {
319                    s.next();
320                    Ok(Var{
321                        name,
322                        option: VarOption::IfNotEmpty(s.collect()),
323                    })
324                } // "${VAR:+value}"
325                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                } // "${VAR:o}" or "${VAR:o:l}" depending on count
345                _ => {
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        // empty
504        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        // null
521        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}