From dbfedd240056001bf86e1b1121ab18dcc9fdc62a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 15 Sep 2023 09:17:58 -0700 Subject: [PATCH] Add --version for outputting version, framework for more actions --- src/cli_action.zig | 107 +++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 35 +++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 src/cli_action.zig diff --git a/src/cli_action.zig b/src/cli_action.zig new file mode 100644 index 000000000..095770b56 --- /dev/null +++ b/src/cli_action.zig @@ -0,0 +1,107 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const Allocator = std.mem.Allocator; +const build_config = @import("build_config.zig"); + +/// Special commands that can be invoked via CLI flags. These are all +/// invoked by using `+` as a CLI flag. The only exception is +/// "version" which can be invoked additionally with `--version`. +pub const Action = enum { + /// Output the version and exit + version, + + pub const Error = error{ + /// Multiple actions were detected. You can specify at most one + /// action on the CLI otherwise the behavior desired is ambiguous. + MultipleActions, + + /// An unknown action was specified. + InvalidAction, + }; + + /// Detect the action from CLI args. + pub fn detectCLI(alloc: Allocator) !?Action { + var iter = try std.process.argsWithAllocator(alloc); + defer iter.deinit(); + return try detectIter(&iter); + } + + /// Detect the action from any iterator, used primarily for tests. + pub fn detectIter(iter: anytype) Error!?Action { + var pending: ?Action = null; + while (iter.next()) |arg| { + // Special case, --version always outputs the version no + // matter what, no matter what other args exist. + if (std.mem.eql(u8, arg, "--version")) return .version; + + // Commands must start with "+" + if (arg.len == 0 or arg[0] != '+') continue; + if (pending != null) return Error.MultipleActions; + pending = std.meta.stringToEnum(Action, arg[1..]) orelse return Error.InvalidAction; + } + + return null; + } + + /// Run the action. This returns the exit code to exit with. + pub fn run(self: Action, alloc: Allocator) !u8 { + _ = alloc; + return switch (self) { + .version => try runVersion(), + }; + } +}; + +fn runVersion() !u8 { + const stdout = std.io.getStdOut().writer(); + try stdout.print("Ghostty {s}\n", .{build_config.version_string}); + return 0; +} + +test "parse action none" { + const testing = std.testing; + const alloc = testing.allocator; + + var iter = try std.process.ArgIteratorGeneral(.{}).init( + alloc, + "--a=42 --b --b-f=false", + ); + defer iter.deinit(); + const action = try Action.detectIter(&iter); + try testing.expect(action == null); +} + +test "parse action version" { + const testing = std.testing; + const alloc = testing.allocator; + + { + var iter = try std.process.ArgIteratorGeneral(.{}).init( + alloc, + "--a=42 --b --b-f=false --version", + ); + defer iter.deinit(); + const action = try Action.detectIter(&iter); + try testing.expect(action.? == .version); + } + + { + var iter = try std.process.ArgIteratorGeneral(.{}).init( + alloc, + "--version --a=42 --b --b-f=false", + ); + defer iter.deinit(); + const action = try Action.detectIter(&iter); + try testing.expect(action.? == .version); + } + + { + var iter = try std.process.ArgIteratorGeneral(.{}).init( + alloc, + "--c=84 --d --version --a=42 --b --b-f=false", + ); + defer iter.deinit(); + const action = try Action.detectIter(&iter); + try testing.expect(action.? == .version); + } +} diff --git a/src/main.zig b/src/main.zig index 03603392d..da7eb665b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,10 +1,12 @@ const std = @import("std"); const builtin = @import("builtin"); +const Allocator = std.mem.Allocator; const build_config = @import("build_config.zig"); const options = @import("build_options"); const glfw = @import("glfw"); const macos = @import("macos"); const tracy = @import("tracy"); +const cli_action = @import("cli_action.zig"); const internal_os = @import("os/main.zig"); const xev = @import("xev"); const fontconfig = @import("fontconfig"); @@ -32,6 +34,38 @@ pub fn main() !void { defer state.deinit(); const alloc = state.alloc; + // Before we do anything else, we need to check for special commands + // via the CLI flags. + if (cli_action.Action.detectCLI(alloc)) |action_| { + if (action_) |action| { + std.log.info("executing CLI action={}", .{action}); + std.os.exit(action.run(alloc) catch |err| err: { + std.log.err("CLI action failed error={}", .{err}); + break :err 1; + }); + return; + } + } else |err| { + const stderr = std.io.getStdErr().writer(); + defer std.os.exit(1); + const ErrSet = @TypeOf(err) || error{Unknown}; + switch (@as(ErrSet, @errSetCast(err))) { + error.MultipleActions => try stderr.print( + "Error: multiple CLI actions specified. You must specify only one\n" ++ + "action starting with the `+` character.\n", + .{}, + ), + + error.InvalidAction => try stderr.print( + "Error: unknown CLI action specified. CLI actions are specified with\n" ++ + "the '+' character.\n", + .{}, + ), + + else => try stderr.print("invalid CLI invocation err={}\n", .{err}), + } + } + // Create our app state var app = try App.create(alloc); defer app.destroy(); @@ -187,6 +221,7 @@ test { _ = @import("renderer.zig"); _ = @import("termio.zig"); _ = @import("input.zig"); + _ = @import("cli_action.zig"); // Libraries _ = @import("segmented_pool.zig");