zig/ci/run.zig
2024-07-18 08:26:30 -04:00

126 lines
5.6 KiB
Zig

//! ./runner command0 [arg0 arg1 ...] [--then command1 arg0 arg1 ...] [--path path0 path1 ...] [--cwd cwd] [--delete file0 file1 ...] [--rename old0 new0 [old1 new1 ...]] -- [replacement0 replacement1 ... replacement9]
//! Replaces $0 through $9 in all non-option arguments with replacement0 through replacement9,
//! which have native path separators replaced with posix path separators.
//! Replaces @0 through @9 in all non-option arguments with the contents of the file
//! replacement0 through replacement9.
//! Then, executes the command before any option arguments with extra paths after --path and at optional cwd after --cwd.
//! Then, executes the command after --then with extra paths after --path and at optional cwd after --cwd.
//! Then, optionally deletes files after --delete.
//! Then, optionally renomes pairs of files after --rename.
const std = @import("std");
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
const allocator = arena.allocator();
const args = try std.process.argsAlloc(allocator);
var first: [][]u8 = &.{};
var then: [][]u8 = &.{};
var cwd: [][]u8 = &.{};
var path: [][]u8 = &.{};
var delete: [][]u8 = &.{};
var rename: [][]u8 = &.{};
var replacements: [][]u8 = &.{};
var prev_separator_index: usize = 0;
var separator_index: usize = 1;
while (separator_index < args.len) : (separator_index += 1) {
if (!std.mem.startsWith(u8, args[separator_index], "--")) continue;
const separated_args = args[prev_separator_index + 1 .. separator_index];
if (prev_separator_index == 0)
first = separated_args
else if (std.mem.eql(u8, args[prev_separator_index], "--then"))
then = separated_args
else if (std.mem.eql(u8, args[prev_separator_index], "--cwd"))
cwd = separated_args
else if (std.mem.eql(u8, args[prev_separator_index], "--path"))
path = separated_args
else if (std.mem.eql(u8, args[prev_separator_index], "--delete"))
delete = separated_args
else if (std.mem.eql(u8, args[prev_separator_index], "--rename"))
rename = separated_args
else
std.debug.panic("unexpected '{s}'", .{args[prev_separator_index]});
if (args[separator_index].len == 2) {
replacements = args[separator_index + 1 ..];
break;
}
prev_separator_index = separator_index;
} else std.debug.panic("expected '--'", .{});
if (first.len == 0) std.debug.panic("expected arguments", .{});
if (cwd.len > 1) std.debug.panic("expected at most 1 cwd argument", .{});
if (rename.len % 2 != 0) std.debug.panic("expected even rename arguments", .{});
for (replacements) |replacement| std.mem.replaceScalar(
u8,
replacement,
std.fs.path.sep,
std.fs.path.sep_posix,
);
for ([_][][]u8{ first, then, cwd, path, delete, rename }) |patterns| for (patterns) |*pattern| {
var replaced = std.ArrayList(u8).init(allocator);
var pos: usize = 0;
while (std.mem.indexOfAnyPos(u8, pattern.*, pos, "$@")) |special_pos| {
try replaced.appendSlice(pattern.*[pos..special_pos]);
const special = pattern.*[special_pos..][0..2];
if (std.fmt.charToDigit(special[1], 10)) |special_index| switch (special[0]) {
'$' => try replaced.appendSlice(replacements[special_index]),
'@' => {
const special_file = try std.fs.cwd().openFile(replacements[special_index], .{});
defer special_file.close();
try special_file.reader().readAllArrayList(&replaced, 1 << 12);
replaced.shrinkRetainingCapacity(std.mem.trimRight(u8, replaced.items, &std.ascii.whitespace).len);
},
else => unreachable,
} else |_| switch (special[0]) {
'$' => try replaced.append(special[1]),
else => unreachable,
}
pos = special_pos + 2;
}
try replaced.appendSlice(pattern.*[pos..]);
pattern.* = replaced.items;
};
var path_buffer = std.ArrayList(u8).init(allocator);
for (path) |path_path| {
try path_buffer.appendSlice(path_path);
try path_buffer.append(std.fs.path.delimiter);
}
var env = try std.process.getEnvMap(allocator);
if (env.getPtr("PATH")) |env_path| {
try path_buffer.appendSlice(env_path.*);
env_path.* = try path_buffer.toOwnedSlice();
} else if (path_buffer.items.len > 0) try env.putMove(
try allocator.dupe(u8, "PATH"),
path_buffer.items[0 .. path_buffer.items.len - 1],
);
var first_child = std.process.Child.init(first, allocator);
first_child.env_map = &env;
if (cwd.len == 1) first_child.cwd = cwd[0];
switch (try first_child.spawnAndWait()) {
.Exited => |status| if (status != 0) std.process.exit(status),
else => |term| std.debug.panic("{}", .{term}),
}
if (then.len > 0) {
var then_child = std.process.Child.init(then, allocator);
then_child.env_map = &env;
if (cwd.len == 1) then_child.cwd = cwd[0];
switch (try then_child.spawnAndWait()) {
.Exited => |status| if (status != 0) std.process.exit(status),
else => |term| std.debug.panic("{}", .{term}),
}
}
for (delete) |delete_path| try std.fs.cwd().deleteFile(delete_path);
var rename_index: usize = 0;
while (rename_index < rename.len) : (rename_index += 2) try std.fs.cwd().rename(
rename[rename_index + 0],
rename[rename_index + 1],
);
}