diff options
author | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2025-08-13 19:10:09 +0200 |
---|---|---|
committer | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2025-08-13 19:10:09 +0200 |
commit | c362799fb932b8530c5d949399bf93904b24faf8 (patch) | |
tree | 0ae0d0c010b729d8215ec4536bf71a6e699ed919 | |
parent | 0f00400df98327dc01681567dc2313a9c6e57953 (diff) |
Add output redirection.
-rw-r--r-- | src/main.zig | 309 |
1 files changed, 257 insertions, 52 deletions
diff --git a/src/main.zig b/src/main.zig index 947ed23..4cfc581 100644 --- a/src/main.zig +++ b/src/main.zig @@ -588,37 +588,82 @@ fn parseAndExecute(input: []const u8, allocator: Allocator) !CommandStatus { return try executeCommand(command, allocator); } -fn executeCommand(command: Command, _: Allocator) !CommandStatus { +const OutputCapture = struct { + buffer: ArrayList(u8), + + pub fn init(allocator: Allocator) OutputCapture { + return OutputCapture{ + .buffer = ArrayList(u8).init(allocator), + }; + } + + pub fn deinit(self: *OutputCapture) void { + self.buffer.deinit(); + } + + pub fn write(self: *OutputCapture, data: []const u8) !void { + try self.buffer.appendSlice(data); + } + + pub fn getContents(self: *const OutputCapture) []const u8 { + return self.buffer.items; + } +}; + +fn executeCommandWithOutput(command: Command, allocator: Allocator, output_capture: ?*OutputCapture) !CommandStatus { switch (command) { .Empty => return CommandStatus{ .Code = 0 }, .Builtin => |builtin_cmd| { switch (builtin_cmd) { .EchoText => |echo| { - print("{s}\n", .{echo.message}); + const output = try std.fmt.allocPrint(allocator, "{s}\n", .{echo.message}); + defer allocator.free(output); + if (output_capture) |capture| { + try capture.write(output); + } else { + print("{s}", .{output}); + } return CommandStatus{ .Code = 0 }; }, .Cls => { - // Clear screen - simplified version - print("\x1B[2J\x1B[H", .{}); + if (output_capture == null) { + // Clear screen - only works when not redirected + print("\x1B[2J\x1B[H", .{}); + } return CommandStatus{ .Code = 0 }; }, .Exit => { return CommandStatus.ExitShell; }, .EchoPlain => { - print("ECHO is on\n", .{}); + const output = "ECHO is on\n"; + if (output_capture) |capture| { + try capture.write(output); + } else { + print("{s}", .{output}); + } return CommandStatus{ .Code = 0 }; }, .EchoOn => { - print("ECHO is on\n", .{}); + const output = "ECHO is on\n"; + if (output_capture) |capture| { + try capture.write(output); + } else { + print("{s}", .{output}); + } return CommandStatus{ .Code = 0 }; }, .EchoOff => { return CommandStatus{ .Code = 0 }; }, .Ver => { - print("MS-DOS Version 6.22 (Zig Implementation)\n", .{}); + const output = "MS-DOS Version 6.22 (Zig Implementation)\n"; + if (output_capture) |capture| { + try capture.write(output); + } else { + print("{s}", .{output}); + } return CommandStatus{ .Code = 0 }; }, .Date => { @@ -662,7 +707,13 @@ fn executeCommand(command: Command, _: Allocator) !CommandStatus { const day = remaining_days + 1; // Days are 1-indexed - print("Current date is {d:0>2}/{d:0>2}/{d}\n", .{ month, day, year }); + const output = try std.fmt.allocPrint(allocator, "Current date is {d:0>2}/{d:0>2}/{d}\n", .{ month, day, year }); + defer allocator.free(output); + if (output_capture) |capture| { + try capture.write(output); + } else { + print("{s}", .{output}); + } return CommandStatus{ .Code = 0 }; }, .Time => { @@ -672,14 +723,29 @@ fn executeCommand(command: Command, _: Allocator) !CommandStatus { const hours = day_seconds / std.time.s_per_hour; const minutes = (day_seconds % std.time.s_per_hour) / std.time.s_per_min; const seconds = day_seconds % std.time.s_per_min; - print("Current time is {d:0>2}:{d:0>2}:{d:0>2}\n", .{ hours, minutes, seconds }); + + const output = try std.fmt.allocPrint(allocator, "Current time is {d:0>2}:{d:0>2}:{d:0>2}\n", .{ hours, minutes, seconds }); + defer allocator.free(output); + if (output_capture) |capture| { + try capture.write(output); + } else { + print("{s}", .{output}); + } return CommandStatus{ .Code = 0 }; }, .Dir => |dir| { - print("Directory of {s}\n\n", .{dir.path}); + var output_buffer = ArrayList(u8).init(allocator); + defer output_buffer.deinit(); + + try output_buffer.writer().print("Directory of {s}\n\n", .{dir.path}); var dir_iterator = std.fs.cwd().openDir(dir.path, .{ .iterate = true }) catch { - print("File not found\n", .{}); + const error_msg = "File not found\n"; + if (output_capture) |capture| { + try capture.write(error_msg); + } else { + print("{s}", .{error_msg}); + } return CommandStatus{ .Code = 1 }; }; defer dir_iterator.close(); @@ -691,54 +757,64 @@ fn executeCommand(command: Command, _: Allocator) !CommandStatus { while (try iterator.next()) |entry| { switch (entry.kind) { .directory => { - print("<DIR> {s}\n", .{entry.name}); + try output_buffer.writer().print("<DIR> {s}\n", .{entry.name}); dir_count += 1; }, .file => { const stat = dir_iterator.statFile(entry.name) catch continue; - print("{d:>14} {s}\n", .{ stat.size, entry.name }); + try output_buffer.writer().print("{d:>14} {s}\n", .{ stat.size, entry.name }); file_count += 1; }, else => {}, } } - print("\n{d} File(s)\n", .{file_count}); - print("{d} Dir(s)\n", .{dir_count}); + try output_buffer.writer().print("\n{d} File(s)\n", .{file_count}); + try output_buffer.writer().print("{d} Dir(s)\n", .{dir_count}); + + if (output_capture) |capture| { + try capture.write(output_buffer.items); + } else { + print("{s}", .{output_buffer.items}); + } return CommandStatus{ .Code = 0 }; }, .Type => |type_cmd| { const file_path = switch (type_cmd.file) { .Con => { - print("Cannot TYPE from CON\n", .{}); + const error_msg = "Cannot TYPE from CON\n"; + if (output_capture) |capture| { + try capture.write(error_msg); + } else { + print("{s}", .{error_msg}); + } return CommandStatus{ .Code = 1 }; }, .Lpt1, .Lpt2, .Lpt3, .Prn => { - print("Cannot TYPE from device\n", .{}); + const error_msg = "Cannot TYPE from device\n"; + if (output_capture) |capture| { + try capture.write(error_msg); + } else { + print("{s}", .{error_msg}); + } return CommandStatus{ .Code = 1 }; }, .Path => |path| path, }; const file = std.fs.cwd().openFile(file_path, .{}) catch |err| { - switch (err) { - error.FileNotFound => { - print("The system cannot find the file specified.\n", .{}); - return CommandStatus{ .Code = 1 }; - }, - error.IsDir => { - print("Access is denied.\n", .{}); - return CommandStatus{ .Code = 1 }; - }, - error.AccessDenied => { - print("Access is denied.\n", .{}); - return CommandStatus{ .Code = 1 }; - }, - else => { - print("Cannot access file.\n", .{}); - return CommandStatus{ .Code = 1 }; - }, + const error_msg = switch (err) { + error.FileNotFound => "The system cannot find the file specified.\n", + error.IsDir => "Access is denied.\n", + error.AccessDenied => "Access is denied.\n", + else => "Cannot access file.\n", + }; + if (output_capture) |capture| { + try capture.write(error_msg); + } else { + print("{s}", .{error_msg}); } + return CommandStatus{ .Code = 1 }; }; defer file.close(); @@ -746,38 +822,47 @@ fn executeCommand(command: Command, _: Allocator) !CommandStatus { var buffer: [4096]u8 = undefined; while (true) { const bytes_read = file.readAll(&buffer) catch |err| { - switch (err) { - error.AccessDenied => { - print("Access is denied.\n", .{}); - return CommandStatus{ .Code = 1 }; - }, - else => { - print("Error reading file.\n", .{}); - return CommandStatus{ .Code = 1 }; - }, + const error_msg = switch (err) { + error.AccessDenied => "Access is denied.\n", + else => "Error reading file.\n", + }; + if (output_capture) |capture| { + try capture.write(error_msg); + } else { + print("{s}", .{error_msg}); } + return CommandStatus{ .Code = 1 }; }; if (bytes_read == 0) break; - // Print the buffer contents, handling both text and binary files + // Process buffer contents for output + var processed_output = ArrayList(u8).init(allocator); + defer processed_output.deinit(); + for (buffer[0..bytes_read]) |byte| { // Convert to printable characters, similar to DOS TYPE behavior if (byte >= 32 and byte <= 126) { - print("{c}", .{byte}); + try processed_output.append(byte); } else if (byte == '\n') { - print("\n", .{}); + try processed_output.append('\n'); } else if (byte == '\r') { // Skip carriage return in DOS-style line endings continue; } else if (byte == '\t') { - print("\t", .{}); + try processed_output.append('\t'); } else { // Replace non-printable characters with '?' - print("?", .{}); + try processed_output.append('?'); } } + if (output_capture) |capture| { + try capture.write(processed_output.items); + } else { + print("{s}", .{processed_output.items}); + } + // If we read less than the buffer size, we're done if (bytes_read < buffer.len) break; } @@ -785,25 +870,145 @@ fn executeCommand(command: Command, _: Allocator) !CommandStatus { return CommandStatus{ .Code = 0 }; }, else => { - print("Command not implemented: {any}\n", .{builtin_cmd}); + const error_msg = try std.fmt.allocPrint(allocator, "Command not implemented: {any}\n", .{builtin_cmd}); + defer allocator.free(error_msg); + if (output_capture) |capture| { + try capture.write(error_msg); + } else { + print("{s}", .{error_msg}); + } return CommandStatus{ .Code = 1 }; }, } }, .External => |external| { - print("'{s}' is not recognized as an internal or external command,\n", .{external.program}); - print("operable program or batch file.\n", .{}); + const error_msg = try std.fmt.allocPrint(allocator, "'{s}' is not recognized as an internal or external command,\noperable program or batch file.\n", .{external.program}); + defer allocator.free(error_msg); + if (output_capture) |capture| { + try capture.write(error_msg); + } else { + print("{s}", .{error_msg}); + } return CommandStatus{ .Code = 1 }; }, + .Redirect => |redirect| { + var captured_output = OutputCapture.init(allocator); + defer captured_output.deinit(); + + // Execute the command with output capture + const status = try executeCommandWithOutput(redirect.command.*, allocator, &captured_output); + + // Handle redirections + for (redirect.redirects.items) |redir| { + const file_path = switch (redir.target) { + .Con => { + // Redirect to console - just print normally + print("{s}", .{captured_output.getContents()}); + continue; + }, + .Lpt1, .Lpt2, .Lpt3, .Prn => { + print("Cannot redirect to device\n", .{}); + return CommandStatus{ .Code = 1 }; + }, + .Path => |path| path, + }; + + // Handle different redirect types + switch (redir.redirect_type) { + .OutputOverwrite => { + // Write to file, overwriting existing content + const file = std.fs.cwd().createFile(file_path, .{}) catch |err| { + switch (err) { + error.AccessDenied => print("Access is denied.\n", .{}), + else => print("Cannot create file.\n", .{}), + } + return CommandStatus{ .Code = 1 }; + }; + defer file.close(); + + file.writeAll(captured_output.getContents()) catch |err| { + switch (err) { + error.AccessDenied => print("Access is denied.\n", .{}), + else => print("Cannot write to file.\n", .{}), + } + return CommandStatus{ .Code = 1 }; + }; + }, + .OutputAppend => { + // Append to file + const file = std.fs.cwd().openFile(file_path, .{ .mode = .write_only }) catch |err| { + switch (err) { + error.FileNotFound => { + // Create new file if it doesn't exist + const new_file = std.fs.cwd().createFile(file_path, .{}) catch |create_err| { + switch (create_err) { + error.AccessDenied => print("Access is denied.\n", .{}), + else => print("Cannot create file.\n", .{}), + } + return CommandStatus{ .Code = 1 }; + }; + defer new_file.close(); + + new_file.writeAll(captured_output.getContents()) catch { + print("Cannot write to file.\n", .{}); + return CommandStatus{ .Code = 1 }; + }; + continue; + }, + error.AccessDenied => { + print("Access is denied.\n", .{}); + return CommandStatus{ .Code = 1 }; + }, + else => { + print("Cannot open file.\n", .{}); + return CommandStatus{ .Code = 1 }; + }, + } + }; + defer file.close(); + + // Seek to end for append + file.seekFromEnd(0) catch { + print("Cannot seek to end of file.\n", .{}); + return CommandStatus{ .Code = 1 }; + }; + + file.writeAll(captured_output.getContents()) catch |err| { + switch (err) { + error.AccessDenied => print("Access is denied.\n", .{}), + else => print("Cannot write to file.\n", .{}), + } + return CommandStatus{ .Code = 1 }; + }; + }, + .InputFrom => { + print("Input redirection not implemented\n", .{}); + return CommandStatus{ .Code = 1 }; + }, + } + } + + return status; + }, + else => { - print("Command type not implemented\n", .{}); + const error_msg = "Command type not implemented\n"; + if (output_capture) |capture| { + try capture.write(error_msg); + } else { + print("{s}", .{error_msg}); + } return CommandStatus{ .Code = 1 }; }, } } +fn executeCommand(command: Command, allocator: Allocator) !CommandStatus { + return executeCommandWithOutput(command, allocator, null); +} + fn readLine(allocator: Allocator, prompt_text: []const u8) !?[]const u8 { const stdin = std.io.getStdIn().reader(); print("{s}", .{prompt_text}); |