const std = @import("std"); /// Parse job template from command line arguments pub const JobTemplate = struct { job_names: std.ArrayList([]const u8), commit_id_override: ?[]const u8, priority: u8, snapshot_id: ?[]const u8, snapshot_sha256: ?[]const u8, args_override: ?[]const u8, note_override: ?[]const u8, cpu: u8, memory: u8, gpu: u8, gpu_memory: ?[]const u8, dry_run: bool, validate: bool, explain: bool, json: bool, force: bool, runner_args_start: ?usize, pub fn init(allocator: std.mem.Allocator) JobTemplate { return .{ .job_names = std.ArrayList([]const u8).init(allocator), .commit_id_override = null, .priority = 5, .snapshot_id = null, .snapshot_sha256 = null, .args_override = null, .note_override = null, .cpu = 2, .memory = 8, .gpu = 0, .gpu_memory = null, .dry_run = false, .validate = false, .explain = false, .json = false, .force = false, .runner_args_start = null, }; } pub fn deinit(self: *JobTemplate, allocator: std.mem.Allocator) void { self.job_names.deinit(allocator); } }; /// Parse command arguments into a job template pub fn parseArgs(allocator: std.mem.Allocator, args: []const []const u8) !JobTemplate { var template = JobTemplate.init(allocator); errdefer template.deinit(allocator); var i: usize = 0; while (i < args.len) : (i += 1) { const arg = args[i]; if (std.mem.eql(u8, arg, "--")) { template.runner_args_start = i + 1; break; } else if (std.mem.eql(u8, arg, "--commit-id")) { if (i + 1 < args.len) { template.commit_id_override = args[i + 1]; i += 1; } } else if (std.mem.eql(u8, arg, "--priority")) { if (i + 1 < args.len) { template.priority = std.fmt.parseInt(u8, args[i + 1], 10) catch 5; i += 1; } } else if (std.mem.eql(u8, arg, "--snapshot")) { if (i + 1 < args.len) { template.snapshot_id = args[i + 1]; i += 1; } } else if (std.mem.eql(u8, arg, "--snapshot-sha256")) { if (i + 1 < args.len) { template.snapshot_sha256 = args[i + 1]; i += 1; } } else if (std.mem.eql(u8, arg, "--args")) { if (i + 1 < args.len) { template.args_override = args[i + 1]; i += 1; } } else if (std.mem.eql(u8, arg, "--note")) { if (i + 1 < args.len) { template.note_override = args[i + 1]; i += 1; } } else if (std.mem.eql(u8, arg, "--cpu")) { if (i + 1 < args.len) { template.cpu = std.fmt.parseInt(u8, args[i + 1], 10) catch 2; i += 1; } } else if (std.mem.eql(u8, arg, "--memory")) { if (i + 1 < args.len) { template.memory = std.fmt.parseInt(u8, args[i + 1], 10) catch 8; i += 1; } } else if (std.mem.eql(u8, arg, "--gpu")) { if (i + 1 < args.len) { template.gpu = std.fmt.parseInt(u8, args[i + 1], 10) catch 0; i += 1; } } else if (std.mem.eql(u8, arg, "--gpu-memory")) { if (i + 1 < args.len) { template.gpu_memory = args[i + 1]; i += 1; } } else if (std.mem.eql(u8, arg, "--dry-run")) { template.dry_run = true; } else if (std.mem.eql(u8, arg, "--validate")) { template.validate = true; } else if (std.mem.eql(u8, arg, "--explain")) { template.explain = true; } else if (std.mem.eql(u8, arg, "--json")) { template.json = true; } else if (std.mem.eql(u8, arg, "--force")) { template.force = true; } else if (!std.mem.startsWith(u8, arg, "-")) { // Positional argument - job name try template.job_names.append(arg); } } return template; } /// Get runner args from the parsed template pub fn getRunnerArgs(self: JobTemplate, all_args: []const []const u8) []const []const u8 { if (self.runner_args_start) |start| { if (start < all_args.len) { return all_args[start..]; } } return &[_][]const u8{}; } /// Resolve commit ID from prefix or full hash pub fn resolveCommitId(allocator: std.mem.Allocator, base_path: []const u8, input: []const u8) ![]u8 { if (input.len < 7 or input.len > 40) return error.InvalidArgs; for (input) |c| { if (!std.ascii.isHex(c)) return error.InvalidArgs; } if (input.len == 40) { return allocator.dupe(u8, input); } var dir = if (std.fs.path.isAbsolute(base_path)) try std.fs.openDirAbsolute(base_path, .{ .iterate = true }) else try std.fs.cwd().openDir(base_path, .{ .iterate = true }); defer dir.close(); var it = dir.iterate(); var found: ?[]u8 = null; errdefer if (found) |s| allocator.free(s); while (try it.next()) |entry| { if (entry.kind != .directory) continue; const name = entry.name; if (name.len != 40) continue; if (!std.mem.startsWith(u8, name, input)) continue; for (name) |c| { if (!std.ascii.isHex(c)) break; } else { if (found != null) return error.InvalidArgs; found = try allocator.dupe(u8, name); } } if (found) |s| return s; return error.FileNotFound; }