shell_tunnel/execution/
command.rs1use std::collections::HashMap;
4use std::path::PathBuf;
5use std::time::Duration;
6
7#[derive(Debug, Clone)]
9pub struct Command {
10 pub command_line: String,
12 pub working_dir: Option<PathBuf>,
14 pub env: HashMap<String, String>,
16 pub timeout: Option<Duration>,
18 pub capture_output: bool,
20 pub max_output_bytes: Option<u64>,
25}
26
27impl Command {
28 pub fn new(command_line: impl Into<String>) -> Self {
30 Self {
31 command_line: command_line.into(),
32 working_dir: None,
33 env: HashMap::new(),
34 timeout: None,
35 capture_output: true,
36 max_output_bytes: None,
37 }
38 }
39
40 pub fn working_dir(mut self, dir: impl Into<PathBuf>) -> Self {
42 self.working_dir = Some(dir.into());
43 self
44 }
45
46 pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
48 self.env.insert(key.into(), value.into());
49 self
50 }
51
52 pub fn envs<I, K, V>(mut self, vars: I) -> Self
54 where
55 I: IntoIterator<Item = (K, V)>,
56 K: Into<String>,
57 V: Into<String>,
58 {
59 for (k, v) in vars {
60 self.env.insert(k.into(), v.into());
61 }
62 self
63 }
64
65 pub fn timeout(mut self, duration: Duration) -> Self {
67 self.timeout = Some(duration);
68 self
69 }
70
71 pub fn capture_output(mut self, capture: bool) -> Self {
73 self.capture_output = capture;
74 self
75 }
76
77 pub fn max_output_bytes(mut self, bytes: u64) -> Self {
79 self.max_output_bytes = Some(bytes);
80 self
81 }
82}
83
84impl Default for Command {
85 fn default() -> Self {
86 Self::new("")
87 }
88}
89
90#[derive(Debug, Default)]
92pub struct CommandBuilder {
93 command_line: Option<String>,
94 working_dir: Option<PathBuf>,
95 env: HashMap<String, String>,
96 timeout: Option<Duration>,
97 capture_output: bool,
98 max_output_bytes: Option<u64>,
99}
100
101impl CommandBuilder {
102 pub fn new() -> Self {
104 Self {
105 capture_output: true,
106 ..Default::default()
107 }
108 }
109
110 pub fn command_line(mut self, cmd: impl Into<String>) -> Self {
112 self.command_line = Some(cmd.into());
113 self
114 }
115
116 pub fn working_dir(mut self, dir: impl Into<PathBuf>) -> Self {
118 self.working_dir = Some(dir.into());
119 self
120 }
121
122 pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
124 self.env.insert(key.into(), value.into());
125 self
126 }
127
128 pub fn timeout(mut self, duration: Duration) -> Self {
130 self.timeout = Some(duration);
131 self
132 }
133
134 pub fn capture_output(mut self, capture: bool) -> Self {
136 self.capture_output = capture;
137 self
138 }
139
140 pub fn build(self) -> Option<Command> {
144 self.command_line.map(|cmd| Command {
145 command_line: cmd,
146 working_dir: self.working_dir,
147 env: self.env,
148 timeout: self.timeout,
149 capture_output: self.capture_output,
150 max_output_bytes: self.max_output_bytes,
151 })
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn test_command_new() {
161 let cmd = Command::new("ls -la");
162 assert_eq!(cmd.command_line, "ls -la");
163 assert!(cmd.working_dir.is_none());
164 assert!(cmd.env.is_empty());
165 assert!(cmd.timeout.is_none());
166 assert!(cmd.capture_output);
167 }
168
169 #[test]
170 fn test_command_builder_chain() {
171 let cmd = Command::new("cargo build")
172 .working_dir("/project")
173 .env("RUST_LOG", "debug")
174 .timeout(Duration::from_secs(60))
175 .capture_output(true);
176
177 assert_eq!(cmd.command_line, "cargo build");
178 assert_eq!(cmd.working_dir, Some(PathBuf::from("/project")));
179 assert_eq!(cmd.env.get("RUST_LOG"), Some(&"debug".to_string()));
180 assert_eq!(cmd.timeout, Some(Duration::from_secs(60)));
181 }
182
183 #[test]
184 fn test_command_envs() {
185 let vars = [("KEY1", "val1"), ("KEY2", "val2")];
186 let cmd = Command::new("echo").envs(vars);
187
188 assert_eq!(cmd.env.len(), 2);
189 assert_eq!(cmd.env.get("KEY1"), Some(&"val1".to_string()));
190 assert_eq!(cmd.env.get("KEY2"), Some(&"val2".to_string()));
191 }
192
193 #[test]
194 fn test_command_builder_build() {
195 let cmd = CommandBuilder::new()
196 .command_line("pwd")
197 .working_dir("/tmp")
198 .build();
199
200 assert!(cmd.is_some());
201 let cmd = cmd.unwrap();
202 assert_eq!(cmd.command_line, "pwd");
203 assert_eq!(cmd.working_dir, Some(PathBuf::from("/tmp")));
204 }
205
206 #[test]
207 fn test_command_builder_empty() {
208 let cmd = CommandBuilder::new().build();
209 assert!(cmd.is_none());
210 }
211}