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; pub const External = struct { program: []const u8, args: ArrayList([]const u8), pub fn eval(external: External, ctx: CommandContext) !CommandStatus { const allocator = ctx.allocator; const input_source = ctx.input_source; // Check if we need to capture output (not going to stdout) const needs_capture = switch (ctx.output_writer) { .stdout => false, .capture => true, }; // Try to execute external command var child_args = ArrayList([]const u8).init(allocator); defer child_args.deinit(); try child_args.append(external.program); for (external.args.items) |arg| { try child_args.append(arg); } var child = std.process.Child.init(child_args.items, allocator); // Set up pipes for capturing output child.stdin_behavior = if (input_source != null) .Pipe else .Inherit; child.stdout_behavior = if (needs_capture) .Pipe else .Inherit; child.stderr_behavior = if (needs_capture) .Pipe else .Inherit; const spawn_result = child.spawn(); if (spawn_result) |_| { // Spawn succeeded, continue with execution } else |err| switch (err) { error.FileNotFound => { 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); var writer = ctx.output_writer; try writer.write(error_msg); return CommandStatus{ .Code = 1 }; }, error.AccessDenied => { const error_msg = "Access is denied.\n"; var writer = ctx.output_writer; try writer.write(error_msg); return CommandStatus{ .Code = 1 }; }, else => { const error_msg = try std.fmt.allocPrint(allocator, "Cannot execute '{s}': {}\n", .{ external.program, err }); defer allocator.free(error_msg); var writer = ctx.output_writer; try writer.write(error_msg); return CommandStatus{ .Code = 1 }; }, } // Handle input redirection if (input_source) |source| { if (child.stdin) |stdin| { const writer = stdin.writer(); // Reset source position for reading var temp_source = source.*; temp_source.position = 0; while (try temp_source.readLine(allocator)) |line| { defer allocator.free(line); try writer.print("{s}\n", .{line}); } child.stdin.?.close(); child.stdin = null; } } // Handle output capture if (needs_capture) { var writer = ctx.output_writer; // Read stdout if (child.stdout) |stdout| { var buffer: [4096]u8 = undefined; while (true) { const bytes_read = stdout.read(&buffer) catch break; if (bytes_read == 0) break; try writer.write(buffer[0..bytes_read]); } } // Read stderr if (child.stderr) |stderr| { var buffer: [4096]u8 = undefined; while (true) { const bytes_read = stderr.read(&buffer) catch break; if (bytes_read == 0) break; try writer.write(buffer[0..bytes_read]); } } } // Wait for process to complete const term = child.wait() catch |err| { const error_msg = switch (err) { error.FileNotFound => try std.fmt.allocPrint(allocator, "'{s}' is not recognized as an internal or external command,\noperable program or batch file.\n", .{external.program}), else => try std.fmt.allocPrint(allocator, "Error waiting for command: {}\n", .{err}), }; defer allocator.free(error_msg); var writer = ctx.output_writer; try writer.write(error_msg); return CommandStatus{ .Code = 1 }; }; // Return exit code switch (term) { .Exited => |code| return CommandStatus{ .Code = @intCast(code) }, .Signal => |_| return CommandStatus{ .Code = 1 }, .Stopped => |_| return CommandStatus{ .Code = 1 }, .Unknown => |_| return CommandStatus{ .Code = 1 }, } } };