1use std::env;
2use std::fs::File;
3use std::io::{self, Read, Write};
4use std::sync::{Arc, Mutex};
5use colored::*;
6pub use wasibox_core::IoContext;
7
8pub fn handle_pipeline<R, W, F>(
9 line: &str,
10 initial_stdin: Box<R>,
11 final_stdout: Box<W>,
12 handler: &mut F,
13) -> Result<(), String>
14where
15 R: Read + Send + 'static,
16 W: Write + Send + 'static,
17 F: FnMut(&[String], &mut IoContext) -> Option<Result<(), String>> + Send,
18{
19 let stages_str: Vec<&str> = line.split('|').collect();
20 let handler = Arc::new(Mutex::new(handler));
21
22 let mut final_stdout = Some(final_stdout);
23
24 std::thread::scope(|s| {
25 let mut prev_reader: Box<dyn Read + Send> = Box::new(initial_stdin);
26 let mut threads = Vec::new();
27
28 for (i, stage_str) in stages_str.iter().enumerate() {
29 let is_last = i == stages_str.len() - 1;
30 let mut tokens = shlex::split(stage_str.trim())
31 .ok_or_else(|| "Error: Invalid input (quoting error)".to_string())?;
32
33 let stdin = std::mem::replace(&mut prev_reader, Box::new(io::empty()));
34 let (stdout, next_reader): (Box<dyn Write + Send>, Option<Box<dyn Read + Send>>) = if is_last {
35 let mut out: Box<dyn Write + Send> = Box::new(final_stdout.take().unwrap());
36 if let Some(pos) = tokens.iter().position(|t| t == ">" || t == ">>") {
37 let append = tokens[pos] == ">>";
38 let filename = tokens.get(pos + 1).ok_or("Error: Missing file for redirection")?;
39 let file = if append {
40 std::fs::OpenOptions::new().create(true).append(true).open(filename)
41 } else {
42 File::create(filename)
43 }.map_err(|e| format!("Error opening file: {}", e))?;
44 out = Box::new(file);
45 tokens.truncate(pos);
46 }
47 (out, None)
48 } else {
49 let (reader, writer) = create_pipe();
50 (Box::new(writer), Some(Box::new(reader)))
51 };
52
53 if let Some(r) = next_reader {
54 prev_reader = r;
55 }
56
57 let handler = Arc::clone(&handler);
58 let thread = s.spawn(move || {
59 let mut ctx = IoContext::new(stdin, stdout, Box::new(io::stderr()));
60 let mut h = |args: &[String], ctx: &mut IoContext| {
61 let mut h_lock = handler.lock().unwrap();
62 (h_lock)(args, ctx)
63 };
64 execute_command(tokens, &mut ctx, &mut h)
65 });
66 threads.push(thread);
67 }
68
69 let mut final_res = Ok(());
70 for thread in threads {
71 if let Ok(res) = thread.join() {
72 if res.is_err() && final_res.is_ok() {
73 final_res = res;
74 }
75 }
76 }
77 final_res
78 })
79}
80
81struct PipeReader {
82 rx: std::sync::mpsc::Receiver<Vec<u8>>,
83 buffer: Vec<u8>,
84 pos: usize,
85}
86impl Read for PipeReader {
87 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
88 if self.pos >= self.buffer.len() {
89 match self.rx.recv() {
90 Ok(data) => {
91 self.buffer = data;
92 self.pos = 0;
93 }
94 Err(_) => return Ok(0),
95 }
96 }
97 let available = self.buffer.len() - self.pos;
98 let to_copy = std::cmp::min(available, buf.len());
99 buf[..to_copy].copy_from_slice(&self.buffer[self.pos..self.pos + to_copy]);
100 self.pos += to_copy;
101 Ok(to_copy)
102 }
103}
104
105struct PipeWriter {
106 tx: std::sync::mpsc::Sender<Vec<u8>>,
107}
108impl Write for PipeWriter {
109 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
110 self.tx.send(buf.to_vec()).map_err(|e| io::Error::new(io::ErrorKind::BrokenPipe, e))?;
111 Ok(buf.len())
112 }
113 fn flush(&mut self) -> io::Result<()> { Ok(()) }
114}
115
116fn create_pipe() -> (PipeReader, PipeWriter) {
117 let (tx, rx) = std::sync::mpsc::channel();
118 (PipeReader { rx, buffer: Vec::new(), pos: 0 }, PipeWriter { tx })
119}
120
121pub struct ArcVecWriter {
122 pub inner: Arc<Mutex<Vec<u8>>>,
123}
124impl Write for ArcVecWriter {
125 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
126 let mut inner = self.inner.lock().map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
127 inner.extend_from_slice(buf);
128 Ok(buf.len())
129 }
130 fn flush(&mut self) -> io::Result<()> { Ok(()) }
131}
132
133pub fn execute_command<F>(
134 args: Vec<String>,
135 ctx: &mut IoContext,
136 handler: &mut F,
137) -> Result<(), String>
138where
139 F: FnMut(&[String], &mut IoContext) -> Option<Result<(), String>>,
140{
141 if args.is_empty() {
142 return Ok(());
143 }
144
145 if let Some(res) = handler(&args, ctx) {
146 return res;
147 }
148
149 let cmd = &args[0];
150
151 match cmd.as_str() {
152 "help" => {
153 writeln!(ctx.stdout, "{}", "Available Commands:".yellow().bold()).map_err(|e| e.to_string())?;
154 writeln!(ctx.stdout, " Shell Built-ins: cd, help, exit").map_err(|e| e.to_string())?;
155 writeln!(ctx.stdout, " Animations: sl").map_err(|e| e.to_string())?;
156 writeln!(ctx.stdout, " Core Utilities: arch, cat, cp, dir, echo, grep, head, ls, mkdir, mv, pwd, rm, rmdir, sleep, tail, tee, touch, tree, uname, wc, whoami, yes, etc.").map_err(|e| e.to_string())?;
157 Ok(())
158 }
159 "cd" => {
160 let new_dir = args.get(1).map(|s| s.as_str()).unwrap_or(".");
161 env::set_current_dir(new_dir).map_err(|e| format!("cd: {}", e))
162 }
163 "exit" => Ok(()),
164 "sl" => {
165 let _ = sl::run(args.iter().cloned());
166 Ok(())
167 }
168 _ => wasibox_core::execute_with_context(args.iter().cloned(), ctx),
169 }
170}
171
172pub fn handle_parallel<F>(
173 lines: Vec<String>,
174 handler: Arc<F>,
175) -> Vec<Result<(), String>>
176where
177 F: Fn(&[String], &mut IoContext) -> Option<Result<(), String>> + Send + Sync + 'static,
178{
179 let mut handles = Vec::new();
180 for line in lines {
181 let handler = Arc::clone(&handler);
182 let handle = std::thread::spawn(move || {
183 let mut h = |args: &[String], ctx: &mut IoContext| handler(args, ctx);
184 handle_pipeline(
185 &line,
186 Box::new(io::empty()),
187 Box::new(io::sink()),
188 &mut h
189 )
190 });
191 handles.push(handle);
192 }
193
194 handles.into_iter()
195 .map(|h| h.join().unwrap_or(Err("Thread panicked".to_string())))
196 .collect()
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use std::io::Cursor;
203
204 fn get_temp_dir() -> tempfile::TempDir {
205 #[cfg(target_os = "wasi")]
206 {
207 tempfile::Builder::new()
209 .prefix("test_")
210 .tempdir_in(".")
211 .expect("Failed to create temp dir in current directory")
212 }
213 #[cfg(not(target_os = "wasi"))]
214 {
215 tempfile::tempdir().expect("Failed to create system temp dir")
216 }
217 }
218
219 #[test]
220 fn test_simple_echo() {
221 let out = Arc::new(Mutex::new(Vec::new()));
222 handle_pipeline("echo hello", Box::new(Cursor::new("")), Box::new(ArcVecWriter { inner: Arc::clone(&out) }), &mut |_, _| None).unwrap();
223 let buf = out.lock().unwrap();
224 assert_eq!(String::from_utf8_lossy(&buf).trim(), "hello");
225 }
226
227 #[test]
228 fn test_pipe_echo_cat() {
229 let out = Arc::new(Mutex::new(Vec::new()));
230 handle_pipeline("echo hello | cat", Box::new(Cursor::new("")), Box::new(ArcVecWriter { inner: Arc::clone(&out) }), &mut |_, _| None).unwrap();
231 let buf = out.lock().unwrap();
232 assert_eq!(String::from_utf8_lossy(&buf).trim(), "hello");
233 }
234
235 #[test]
236 fn test_pipe_grep() {
237 let out = Arc::new(Mutex::new(Vec::new()));
238 handle_pipeline("echo world | grep world", Box::new(Cursor::new("")), Box::new(ArcVecWriter { inner: Arc::clone(&out) }), &mut |_, _| None).unwrap();
240 let buf = out.lock().unwrap();
241 assert_eq!(String::from_utf8_lossy(&buf).trim(), "world");
242 }
243
244 #[test]
245 fn test_redirection_create() {
246 let dir = get_temp_dir();
247 let file_path = dir.path().join("test_create.txt");
248 let cmd = format!("echo hello > \"{}\"", file_path.display());
249
250 handle_pipeline(&cmd, Box::new(Cursor::new("")), Box::new(io::sink()), &mut |_, _| None).unwrap();
251
252 let content = std::fs::read_to_string(file_path).unwrap();
253 assert_eq!(content.trim(), "hello");
254 }
255
256 #[test]
257 fn test_redirection_append() {
258 let dir = get_temp_dir();
259 let file_path = dir.path().join("test_append.txt");
260
261 let cmd1 = format!("echo hello > \"{}\"", file_path.display());
262 handle_pipeline(&cmd1, Box::new(Cursor::new("")), Box::new(io::sink()), &mut |_, _| None).unwrap();
263
264 let cmd2 = format!("echo world >> \"{}\"", file_path.display());
265 handle_pipeline(&cmd2, Box::new(Cursor::new("")), Box::new(io::sink()), &mut |_, _| None).unwrap();
266
267 let content = std::fs::read_to_string(file_path).unwrap();
268 assert!(content.contains("hello"));
269 assert!(content.contains("world"));
270 }
271
272 #[test]
273 fn test_complex_pipeline() {
274 let out = Arc::new(Mutex::new(Vec::new()));
275 handle_pipeline("echo hello | grep h | wc -c", Box::new(Cursor::new("")), Box::new(ArcVecWriter { inner: Arc::clone(&out) }), &mut |_, _| None).unwrap();
276 let buf = out.lock().unwrap();
277 let result = String::from_utf8_lossy(&buf).trim().to_string();
278 assert_eq!(result, "6");
279 }
280 #[test]
281 fn test_custom_handler() {
282 let out = Arc::new(Mutex::new(Vec::new()));
283 let mut handler = |args: &[String], ctx: &mut IoContext| {
284 if args[0] == "magic" {
285 write!(ctx.stdout, "magic happen").unwrap();
286 return Some(Ok(()));
287 }
288 None
289 };
290 handle_pipeline("magic", Box::new(io::empty()), Box::new(ArcVecWriter { inner: Arc::clone(&out) }), &mut handler).unwrap();
291 let buf = out.lock().unwrap();
292 assert_eq!(String::from_utf8_lossy(&buf), "magic happen");
293 }
294
295 #[test]
296 fn test_parallel_execution() {
297 let handler = Arc::new(|args: &[String], _ctx: &mut IoContext| {
298 if args[0] == "slow" {
299 std::thread::sleep(std::time::Duration::from_millis(100));
300 return Some(Ok(()));
301 }
302 None
303 });
304 let lines = vec!["slow".to_string(), "slow".to_string(), "slow".to_string()];
305 let start = std::time::Instant::now();
306 let results = handle_parallel(lines, handler);
307 let duration = start.elapsed();
308
309 assert_eq!(results.len(), 3);
310 for res in results {
311 assert!(res.is_ok());
312 }
313 assert!(duration < std::time::Duration::from_millis(250));
315 }
316
317 #[test]
318 fn test_streaming_pipeline() {
319 let out = Arc::new(Mutex::new(Vec::new()));
320 handle_pipeline("yes | head -n 2", Box::new(io::empty()), Box::new(ArcVecWriter { inner: Arc::clone(&out) }), &mut |_, _| None).unwrap();
321 let buf = out.lock().unwrap();
322 let result = String::from_utf8_lossy(&buf);
323 assert_eq!(result.trim(), "y\ny");
324 }
325}