summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2025-08-13 19:10:09 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2025-08-13 19:10:09 +0200
commitc362799fb932b8530c5d949399bf93904b24faf8 (patch)
tree0ae0d0c010b729d8215ec4536bf71a6e699ed919
parent0f00400df98327dc01681567dc2313a9c6e57953 (diff)
Add output redirection.
-rw-r--r--src/main.zig309
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});