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");
|
logWarnMissingNodeArg(name, "command");
|
||||||
continue;
|
continue;
|
||||||
});
|
});
|
||||||
var split_exec = try utils.tokenizeToOwnedSlices(exec_str, ' ');
|
var split_exec = try utils.tokenizeShell(exec_str);
|
||||||
if (split_exec.len > 0) {
|
if (split_exec.len > 0) {
|
||||||
// Expand ~ in executable paths
|
// Expand ~ in executable paths
|
||||||
const expanded = helpers.expandTilde(split_exec[0]) catch |e| {
|
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;
|
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.
|
// free the original value before replacing it.
|
||||||
utils.gpa.free(split_exec[0]);
|
utils.gpa.free(split_exec[0]);
|
||||||
split_exec[0] = expanded;
|
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;
|
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 list: std.ArrayList([]const u8) = .empty;
|
||||||
var it = std.mem.tokenizeScalar(u8, input, delimiter);
|
errdefer {
|
||||||
while (it.next()) |part| {
|
for (list.items) |item| gpa.free(item);
|
||||||
const duped = try gpa.dupe(u8, part);
|
list.deinit(gpa);
|
||||||
try list.append(gpa, duped);
|
}
|
||||||
|
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);
|
return list.toOwnedSlice(gpa);
|
||||||
}
|
}
|
||||||
|
|
@ -356,3 +382,68 @@ test "parseModifiers mod3 and mod5" {
|
||||||
try testing.expect(mods.mod3);
|
try testing.expect(mods.mod3);
|
||||||
try testing.expect(mods.mod5);
|
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