| 1 | module os |
| 2 | |
| 3 | fn C._dup(fd i32) i32 |
| 4 | fn C._dup2(fd1 i32, fd2 i32) i32 |
| 5 | fn C._pipe(fds &int, size u32, mode i32) i32 |
| 6 | fn C.dup(fd i32) i32 |
| 7 | |
| 8 | const fd_stdout = $if windows { 1 } $else { C.STDOUT_FILENO } |
| 9 | const fd_stderr = $if windows { 2 } $else { C.STDERR_FILENO } |
| 10 | |
| 11 | // fd_dup duplicates a file descriptor |
| 12 | pub fn fd_dup(fd int) int { |
| 13 | return $if windows { C._dup(fd) } $else { C.dup(fd) } |
| 14 | } |
| 15 | |
| 16 | // fd_dup2 duplicates file descriptor `fd1` to descriptor number `fd2` |
| 17 | // If `fd2` is already open, it is closed first before being reused. |
| 18 | // Returns the new file descriptor on success, or -1 on error. |
| 19 | pub fn fd_dup2(fd1 int, fd2 int) int { |
| 20 | return $if windows { C._dup2(fd1, fd2) } $else { C.dup2(fd1, fd2) } |
| 21 | } |
| 22 | |
| 23 | // Pipe represents a bidirectional communication channel |
| 24 | @[noinit] |
| 25 | pub struct Pipe { |
| 26 | pub mut: |
| 27 | read_fd int = -1 |
| 28 | write_fd int = -1 |
| 29 | } |
| 30 | |
| 31 | // pipe creates a new pipe for inter-process communication |
| 32 | pub fn pipe() !Pipe { |
| 33 | mut fds := [2]int{} |
| 34 | $if windows { |
| 35 | if C._pipe(&fds[0], 0, 0) == -1 { |
| 36 | return error('Failed to create pipe') |
| 37 | } |
| 38 | } $else { |
| 39 | if C.pipe(&fds[0]) == -1 { |
| 40 | return error('Failed to create pipe') |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | return Pipe{ |
| 45 | read_fd: fds[0] |
| 46 | write_fd: fds[1] |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | // close closes the pipe and releases associated resources |
| 51 | pub fn (mut p Pipe) close() { |
| 52 | if p.read_fd != -1 { |
| 53 | fd_close(p.read_fd) |
| 54 | p.read_fd = -1 |
| 55 | } |
| 56 | if p.write_fd != -1 { |
| 57 | fd_close(p.write_fd) |
| 58 | p.write_fd = -1 |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | // read reads data from the pipe into the provided buffer |
| 63 | pub fn (p &Pipe) read(mut buffer []u8) !int { |
| 64 | result := C.read(p.read_fd, buffer.data, buffer.len) |
| 65 | if result == -1 { |
| 66 | return error('Read failed') |
| 67 | } |
| 68 | return int(result) |
| 69 | } |
| 70 | |
| 71 | // write writes data from the buffer to the pipe |
| 72 | pub fn (p &Pipe) write(buffer []u8) !int { |
| 73 | result := C.write(p.write_fd, buffer.data, buffer.len) |
| 74 | if result == -1 { |
| 75 | return error('Write failed') |
| 76 | } |
| 77 | return int(result) |
| 78 | } |
| 79 | |
| 80 | // write_string writes data from the string to the pipe |
| 81 | pub fn (p &Pipe) write_string(s string) !int { |
| 82 | result := C.write(p.write_fd, voidptr(s.str), s.len) |
| 83 | if result == -1 { |
| 84 | return error('Write failed') |
| 85 | } |
| 86 | return int(result) |
| 87 | } |
| 88 | |
| 89 | // slurp reads all data from the pipe until EOF |
| 90 | pub fn (mut p Pipe) slurp() []string { |
| 91 | // Close write end to send EOF signal to the pipe |
| 92 | fd_close(p.write_fd) |
| 93 | p.write_fd = -1 |
| 94 | result := fd_slurp(p.read_fd) |
| 95 | fd_close(p.read_fd) |
| 96 | p.read_fd = -1 |
| 97 | return result |
| 98 | } |
| 99 | |
| 100 | // IOCapture manages redirection of standard output and error streams |
| 101 | @[noinit] |
| 102 | pub struct IOCapture { |
| 103 | pub mut: |
| 104 | stdout Pipe |
| 105 | stderr Pipe |
| 106 | mut: |
| 107 | original_stdout_fd int = -1 |
| 108 | original_stderr_fd int = -1 |
| 109 | } |
| 110 | |
| 111 | // stdio_capture starts capturing stdout and stderr by redirecting them to pipes |
| 112 | // Example: |
| 113 | // ```v |
| 114 | // import os |
| 115 | // unbuffer_stdout() |
| 116 | // mut cap := os.stdio_capture()! |
| 117 | // println('hello println') |
| 118 | // eprintln('hello eprintln') |
| 119 | // cap.stop() |
| 120 | // sout := cap.stdout.slurp() |
| 121 | // serr := cap.stderr.slurp() |
| 122 | // cap.close() |
| 123 | // assert sout == ['hello println\n'] |
| 124 | // assert serr == ['hello eprintln\n'] |
| 125 | // ``` |
| 126 | // or |
| 127 | // ```v |
| 128 | // import os |
| 129 | // unbuffer_stdout() |
| 130 | // mut cap := os.stdio_capture()! |
| 131 | // println('hello println') |
| 132 | // eprintln('hello eprintln') |
| 133 | // eprintln('World eprintln') |
| 134 | // println('World println') |
| 135 | // sout, serr := cap.finish() |
| 136 | // assert sout == ['hello println\nWorld println\n'] |
| 137 | // assert serr == ['hello eprintln\nWorld eprintln\n'] |
| 138 | // ``` |
| 139 | pub fn stdio_capture() !IOCapture { |
| 140 | mut c := IOCapture{} |
| 141 | mut pipe_stdout := pipe()! |
| 142 | mut pipe_stderr := pipe()! |
| 143 | |
| 144 | flush_stdout() |
| 145 | flush_stderr() |
| 146 | |
| 147 | // Save original file descriptors |
| 148 | c.original_stdout_fd = fd_dup(fd_stdout) |
| 149 | c.original_stderr_fd = fd_dup(fd_stderr) |
| 150 | |
| 151 | // Redirect stdout to pipe |
| 152 | if fd_dup2(pipe_stdout.write_fd, fd_stdout) == -1 { |
| 153 | pipe_stdout.close() |
| 154 | pipe_stderr.close() |
| 155 | return error('Failed to redirect stdout') |
| 156 | } |
| 157 | |
| 158 | // Redirect stderr to pipe |
| 159 | if fd_dup2(pipe_stderr.write_fd, fd_stderr) == -1 { |
| 160 | fd_dup2(c.original_stdout_fd, fd_stdout) // Restore stdout |
| 161 | pipe_stdout.close() |
| 162 | pipe_stderr.close() |
| 163 | return error('Failed to redirect stderr') |
| 164 | } |
| 165 | |
| 166 | // Close original write ends (duplicated by dup2) |
| 167 | fd_close(pipe_stdout.write_fd) |
| 168 | fd_close(pipe_stderr.write_fd) |
| 169 | |
| 170 | pipe_stdout.write_fd = -1 |
| 171 | pipe_stderr.write_fd = -1 |
| 172 | |
| 173 | // Store pipes for later reading |
| 174 | c.stdout = pipe_stdout |
| 175 | c.stderr = pipe_stderr |
| 176 | return c |
| 177 | } |
| 178 | |
| 179 | // stop restores the original stdout and stderr streams |
| 180 | // This should be called to resume normal console output |
| 181 | pub fn (mut c IOCapture) stop() { |
| 182 | flush_stdout() |
| 183 | flush_stderr() |
| 184 | |
| 185 | // Restore original stdout |
| 186 | if c.original_stdout_fd != -1 { |
| 187 | fd_dup2(c.original_stdout_fd, fd_stdout) |
| 188 | fd_close(c.original_stdout_fd) |
| 189 | c.original_stdout_fd = -1 |
| 190 | } |
| 191 | |
| 192 | // Restore original stderr |
| 193 | if c.original_stderr_fd != -1 { |
| 194 | fd_dup2(c.original_stderr_fd, fd_stderr) |
| 195 | fd_close(c.original_stderr_fd) |
| 196 | c.original_stderr_fd = -1 |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | // close releases all resources associated with the capture |
| 201 | pub fn (mut c IOCapture) close() { |
| 202 | c.stdout.close() |
| 203 | c.stderr.close() |
| 204 | } |
| 205 | |
| 206 | // finish stops capturing, reads all captured data, and releases resources |
| 207 | pub fn (mut c IOCapture) finish() ([]string, []string) { |
| 208 | c.stop() |
| 209 | stdout_str := c.stdout.slurp() |
| 210 | stderr_str := c.stderr.slurp() |
| 211 | c.close() |
| 212 | return stdout_str, stderr_str |
| 213 | } |
| 214 | |