From b438998fb82d07e7f29a71cec30e1f46a7a7a0fc Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 20 Jan 2024 09:29:26 -0800 Subject: [PATCH] cli: support --help and -h for actions --- src/cli/action.zig | 34 +++++++++++++++++++++++++++++++++- src/cli/args.zig | 11 +++++++++++ src/cli/list_colors.zig | 7 +++++++ src/cli/list_fonts.zig | 7 +++++++ src/cli/list_keybinds.zig | 7 +++++++ src/cli/list_themes.zig | 7 +++++++ src/cli/version.zig | 26 +++++++++++++++++++++++++- 7 files changed, 97 insertions(+), 2 deletions(-) diff --git a/src/cli/action.zig b/src/cli/action.zig index 2a7e0c6a9..a0fa216eb 100644 --- a/src/cli/action.zig +++ b/src/cli/action.zig @@ -1,5 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; +const help_strings = @import("help_strings"); const list_fonts = @import("list_fonts.zig"); const version = @import("version.zig"); @@ -35,6 +36,9 @@ pub const Action = enum { InvalidAction, }; + /// This should be returned by actions that want to print the help text. + pub const help_error = error.ActionHelpRequested; + /// Detect the action from CLI args. pub fn detectCLI(alloc: Allocator) !?Action { var iter = try std.process.argsWithAllocator(alloc); @@ -61,8 +65,36 @@ pub const Action = enum { /// Run the action. This returns the exit code to exit with. pub fn run(self: Action, alloc: Allocator) !u8 { + return self.runMain(alloc) catch |err| switch (err) { + // If help is requested, then we use some comptime trickery + // to find this action in the help strings and output that. + help_error => err: { + inline for (@typeInfo(Action).Enum.fields) |field| { + // Future note: for now we just output the help text directly + // to stdout. In the future we can style this much prettier + // for all commands by just changing this one place. + + if (std.mem.eql(u8, field.name, @tagName(self))) { + const stdout = std.io.getStdOut().writer(); + const text = @field(help_strings.Action, field.name) ++ "\n"; + stdout.writeAll(text) catch |write_err| { + std.log.warn("failed to write help text: {}\n", .{write_err}); + break :err 1; + }; + + break :err 0; + } + } + + break :err err; + }, + else => err, + }; + } + + fn runMain(self: Action, alloc: Allocator) !u8 { return switch (self) { - .version => try version.run(), + .version => try version.run(alloc), .@"list-fonts" => try list_fonts.run(alloc), .@"list-keybinds" => try list_keybinds.run(alloc), .@"list-themes" => try list_themes.run(alloc), diff --git a/src/cli/args.zig b/src/cli/args.zig index c226493f9..773457cf8 100644 --- a/src/cli/args.zig +++ b/src/cli/args.zig @@ -77,6 +77,17 @@ pub fn parse(comptime T: type, alloc: Allocator, dst: *T, iter: anytype) !void { if (!try dst.parseManuallyHook(arena_alloc, arg, iter)) return; } + // If the destination supports help then we check for it, call + // the help function and return. + if (@hasDecl(T, "help")) { + if (mem.eql(u8, arg, "--help") or + mem.eql(u8, arg, "-h")) + { + try dst.help(); + return; + } + } + if (mem.startsWith(u8, arg, "--")) { var key: []const u8 = arg[2..]; const value: ?[]const u8 = value: { diff --git a/src/cli/list_colors.zig b/src/cli/list_colors.zig index 2b6dd1d0e..447f70552 100644 --- a/src/cli/list_colors.zig +++ b/src/cli/list_colors.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Action = @import("action.zig").Action; const args = @import("args.zig"); const x11_color = @import("../terminal/main.zig").x11_color; @@ -6,6 +7,12 @@ pub const Options = struct { pub fn deinit(self: Options) void { _ = self; } + + /// Enables "-h" and "--help" to work. + pub fn help(self: Options) !void { + _ = self; + return Action.help_error; + } }; /// The "list-colors" command is used to list all the named RGB colors in diff --git a/src/cli/list_fonts.zig b/src/cli/list_fonts.zig index 0e48bf0f9..b49e43a30 100644 --- a/src/cli/list_fonts.zig +++ b/src/cli/list_fonts.zig @@ -1,6 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; +const Action = @import("action.zig").Action; const args = @import("args.zig"); const font = @import("../font/main.zig"); @@ -26,6 +27,12 @@ pub const Config = struct { if (self._arena) |arena| arena.deinit(); self.* = undefined; } + + /// Enables "-h" and "--help" to work. + pub fn help(self: Config) !void { + _ = self; + return Action.help_error; + } }; /// The list-fonts command is used to list all the available fonts for Ghostty. diff --git a/src/cli/list_keybinds.zig b/src/cli/list_keybinds.zig index 8d9585be9..15df1815e 100644 --- a/src/cli/list_keybinds.zig +++ b/src/cli/list_keybinds.zig @@ -1,6 +1,7 @@ const std = @import("std"); const inputpkg = @import("../input.zig"); const args = @import("args.zig"); +const Action = @import("action.zig").Action; const Arena = std.heap.ArenaAllocator; const Allocator = std.mem.Allocator; const Config = @import("../config/Config.zig"); @@ -13,6 +14,12 @@ pub const Options = struct { pub fn deinit(self: Options) void { _ = self; } + + /// Enables "-h" and "--help" to work. + pub fn help(self: Options) !void { + _ = self; + return Action.help_error; + } }; /// The "list-keybinds" command is used to list all the available keybinds diff --git a/src/cli/list_themes.zig b/src/cli/list_themes.zig index be3a41c14..98c3859c6 100644 --- a/src/cli/list_themes.zig +++ b/src/cli/list_themes.zig @@ -1,6 +1,7 @@ const std = @import("std"); const inputpkg = @import("../input.zig"); const args = @import("args.zig"); +const Action = @import("action.zig").Action; const Arena = std.heap.ArenaAllocator; const Allocator = std.mem.Allocator; const Config = @import("../config/Config.zig"); @@ -10,6 +11,12 @@ pub const Options = struct { pub fn deinit(self: Options) void { _ = self; } + + /// Enables "-h" and "--help" to work. + pub fn help(self: Options) !void { + _ = self; + return Action.help_error; + } }; /// The "list-themes" command is used to list all the available themes diff --git a/src/cli/version.zig b/src/cli/version.zig index dca20ad2f..fb12faa2e 100644 --- a/src/cli/version.zig +++ b/src/cli/version.zig @@ -1,12 +1,36 @@ const std = @import("std"); +const Allocator = std.mem.Allocator; const builtin = @import("builtin"); const build_config = @import("../build_config.zig"); const xev = @import("xev"); const renderer = @import("../renderer.zig"); +const args = @import("args.zig"); +const Action = @import("action.zig").Action; + +pub const Options = struct { + pub fn deinit(self: Options) void { + _ = self; + } + + /// Enables "-h" and "--help" to work. + pub fn help(self: Options) !void { + _ = self; + return Action.help_error; + } +}; /// The `version` command is used to display information /// about Ghostty. -pub fn run() !u8 { +pub fn run(alloc: Allocator) !u8 { + var opts: Options = .{}; + defer opts.deinit(); + + { + var iter = try std.process.argsWithAllocator(alloc); + defer iter.deinit(); + try args.parse(Options, alloc, &opts, &iter); + } + const stdout = std.io.getStdOut().writer(); try stdout.print("Ghostty {s}\n\n", .{build_config.version_string}); try stdout.print("Build Config\n", .{});