const std = @import("std"); const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; const types = @import("./lib/types.zig"); const CommandStatus = types.CommandStatus; const CommandContext = types.CommandContext; 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; pub const RedirectCommand = struct { command: *Command, redirects: ArrayList(Redirect), pub fn eval(redirect: RedirectCommand, ctx: CommandContext) !CommandStatus { const allocator = ctx.allocator; var stdout_buffer: [1024]u8 = undefined; var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); const stdout = &stdout_writer.interface; var stderr_buffer: [1024]u8 = undefined; var stderr_writer = std.fs.File.stderr().writer(&stderr_buffer); const stderr = &stderr_writer.interface; // Extract output_capture from the output_writer for redirection logic const output_capture = switch (ctx.output_writer) { .capture => |capture| capture, .stdout => null, }; const execute_command = ctx.execute_command; // 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 => { try stderr.writeAll("Input redirection from CON not supported\n"); return CommandStatus{ .Code = 1 }; }, .Lpt1, .Lpt2, .Lpt3, .Prn => { try stderr.writeAll("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 => stderr.writeAll("The system cannot find the file specified.\n") catch {}, error.AccessDenied => stderr.writeAll("Access is denied.\n") catch {}, else => stderr.writeAll("Cannot open input file.\n") catch {}, } return CommandStatus{ .Code = 1 }; }; defer file.close(); input_data = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| { switch (err) { error.AccessDenied => stderr.writeAll("Access is denied.\n") catch {}, else => stderr.writeAll("Cannot read input file.\n") catch {}, } 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 execute_command(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 try stdout.writeAll(captured_output.getContents()); continue; }, .Lpt1, .Lpt2, .Lpt3, .Prn => { try stderr.writeAll("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 => stderr.writeAll("Access is denied.\n") catch {}, else => stderr.writeAll("Cannot create file.\n") catch {}, } return CommandStatus{ .Code = 1 }; }; defer file.close(); file.writeAll(captured_output.getContents()) catch |err| { switch (err) { error.AccessDenied => stderr.writeAll("Access is denied.\n") catch {}, else => stderr.writeAll("Cannot write to file.\n") catch {}, } 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 => stderr.writeAll("Access is denied.\n") catch {}, else => stderr.writeAll("Cannot create file.\n") catch {}, } return CommandStatus{ .Code = 1 }; }; defer new_file.close(); new_file.writeAll(captured_output.getContents()) catch { stderr.writeAll("Cannot write to file.\n") catch {}; return CommandStatus{ .Code = 1 }; }; continue; }, error.AccessDenied => { stderr.writeAll("Access is denied.\n") catch {}; return CommandStatus{ .Code = 1 }; }, else => { stderr.writeAll("Cannot open file.\n") catch {}; return CommandStatus{ .Code = 1 }; }, } }; defer file.close(); // Seek to end for append file.seekFromEnd(0) catch { stderr.writeAll("Cannot seek to end of file.\n") catch {}; return CommandStatus{ .Code = 1 }; }; file.writeAll(captured_output.getContents()) catch |err| { switch (err) { error.AccessDenied => stderr.writeAll("Access is denied.\n") catch {}, else => stderr.writeAll("Cannot write to file.\n") catch {}, } return CommandStatus{ .Code = 1 }; }; }, .InputFrom => { // Input redirection already handled above continue; }, } } return status; } };