main.zig

10.7 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    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    // Initialize output module with buffered stdout writer32    var stdout_buf: [16384]u8 = undefined;33    var stdout_fw: Io.File.Writer = .init(.stdout(), io, &stdout_buf);34    output.init(&stdout_fw.interface, startup.environ_map, true);35    defer output.flush();3637    const argv = try startup.minimal.args.toSlice(arena);38    const args = try parseArgs(arena, argv);3940    if (args.version) {41        output.version();42        return;43    }4445    // Help and hello work without config46    if (args.command.len == 0 or std.mem.eql(u8, args.command, "help")) {47        output.help();48        return;49    }5051    // setup and update run before config so they work without ~/.nina.conf52    if (std.mem.eql(u8, args.command, "setup") or std.mem.eql(u8, args.command, "update")) {53        const dummy_machine: types.Machine = .{ .name = "local", .local = true };54        const dummy_cfg: types.NinaConfig = .{};55        const pre_ctx = commands.Ctx{56            .gpa = gpa,57            .io = io,58            .environ = startup.environ_map,59            .machine = dummy_machine,60            .cfg = dummy_cfg,61            .args = args.rest,62            .sub = args.sub,63            .dry = args.dry,64            .check = args.check,65            .no_apply = args.no_apply,66            .all = args.all,67            .last = args.last,68            .json = args.json,69        };70        if (std.mem.eql(u8, args.command, "setup")) {71            commands.setup(gpa, io, startup.environ_map) catch |err| {72                const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);73                const detail = errors.error_info.getDetail();74                const suggestion = errors.error_info.getSuggestion();75                output.printError(msg, detail, suggestion);76                output.flush();77                std.process.exit(1);78            };79        } else {80            dispatch(pre_ctx, args.command) catch |err| {81                const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);82                const detail = errors.error_info.getDetail();83                const suggestion = errors.error_info.getSuggestion();84                output.printError(msg, detail, suggestion);85                output.flush();86                std.process.exit(1);87            };88        }89        return;90    }9192    // Load config and resolve machine93    const cfg = config.load(arena, 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    };101102    output.setTeach(cfg.teach);103104    const machine = config.resolveMachine(&cfg, args.machine) catch |err| {105        const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);106        const detail = errors.error_info.getDetail();107        const suggestion = errors.error_info.getSuggestion();108        output.printError(msg, detail, suggestion);109        output.flush();110        std.process.exit(1);111    };112113    const ctx = commands.Ctx{114        .gpa = gpa,115        .io = io,116        .environ = startup.environ_map,117        .machine = machine,118        .cfg = cfg,119        .args = args.rest,120        .sub = args.sub,121        .dry = args.dry,122        .check = args.check,123        .no_apply = args.no_apply,124        .all = args.all,125        .last = args.last,126        .json = args.json,127    };128129    dispatch(ctx, args.command) catch |err| {130        const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);131        const detail = errors.error_info.getDetail();132        const suggestion = errors.error_info.getSuggestion();133        output.printError(msg, detail, suggestion);134        output.flush();135        std.process.exit(1);136    };137}138139fn dispatch(ctx: commands.Ctx, cmd: []const u8) !void {140    if (std.mem.eql(u8, cmd, "apply")) return commands.apply(ctx);141    if (std.mem.eql(u8, cmd, "check")) return commands.check(ctx);142    if (std.mem.eql(u8, cmd, "back")) return commands.back(ctx);143    if (std.mem.eql(u8, cmd, "go")) return commands.go(ctx);144    if (std.mem.eql(u8, cmd, "history")) return commands.history(ctx);145    if (std.mem.eql(u8, cmd, "gen")) return commands.genCmd(ctx);146    if (std.mem.eql(u8, cmd, "clean")) return commands.clean(ctx);147    if (std.mem.eql(u8, cmd, "status")) return commands.statusCmd(ctx);148    if (std.mem.eql(u8, cmd, "diff")) return commands.diff(ctx);149    if (std.mem.eql(u8, cmd, "doctor")) return commands.doctor(ctx);150    if (std.mem.eql(u8, cmd, "update")) return commands.update(ctx);151    if (std.mem.eql(u8, cmd, "upgrade")) return commands.upgrade(ctx);152    if (std.mem.eql(u8, cmd, "log")) return commands.log(ctx);153    if (std.mem.eql(u8, cmd, "edit")) return commands.edit(ctx);154    if (std.mem.eql(u8, cmd, "fmt")) return commands.fmt(ctx);155    if (std.mem.eql(u8, cmd, "repl")) return commands.repl(ctx);156    if (std.mem.eql(u8, cmd, "develop")) return commands.develop(ctx);157    if (std.mem.eql(u8, cmd, "info")) return commands.info(ctx);158    if (std.mem.eql(u8, cmd, "boot")) return commands.boot(ctx);159    if (std.mem.eql(u8, cmd, "search")) return commands.searchCmd(ctx);160    if (std.mem.eql(u8, cmd, "option")) return commands.optionCmd(ctx);161    if (std.mem.eql(u8, cmd, "install")) return commands.install(ctx);162    if (std.mem.eql(u8, cmd, "remove")) return commands.remove(ctx);163    if (std.mem.eql(u8, cmd, "list")) return commands.list(ctx);164    if (std.mem.eql(u8, cmd, "try")) return commands.tryCmd(ctx);165    if (std.mem.eql(u8, cmd, "service")) return commands.service(ctx);166    if (std.mem.eql(u8, cmd, "flake")) return commands.flake(ctx);167    if (std.mem.eql(u8, cmd, "store")) return commands.store(ctx);168    if (std.mem.eql(u8, cmd, "channel")) return commands.channel(ctx);169    if (std.mem.eql(u8, cmd, "profile")) return commands.profile(ctx);170    if (std.mem.eql(u8, cmd, "pkg")) return commands.pkg(ctx);171    if (std.mem.eql(u8, cmd, "pin")) return commands.pin(ctx);172    if (std.mem.eql(u8, cmd, "unpin")) return commands.unpin(ctx);173    if (std.mem.eql(u8, cmd, "hash")) return commands.hash(ctx);174    if (std.mem.eql(u8, cmd, "fetch")) return commands.fetch(ctx);175    if (std.mem.eql(u8, cmd, "build")) return commands.build(ctx);176    if (std.mem.eql(u8, cmd, "run")) return commands.runCmd(ctx);177    if (std.mem.eql(u8, cmd, "hello")) return commands.hello(ctx);178    if (std.mem.eql(u8, cmd, "help")) { output.help(); return; }179180    errors.error_info.setMessage("unknown command: {s}", .{cmd});181    errors.error_info.setSuggestion("nina help", .{});182    return error.Internal;183}184185fn parseArgs(arena: std.mem.Allocator, argv: []const [:0]const u8) !Args {186    var result = Args{};187    var rest: std.ArrayList([]const u8) = .empty;188    var i: usize = 1; // skip program name189190    // First positional = command191    if (i < argv.len and argv[i][0] != '-') {192        result.command = argv[i];193        i += 1;194    }195196    // Second positional (if not a flag) = subcommand for multi-word commands197    if (i < argv.len and argv[i][0] != '-') {198        const multi_word = isMultiWord(result.command);199        if (multi_word) {200            result.sub = argv[i];201            i += 1;202        }203    }204205    while (i < argv.len) : (i += 1) {206        const a = argv[i];207        if (std.mem.eql(u8, a, "--on") or std.mem.eql(u8, a, "-on")) {208            i += 1;209            if (i < argv.len) result.machine = argv[i];210        } else if (std.mem.eql(u8, a, "--dry")) {211            result.dry = true;212        } else if (std.mem.eql(u8, a, "--check")) {213            result.check = true;214        } else if (std.mem.eql(u8, a, "--version")) {215            result.version = true;216        } else if (std.mem.eql(u8, a, "--no-apply")) {217            result.no_apply = true;218        } else if (std.mem.eql(u8, a, "--all")) {219            result.all = true;220        } else if (std.mem.eql(u8, a, "--json")) {221            result.json = true;222        } else if (std.mem.eql(u8, a, "--last")) {223            i += 1;224            if (i < argv.len) result.last = std.fmt.parseInt(u32, argv[i], 10) catch 50;225        } else if (std.mem.eql(u8, a, "-f")) {226            // -f flag for service logs follow227            try rest.append(arena, "-f");228        } else if (a.len > 0 and a[0] != '-') {229            try rest.append(arena, a);230        }231        // Ignore unknown flags silently232    }233234    result.rest = try rest.toOwnedSlice(arena);235    return result;236}237238fn isMultiWord(cmd: []const u8) bool {239    const multi = [_][]const u8{ "service", "flake", "store", "channel", "profile", "pkg", "gen", "develop", "edit" };240    for (multi) |m| {241        if (std.mem.eql(u8, cmd, m)) return true;242    }243    return false;244}245246test "parse args basic" {247    const gpa = std.testing.allocator;248    const argv = [_][:0]const u8{ "nina", "apply", "--dry" };249    const args = try parseArgs(gpa, &argv);250    defer gpa.free(args.rest);251    try std.testing.expectEqualStrings("apply", args.command);252    try std.testing.expect(args.dry);253}254255test "parse args with machine" {256    const gpa = std.testing.allocator;257    const argv = [_][:0]const u8{ "nina", "status", "--on", "build-server" };258    const args = try parseArgs(gpa, &argv);259    defer gpa.free(args.rest);260    try std.testing.expectEqualStrings("status", args.command);261    try std.testing.expectEqualStrings("build-server", args.machine.?);262}263264test "parse subcommand" {265    const gpa = std.testing.allocator;266    const argv = [_][:0]const u8{ "nina", "service", "list" };267    const args = try parseArgs(gpa, &argv);268    defer gpa.free(args.rest);269    try std.testing.expectEqualStrings("service", args.command);270    try std.testing.expectEqualStrings("list", args.sub);271}272273test "parse version flag" {274    const gpa = std.testing.allocator;275    const argv = [_][:0]const u8{ "nina", "--version" };276    const args = try parseArgs(gpa, &argv);277    defer gpa.free(args.rest);278    try std.testing.expect(args.version);279}