exec_target/
lib.rs

1/*!
2the simple invoke command for test
3
4This invokes external a command and manipulates standard in out.
5You can use `std::process::Command` more easily.
6
7# Features
8
9- minimum support rustc 1.58.0 (02072b482 2022-01-11)
10
11# Example
12
13```rust
14use exec_target::exec_target_with_env_in;
15
16let command = "target/debug/exe-stab-grep";
17let args = &["--color=always", "-e", "c"];
18let envs = vec![("GREP_COLORS", "ms=01;32")];
19let inp = b"abcdefg\n" as &[u8];
20
21let oup = exec_target_with_env_in(command, args, envs, inp);
22
23assert_eq!(oup.stderr, "");
24assert_eq!(oup.stdout, "ab\u{1b}[01;32m\u{1b}[Kc\u{1b}[m\u{1b}[Kdefg\n");
25assert_eq!(oup.status.success(), true);
26```
27*/
28use std::collections::HashMap;
29use std::env;
30use std::ffi::OsStr;
31use std::process::{Command, ExitStatus, Output, Stdio};
32
33// trats
34use std::io::Write;
35use std::iter::IntoIterator;
36
37//
38pub struct OutputString {
39    pub status: ExitStatus,
40    pub stdout: String,
41    pub stderr: String,
42}
43
44fn setup_envs<I, K, V>(cmd: &mut Command, vars: I) -> &mut Command
45where
46    I: IntoIterator<Item = (K, V)>,
47    K: AsRef<OsStr>,
48    V: AsRef<OsStr>,
49{
50    let filtered_env: HashMap<String, String> = env::vars()
51        .filter(|(k, _)| k == "TERM" || k == "TZ" || k == "PATH" || k == "LD_LIBRARY_PATH")
52        .collect();
53    cmd.env_clear()
54        .envs(filtered_env)
55        .envs(vars)
56        .env("LANG", "C")
57}
58
59pub fn exec_target<I, S>(target_exe: &str, args: I) -> OutputString
60where
61    I: IntoIterator<Item = S>,
62    S: AsRef<OsStr>,
63{
64    let mut cmd: Command = Command::new(target_exe);
65    setup_envs(&mut cmd, Vec::<(&str, &str)>::new())
66        .args(args)
67        .stdout(Stdio::piped())
68        .stderr(Stdio::piped());
69    let child = cmd.spawn().expect("failed to execute child");
70    let output: Output = child.wait_with_output().expect("failed to wait on child");
71    //
72    OutputString {
73        status: output.status,
74        stdout: String::from(String::from_utf8_lossy(&output.stdout)),
75        stderr: String::from(String::from_utf8_lossy(&output.stderr)),
76    }
77}
78
79pub fn exec_target_with_env<I, S, IKV, K, V>(target_exe: &str, args: I, env: IKV) -> OutputString
80where
81    I: IntoIterator<Item = S>,
82    S: AsRef<OsStr>,
83    IKV: IntoIterator<Item = (K, V)>,
84    K: AsRef<OsStr>,
85    V: AsRef<OsStr>,
86{
87    let mut cmd: Command = Command::new(target_exe);
88    setup_envs(&mut cmd, env)
89        .args(args)
90        .stdout(Stdio::piped())
91        .stderr(Stdio::piped());
92    let child = cmd.spawn().expect("failed to execute child");
93    let output: Output = child.wait_with_output().expect("failed to wait on child");
94    //
95    OutputString {
96        status: output.status,
97        stdout: String::from(String::from_utf8_lossy(&output.stdout)),
98        stderr: String::from(String::from_utf8_lossy(&output.stderr)),
99    }
100}
101
102pub fn exec_target_with_in<I, S>(target_exe: &str, args: I, in_bytes: &[u8]) -> OutputString
103where
104    I: IntoIterator<Item = S>,
105    S: AsRef<OsStr>,
106{
107    let mut cmd: Command = Command::new(target_exe);
108    setup_envs(&mut cmd, Vec::<(&str, &str)>::new())
109        .args(args)
110        .stdin(Stdio::piped())
111        .stdout(Stdio::piped())
112        .stderr(Stdio::piped());
113    let mut child = cmd.spawn().expect("failed to execute child");
114    {
115        let stdin = child.stdin.as_mut().expect("failed to get stdin");
116        let r = stdin.write_all(in_bytes);
117        match r {
118            Err(ioe) if ioe.kind() == std::io::ErrorKind::BrokenPipe => {
119                // nothing todo
120            }
121            _ => {
122                r.expect("failed to write to stdin");
123            }
124        }
125    }
126    let output: Output = child.wait_with_output().expect("failed to wait on child");
127    //
128    OutputString {
129        status: output.status,
130        stdout: String::from(String::from_utf8_lossy(&output.stdout)),
131        stderr: String::from(String::from_utf8_lossy(&output.stderr)),
132    }
133}
134
135///
136/// This invokes external a command and manipulates standard in out.
137/// You can use `std::process::Command` more easily.
138///
139/// # Example
140///
141/// ```
142/// use exec_target::exec_target_with_env_in;
143///
144/// let command = "target/debug/exe-stab-grep";
145/// let args = &["--color=always", "-e", "c"];
146/// let envs = vec![("GREP_COLORS", "ms=01;32")];
147/// let inp = b"abcdefg\n" as &[u8];
148///
149/// let oup = exec_target_with_env_in(command, args, envs, inp);
150///
151/// assert_eq!(oup.stderr, "");
152/// assert_eq!(oup.stdout, "ab\u{1b}[01;32m\u{1b}[Kc\u{1b}[m\u{1b}[Kdefg\n");
153/// assert_eq!(oup.status.success(), true);
154/// ```
155///
156pub fn exec_target_with_env_in<I, S, IKV, K, V>(
157    target_exe: &str,
158    args: I,
159    env: IKV,
160    in_bytes: &[u8],
161) -> OutputString
162where
163    I: IntoIterator<Item = S>,
164    S: AsRef<OsStr>,
165    IKV: IntoIterator<Item = (K, V)>,
166    K: AsRef<OsStr>,
167    V: AsRef<OsStr>,
168{
169    let mut cmd: Command = Command::new(target_exe);
170    setup_envs(&mut cmd, env)
171        .args(args)
172        .stdin(Stdio::piped())
173        .stdout(Stdio::piped())
174        .stderr(Stdio::piped());
175    let mut child = cmd.spawn().expect("failed to execute child");
176    {
177        let stdin = child.stdin.as_mut().expect("failed to get stdin");
178        let r = stdin.write_all(in_bytes);
179        match r {
180            Err(ioe) if ioe.kind() == std::io::ErrorKind::BrokenPipe => {
181                // nothing todo
182            }
183            _ => {
184                r.expect("failed to write to stdin");
185            }
186        }
187    }
188    let output: Output = child.wait_with_output().expect("failed to wait on child");
189    //
190    OutputString {
191        status: output.status,
192        stdout: String::from(String::from_utf8_lossy(&output.stdout)),
193        stderr: String::from(String::from_utf8_lossy(&output.stderr)),
194    }
195}
196
197///
198/// parse a command line strings
199///
200/// This separates the string with blanks.
201/// This considers special characters.
202///
203/// the special characters:
204/// - "" : double quote
205/// - '' : single quote
206/// - \\ : back_slash
207///
208pub fn args_from(s: &str) -> Vec<String> {
209    let mut v: Vec<String> = Vec::new();
210    let mut ss = String::new();
211    let mut enter_q: bool = false;
212    let mut enter_qq: bool = false;
213    let mut back_slash: bool = false;
214    //
215    for c in s.chars() {
216        if back_slash {
217            ss.push(c);
218            back_slash = false;
219            continue;
220        }
221        if c == '\\' {
222            back_slash = true;
223            continue;
224        }
225        if enter_q {
226            if c == '\'' {
227                v.push(ss.clone());
228                ss.clear();
229                enter_q = false;
230            } else {
231                ss.push(c);
232            }
233            continue;
234        }
235        if enter_qq {
236            if c == '\"' {
237                v.push(ss.clone());
238                ss.clear();
239                enter_qq = false;
240            } else {
241                ss.push(c);
242            }
243            continue;
244        }
245        match c {
246            '\'' => {
247                enter_q = true;
248                continue;
249            }
250            '\"' => {
251                enter_qq = true;
252                continue;
253            }
254            ' ' => {
255                if !ss.is_empty() {
256                    v.push(ss.clone());
257                    ss.clear();
258                }
259            }
260            _ => {
261                ss.push(c);
262            }
263        }
264    }
265    if !ss.is_empty() {
266        v.push(ss.clone());
267        ss.clear();
268    }
269    //
270    v
271}