summaryrefslogtreecommitdiff
path: root/src/subcommand.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/subcommand.zig')
-rw-r--r--src/subcommand.zig231
1 files changed, 231 insertions, 0 deletions
diff --git a/src/subcommand.zig b/src/subcommand.zig
new file mode 100644
index 0000000..97eb977
--- /dev/null
+++ b/src/subcommand.zig
@@ -0,0 +1,231 @@
+//! 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;
+}