diff options
Diffstat (limited to 'src/selection_pipe.zig')
| -rw-r--r-- | src/selection_pipe.zig | 90 |
1 files changed, 79 insertions, 11 deletions
diff --git a/src/selection_pipe.zig b/src/selection_pipe.zig index 44cc1709..4fb024bb 100644 --- a/src/selection_pipe.zig +++ b/src/selection_pipe.zig @@ -18,11 +18,18 @@ pub const command_timeout_seconds: u64 = 10; pub const Input = struct { bytes: []const u8 }; /// Borrowed view exposed by the core while an effect is being drained. +/// An Edit's `<`, `|` and `>` (src/edit_cmd.zig) run a command of their own +/// per input, each in its file's directory: `commands` and `cwds`, when +/// given, name input i's, and `shell` (the session's Shell) runs them, +/// /bin/sh when it is empty. pub const Request = struct { id: u32, command: []const u8, cwd: []const u8, inputs: []const Input, + commands: []const []const u8 = &.{}, + cwds: []const []const u8 = &.{}, + shell: []const u8 = "", }; /// Worker-owned snapshot. `copy` is intentionally called synchronously while @@ -33,6 +40,10 @@ pub const Job = struct { command: []u8, cwd: []u8, inputs: [][]u8, + /// Input i's own command and directory, when the request gave them. + commands: [][]u8 = &.{}, + cwds: [][]u8 = &.{}, + shell: []u8 = &.{}, pub fn copy(gpa: std.mem.Allocator, request: Request) !*Job { const job = try gpa.create(Job); @@ -46,22 +57,45 @@ pub const Job = struct { errdefer gpa.free(job.command); job.cwd = try gpa.dupe(u8, filesystem.localPath(request.cwd) orelse request.cwd); errdefer gpa.free(job.cwd); - job.inputs = try gpa.alloc([]u8, request.inputs.len); - errdefer gpa.free(job.inputs); + job.inputs = try dupeAll(gpa, request.inputs.len, request.inputs, false); + errdefer freeAll(gpa, job.inputs); + job.commands = try dupeAll(gpa, request.commands.len, request.commands, false); + errdefer freeAll(gpa, job.commands); + job.cwds = try dupeAll(gpa, request.cwds.len, request.cwds, true); + errdefer freeAll(gpa, job.cwds); + job.shell = try gpa.dupe(u8, request.shell); + return job; + } + + /// Owned copies of `items` (Inputs or strings); a directory as the + /// host's own path, as `cwd` is. + fn dupeAll(gpa: std.mem.Allocator, n: usize, items: anytype, dirs: bool) ![][]u8 { + const out = try gpa.alloc([]u8, n); var made: usize = 0; - errdefer for (job.inputs[0..made]) |input| gpa.free(input); - for (request.inputs, 0..) |input, i| { - job.inputs[i] = try gpa.dupe(u8, input.bytes); + errdefer { + for (out[0..made]) |item| gpa.free(item); + gpa.free(out); + } + for (items) |item| { + const bytes: []const u8 = if (@TypeOf(item) == Input) item.bytes else item; + out[made] = try gpa.dupe(u8, if (dirs) filesystem.localPath(bytes) orelse bytes else bytes); made += 1; } - return job; + return out; + } + + fn freeAll(gpa: std.mem.Allocator, items: [][]u8) void { + for (items) |item| gpa.free(item); + gpa.free(items); } pub fn deinit(job: *Job, gpa: std.mem.Allocator) void { gpa.free(job.command); gpa.free(job.cwd); - for (job.inputs) |input| gpa.free(input); - gpa.free(job.inputs); + freeAll(gpa, job.inputs); + freeAll(gpa, job.commands); + freeAll(gpa, job.cwds); + gpa.free(job.shell); gpa.destroy(job); } }; @@ -194,13 +228,26 @@ pub fn runOne( cwd: []const u8, input: []const u8, ) Outcome { + return runIn(gpa, io, "", command, cwd, input); +} + +/// `runOne` through `shell -c` (/bin/sh when it is empty). +pub fn runIn( + gpa: std.mem.Allocator, + io: std.Io, + shell: []const u8, + command: []const u8, + cwd: []const u8, + input: []const u8, +) Outcome { const fail = struct { fn k(kind: Failure.Kind) Outcome { return .{ .failed = .{ .kind = kind } }; } }; if (std.mem.indexOfScalar(u8, command, 0) != null or - std.mem.indexOfScalar(u8, cwd, 0) != null) return fail.k(.spawn); + std.mem.indexOfScalar(u8, cwd, 0) != null or + std.mem.indexOfScalar(u8, shell, 0) != null) return fail.k(.spawn); // std's spawn runs no code in the child: this thread's mask, which the // child inherits, is cleared across the fork (only the tty's SIGWINCH is @@ -211,7 +258,7 @@ pub fn runOne( std.posix.sigprocmask(std.posix.SIG.SETMASK, &none, &kept); defer std.posix.sigprocmask(std.posix.SIG.SETMASK, &kept, null); var child = std.process.spawn(io, .{ - .argv = &.{ "/bin/sh", "-c", command }, + .argv = &.{ if (shell.len == 0) "/bin/sh" else shell, "-c", command }, .cwd = if (cwd.len == 0) .inherit else .{ .path = cwd }, .stdin = .pipe, .stdout = .pipe, @@ -301,7 +348,9 @@ pub fn runJob(gpa: std.mem.Allocator, io: std.Io, job: *const Job) Response { var made: usize = 0; var total: usize = 0; for (job.inputs, 0..) |input, i| { - const output = switch (runOne(gpa, io, job.command, job.cwd, input)) { + const command = if (job.commands.len > 0) job.commands[i] else job.command; + const cwd = if (job.cwds.len > 0) job.cwds[i] else job.cwd; + const output = switch (runIn(gpa, io, job.shell, command, cwd, input)) { .ok => |bytes| bytes, .failed => |f| { // WHICH selection, because with several cursors "it failed" is @@ -400,3 +449,22 @@ test "native pipe runner preserves stdin/stdout bytes and reports how it failed" .failed => return error.EarlyStdinCloseShouldNotFail, } } + +test "a request may give each input its own command and directory, run by the shell it names" { + const gpa = std.testing.allocator; + const job = try Job.copy(gpa, .{ + .id = 3, + .command = "", + .cwd = "", + .inputs = &.{ .{ .bytes = "" }, .{ .bytes = "abc" } }, + .commands = &.{ "pwd", "tr a-z A-Z" }, + .cwds = &.{ "/tmp", "/" }, + .shell = "/bin/sh", + }); + defer job.deinit(gpa); + var response = runJob(gpa, std.testing.io, job); + defer response.deinit(gpa); + try std.testing.expect(response.success); + try std.testing.expectEqualStrings("/tmp\n", response.outputs[0]); + try std.testing.expectEqualStrings("ABC", response.outputs[1]); +} |
