From 354dec925d6089133d1a0632ecdab4bd96d6c8ef Mon Sep 17 00:00:00 2001 From: Matthias Andreas Benkard Date: Wed, 13 Aug 2025 19:18:50 +0200 Subject: Add support for external commands. --- src/main.zig | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 6 deletions(-) diff --git a/src/main.zig b/src/main.zig index 51ebf3c..ef537ea 100644 --- a/src/main.zig +++ b/src/main.zig @@ -967,14 +967,123 @@ fn executeCommandWithOutput(command: Command, allocator: Allocator, output_captu }, .External => |external| { - 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); + // 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 (output_capture != null) .Pipe else .Inherit; + child.stderr_behavior = if (output_capture != null) .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); + + if (output_capture) |capture| { + try capture.write(error_msg); + } else { + print("{s}", .{error_msg}); + } + return CommandStatus{ .Code = 1 }; + }, + error.AccessDenied => { + const error_msg = "Access is denied.\n"; + if (output_capture) |capture| { + try capture.write(error_msg); + } else { + print("{s}", .{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); + + if (output_capture) |capture| { + try capture.write(error_msg); + } else { + print("{s}", .{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 (output_capture) |capture| { - try capture.write(error_msg); - } else { - print("{s}", .{error_msg}); + // 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 capture.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 capture.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); + + if (output_capture) |capture| { + try capture.write(error_msg); + } else { + print("{s}", .{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 }, } - return CommandStatus{ .Code = 1 }; }, .Redirect => |redirect| { -- cgit v1.2.1