module os fn C._dup(fd i32) i32 fn C._dup2(fd1 i32, fd2 i32) i32 fn C._pipe(fds &int, size u32, mode i32) i32 fn C.dup(fd i32) i32 const fd_stdout = $if windows { 1 } $else { C.STDOUT_FILENO } const fd_stderr = $if windows { 2 } $else { C.STDERR_FILENO } // fd_dup duplicates a file descriptor pub fn fd_dup(fd int) int { return $if windows { C._dup(fd) } $else { C.dup(fd) } } // fd_dup2 duplicates file descriptor `fd1` to descriptor number `fd2` // If `fd2` is already open, it is closed first before being reused. // Returns the new file descriptor on success, or -1 on error. pub fn fd_dup2(fd1 int, fd2 int) int { return $if windows { C._dup2(fd1, fd2) } $else { C.dup2(fd1, fd2) } } // Pipe represents a bidirectional communication channel @[noinit] pub struct Pipe { pub mut: read_fd int = -1 write_fd int = -1 } // pipe creates a new pipe for inter-process communication pub fn pipe() !Pipe { mut fds := [2]int{} $if windows { if C._pipe(&fds[0], 0, 0) == -1 { return error('Failed to create pipe') } } $else { if C.pipe(&fds[0]) == -1 { return error('Failed to create pipe') } } return Pipe{ read_fd: fds[0] write_fd: fds[1] } } // close closes the pipe and releases associated resources pub fn (mut p Pipe) close() { if p.read_fd != -1 { fd_close(p.read_fd) p.read_fd = -1 } if p.write_fd != -1 { fd_close(p.write_fd) p.write_fd = -1 } } // read reads data from the pipe into the provided buffer pub fn (p &Pipe) read(mut buffer []u8) !int { result := C.read(p.read_fd, buffer.data, buffer.len) if result == -1 { return error('Read failed') } return int(result) } // write writes data from the buffer to the pipe pub fn (p &Pipe) write(buffer []u8) !int { result := C.write(p.write_fd, buffer.data, buffer.len) if result == -1 { return error('Write failed') } return int(result) } // write_string writes data from the string to the pipe pub fn (p &Pipe) write_string(s string) !int { result := C.write(p.write_fd, voidptr(s.str), s.len) if result == -1 { return error('Write failed') } return int(result) } // slurp reads all data from the pipe until EOF pub fn (mut p Pipe) slurp() []string { // Close write end to send EOF signal to the pipe fd_close(p.write_fd) p.write_fd = -1 result := fd_slurp(p.read_fd) fd_close(p.read_fd) p.read_fd = -1 return result } // IOCapture manages redirection of standard output and error streams @[noinit] pub struct IOCapture { pub mut: stdout Pipe stderr Pipe mut: original_stdout_fd int = -1 original_stderr_fd int = -1 } // stdio_capture starts capturing stdout and stderr by redirecting them to pipes // Example: // ```v // import os // unbuffer_stdout() // mut cap := os.stdio_capture()! // println('hello println') // eprintln('hello eprintln') // cap.stop() // sout := cap.stdout.slurp() // serr := cap.stderr.slurp() // cap.close() // assert sout == ['hello println\n'] // assert serr == ['hello eprintln\n'] // ``` // or // ```v // import os // unbuffer_stdout() // mut cap := os.stdio_capture()! // println('hello println') // eprintln('hello eprintln') // eprintln('World eprintln') // println('World println') // sout, serr := cap.finish() // assert sout == ['hello println\nWorld println\n'] // assert serr == ['hello eprintln\nWorld eprintln\n'] // ``` pub fn stdio_capture() !IOCapture { mut c := IOCapture{} mut pipe_stdout := pipe()! mut pipe_stderr := pipe()! flush_stdout() flush_stderr() // Save original file descriptors c.original_stdout_fd = fd_dup(fd_stdout) c.original_stderr_fd = fd_dup(fd_stderr) // Redirect stdout to pipe if fd_dup2(pipe_stdout.write_fd, fd_stdout) == -1 { pipe_stdout.close() pipe_stderr.close() return error('Failed to redirect stdout') } // Redirect stderr to pipe if fd_dup2(pipe_stderr.write_fd, fd_stderr) == -1 { fd_dup2(c.original_stdout_fd, fd_stdout) // Restore stdout pipe_stdout.close() pipe_stderr.close() return error('Failed to redirect stderr') } // Close original write ends (duplicated by dup2) fd_close(pipe_stdout.write_fd) fd_close(pipe_stderr.write_fd) pipe_stdout.write_fd = -1 pipe_stderr.write_fd = -1 // Store pipes for later reading c.stdout = pipe_stdout c.stderr = pipe_stderr return c } // stop restores the original stdout and stderr streams // This should be called to resume normal console output pub fn (mut c IOCapture) stop() { flush_stdout() flush_stderr() // Restore original stdout if c.original_stdout_fd != -1 { fd_dup2(c.original_stdout_fd, fd_stdout) fd_close(c.original_stdout_fd) c.original_stdout_fd = -1 } // Restore original stderr if c.original_stderr_fd != -1 { fd_dup2(c.original_stderr_fd, fd_stderr) fd_close(c.original_stderr_fd) c.original_stderr_fd = -1 } } // close releases all resources associated with the capture pub fn (mut c IOCapture) close() { c.stdout.close() c.stderr.close() } // finish stops capturing, reads all captured data, and releases resources pub fn (mut c IOCapture) finish() ([]string, []string) { c.stop() stdout_str := c.stdout.slurp() stderr_str := c.stderr.slurp() c.close() return stdout_str, stderr_str }