1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
use std::collections::VecDeque;
use std::process::Command;
use std::process::Stdio;
use crate::common::types::ResultAnyError;
use crate::common::utils;
use anyhow::anyhow;
pub struct PresetCommand {
pub working_dir: String,
}
impl PresetCommand {
pub fn exec(&self, command_str: &str) -> ResultAnyError<String> {
let command_result = self
.spawn_command_from_str(command_str, None, None)?
.wait_with_output()?;
if !command_result.stderr.is_empty() {
return stderr_to_err(command_result.stderr);
}
return utils::bytes_to_string(command_result.stdout);
}
pub fn spawn_command_from_str(
&self,
command_str: &str,
stdin: Option<Stdio>,
stdout: Option<Stdio>,
) -> ResultAnyError<std::process::Child> {
let mut command_parts: VecDeque<String> =
PresetCommand::create_command_parts_from_string(command_str);
let command = command_parts
.pop_front()
.ok_or(anyhow!("Invalid command: {}", command_str))?;
let handle = Command::new(command)
.args(command_parts)
.current_dir(&self.working_dir)
.stdin(stdin.unwrap_or(Stdio::piped()))
.stdout(stdout.unwrap_or(Stdio::piped()))
.spawn()?;
return Ok(handle);
}
}
impl PresetCommand {
fn create_command_parts_from_string(command_str: &str) -> VecDeque<String> {
let command_parts_raw: Vec<String> = command_str.split(' ').map(String::from).collect();
let mut command_parts: VecDeque<String> = Default::default();
let mut has_unpaired_string_quote: bool = false;
for (_, token) in command_parts_raw.iter().enumerate() {
if command_parts.len() > 1 && has_unpaired_string_quote {
let previous_token = command_parts.pop_back().unwrap();
let previous_token = format!("{} {}", previous_token, token);
command_parts.push_back(previous_token);
if token.contains("\"") {
has_unpaired_string_quote = false;
}
} else {
if has_unpaired_string_quote == false && token.contains("\"") {
has_unpaired_string_quote = true;
}
command_parts.push_back(token.to_owned());
}
}
return command_parts;
}
}
pub fn stderr_to_err(stderr: Vec<u8>) -> ResultAnyError<String> {
let output_err = utils::bytes_to_string(stderr)?;
return Err(anyhow!(output_err));
}
pub fn handle_command_output(output: std::process::Output) -> ResultAnyError<String> {
if !output.stderr.is_empty() {
return stderr_to_err(output.stderr);
}
return utils::bytes_to_string(output.stdout);
}
#[cfg(test)]
mod test {
use super::*;
mod create_command_parts_from_string {
use super::*;
#[test]
fn it_should_parse_string_params() {
let command_parts: VecDeque<String> =
PresetCommand::create_command_parts_from_string("git log --oneline --pretty=format:%s");
assert_eq!(
vec![
"git".to_owned(),
"log".to_owned(),
"--oneline".to_owned(),
"--pretty=format:%s".to_owned()
],
command_parts.into_iter().collect::<Vec<String>>()
);
}
}
}