summaryrefslogtreecommitdiff
path: root/src/cmd/external.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/external.zig')
-rw-r--r--src/cmd/external.zig134
1 files changed, 134 insertions, 0 deletions
diff --git a/src/cmd/external.zig b/src/cmd/external.zig
new file mode 100644
index 0000000..92bfba6
--- /dev/null
+++ b/src/cmd/external.zig
@@ -0,0 +1,134 @@
+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;
+
+pub const External = struct {
+ program: []const u8,
+ args: ArrayList([]const u8),
+
+ pub fn eval(external: External, allocator: Allocator, output_capture: ?*OutputCapture, input_source: ?*InputSource) !CommandStatus {
+ // 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| {
+ // 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 },
+ }
+ }
+};