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};2526pub fn main(startup: std.process.Init) !void {27 const arena = startup.arena.allocator();28 const gpa = startup.gpa;29 const io = startup.io;3031 32 33 34 35 var cmd_arena = std.heap.ArenaAllocator.init(gpa);36 defer cmd_arena.deinit();37 const ca = cmd_arena.allocator();3839 40 41 42 43 44 var stdout_buf: [16384]u8 = undefined;45 var stdout_fw: Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buf);46 output.init(&stdout_fw.interface, io, startup.environ_map, true);47 defer output.flush();4849 const argv = try startup.minimal.args.toSlice(arena);50 const args = try parseArgs(arena, argv);5152 if (args.version) {53 output.version();54 return;55 }5657 58 if (args.command.len == 0 or std.mem.eql(u8, args.command, "help")) {59 output.help();60 return;61 }6263 64 if (std.mem.eql(u8, args.command, "setup") or std.mem.eql(u8, args.command, "update")) {65 const dummy_machine: types.Machine = .{ .name = "local", .local = true };66 const dummy_cfg: types.NinaConfig = .{};67 const pre_ctx = commands.Ctx{68 .gpa = ca,69 .io = io,70 .environ = startup.environ_map,71 .machine = dummy_machine,72 .cfg = dummy_cfg,73 .args = args.rest,74 .sub = args.sub,75 .dry = args.dry,76 .check = args.check,77 .no_apply = args.no_apply,78 .all = args.all,79 .last = args.last,80 .json = args.json,81 };82 if (std.mem.eql(u8, args.command, "setup")) {83 commands.setup(ca, io, startup.environ_map) catch |err| {84 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);85 const detail = errors.error_info.getDetail();86 const suggestion = errors.error_info.getSuggestion();87 output.printError(msg, detail, suggestion);88 output.flush();89 std.process.exit(1);90 };91 } else {92 dispatch(pre_ctx, args.command) catch |err| {93 reportError(err);94 output.flush();95 std.process.exit(1);96 };97 }98 return;99 }100101 102 const cfg = config.load(arena, io, startup.environ_map) catch |err| {103 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);104 const detail = errors.error_info.getDetail();105 const suggestion = errors.error_info.getSuggestion();106 output.printError(msg, detail, suggestion);107 output.flush();108 std.process.exit(1);109 };110111 output.setTeach(cfg.teach);112113 const machine = config.resolveMachine(&cfg, args.machine) catch |err| {114 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);115 const detail = errors.error_info.getDetail();116 const suggestion = errors.error_info.getSuggestion();117 output.printError(msg, detail, suggestion);118 output.flush();119 std.process.exit(1);120 };121122 const ctx = commands.Ctx{123 .gpa = ca,124 .io = io,125 .environ = startup.environ_map,126 .machine = machine,127 .cfg = cfg,128 .args = args.rest,129 .sub = args.sub,130 .dry = args.dry,131 .check = args.check,132 .no_apply = args.no_apply,133 .all = args.all,134 .last = args.last,135 .json = args.json,136 };137138 dispatch(ctx, args.command) catch |err| {139 reportError(err);140 output.flush();141 std.process.exit(1);142 };143}144145146147148fn reportError(err: anyerror) void {149 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);150 const detail = errors.error_info.getDetail();151 const suggestion = errors.error_info.getSuggestion();152 switch (err) {153 error.NetworkError, error.SshFailed, error.SearchFailed => output.printSystemError(msg, detail, suggestion),154 else => output.printError(msg, detail, suggestion),155 }156}157158fn dispatch(ctx: commands.Ctx, cmd: []const u8) !void {159 if (std.mem.eql(u8, cmd, "apply")) return commands.apply(ctx);160 if (std.mem.eql(u8, cmd, "check")) return commands.check(ctx);161 if (std.mem.eql(u8, cmd, "back")) return commands.back(ctx);162 if (std.mem.eql(u8, cmd, "go")) return commands.go(ctx);163 if (std.mem.eql(u8, cmd, "history")) return commands.history(ctx);164 if (std.mem.eql(u8, cmd, "gen")) return commands.genCmd(ctx);165 if (std.mem.eql(u8, cmd, "clean")) return commands.clean(ctx);166 if (std.mem.eql(u8, cmd, "status")) return commands.statusCmd(ctx);167 if (std.mem.eql(u8, cmd, "diff")) return commands.diff(ctx);168 if (std.mem.eql(u8, cmd, "doctor")) return commands.doctor(ctx);169 if (std.mem.eql(u8, cmd, "mood")) return commands.mood(ctx);170 if (std.mem.eql(u8, cmd, "update")) return commands.update(ctx);171 if (std.mem.eql(u8, cmd, "upgrade")) return commands.upgrade(ctx);172 if (std.mem.eql(u8, cmd, "log")) return commands.log(ctx);173 if (std.mem.eql(u8, cmd, "edit")) return commands.edit(ctx);174 if (std.mem.eql(u8, cmd, "fmt")) return commands.fmt(ctx);175 if (std.mem.eql(u8, cmd, "repl")) return commands.repl(ctx);176 if (std.mem.eql(u8, cmd, "develop")) return commands.develop(ctx);177 if (std.mem.eql(u8, cmd, "info")) return commands.info(ctx);178 if (std.mem.eql(u8, cmd, "boot")) return commands.boot(ctx);179 if (std.mem.eql(u8, cmd, "search")) return commands.searchCmd(ctx);180 if (std.mem.eql(u8, cmd, "option")) return commands.optionCmd(ctx);181 if (std.mem.eql(u8, cmd, "install")) return commands.install(ctx);182 if (std.mem.eql(u8, cmd, "remove")) return commands.remove(ctx);183 if (std.mem.eql(u8, cmd, "list")) return commands.list(ctx);184 if (std.mem.eql(u8, cmd, "try")) return commands.tryCmd(ctx);185 if (std.mem.eql(u8, cmd, "service")) return commands.service(ctx);186 if (std.mem.eql(u8, cmd, "flake")) return commands.flake(ctx);187 if (std.mem.eql(u8, cmd, "store")) return commands.store(ctx);188 if (std.mem.eql(u8, cmd, "channel")) return commands.channel(ctx);189 if (std.mem.eql(u8, cmd, "profile")) return commands.profile(ctx);190 if (std.mem.eql(u8, cmd, "pkg")) return commands.pkg(ctx);191 if (std.mem.eql(u8, cmd, "pin")) return commands.pin(ctx);192 if (std.mem.eql(u8, cmd, "unpin")) return commands.unpin(ctx);193 if (std.mem.eql(u8, cmd, "hash")) return commands.hash(ctx);194 if (std.mem.eql(u8, cmd, "fetch")) return commands.fetch(ctx);195 if (std.mem.eql(u8, cmd, "build")) return commands.build(ctx);196 if (std.mem.eql(u8, cmd, "run")) return commands.runCmd(ctx);197 if (std.mem.eql(u8, cmd, "goodbye")) return commands.goodbye(ctx);198 if (std.mem.eql(u8, cmd, "hello")) return commands.hello(ctx);199 if (std.mem.eql(u8, cmd, "help")) { output.help(); return; }200201 errors.error_info.setMessage("unknown command: {s}", .{cmd});202 errors.error_info.setSuggestion("nina help", .{});203 return error.Internal;204}205206fn parseArgs(arena: std.mem.Allocator, argv: []const [:0]const u8) !Args {207 var result = Args{};208 var rest: std.ArrayList([]const u8) = .empty;209 var i: usize = 1; 210211 212 if (i < argv.len and argv[i][0] != '-') {213 result.command = argv[i];214 i += 1;215 }216217 218 if (i < argv.len and argv[i][0] != '-') {219 const multi_word = isMultiWord(result.command);220 if (multi_word) {221 result.sub = argv[i];222 i += 1;223 }224 }225226 while (i < argv.len) : (i += 1) {227 const a = argv[i];228 if (std.mem.eql(u8, a, "--on") or std.mem.eql(u8, a, "-on")) {229 i += 1;230 if (i < argv.len) result.machine = argv[i];231 } else if (std.mem.eql(u8, a, "--dry")) {232 result.dry = true;233 } else if (std.mem.eql(u8, a, "--check")) {234 result.check = true;235 } else if (std.mem.eql(u8, a, "--version")) {236 result.version = true;237 } else if (std.mem.eql(u8, a, "--no-apply")) {238 result.no_apply = true;239 } else if (std.mem.eql(u8, a, "--all")) {240 result.all = true;241 } else if (std.mem.eql(u8, a, "--json")) {242 result.json = true;243 } else if (std.mem.eql(u8, a, "--last")) {244 i += 1;245 if (i < argv.len) result.last = std.fmt.parseInt(u32, argv[i], 10) catch 50;246 } else if (std.mem.eql(u8, a, "-f")) {247 248 try rest.append(arena, "-f");249 } else if (a.len > 0 and a[0] != '-') {250 try rest.append(arena, a);251 }252 253 }254255 result.rest = try rest.toOwnedSlice(arena);256 return result;257}258259fn isMultiWord(cmd: []const u8) bool {260 const multi = [_][]const u8{ "service", "flake", "store", "channel", "profile", "pkg", "gen", "develop", "edit" };261 for (multi) |m| {262 if (std.mem.eql(u8, cmd, m)) return true;263 }264 return false;265}266267test "parse args basic" {268 const gpa = std.testing.allocator;269 const argv = [_][:0]const u8{ "nina", "apply", "--dry" };270 const args = try parseArgs(gpa, &argv);271 defer gpa.free(args.rest);272 try std.testing.expectEqualStrings("apply", args.command);273 try std.testing.expect(args.dry);274}275276test "parse args with machine" {277 const gpa = std.testing.allocator;278 const argv = [_][:0]const u8{ "nina", "status", "--on", "build-server" };279 const args = try parseArgs(gpa, &argv);280 defer gpa.free(args.rest);281 try std.testing.expectEqualStrings("status", args.command);282 try std.testing.expectEqualStrings("build-server", args.machine.?);283}284285test "parse subcommand" {286 const gpa = std.testing.allocator;287 const argv = [_][:0]const u8{ "nina", "service", "list" };288 const args = try parseArgs(gpa, &argv);289 defer gpa.free(args.rest);290 try std.testing.expectEqualStrings("service", args.command);291 try std.testing.expectEqualStrings("list", args.sub);292}293294test "parse version flag" {295 const gpa = std.testing.allocator;296 const argv = [_][:0]const u8{ "nina", "--version" };297 const args = try parseArgs(gpa, &argv);298 defer gpa.free(args.rest);299 try std.testing.expect(args.version);300}