1use shell_command::ShellCommand;
16use nom::IResult;
17use std::process::Command;
18use std::env;
19use std::env::VarError;
20
21fn token_char(ch: char) -> bool {
22 if ch.len_utf8() > 1 {
23 return false;
24 }
25 match ch {
26 '\x00' ... '\x20' => false,
27 '\x7f' | '"' | '\'' | '>' | '<' | '|' | ';' | '{' | '}' | '$' => false,
28 _ => true,
29 }
30}
31
32fn var_char(ch: char) -> bool {
33 match ch {
34 'a' ... 'z' => true,
35 'A' ... 'Z' => true,
36 '0' ... '9' => true,
37 '_' => true,
38 _ => false,
39 }
40}
41
42enum TokenPart {
43 Bare(String),
44 Placeholder,
45 EnvVariable(String),
46}
47
48struct Token(Vec<TokenPart>);
49
50impl Token {
51 fn into_string(self, args: &mut Iterator<Item = &str>)
52 -> Result<String, VarError> {
53 let mut token = String::from("");
54 for part in self.0 {
55 match part {
56 TokenPart::Bare(s) => token += &s,
57 TokenPart::Placeholder =>
58 token += args.next().expect("Too many placeholders"),
59 TokenPart::EnvVariable(name) => {
60 debug!("Environment variable {}", name);
61 token += &env::var(name)?
62 }
63 }
64 }
65 Ok(token)
66 }
67}
68
69named!(bare_token<&str, TokenPart>,
70 map!(take_while1_s!(token_char), |s| TokenPart::Bare(String::from(s))));
71named!(quoted_token<&str, TokenPart>,
72 map!(delimited!(tag_s!("\""), take_until_s!("\""), tag_s!("\"")),
73 |s| TokenPart::Bare(String::from(s))));
74named!(place_holder<&str, TokenPart>,
75 map!(tag_s!("{}"), |_| TokenPart::Placeholder));
76named!(env_var<&str, TokenPart>,
77 map!(preceded!(tag!("$"), take_while1_s!(var_char)),
78 |name| TokenPart::EnvVariable(String::from(name))));
79named!(command_token<&str, Token>,
80 map!(many1!(alt!(bare_token | quoted_token | place_holder | env_var)),
81 |vec| Token(vec)));
82
83named!(command< &str, Vec<Token> >,
84 terminated!(ws!(many1!(command_token)), eof!()));
85
86#[macro_export]
89macro_rules! cmd {
90 ($format:expr) => ($crate::new_command($format, &[]).unwrap());
91 ($format:expr, $($arg:expr),+) =>
92 ($crate::new_command($format, &[$($arg),+]).unwrap());
93}
94
95fn parse_cmd<'a>(format: &'a str, args: &'a [&str])
96 -> Result<Vec<String>, VarError> {
97 let tokens = match command(format) {
98 IResult::Done(_, result) => result,
99 IResult::Error(error) => panic!("Error {:?}", error),
100 IResult::Incomplete(needed) => panic!("Needed {:?}", needed)
101 };
102 let args = args.iter().map(|a| *a).collect::<Vec<_>>();
103 let mut args = args.into_iter();
104 tokens.into_iter().map(|token| token.into_string(&mut args))
105 .collect::<Result<Vec<_>, _>>()
106}
107
108pub fn new_command(format: &str, args: &[&str])
111 -> Result<ShellCommand, VarError> {
112 let vec = parse_cmd(format, args)?;
113 let mut command = Command::new(&vec[0]);
114 if vec.len() > 1 {
115 command.args(&vec[1..]);
116 }
117 let line = vec.join(" ");
118 Ok(ShellCommand::new(line, command))
119}
120
121
122#[test]
123fn test_parse_cmd() {
124 let tokens = parse_cmd(r#"cmd 1 2
125 3 "
126 4" {}"#, &["5"]).unwrap();
127 assert_eq!("cmd", tokens[0]);
128 assert_eq!("1", tokens[1]);
129 assert_eq!("2", tokens[2]);
130 assert_eq!("3", tokens[3]);
131 assert_eq!("\n 4", tokens[4]);
132 assert_eq!("5", tokens[5]);
133}
134
135#[test]
136fn test_parse_cmd_env() {
137 use env_logger;
138 env_logger::init().unwrap();
139 env::set_var("MY_VAR", "VALUE");
140 let tokens = parse_cmd("echo $MY_VAR/dir", &[]).unwrap();
141 assert_eq!("VALUE/dir", tokens[1]);
142}