123const std = @import("std");4const Io = std.Io;56const types = @import("types.zig");7const errors = @import("errors.zig");8const config = @import("config.zig");9const commands = @import("commands.zig");10const output = @import("output.zig");1112const Args = struct {13 command: []const u8 = "",14 sub: []const u8 = "",15 rest: []const []const u8 = &.{},16 machine: ?[]const u8 = null,17 version: bool = false,18 dry: bool = false,19 check: bool = false,20 no_apply: bool = false,21 all: bool = false,22 json: bool = false,23 last: u32 = 50,24 user: bool = false,25};2627pub fn main(startup: std.process.Init) !void {28 const arena = startup.arena.allocator();29 const gpa = startup.gpa;30 const io = startup.io;3132 33 34 35 36 var cmd_arena = std.heap.ArenaAllocator.init(gpa);37 defer cmd_arena.deinit();38 const ca = cmd_arena.allocator();3940 41 42 43 44 45 var stdout_buf: [16384]u8 = undefined;46 var stdout_fw: Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buf);47 output.init(&stdout_fw.interface, io, startup.environ_map, true);48 defer output.flush();4950 const argv = try startup.minimal.args.toSlice(arena);51 const args = try parseArgs(arena, argv);5253 if (args.version) {54 output.version();55 return;56 }5758 59 if (args.command.len == 0 or std.mem.eql(u8, args.command, "help")) {60 output.help();61 return;62 }6364 65 if (std.mem.eql(u8, args.command, "setup") or std.mem.eql(u8, args.command, "update")) {66 const dummy_machine: types.Machine = .{ .name = "local", .local = true };67 const dummy_cfg: types.NinaConfig = .{};68 const pre_ctx = commands.Ctx{69 .gpa = ca,70 .io = io,71 .environ = startup.environ_map,72 .machine = dummy_machine,73 .cfg = dummy_cfg,74 .args = args.rest,75 .sub = args.sub,76 .dry = args.dry,77 .check = args.check,78 .no_apply = args.no_apply,79 .all = args.all,80 .last = args.last,81 .json = args.json,82 .user = args.user,83 };84 if (std.mem.eql(u8, args.command, "setup")) {85 commands.setup(ca, io, startup.environ_map) catch |err| {86 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);87 const detail = errors.error_info.getDetail();88 const suggestion = errors.error_info.getSuggestion();89 output.printError(msg, detail, suggestion);90 output.flush();91 std.process.exit(1);92 };93 } else {94 dispatch(pre_ctx, args.command) catch |err| {95 reportError(err, ca);96 output.flush();97 std.process.exit(1);98 };99 }100 return;101 }102103 104 105 const cfg = cfg_blk: {106 if (config.load(arena, io, startup.environ_map)) |c| {107 break :cfg_blk c;108 } else |err| {109 if (err == error.ConfigNotFound and (std.Io.File.stdout().isTty(io) catch false)) {110 commands.firstRunWizard(ca, io, startup.environ_map) catch |wizard_err| {111 const msg = errors.error_info.getMessage() orelse errors.staticMessage(wizard_err);112 const detail = errors.error_info.getDetail();113 const suggestion = errors.error_info.getSuggestion();114 output.printError(msg, detail, suggestion);115 output.flush();116 std.process.exit(1);117 };118 errors.error_info.reset();119 break :cfg_blk config.load(arena, io, startup.environ_map) catch |err2| {120 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err2);121 const detail = errors.error_info.getDetail();122 const suggestion = errors.error_info.getSuggestion();123 output.printError(msg, detail, suggestion);124 output.flush();125 std.process.exit(1);126 };127 }128 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);129 const detail = errors.error_info.getDetail();130 const suggestion = errors.error_info.getSuggestion();131 output.printError(msg, detail, suggestion);132 output.flush();133 std.process.exit(1);134 }135 };136137 output.setTeach(cfg.teach);138139 const machine = config.resolveMachine(&cfg, args.machine) catch |err| {140 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);141 const detail = errors.error_info.getDetail();142 const suggestion = errors.error_info.getSuggestion();143 output.printError(msg, detail, suggestion);144 output.flush();145 std.process.exit(1);146 };147148 const ctx = commands.Ctx{149 .gpa = ca,150 .io = io,151 .environ = startup.environ_map,152 .machine = machine,153 .cfg = cfg,154 .args = args.rest,155 .sub = args.sub,156 .dry = args.dry,157 .check = args.check,158 .no_apply = args.no_apply,159 .all = args.all,160 .last = args.last,161 .json = args.json,162 .user = args.user,163 };164165 dispatch(ctx, args.command) catch |err| {166 reportError(err, ca);167 output.flush();168 std.process.exit(1);169 };170}171172173174175fn reportError(err: anyerror, allocator: std.mem.Allocator) void {176 switch (err) {177 178 error.HookAborted => {},179 error.BuildFailed => {180 const stderr = errors.getBuildStderr();181 const translated = errors.translateError(allocator, stderr);182 output.buildError(translated);183 },184 error.NetworkError, error.SshFailed, error.SearchFailed => {185 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);186 const detail = errors.error_info.getDetail();187 const suggestion = errors.error_info.getSuggestion();188 output.printSystemError(msg, detail, suggestion);189 },190 else => {191 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);192 const detail = errors.error_info.getDetail();193 const suggestion = errors.error_info.getSuggestion();194 output.printError(msg, detail, suggestion);195 },196 }197}198199fn dispatch(ctx: commands.Ctx, cmd: []const u8) !void {200 if (std.mem.eql(u8, cmd, "apply")) return commands.apply(ctx);201 if (std.mem.eql(u8, cmd, "check")) return commands.check(ctx);202 if (std.mem.eql(u8, cmd, "back")) return commands.back(ctx);203 if (std.mem.eql(u8, cmd, "go")) return commands.go(ctx);204 if (std.mem.eql(u8, cmd, "history")) return commands.history(ctx);205 if (std.mem.eql(u8, cmd, "gen")) return commands.genCmd(ctx);206 if (std.mem.eql(u8, cmd, "clean")) return commands.clean(ctx);207 if (std.mem.eql(u8, cmd, "status")) return commands.statusCmd(ctx);208 if (std.mem.eql(u8, cmd, "diff")) return commands.diff(ctx);209 if (std.mem.eql(u8, cmd, "doctor")) return commands.doctor(ctx);210 if (std.mem.eql(u8, cmd, "mood")) return commands.mood(ctx);211 if (std.mem.eql(u8, cmd, "update")) return commands.update(ctx);212 if (std.mem.eql(u8, cmd, "upgrade")) return commands.upgrade(ctx);213 if (std.mem.eql(u8, cmd, "log")) return commands.log(ctx);214 if (std.mem.eql(u8, cmd, "edit")) return commands.edit(ctx);215 if (std.mem.eql(u8, cmd, "fmt")) return commands.fmt(ctx);216 if (std.mem.eql(u8, cmd, "repl")) return commands.repl(ctx);217 if (std.mem.eql(u8, cmd, "develop")) return commands.develop(ctx);218 if (std.mem.eql(u8, cmd, "info")) return commands.info(ctx);219 if (std.mem.eql(u8, cmd, "boot")) return commands.boot(ctx);220 if (std.mem.eql(u8, cmd, "search")) return commands.searchCmd(ctx);221 if (std.mem.eql(u8, cmd, "option")) return commands.optionCmd(ctx);222 if (std.mem.eql(u8, cmd, "install")) return commands.install(ctx);223 if (std.mem.eql(u8, cmd, "remove")) return commands.remove(ctx);224 if (std.mem.eql(u8, cmd, "list")) return commands.list(ctx);225 if (std.mem.eql(u8, cmd, "try")) return commands.tryCmd(ctx);226 if (std.mem.eql(u8, cmd, "service")) return commands.service(ctx);227 if (std.mem.eql(u8, cmd, "flake")) return commands.flake(ctx);228 if (std.mem.eql(u8, cmd, "store")) return commands.store(ctx);229 if (std.mem.eql(u8, cmd, "channel")) return commands.channel(ctx);230 if (std.mem.eql(u8, cmd, "profile")) return commands.profile(ctx);231 if (std.mem.eql(u8, cmd, "pkg")) return commands.pkg(ctx);232 if (std.mem.eql(u8, cmd, "why")) return commands.why(ctx);233 if (std.mem.eql(u8, cmd, "pin")) return commands.pin(ctx);234 if (std.mem.eql(u8, cmd, "unpin")) return commands.unpin(ctx);235 if (std.mem.eql(u8, cmd, "hash")) return commands.hash(ctx);236 if (std.mem.eql(u8, cmd, "fetch")) return commands.fetch(ctx);237 if (std.mem.eql(u8, cmd, "build")) return commands.build(ctx);238 if (std.mem.eql(u8, cmd, "run")) return commands.runCmd(ctx);239 if (std.mem.eql(u8, cmd, "home")) return commands.home(ctx);240 if (std.mem.eql(u8, cmd, "goodbye")) return commands.goodbye(ctx);241 if (std.mem.eql(u8, cmd, "hello")) return commands.hello(ctx);242 if (std.mem.eql(u8, cmd, "help")) { output.help(); return; }243244 errors.error_info.setMessage("unknown command: {s}", .{cmd});245 errors.error_info.setSuggestion("nina help", .{});246 return error.Internal;247}248249fn parseArgs(arena: std.mem.Allocator, argv: []const [:0]const u8) !Args {250 var result = Args{};251 var rest: std.ArrayList([]const u8) = .empty;252 var i: usize = 1; 253254 255 if (i < argv.len and argv[i][0] != '-') {256 result.command = argv[i];257 i += 1;258 }259260 261 if (i < argv.len and argv[i][0] != '-') {262 const multi_word = isMultiWord(result.command);263 if (multi_word) {264 result.sub = argv[i];265 i += 1;266 }267 }268269 while (i < argv.len) : (i += 1) {270 const a = argv[i];271 if (std.mem.eql(u8, a, "--on") or std.mem.eql(u8, a, "-on")) {272 i += 1;273 if (i < argv.len) result.machine = argv[i];274 } else if (std.mem.eql(u8, a, "--dry")) {275 result.dry = true;276 } else if (std.mem.eql(u8, a, "--check")) {277 result.check = true;278 } else if (std.mem.eql(u8, a, "--version")) {279 result.version = true;280 } else if (std.mem.eql(u8, a, "--no-apply")) {281 result.no_apply = true;282 } else if (std.mem.eql(u8, a, "--all")) {283 result.all = true;284 } else if (std.mem.eql(u8, a, "--user")) {285 result.user = true;286 } else if (std.mem.eql(u8, a, "--json")) {287 result.json = true;288 } else if (std.mem.eql(u8, a, "--last")) {289 i += 1;290 if (i < argv.len) result.last = std.fmt.parseInt(u32, argv[i], 10) catch 50;291 } else if (std.mem.eql(u8, a, "-f")) {292 293 try rest.append(arena, "-f");294 } else if (std.mem.eql(u8, a, "--switch")) {295 296 try rest.append(arena, "--switch");297 } else if (a.len > 0 and a[0] != '-') {298 try rest.append(arena, a);299 }300 301 }302303 result.rest = try rest.toOwnedSlice(arena);304 return result;305}306307fn isMultiWord(cmd: []const u8) bool {308 const multi = [_][]const u8{ "service", "flake", "store", "channel", "profile", "pkg", "gen", "develop", "edit", "home" };309 for (multi) |m| {310 if (std.mem.eql(u8, cmd, m)) return true;311 }312 return false;313}314315test "parse args basic" {316 const gpa = std.testing.allocator;317 const argv = [_][:0]const u8{ "nina", "apply", "--dry" };318 const args = try parseArgs(gpa, &argv);319 defer gpa.free(args.rest);320 try std.testing.expectEqualStrings("apply", args.command);321 try std.testing.expect(args.dry);322}323324test "parse args with machine" {325 const gpa = std.testing.allocator;326 const argv = [_][:0]const u8{ "nina", "status", "--on", "build-server" };327 const args = try parseArgs(gpa, &argv);328 defer gpa.free(args.rest);329 try std.testing.expectEqualStrings("status", args.command);330 try std.testing.expectEqualStrings("build-server", args.machine.?);331}332333test "parse subcommand" {334 const gpa = std.testing.allocator;335 const argv = [_][:0]const u8{ "nina", "service", "list" };336 const args = try parseArgs(gpa, &argv);337 defer gpa.free(args.rest);338 try std.testing.expectEqualStrings("service", args.command);339 try std.testing.expectEqualStrings("list", args.sub);340}341342test "parse version flag" {343 const gpa = std.testing.allocator;344 const argv = [_][:0]const u8{ "nina", "--version" };345 const args = try parseArgs(gpa, &argv);346 defer gpa.free(args.rest);347 try std.testing.expect(args.version);348}