//! Subcommand dispatch for the `panto` CLI. //! //! Routes argv[1] to one of: //! - `lua` — drop into the embedded standalone Lua interpreter //! (panto's `lua.c` build), with luarocks's runtime //! bootstrap completed first so `require("luarocks.*")` //! and the configured rocks tree work the same as in //! the agent process. //! - `bootstrap` — run the luarocks runtime bootstrap pipeline only; //! exit before entering any agent loop. Lets users //! do first-run setup on a fresh machine without //! starting a chat session. //! - anything else (or absent) — fall through to the agent REPL. //! //! Both `lua` and `bootstrap` end up calling `luarocks_runtime.bootstrap` //! before doing their thing. The agent path does the same; the only //! difference is whether the agent loop runs afterward. const std = @import("std"); const Allocator = std.mem.Allocator; const Io = std.Io; const lua_bridge = @import("lua_bridge.zig"); const luarocks_runtime = @import("luarocks_runtime.zig"); const self_exe = @import("self_exe.zig"); const c = lua_bridge.c; pub const Action = enum { /// Continue with the default agent REPL. agent, /// Bootstrap is already done; the dispatcher consumed the subcommand. /// `main` should exit immediately. done, }; /// Inspect `argv[1]`, run the appropriate subcommand, and return what /// the caller should do next. On `.agent`, the dispatcher leaves argv /// untouched and `main` continues as before. On `.done`, the caller /// must return promptly (the subcommand has already produced output). /// /// `panto_executable_path` is the absolute path of the running panto /// binary, used both to wire up the embedded luarocks `LUA` variable /// and to `exec` ourselves where needed. pub fn dispatch( allocator: Allocator, io: Io, environ_map: *const std.process.Environ.Map, args: std.process.Args, panto_executable_path: []const u8, ) !Action { var it = args.iterate(); defer it.deinit(); _ = it.next(); // argv[0] const sub = it.next() orelse return .agent; if (std.mem.eql(u8, sub, "lua")) { try runLuaSubcommand(allocator, io, environ_map, args, panto_executable_path); return .done; } if (std.mem.eql(u8, sub, "bootstrap")) { var force = false; while (it.next()) |flag| { if (std.mem.eql(u8, flag, "--force")) { force = true; } else { std.log.err("panto bootstrap: unknown flag '{s}'", .{flag}); return error.UnknownFlag; } } try runBootstrapSubcommand(allocator, io, environ_map, panto_executable_path, .{ .force = force }); return .done; } return .agent; } pub const BootstrapOptions = struct { /// Wipe the per-Lua-version tree before reinstalling everything. /// Surfaced as `panto bootstrap --force`. Equivalent to deleting /// `$PANTO_HOME/rocks/lua-X.Y.Z/` by hand and then running /// `panto bootstrap`. force: bool = false, }; // --------------------------------------------------------------------------- // `panto lua` // --------------------------------------------------------------------------- extern "c" fn panto_lua_pmain(L: *c.lua_State, argc: c_int, argv: [*]?[*:0]u8) c_int; /// Drop into the embedded Lua standalone interpreter, with the /// luarocks runtime bootstrap completed so `require("luarocks.*")` /// and rocks installed under `$PANTO_HOME` are visible. /// /// argv is rewritten so the interpreter sees `lua [...args]` rather /// than `panto lua [...args]` — matching upstream behavior. The first /// argument visible to `pmain` is the program name; this matters for /// `arg[0]` and error reporting. fn runLuaSubcommand( allocator: Allocator, io: Io, environ_map: *const std.process.Environ.Map, args: std.process.Args, panto_executable_path: []const u8, ) !void { // Build a fresh lua_State that we own, configure it like luarocks // expects, then hand it to `pmain`. const L = c.luaL_newstate() orelse return error.LuaInitFailed; defer c.lua_close(L); // Run bootstrap against this state. This installs the embedded // searcher, configures package.path/cpath, and stages on-disk // resources. We deliberately do NOT call `luaL_openlibs` here — // `pmain` does that itself, and we want exactly the upstream // ordering for everything that runs inside the REPL. // // The searcher install only requires `package.searchers` to be // present; the stock libs ship it. We open libs once here just // to satisfy that, then pmain's own `luaL_openlibs` is idempotent. c.luaL_openlibs(L); const rt = try luarocks_runtime.bootstrap( allocator, io, environ_map, L, panto_executable_path, ); defer rt.deinit(); // Re-create the argv the standalone interpreter expects. argv[0] // is the program name; argv[1..] are the user's args. var raw_args = args.iterate(); defer raw_args.deinit(); _ = raw_args.next(); // panto _ = raw_args.next(); // lua var argv_list: std.array_list.Managed([:0]u8) = .init(allocator); defer { for (argv_list.items) |s| allocator.free(s); argv_list.deinit(); } // Program name first. try argv_list.append(try allocator.dupeZ(u8, "lua")); while (raw_args.next()) |a| { try argv_list.append(try allocator.dupeZ(u8, a)); } // Build a `[*]?[*:0]u8` argv pointer array. lua.c expects a // NULL-terminated array (it uses `argv[i]` indexed access through // argc; the trailing NULL is conventional for C `main`). var argv_c: std.array_list.Managed(?[*:0]u8) = .init(allocator); defer argv_c.deinit(); for (argv_list.items) |s| { try argv_c.append(s.ptr); } try argv_c.append(null); const exit_code = panto_lua_pmain(L, @intCast(argv_list.items.len), argv_c.items.ptr); if (exit_code != 0) std.process.exit(@intCast(exit_code)); } // --------------------------------------------------------------------------- // `panto bootstrap` // --------------------------------------------------------------------------- /// Run the luarocks bootstrap and exit. Useful for first-run setup on /// a clean machine (downloads + compiles batteries, stages headers, /// materializes config) and for CI/scripted installs. /// /// Idempotent: subsequent invocations no-op fast, unless `force` was /// passed — then the entire per-Lua-version tree is wiped before the /// regular bootstrap pipeline runs. fn runBootstrapSubcommand( allocator: Allocator, io: Io, environ_map: *const std.process.Environ.Map, panto_executable_path: []const u8, opts: BootstrapOptions, ) !void { if (opts.force) { try luarocks_runtime.wipeTree(allocator, io, environ_map); } const L = c.luaL_newstate() orelse return error.LuaInitFailed; defer c.lua_close(L); c.luaL_openlibs(L); const rt = try luarocks_runtime.bootstrap( allocator, io, environ_map, L, panto_executable_path, ); defer rt.deinit(); // Pleasant single-line confirmation. The interesting bits (rock // installs etc.) print their own progress. std.log.info( "panto bootstrap: tree ready at {s}", .{rt.layout.tree}, ); } // --------------------------------------------------------------------------- // `panto lua` argv plumbing — sketched against the older Args API for // reference (kept here so the design notes survive the implementation). // --------------------------------------------------------------------------- // // Because we own the `lua_State` end-to-end, the subcommand can also // expose extra panto-specific globals to user code (e.g. surface the // resolved $PANTO_HOME) without disturbing upstream `lua.c` behavior. // Step out of scope for the current makeover; add when needed. // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- const testing = std.testing; // Note: `dispatch` reads from the process's real argv, which isn't // controllable from a unit test. The behavior is exercised by // integration runs of the panto binary. We test the smaller pieces. // // Suppress dead-code warnings for `self_exe` (it's used by main, not // by tests in this module). test { _ = self_exe; }