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");11const man = @import("man.zig");1213const Args = struct {14 command: []const u8 = "",15 sub: []const u8 = "",16 rest: []const []const u8 = &.{},17 machine: ?[]const u8 = null,18 version: bool = false,19 dry: bool = false,20 check: bool = false,21 no_apply: bool = false,22 all: bool = false,23 json: bool = false,24 last: u32 = 50,25 user: bool = false,26};2728pub fn main(startup: std.process.Init) !void {29 const arena = startup.arena.allocator();30 const gpa = startup.gpa;31 const io = startup.io;3233 34 35 36 37 var cmd_arena = std.heap.ArenaAllocator.init(gpa);38 defer cmd_arena.deinit();39 const ca = cmd_arena.allocator();4041 42 43 44 45 46 var stdout_buf: [16384]u8 = undefined;47 var stdout_fw: Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buf);48 output.init(&stdout_fw.interface, io, startup.environ_map, true);49 defer output.flush();5051 const argv = try startup.minimal.args.toSlice(arena);52 const args = try parseArgs(arena, argv);5354 if (args.version) {55 output.version();56 return;57 }5859 60 if (args.command.len == 0 or std.mem.eql(u8, args.command, "help")) {61 output.help();62 return;63 }6465 66 if (std.mem.eql(u8, args.command, "man")) {67 const topic: []const u8 = if (args.rest.len > 0) args.rest[0] else "";68 man.run(io, topic) catch {};69 return;70 }7172 73 if (std.mem.eql(u8, args.command, "setup") or std.mem.eql(u8, args.command, "update")) {74 const dummy_machine: types.Machine = .{ .name = "local", .local = true };75 const dummy_cfg: types.NinaConfig = .{};76 const pre_ctx = commands.Ctx{77 .gpa = ca,78 .io = io,79 .environ = startup.environ_map,80 .machine = dummy_machine,81 .cfg = dummy_cfg,82 .args = args.rest,83 .sub = args.sub,84 .dry = args.dry,85 .check = args.check,86 .no_apply = args.no_apply,87 .all = args.all,88 .last = args.last,89 .json = args.json,90 .user = args.user,91 };92 if (std.mem.eql(u8, args.command, "setup")) {93 commands.setup(ca, io, startup.environ_map) catch |err| {94 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);95 const detail = errors.error_info.getDetail();96 const suggestion = errors.error_info.getSuggestion();97 output.printError(msg, detail, suggestion);98 output.flush();99 std.process.exit(1);100 };101 } else {102 dispatch(pre_ctx, args.command) catch |err| {103 reportError(err, ca);104 output.flush();105 std.process.exit(1);106 };107 }108 return;109 }110111 112 113 const cfg = cfg_blk: {114 if (config.load(arena, io, startup.environ_map)) |c| {115 break :cfg_blk c;116 } else |err| {117 if (err == error.ConfigNotFound and (std.Io.File.stdout().isTty(io) catch false)) {118 commands.firstRunWizard(ca, io, startup.environ_map) catch |wizard_err| {119 const msg = errors.error_info.getMessage() orelse errors.staticMessage(wizard_err);120 const detail = errors.error_info.getDetail();121 const suggestion = errors.error_info.getSuggestion();122 output.printError(msg, detail, suggestion);123 output.flush();124 std.process.exit(1);125 };126 errors.error_info.reset();127 break :cfg_blk config.load(arena, io, startup.environ_map) catch |err2| {128 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err2);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 }136 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);137 const detail = errors.error_info.getDetail();138 const suggestion = errors.error_info.getSuggestion();139 output.printError(msg, detail, suggestion);140 output.flush();141 std.process.exit(1);142 }143 };144145 output.setTeach(cfg.teach);146147 const machine = config.resolveMachine(&cfg, args.machine) catch |err| {148 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);149 const detail = errors.error_info.getDetail();150 const suggestion = errors.error_info.getSuggestion();151 output.printError(msg, detail, suggestion);152 output.flush();153 std.process.exit(1);154 };155156 const ctx = commands.Ctx{157 .gpa = ca,158 .io = io,159 .environ = startup.environ_map,160 .machine = machine,161 .cfg = cfg,162 .args = args.rest,163 .sub = args.sub,164 .dry = args.dry,165 .check = args.check,166 .no_apply = args.no_apply,167 .all = args.all,168 .last = args.last,169 .json = args.json,170 .user = args.user,171 };172173 dispatch(ctx, args.command) catch |err| {174 reportError(err, ca);175 output.flush();176 std.process.exit(1);177 };178}179180181182183fn reportError(err: anyerror, allocator: std.mem.Allocator) void {184 switch (err) {185 186 error.HookAborted => {},187 error.BuildFailed => {188 const stderr = errors.getBuildStderr();189 const translated = errors.translateError(allocator, stderr);190 output.buildError(translated);191 },192 error.NetworkError, error.SshFailed, error.SearchFailed => {193 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);194 const detail = errors.error_info.getDetail();195 const suggestion = errors.error_info.getSuggestion();196 output.printSystemError(msg, detail, suggestion);197 },198 else => {199 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);200 const detail = errors.error_info.getDetail();201 const suggestion = errors.error_info.getSuggestion();202 output.printError(msg, detail, suggestion);203 },204 }205}206207fn dispatch(ctx: commands.Ctx, cmd: []const u8) !void {208 if (std.mem.eql(u8, cmd, "apply")) return commands.apply(ctx);209 if (std.mem.eql(u8, cmd, "check")) return commands.check(ctx);210 if (std.mem.eql(u8, cmd, "back")) return commands.back(ctx);211 if (std.mem.eql(u8, cmd, "go")) return commands.go(ctx);212 if (std.mem.eql(u8, cmd, "history")) return commands.history(ctx);213 if (std.mem.eql(u8, cmd, "gen")) return commands.genCmd(ctx);214 if (std.mem.eql(u8, cmd, "clean")) return commands.clean(ctx);215 if (std.mem.eql(u8, cmd, "status")) return commands.statusCmd(ctx);216 if (std.mem.eql(u8, cmd, "diff")) return commands.diff(ctx);217 if (std.mem.eql(u8, cmd, "doctor")) return commands.doctor(ctx);218 if (std.mem.eql(u8, cmd, "mood")) return commands.mood(ctx);219 if (std.mem.eql(u8, cmd, "update")) return commands.update(ctx);220 if (std.mem.eql(u8, cmd, "upgrade")) return commands.upgrade(ctx);221 if (std.mem.eql(u8, cmd, "log")) return commands.log(ctx);222 if (std.mem.eql(u8, cmd, "edit")) return commands.edit(ctx);223 if (std.mem.eql(u8, cmd, "fmt")) return commands.fmt(ctx);224 if (std.mem.eql(u8, cmd, "repl")) return commands.repl(ctx);225 if (std.mem.eql(u8, cmd, "develop")) return commands.develop(ctx);226 if (std.mem.eql(u8, cmd, "info")) return commands.info(ctx);227 if (std.mem.eql(u8, cmd, "boot")) return commands.boot(ctx);228 if (std.mem.eql(u8, cmd, "search")) return commands.searchCmd(ctx);229 if (std.mem.eql(u8, cmd, "option")) return commands.optionCmd(ctx);230 if (std.mem.eql(u8, cmd, "install")) return commands.install(ctx);231 if (std.mem.eql(u8, cmd, "remove")) return commands.remove(ctx);232 if (std.mem.eql(u8, cmd, "list")) return commands.list(ctx);233 if (std.mem.eql(u8, cmd, "try")) return commands.tryCmd(ctx);234 if (std.mem.eql(u8, cmd, "service")) return commands.service(ctx);235 if (std.mem.eql(u8, cmd, "flake")) return commands.flake(ctx);236 if (std.mem.eql(u8, cmd, "store")) return commands.store(ctx);237 if (std.mem.eql(u8, cmd, "channel")) return commands.channel(ctx);238 if (std.mem.eql(u8, cmd, "profile")) return commands.profile(ctx);239 if (std.mem.eql(u8, cmd, "pkg")) return commands.pkg(ctx);240 if (std.mem.eql(u8, cmd, "why")) return commands.why(ctx);241 if (std.mem.eql(u8, cmd, "pin")) return commands.pin(ctx);242 if (std.mem.eql(u8, cmd, "unpin")) return commands.unpin(ctx);243 if (std.mem.eql(u8, cmd, "hash")) return commands.hash(ctx);244 if (std.mem.eql(u8, cmd, "fetch")) return commands.fetch(ctx);245 if (std.mem.eql(u8, cmd, "build")) return commands.build(ctx);246 if (std.mem.eql(u8, cmd, "run")) return commands.runCmd(ctx);247 if (std.mem.eql(u8, cmd, "home")) return commands.home(ctx);248 if (std.mem.eql(u8, cmd, "goodbye")) return commands.goodbye(ctx);249 if (std.mem.eql(u8, cmd, "hello")) return commands.hello(ctx);250 if (std.mem.eql(u8, cmd, "help")) { output.help(); return; }251252 errors.error_info.setMessage("unknown command: {s}", .{cmd});253 errors.error_info.setSuggestion("nina help", .{});254 return error.Internal;255}256257fn parseArgs(arena: std.mem.Allocator, argv: []const [:0]const u8) !Args {258 var result = Args{};259 var rest: std.ArrayList([]const u8) = .empty;260 var i: usize = 1; 261262 263 if (i < argv.len and argv[i][0] != '-') {264 result.command = argv[i];265 i += 1;266 }267268 269 if (i < argv.len and argv[i][0] != '-') {270 const multi_word = isMultiWord(result.command);271 if (multi_word) {272 result.sub = argv[i];273 i += 1;274 }275 }276277 while (i < argv.len) : (i += 1) {278 const a = argv[i];279 if (std.mem.eql(u8, a, "--on") or std.mem.eql(u8, a, "-on")) {280 i += 1;281 if (i < argv.len) result.machine = argv[i];282 } else if (std.mem.eql(u8, a, "--dry")) {283 result.dry = true;284 } else if (std.mem.eql(u8, a, "--check")) {285 result.check = true;286 } else if (std.mem.eql(u8, a, "--version")) {287 result.version = true;288 } else if (std.mem.eql(u8, a, "--no-apply")) {289 result.no_apply = true;290 } else if (std.mem.eql(u8, a, "--all")) {291 result.all = true;292 } else if (std.mem.eql(u8, a, "--user")) {293 result.user = true;294 } else if (std.mem.eql(u8, a, "--json")) {295 result.json = true;296 } else if (std.mem.eql(u8, a, "--last")) {297 i += 1;298 if (i < argv.len) result.last = std.fmt.parseInt(u32, argv[i], 10) catch 50;299 } else if (std.mem.eql(u8, a, "-f")) {300 301 try rest.append(arena, "-f");302 } else if (std.mem.eql(u8, a, "--switch")) {303 304 try rest.append(arena, "--switch");305 } else if (std.mem.eql(u8, a, "--dir")) {306 307 try rest.append(arena, "--dir");308 } else if (a.len > 0 and a[0] != '-') {309 try rest.append(arena, a);310 }311 312 }313314 result.rest = try rest.toOwnedSlice(arena);315 return result;316}317318fn isMultiWord(cmd: []const u8) bool {319 const multi = [_][]const u8{ "service", "flake", "store", "channel", "profile", "pkg", "gen", "develop", "edit", "home" };320 for (multi) |m| {321 if (std.mem.eql(u8, cmd, m)) return true;322 }323 return false;324}325326test "parse args basic" {327 const gpa = std.testing.allocator;328 const argv = [_][:0]const u8{ "nina", "apply", "--dry" };329 const args = try parseArgs(gpa, &argv);330 defer gpa.free(args.rest);331 try std.testing.expectEqualStrings("apply", args.command);332 try std.testing.expect(args.dry);333}334335test "parse args with machine" {336 const gpa = std.testing.allocator;337 const argv = [_][:0]const u8{ "nina", "status", "--on", "build-server" };338 const args = try parseArgs(gpa, &argv);339 defer gpa.free(args.rest);340 try std.testing.expectEqualStrings("status", args.command);341 try std.testing.expectEqualStrings("build-server", args.machine.?);342}343344test "parse subcommand" {345 const gpa = std.testing.allocator;346 const argv = [_][:0]const u8{ "nina", "service", "list" };347 const args = try parseArgs(gpa, &argv);348 defer gpa.free(args.rest);349 try std.testing.expectEqualStrings("service", args.command);350 try std.testing.expectEqualStrings("list", args.sub);351}352353test "parse version flag" {354 const gpa = std.testing.allocator;355 const argv = [_][:0]const u8{ "nina", "--version" };356 const args = try parseArgs(gpa, &argv);357 defer gpa.free(args.rest);358 try std.testing.expect(args.version);359}