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 36 var stdout_buf: [16384]u8 = undefined;37 var stdout_fw: Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buf);38 output.init(&stdout_fw.interface, io, startup.environ_map, true);39 defer output.flush();4041 const argv = try startup.minimal.args.toSlice(arena);42 const args = try parseArgs(arena, argv);4344 if (args.version) {45 output.version();46 return;47 }4849 50 if (args.command.len == 0 or std.mem.eql(u8, args.command, "help")) {51 output.help();52 return;53 }5455 56 if (std.mem.eql(u8, args.command, "setup") or std.mem.eql(u8, args.command, "update")) {57 const dummy_machine: types.Machine = .{ .name = "local", .local = true };58 const dummy_cfg: types.NinaConfig = .{};59 const pre_ctx = commands.Ctx{60 .gpa = gpa,61 .io = io,62 .environ = startup.environ_map,63 .machine = dummy_machine,64 .cfg = dummy_cfg,65 .args = args.rest,66 .sub = args.sub,67 .dry = args.dry,68 .check = args.check,69 .no_apply = args.no_apply,70 .all = args.all,71 .last = args.last,72 .json = args.json,73 };74 if (std.mem.eql(u8, args.command, "setup")) {75 commands.setup(gpa, io, startup.environ_map) catch |err| {76 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);77 const detail = errors.error_info.getDetail();78 const suggestion = errors.error_info.getSuggestion();79 output.printError(msg, detail, suggestion);80 output.flush();81 std.process.exit(1);82 };83 } else {84 dispatch(pre_ctx, args.command) catch |err| {85 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);86 const detail = errors.error_info.getDetail();87 const suggestion = errors.error_info.getSuggestion();88 output.printError(msg, detail, suggestion);89 output.flush();90 std.process.exit(1);91 };92 }93 return;94 }9596 97 const cfg = config.load(arena, io, startup.environ_map) catch |err| {98 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);99 const detail = errors.error_info.getDetail();100 const suggestion = errors.error_info.getSuggestion();101 output.printError(msg, detail, suggestion);102 output.flush();103 std.process.exit(1);104 };105106 output.setTeach(cfg.teach);107108 const machine = config.resolveMachine(&cfg, args.machine) catch |err| {109 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);110 const detail = errors.error_info.getDetail();111 const suggestion = errors.error_info.getSuggestion();112 output.printError(msg, detail, suggestion);113 output.flush();114 std.process.exit(1);115 };116117 const ctx = commands.Ctx{118 .gpa = gpa,119 .io = io,120 .environ = startup.environ_map,121 .machine = machine,122 .cfg = cfg,123 .args = args.rest,124 .sub = args.sub,125 .dry = args.dry,126 .check = args.check,127 .no_apply = args.no_apply,128 .all = args.all,129 .last = args.last,130 .json = args.json,131 };132133 dispatch(ctx, args.command) catch |err| {134 const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);135 const detail = errors.error_info.getDetail();136 const suggestion = errors.error_info.getSuggestion();137 output.printError(msg, detail, suggestion);138 output.flush();139 std.process.exit(1);140 };141}142143fn dispatch(ctx: commands.Ctx, cmd: []const u8) !void {144 if (std.mem.eql(u8, cmd, "apply")) return commands.apply(ctx);145 if (std.mem.eql(u8, cmd, "check")) return commands.check(ctx);146 if (std.mem.eql(u8, cmd, "back")) return commands.back(ctx);147 if (std.mem.eql(u8, cmd, "go")) return commands.go(ctx);148 if (std.mem.eql(u8, cmd, "history")) return commands.history(ctx);149 if (std.mem.eql(u8, cmd, "gen")) return commands.genCmd(ctx);150 if (std.mem.eql(u8, cmd, "clean")) return commands.clean(ctx);151 if (std.mem.eql(u8, cmd, "status")) return commands.statusCmd(ctx);152 if (std.mem.eql(u8, cmd, "diff")) return commands.diff(ctx);153 if (std.mem.eql(u8, cmd, "doctor")) return commands.doctor(ctx);154 if (std.mem.eql(u8, cmd, "update")) return commands.update(ctx);155 if (std.mem.eql(u8, cmd, "upgrade")) return commands.upgrade(ctx);156 if (std.mem.eql(u8, cmd, "log")) return commands.log(ctx);157 if (std.mem.eql(u8, cmd, "edit")) return commands.edit(ctx);158 if (std.mem.eql(u8, cmd, "fmt")) return commands.fmt(ctx);159 if (std.mem.eql(u8, cmd, "repl")) return commands.repl(ctx);160 if (std.mem.eql(u8, cmd, "develop")) return commands.develop(ctx);161 if (std.mem.eql(u8, cmd, "info")) return commands.info(ctx);162 if (std.mem.eql(u8, cmd, "boot")) return commands.boot(ctx);163 if (std.mem.eql(u8, cmd, "search")) return commands.searchCmd(ctx);164 if (std.mem.eql(u8, cmd, "option")) return commands.optionCmd(ctx);165 if (std.mem.eql(u8, cmd, "install")) return commands.install(ctx);166 if (std.mem.eql(u8, cmd, "remove")) return commands.remove(ctx);167 if (std.mem.eql(u8, cmd, "list")) return commands.list(ctx);168 if (std.mem.eql(u8, cmd, "try")) return commands.tryCmd(ctx);169 if (std.mem.eql(u8, cmd, "service")) return commands.service(ctx);170 if (std.mem.eql(u8, cmd, "flake")) return commands.flake(ctx);171 if (std.mem.eql(u8, cmd, "store")) return commands.store(ctx);172 if (std.mem.eql(u8, cmd, "channel")) return commands.channel(ctx);173 if (std.mem.eql(u8, cmd, "profile")) return commands.profile(ctx);174 if (std.mem.eql(u8, cmd, "pkg")) return commands.pkg(ctx);175 if (std.mem.eql(u8, cmd, "pin")) return commands.pin(ctx);176 if (std.mem.eql(u8, cmd, "unpin")) return commands.unpin(ctx);177 if (std.mem.eql(u8, cmd, "hash")) return commands.hash(ctx);178 if (std.mem.eql(u8, cmd, "fetch")) return commands.fetch(ctx);179 if (std.mem.eql(u8, cmd, "build")) return commands.build(ctx);180 if (std.mem.eql(u8, cmd, "run")) return commands.runCmd(ctx);181 if (std.mem.eql(u8, cmd, "goodbye")) return commands.goodbye(ctx);182 if (std.mem.eql(u8, cmd, "hello")) return commands.hello(ctx);183 if (std.mem.eql(u8, cmd, "help")) { output.help(); return; }184185 errors.error_info.setMessage("unknown command: {s}", .{cmd});186 errors.error_info.setSuggestion("nina help", .{});187 return error.Internal;188}189190fn parseArgs(arena: std.mem.Allocator, argv: []const [:0]const u8) !Args {191 var result = Args{};192 var rest: std.ArrayList([]const u8) = .empty;193 var i: usize = 1; 194195 196 if (i < argv.len and argv[i][0] != '-') {197 result.command = argv[i];198 i += 1;199 }200201 202 if (i < argv.len and argv[i][0] != '-') {203 const multi_word = isMultiWord(result.command);204 if (multi_word) {205 result.sub = argv[i];206 i += 1;207 }208 }209210 while (i < argv.len) : (i += 1) {211 const a = argv[i];212 if (std.mem.eql(u8, a, "--on") or std.mem.eql(u8, a, "-on")) {213 i += 1;214 if (i < argv.len) result.machine = argv[i];215 } else if (std.mem.eql(u8, a, "--dry")) {216 result.dry = true;217 } else if (std.mem.eql(u8, a, "--check")) {218 result.check = true;219 } else if (std.mem.eql(u8, a, "--version")) {220 result.version = true;221 } else if (std.mem.eql(u8, a, "--no-apply")) {222 result.no_apply = true;223 } else if (std.mem.eql(u8, a, "--all")) {224 result.all = true;225 } else if (std.mem.eql(u8, a, "--json")) {226 result.json = true;227 } else if (std.mem.eql(u8, a, "--last")) {228 i += 1;229 if (i < argv.len) result.last = std.fmt.parseInt(u32, argv[i], 10) catch 50;230 } else if (std.mem.eql(u8, a, "-f")) {231 232 try rest.append(arena, "-f");233 } else if (a.len > 0 and a[0] != '-') {234 try rest.append(arena, a);235 }236 237 }238239 result.rest = try rest.toOwnedSlice(arena);240 return result;241}242243fn isMultiWord(cmd: []const u8) bool {244 const multi = [_][]const u8{ "service", "flake", "store", "channel", "profile", "pkg", "gen", "develop", "edit" };245 for (multi) |m| {246 if (std.mem.eql(u8, cmd, m)) return true;247 }248 return false;249}250251test "parse args basic" {252 const gpa = std.testing.allocator;253 const argv = [_][:0]const u8{ "nina", "apply", "--dry" };254 const args = try parseArgs(gpa, &argv);255 defer gpa.free(args.rest);256 try std.testing.expectEqualStrings("apply", args.command);257 try std.testing.expect(args.dry);258}259260test "parse args with machine" {261 const gpa = std.testing.allocator;262 const argv = [_][:0]const u8{ "nina", "status", "--on", "build-server" };263 const args = try parseArgs(gpa, &argv);264 defer gpa.free(args.rest);265 try std.testing.expectEqualStrings("status", args.command);266 try std.testing.expectEqualStrings("build-server", args.machine.?);267}268269test "parse subcommand" {270 const gpa = std.testing.allocator;271 const argv = [_][:0]const u8{ "nina", "service", "list" };272 const args = try parseArgs(gpa, &argv);273 defer gpa.free(args.rest);274 try std.testing.expectEqualStrings("service", args.command);275 try std.testing.expectEqualStrings("list", args.sub);276}277278test "parse version flag" {279 const gpa = std.testing.allocator;280 const argv = [_][:0]const u8{ "nina", "--version" };281 const args = try parseArgs(gpa, &argv);282 defer gpa.free(args.rest);283 try std.testing.expect(args.version);284}