From 63f7353b630c3eb5a6c60118418923b4b358e670 Mon Sep 17 00:00:00 2001 From: Matthias Andreas Benkard Date: Thu, 14 Aug 2025 16:55:23 +0200 Subject: Factor out redirect handling. --- src/cmd.zig | 6 +- src/cmd/redirect.zig | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/eval.zig | 154 +------------------------------------------ 3 files changed, 184 insertions(+), 156 deletions(-) create mode 100644 src/cmd/redirect.zig diff --git a/src/cmd.zig b/src/cmd.zig index 25c0711..6fec627 100644 --- a/src/cmd.zig +++ b/src/cmd.zig @@ -27,6 +27,7 @@ const Time = @import("cmd/time.zig").Time; const Sort = @import("cmd/sort.zig").Sort; const Move = @import("cmd/move.zig").Move; const External = @import("cmd/external.zig").External; +const RedirectCommand = @import("cmd/redirect.zig").RedirectCommand; pub const BuiltinCommand = union(enum) { // File-oriented @@ -152,10 +153,7 @@ pub const Command = union(enum) { left: *Command, right: *Command, }, - Redirect: struct { - command: *Command, - redirects: ArrayList(Redirect), - }, + Redirect: RedirectCommand, External: External, Builtin: BuiltinCommand, Empty, diff --git a/src/cmd/redirect.zig b/src/cmd/redirect.zig new file mode 100644 index 0000000..dbdeafe --- /dev/null +++ b/src/cmd/redirect.zig @@ -0,0 +1,180 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const ArrayList = std.ArrayList; +const print = std.debug.print; + +const types = @import("./lib/types.zig"); +const CommandStatus = types.CommandStatus; +const OutputCapture = types.OutputCapture; +const InputSource = types.InputSource; + +const syntax = @import("../syntax.zig"); +const RedirectType = syntax.RedirectType; +const Redirect = syntax.Redirect; + +const cmd = @import("../cmd.zig"); +const Command = cmd.Command; + +// Function type for executing commands with output capture +const ExecuteCommandFn = *const fn (Command, Allocator, ?*OutputCapture, ?*InputSource) anyerror!CommandStatus; + +pub const RedirectCommand = struct { + command: *Command, + redirects: ArrayList(Redirect), + + pub fn eval(redirect: RedirectCommand, allocator: Allocator, output_capture: ?*OutputCapture, input_source: ?*InputSource, executeCommandWithOutput: ExecuteCommandFn) !CommandStatus { + _ = input_source; // Redirect handles its own input source + + // Check if we have any output redirections + var has_output_redirect = false; + for (redirect.redirects.items) |redir| { + if (redir.redirect_type == .OutputOverwrite or redir.redirect_type == .OutputAppend) { + has_output_redirect = true; + break; + } + } + + var captured_output = OutputCapture.init(allocator); + defer captured_output.deinit(); + + // Prepare input redirection if needed + var input_data: ?[]const u8 = null; + var redirect_input_source: ?InputSource = null; + defer if (input_data) |data| allocator.free(data); + + // Process input redirections first + for (redirect.redirects.items) |redir| { + if (redir.redirect_type == .InputFrom) { + const file_path = switch (redir.target) { + .Con => { + print("Input redirection from CON not supported\n", .{}); + return CommandStatus{ .Code = 1 }; + }, + .Lpt1, .Lpt2, .Lpt3, .Prn => { + print("Cannot redirect input from device\n", .{}); + return CommandStatus{ .Code = 1 }; + }, + .Path => |path| path, + }; + + // Read input file + const file = std.fs.cwd().openFile(file_path, .{}) catch |err| { + switch (err) { + error.FileNotFound => print("The system cannot find the file specified.\n", .{}), + error.AccessDenied => print("Access is denied.\n", .{}), + else => print("Cannot open input file.\n", .{}), + } + return CommandStatus{ .Code = 1 }; + }; + defer file.close(); + + input_data = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| { + switch (err) { + error.AccessDenied => print("Access is denied.\n", .{}), + else => print("Cannot read input file.\n", .{}), + } + return CommandStatus{ .Code = 1 }; + }; + + redirect_input_source = InputSource.init(input_data.?); + break; // Only handle first input redirection + } + } + + // Execute the command with input and output capture (only capture output if needed) + const status = try executeCommandWithOutput(redirect.command.*, allocator, if (has_output_redirect) &captured_output else output_capture, if (redirect_input_source) |*source| source else null); + + // Handle output redirections + for (redirect.redirects.items) |redir| { + if (redir.redirect_type == .InputFrom) continue; // Already handled + 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 => { + // Input redirection already handled above + continue; + }, + } + } + + return status; + } +}; diff --git a/src/eval.zig b/src/eval.zig index 1568411..9399c75 100644 --- a/src/eval.zig +++ b/src/eval.zig @@ -38,7 +38,7 @@ pub fn executeCommand(command: Command, allocator: Allocator) !CommandStatus { return executeCommandWithOutput(command, allocator, null, null); } -fn executeCommandWithOutput(command: Command, allocator: Allocator, output_capture: ?*OutputCapture, input_source: ?*InputSource) !CommandStatus { +pub fn executeCommandWithOutput(command: Command, allocator: Allocator, output_capture: ?*OutputCapture, input_source: ?*InputSource) !CommandStatus { switch (command) { .Empty => return CommandStatus{ .Code = 0 }, @@ -125,157 +125,7 @@ fn executeCommandWithOutput(command: Command, allocator: Allocator, output_captu }, .Redirect => |redirect| { - // Check if we have any output redirections - var has_output_redirect = false; - for (redirect.redirects.items) |redir| { - if (redir.redirect_type == .OutputOverwrite or redir.redirect_type == .OutputAppend) { - has_output_redirect = true; - break; - } - } - - var captured_output = OutputCapture.init(allocator); - defer captured_output.deinit(); - - // Prepare input redirection if needed - var input_data: ?[]const u8 = null; - var redirect_input_source: ?InputSource = null; - defer if (input_data) |data| allocator.free(data); - - // Process input redirections first - for (redirect.redirects.items) |redir| { - if (redir.redirect_type == .InputFrom) { - const file_path = switch (redir.target) { - .Con => { - print("Input redirection from CON not supported\n", .{}); - return CommandStatus{ .Code = 1 }; - }, - .Lpt1, .Lpt2, .Lpt3, .Prn => { - print("Cannot redirect input from device\n", .{}); - return CommandStatus{ .Code = 1 }; - }, - .Path => |path| path, - }; - - // Read input file - const file = std.fs.cwd().openFile(file_path, .{}) catch |err| { - switch (err) { - error.FileNotFound => print("The system cannot find the file specified.\n", .{}), - error.AccessDenied => print("Access is denied.\n", .{}), - else => print("Cannot open input file.\n", .{}), - } - return CommandStatus{ .Code = 1 }; - }; - defer file.close(); - - input_data = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| { - switch (err) { - error.AccessDenied => print("Access is denied.\n", .{}), - else => print("Cannot read input file.\n", .{}), - } - return CommandStatus{ .Code = 1 }; - }; - - redirect_input_source = InputSource.init(input_data.?); - break; // Only handle first input redirection - } - } - - // Execute the command with input and output capture (only capture output if needed) - const status = try executeCommandWithOutput(redirect.command.*, allocator, if (has_output_redirect) &captured_output else null, if (redirect_input_source) |*source| source else null); - - // Handle output redirections - for (redirect.redirects.items) |redir| { - if (redir.redirect_type == .InputFrom) continue; // Already handled - 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 => { - // Input redirection already handled above - continue; - }, - } - } - - return status; + return redirect.eval(allocator, output_capture, input_source, executeCommandWithOutput); }, else => { -- cgit v1.2.1