Add support for handling quotes in spawn args
The spawn keybind takes a command to launch with `std.process.Child.init` but we weren't handling quotes in the arguments. We had to add special tokenization to respect quotes.
This commit is contained in:
parent
8b5681a26f
commit
bf5ee081d6
2 changed files with 98 additions and 7 deletions
|
|
@ -100,7 +100,7 @@ pub fn load(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8) !void {
|
|||
logWarnMissingNodeArg(name, "command");
|
||||
continue;
|
||||
});
|
||||
var split_exec = try utils.tokenizeToOwnedSlices(exec_str, ' ');
|
||||
var split_exec = try utils.tokenizeShell(exec_str);
|
||||
if (split_exec.len > 0) {
|
||||
// Expand ~ in executable paths
|
||||
const expanded = helpers.expandTilde(split_exec[0]) catch |e| {
|
||||
|
|
@ -111,7 +111,7 @@ pub fn load(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8) !void {
|
|||
return e;
|
||||
}
|
||||
};
|
||||
// tokenizeToOwnedSlices dupes each token, so we have to
|
||||
// tokenizeShell dupes each token, so we have to
|
||||
// free the original value before replacing it.
|
||||
utils.gpa.free(split_exec[0]);
|
||||
split_exec[0] = expanded;
|
||||
|
|
|
|||
101
src/utils.zig
101
src/utils.zig
|
|
@ -150,12 +150,38 @@ pub fn parseModifiers(s: []const u8) !?river.SeatV1.Modifiers {
|
|||
return modifiers;
|
||||
}
|
||||
|
||||
pub fn tokenizeToOwnedSlices(input: []const u8, delimiter: u8) ![][]const u8 {
|
||||
/// Split a string into tokens, respecting single and double quotes.
|
||||
/// Quoted sections have their quotes stripped. Returns an error if
|
||||
/// a quote is opened but never closed.
|
||||
pub fn tokenizeShell(input: []const u8) ![][]const u8 {
|
||||
var list: std.ArrayList([]const u8) = .empty;
|
||||
var it = std.mem.tokenizeScalar(u8, input, delimiter);
|
||||
while (it.next()) |part| {
|
||||
const duped = try gpa.dupe(u8, part);
|
||||
try list.append(gpa, duped);
|
||||
errdefer {
|
||||
for (list.items) |item| gpa.free(item);
|
||||
list.deinit(gpa);
|
||||
}
|
||||
var i: usize = 0;
|
||||
while (i < input.len) {
|
||||
// Skip whitespace
|
||||
while (i < input.len and std.ascii.isWhitespace(input[i])) : (i += 1) {}
|
||||
if (i >= input.len) break;
|
||||
|
||||
var token: std.ArrayList(u8) = .empty;
|
||||
errdefer token.deinit(gpa);
|
||||
while (i < input.len and !std.ascii.isWhitespace(input[i])) {
|
||||
if (input[i] == '\'' or input[i] == '"') {
|
||||
const quote = input[i];
|
||||
i += 1;
|
||||
while (i < input.len and input[i] != quote) : (i += 1) {
|
||||
try token.append(gpa, input[i]);
|
||||
}
|
||||
if (i >= input.len) return error.UnmatchedQuote;
|
||||
i += 1; // skip closing quote
|
||||
} else {
|
||||
try token.append(gpa, input[i]);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
try list.append(gpa, try token.toOwnedSlice(gpa));
|
||||
}
|
||||
return list.toOwnedSlice(gpa);
|
||||
}
|
||||
|
|
@ -356,3 +382,68 @@ test "parseModifiers mod3 and mod5" {
|
|||
try testing.expect(mods.mod3);
|
||||
try testing.expect(mods.mod5);
|
||||
}
|
||||
|
||||
test "tokenizeShell simple" {
|
||||
const result = try tokenizeShell("foot -e htop");
|
||||
defer {
|
||||
for (result) |item| gpa.free(item);
|
||||
gpa.free(result);
|
||||
}
|
||||
try testing.expectEqual(3, result.len);
|
||||
try testing.expectEqualStrings("foot", result[0]);
|
||||
try testing.expectEqualStrings("-e", result[1]);
|
||||
try testing.expectEqualStrings("htop", result[2]);
|
||||
}
|
||||
|
||||
test "tokenizeShell double quotes" {
|
||||
const result = try tokenizeShell("notify-send \"Hello World\"");
|
||||
defer {
|
||||
for (result) |item| gpa.free(item);
|
||||
gpa.free(result);
|
||||
}
|
||||
try testing.expectEqual(2, result.len);
|
||||
try testing.expectEqualStrings("notify-send", result[0]);
|
||||
try testing.expectEqualStrings("Hello World", result[1]);
|
||||
}
|
||||
|
||||
test "tokenizeShell single quotes" {
|
||||
const result = try tokenizeShell("notify-send 'Hello World'");
|
||||
defer {
|
||||
for (result) |item| gpa.free(item);
|
||||
gpa.free(result);
|
||||
}
|
||||
try testing.expectEqual(2, result.len);
|
||||
try testing.expectEqualStrings("notify-send", result[0]);
|
||||
try testing.expectEqualStrings("Hello World", result[1]);
|
||||
}
|
||||
|
||||
test "tokenizeShell mixed quotes" {
|
||||
const result = try tokenizeShell("echo \"hello 'world'\"");
|
||||
defer {
|
||||
for (result) |item| gpa.free(item);
|
||||
gpa.free(result);
|
||||
}
|
||||
try testing.expectEqual(2, result.len);
|
||||
try testing.expectEqualStrings("echo", result[0]);
|
||||
try testing.expectEqualStrings("hello 'world'", result[1]);
|
||||
}
|
||||
|
||||
test "tokenizeShell empty string" {
|
||||
const result = try tokenizeShell("");
|
||||
defer gpa.free(result);
|
||||
try testing.expectEqual(0, result.len);
|
||||
}
|
||||
|
||||
test "tokenizeShell unmatched quote" {
|
||||
try testing.expectError(error.UnmatchedQuote, tokenizeShell("echo \"hello"));
|
||||
}
|
||||
|
||||
test "tokenizeShell quotes mid-token" {
|
||||
const result = try tokenizeShell("foo'bar baz'qux");
|
||||
defer {
|
||||
for (result) |item| gpa.free(item);
|
||||
gpa.free(result);
|
||||
}
|
||||
try testing.expectEqual(1, result.len);
|
||||
try testing.expectEqualStrings("foobar bazqux", result[0]);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue