main.zig

9.1 KB · raw

1// main.zig — entry point, arg parsing, dispatch, error handler2// THE ONLY FILE that creates the Io.Writer and passes it to output.init().3const 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    dry: bool = false,18    check: bool = false,19    no_apply: bool = false,20    all: bool = false,21    json: bool = false,22    last: u32 = 50,23};2425pub fn main(startup: std.process.Init) !void {26    const arena = startup.arena.allocator();27    const gpa = startup.gpa;28    const io = startup.io;2930    // Initialize output module with buffered stdout writer31    var stdout_buf: [16384]u8 = undefined;32    var stdout_fw: Io.File.Writer = .init(.stdout(), io, &stdout_buf);33    output.init(&stdout_fw.interface, startup.environ_map, true);34    defer output.flush();3536    const argv = try startup.minimal.args.toSlice(arena);37    const args = try parseArgs(arena, argv);3839    // Help and hello work without config40    if (args.command.len == 0 or std.mem.eql(u8, args.command, "help")) {41        output.help();42        return;43    }4445    // setup runs before config so it works as an onboarding step46    if (std.mem.eql(u8, args.command, "setup")) {47        commands.setup(gpa, io, startup.environ_map) catch |err| {48            const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);49            const detail = errors.error_info.getDetail();50            const suggestion = errors.error_info.getSuggestion();51            output.printError(msg, detail, suggestion);52            output.flush();53            std.process.exit(1);54        };55        return;56    }5758    // Load config and resolve machine59    const cfg = config.load(arena, io, startup.environ_map) catch |err| {60        const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);61        const detail = errors.error_info.getDetail();62        const suggestion = errors.error_info.getSuggestion();63        output.printError(msg, detail, suggestion);64        output.flush();65        std.process.exit(1);66    };6768    output.setTeach(cfg.teach);6970    const machine = config.resolveMachine(&cfg, args.machine) catch |err| {71        const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);72        const detail = errors.error_info.getDetail();73        const suggestion = errors.error_info.getSuggestion();74        output.printError(msg, detail, suggestion);75        output.flush();76        std.process.exit(1);77    };7879    const ctx = commands.Ctx{80        .gpa = gpa,81        .io = io,82        .environ = startup.environ_map,83        .machine = machine,84        .cfg = cfg,85        .args = args.rest,86        .sub = args.sub,87        .dry = args.dry,88        .check = args.check,89        .no_apply = args.no_apply,90        .all = args.all,91        .last = args.last,92        .json = args.json,93    };9495    dispatch(ctx, args.command) catch |err| {96        const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);97        const detail = errors.error_info.getDetail();98        const suggestion = errors.error_info.getSuggestion();99        output.printError(msg, detail, suggestion);100        output.flush();101        std.process.exit(1);102    };103}104105fn dispatch(ctx: commands.Ctx, cmd: []const u8) !void {106    if (std.mem.eql(u8, cmd, "apply")) return commands.apply(ctx);107    if (std.mem.eql(u8, cmd, "check")) return commands.check(ctx);108    if (std.mem.eql(u8, cmd, "back")) return commands.back(ctx);109    if (std.mem.eql(u8, cmd, "go")) return commands.go(ctx);110    if (std.mem.eql(u8, cmd, "history")) return commands.history(ctx);111    if (std.mem.eql(u8, cmd, "gen")) return commands.genCmd(ctx);112    if (std.mem.eql(u8, cmd, "clean")) return commands.clean(ctx);113    if (std.mem.eql(u8, cmd, "status")) return commands.statusCmd(ctx);114    if (std.mem.eql(u8, cmd, "diff")) return commands.diff(ctx);115    if (std.mem.eql(u8, cmd, "doctor")) return commands.doctor(ctx);116    if (std.mem.eql(u8, cmd, "update")) return commands.update(ctx);117    if (std.mem.eql(u8, cmd, "upgrade")) return commands.upgrade(ctx);118    if (std.mem.eql(u8, cmd, "log")) return commands.log(ctx);119    if (std.mem.eql(u8, cmd, "edit")) return commands.edit(ctx);120    if (std.mem.eql(u8, cmd, "fmt")) return commands.fmt(ctx);121    if (std.mem.eql(u8, cmd, "repl")) return commands.repl(ctx);122    if (std.mem.eql(u8, cmd, "develop")) return commands.develop(ctx);123    if (std.mem.eql(u8, cmd, "info")) return commands.info(ctx);124    if (std.mem.eql(u8, cmd, "boot")) return commands.boot(ctx);125    if (std.mem.eql(u8, cmd, "search")) return commands.searchCmd(ctx);126    if (std.mem.eql(u8, cmd, "option")) return commands.optionCmd(ctx);127    if (std.mem.eql(u8, cmd, "install")) return commands.install(ctx);128    if (std.mem.eql(u8, cmd, "remove")) return commands.remove(ctx);129    if (std.mem.eql(u8, cmd, "list")) return commands.list(ctx);130    if (std.mem.eql(u8, cmd, "try")) return commands.tryCmd(ctx);131    if (std.mem.eql(u8, cmd, "service")) return commands.service(ctx);132    if (std.mem.eql(u8, cmd, "flake")) return commands.flake(ctx);133    if (std.mem.eql(u8, cmd, "store")) return commands.store(ctx);134    if (std.mem.eql(u8, cmd, "channel")) return commands.channel(ctx);135    if (std.mem.eql(u8, cmd, "profile")) return commands.profile(ctx);136    if (std.mem.eql(u8, cmd, "pkg")) return commands.pkg(ctx);137    if (std.mem.eql(u8, cmd, "pin")) return commands.pin(ctx);138    if (std.mem.eql(u8, cmd, "unpin")) return commands.unpin(ctx);139    if (std.mem.eql(u8, cmd, "hash")) return commands.hash(ctx);140    if (std.mem.eql(u8, cmd, "fetch")) return commands.fetch(ctx);141    if (std.mem.eql(u8, cmd, "build")) return commands.build(ctx);142    if (std.mem.eql(u8, cmd, "run")) return commands.runCmd(ctx);143    if (std.mem.eql(u8, cmd, "hello")) return commands.hello(ctx);144    if (std.mem.eql(u8, cmd, "help")) { output.help(); return; }145146    errors.error_info.setMessage("unknown command: {s}", .{cmd});147    errors.error_info.setSuggestion("nina help", .{});148    return error.Internal;149}150151fn parseArgs(arena: std.mem.Allocator, argv: []const [:0]const u8) !Args {152    var result = Args{};153    var rest: std.ArrayList([]const u8) = .empty;154    var i: usize = 1; // skip program name155156    // First positional = command157    if (i < argv.len and argv[i][0] != '-') {158        result.command = argv[i];159        i += 1;160    }161162    // Second positional (if not a flag) = subcommand for multi-word commands163    if (i < argv.len and argv[i][0] != '-') {164        const multi_word = isMultiWord(result.command);165        if (multi_word) {166            result.sub = argv[i];167            i += 1;168        }169    }170171    while (i < argv.len) : (i += 1) {172        const a = argv[i];173        if (std.mem.eql(u8, a, "--on") or std.mem.eql(u8, a, "-on")) {174            i += 1;175            if (i < argv.len) result.machine = argv[i];176        } else if (std.mem.eql(u8, a, "--dry")) {177            result.dry = true;178        } else if (std.mem.eql(u8, a, "--check")) {179            result.check = true;180        } else if (std.mem.eql(u8, a, "--no-apply")) {181            result.no_apply = true;182        } else if (std.mem.eql(u8, a, "--all")) {183            result.all = true;184        } else if (std.mem.eql(u8, a, "--json")) {185            result.json = true;186        } else if (std.mem.eql(u8, a, "--last")) {187            i += 1;188            if (i < argv.len) result.last = std.fmt.parseInt(u32, argv[i], 10) catch 50;189        } else if (std.mem.eql(u8, a, "-f")) {190            // -f flag for service logs follow191            try rest.append(arena, "-f");192        } else if (a.len > 0 and a[0] != '-') {193            try rest.append(arena, a);194        }195        // Ignore unknown flags silently196    }197198    result.rest = try rest.toOwnedSlice(arena);199    return result;200}201202fn isMultiWord(cmd: []const u8) bool {203    const multi = [_][]const u8{ "service", "flake", "store", "channel", "profile", "pkg", "gen", "develop", "edit" };204    for (multi) |m| {205        if (std.mem.eql(u8, cmd, m)) return true;206    }207    return false;208}209210test "parse args basic" {211    const gpa = std.testing.allocator;212    const argv = [_][:0]const u8{ "nina", "apply", "--dry" };213    const args = try parseArgs(gpa, &argv);214    defer gpa.free(args.rest);215    try std.testing.expectEqualStrings("apply", args.command);216    try std.testing.expect(args.dry);217}218219test "parse args with machine" {220    const gpa = std.testing.allocator;221    const argv = [_][:0]const u8{ "nina", "status", "--on", "build-server" };222    const args = try parseArgs(gpa, &argv);223    defer gpa.free(args.rest);224    try std.testing.expectEqualStrings("status", args.command);225    try std.testing.expectEqualStrings("build-server", args.machine.?);226}227228test "parse subcommand" {229    const gpa = std.testing.allocator;230    const argv = [_][:0]const u8{ "nina", "service", "list" };231    const args = try parseArgs(gpa, &argv);232    defer gpa.free(args.rest);233    try std.testing.expectEqualStrings("service", args.command);234    try std.testing.expectEqualStrings("list", args.sub);235}