rust_shell/
command.rs

1// Copyright 2017 Google Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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/// Creates a new commadn from `format` and `args`
87/// # Examples
88#[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
108/// Creates a new command from `format` and `args`.
109/// The function is invoked from `cmd!` macro internally.
110pub 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}