mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
Merge pull request #1338 from mitchellh/help-rebase
Add `--help` to the Ghostty CLI
This commit is contained in:
49
build.zig
49
build.zig
@ -114,6 +114,12 @@ pub fn build(b: *std.Build) !void {
|
|||||||
"Build and install the benchmark executables.",
|
"Build and install the benchmark executables.",
|
||||||
) orelse false;
|
) orelse false;
|
||||||
|
|
||||||
|
const emit_helpgen = b.option(
|
||||||
|
bool,
|
||||||
|
"emit-helpgen",
|
||||||
|
"Build and install the helpgen executable.",
|
||||||
|
) orelse false;
|
||||||
|
|
||||||
// On NixOS, the built binary from `zig build` needs to patch the rpath
|
// On NixOS, the built binary from `zig build` needs to patch the rpath
|
||||||
// into the built binary for it to be portable across the NixOS system
|
// into the built binary for it to be portable across the NixOS system
|
||||||
// it was built for. We default this to true if we can detect we're in
|
// it was built for. We default this to true if we can detect we're in
|
||||||
@ -177,6 +183,10 @@ pub fn build(b: *std.Build) !void {
|
|||||||
// We can use wasmtime to test wasm
|
// We can use wasmtime to test wasm
|
||||||
b.enable_wasmtime = true;
|
b.enable_wasmtime = true;
|
||||||
|
|
||||||
|
// Help exe. This must be run before any dependent executables because
|
||||||
|
// otherwise the build will be cached without emit. That's clunky but meh.
|
||||||
|
if (emit_helpgen) addHelp(b, null);
|
||||||
|
|
||||||
// Add our benchmarks
|
// Add our benchmarks
|
||||||
try benchSteps(b, target, optimize, config, emit_bench);
|
try benchSteps(b, target, optimize, config, emit_bench);
|
||||||
|
|
||||||
@ -475,7 +485,10 @@ pub fn build(b: *std.Build) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// On Mac we can build the embedding library. This only handles the macOS lib.
|
// On Mac we can build the embedding library. This only handles the macOS lib.
|
||||||
if (builtin.target.isDarwin() and target.result.os.tag == .macos) {
|
if (builtin.target.isDarwin() and
|
||||||
|
target.result.os.tag == .macos and
|
||||||
|
config.app_runtime == .none)
|
||||||
|
{
|
||||||
// Create the universal macOS lib.
|
// Create the universal macOS lib.
|
||||||
const macos_lib_step, const macos_lib_path = try createMacOSLib(
|
const macos_lib_step, const macos_lib_path = try createMacOSLib(
|
||||||
b,
|
b,
|
||||||
@ -1098,9 +1111,43 @@ fn addDeps(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addHelp(b, step);
|
||||||
|
|
||||||
return static_libs;
|
return static_libs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate help files
|
||||||
|
fn addHelp(
|
||||||
|
b: *std.Build,
|
||||||
|
step_: ?*std.Build.Step.Compile,
|
||||||
|
) void {
|
||||||
|
// Our static state between runs. We memoize our help strings
|
||||||
|
// so that we only execute the help generation once.
|
||||||
|
const HelpState = struct {
|
||||||
|
var generated: ?std.Build.LazyPath = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const help_output = HelpState.generated orelse strings: {
|
||||||
|
const help_exe = b.addExecutable(.{
|
||||||
|
.name = "helpgen",
|
||||||
|
.root_source_file = .{ .path = "src/helpgen.zig" },
|
||||||
|
.target = b.host,
|
||||||
|
});
|
||||||
|
if (step_ == null) b.installArtifact(help_exe);
|
||||||
|
|
||||||
|
const help_run = b.addRunArtifact(help_exe);
|
||||||
|
HelpState.generated = help_run.captureStdOut();
|
||||||
|
break :strings HelpState.generated.?;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (step_) |step| {
|
||||||
|
help_output.addStepDependencies(&step.step);
|
||||||
|
step.root_module.addAnonymousImport("help_strings", .{
|
||||||
|
.root_source_file = help_output,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn benchSteps(
|
fn benchSteps(
|
||||||
b: *std.Build,
|
b: *std.Build,
|
||||||
target: std.Build.ResolvedTarget,
|
target: std.Build.ResolvedTarget,
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const help_strings = @import("help_strings");
|
||||||
|
|
||||||
const list_fonts = @import("list_fonts.zig");
|
const list_fonts = @import("list_fonts.zig");
|
||||||
|
const help = @import("help.zig");
|
||||||
const version = @import("version.zig");
|
const version = @import("version.zig");
|
||||||
const list_keybinds = @import("list_keybinds.zig");
|
const list_keybinds = @import("list_keybinds.zig");
|
||||||
const list_themes = @import("list_themes.zig");
|
const list_themes = @import("list_themes.zig");
|
||||||
@ -14,6 +16,9 @@ pub const Action = enum {
|
|||||||
/// Output the version and exit
|
/// Output the version and exit
|
||||||
version,
|
version,
|
||||||
|
|
||||||
|
/// Output help information for the CLI or configuration
|
||||||
|
help,
|
||||||
|
|
||||||
/// List available fonts
|
/// List available fonts
|
||||||
@"list-fonts",
|
@"list-fonts",
|
||||||
|
|
||||||
@ -35,6 +40,9 @@ pub const Action = enum {
|
|||||||
InvalidAction,
|
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.
|
/// Detect the action from CLI args.
|
||||||
pub fn detectCLI(alloc: Allocator) !?Action {
|
pub fn detectCLI(alloc: Allocator) !?Action {
|
||||||
var iter = try std.process.argsWithAllocator(alloc);
|
var iter = try std.process.argsWithAllocator(alloc);
|
||||||
@ -44,31 +52,90 @@ pub const Action = enum {
|
|||||||
|
|
||||||
/// Detect the action from any iterator, used primarily for tests.
|
/// Detect the action from any iterator, used primarily for tests.
|
||||||
pub fn detectIter(iter: anytype) Error!?Action {
|
pub fn detectIter(iter: anytype) Error!?Action {
|
||||||
|
var pending_help: bool = false;
|
||||||
var pending: ?Action = null;
|
var pending: ?Action = null;
|
||||||
while (iter.next()) |arg| {
|
while (iter.next()) |arg| {
|
||||||
// Special case, --version always outputs the version no
|
// Special case, --version always outputs the version no
|
||||||
// matter what, no matter what other args exist.
|
// matter what, no matter what other args exist.
|
||||||
if (std.mem.eql(u8, arg, "--version")) return .version;
|
if (std.mem.eql(u8, arg, "--version")) return .version;
|
||||||
|
|
||||||
|
// --help matches "help" but if a subcommand is specified
|
||||||
|
// then we match the subcommand.
|
||||||
|
if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
|
||||||
|
pending_help = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Commands must start with "+"
|
// Commands must start with "+"
|
||||||
if (arg.len == 0 or arg[0] != '+') continue;
|
if (arg.len == 0 or arg[0] != '+') continue;
|
||||||
if (pending != null) return Error.MultipleActions;
|
if (pending != null) return Error.MultipleActions;
|
||||||
pending = std.meta.stringToEnum(Action, arg[1..]) orelse return Error.InvalidAction;
|
pending = std.meta.stringToEnum(Action, arg[1..]) orelse return Error.InvalidAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have an action, we always return that action, even if we've
|
||||||
|
// seen "--help" or "-h" because the action may have its own help text.
|
||||||
|
if (pending != null) return pending;
|
||||||
|
|
||||||
|
// If we've seen "--help" or "-h" then we return the help action.
|
||||||
|
if (pending_help) return .help;
|
||||||
|
|
||||||
return pending;
|
return pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run the action. This returns the exit code to exit with.
|
/// Run the action. This returns the exit code to exit with.
|
||||||
pub fn run(self: Action, alloc: Allocator) !u8 {
|
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) {
|
return switch (self) {
|
||||||
.version => try version.run(),
|
.version => try version.run(alloc),
|
||||||
|
.help => try help.run(alloc),
|
||||||
.@"list-fonts" => try list_fonts.run(alloc),
|
.@"list-fonts" => try list_fonts.run(alloc),
|
||||||
.@"list-keybinds" => try list_keybinds.run(alloc),
|
.@"list-keybinds" => try list_keybinds.run(alloc),
|
||||||
.@"list-themes" => try list_themes.run(alloc),
|
.@"list-themes" => try list_themes.run(alloc),
|
||||||
.@"list-colors" => try list_colors.run(alloc),
|
.@"list-colors" => try list_colors.run(alloc),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the filename associated with an action. This is a relative
|
||||||
|
/// path from the root src/ directory.
|
||||||
|
pub fn file(comptime self: Action) []const u8 {
|
||||||
|
comptime {
|
||||||
|
const filename = filename: {
|
||||||
|
const tag = @tagName(self);
|
||||||
|
var filename: [tag.len]u8 = undefined;
|
||||||
|
_ = std.mem.replace(u8, tag, "-", "_", &filename);
|
||||||
|
break :filename &filename;
|
||||||
|
};
|
||||||
|
|
||||||
|
return "cli/" ++ filename ++ ".zig";
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
test "parse action none" {
|
test "parse action none" {
|
||||||
|
@ -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 (!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, "--")) {
|
if (mem.startsWith(u8, arg, "--")) {
|
||||||
var key: []const u8 = arg[2..];
|
var key: []const u8 = arg[2..];
|
||||||
const value: ?[]const u8 = value: {
|
const value: ?[]const u8 = value: {
|
||||||
|
71
src/cli/help.zig
Normal file
71
src/cli/help.zig
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const args = @import("args.zig");
|
||||||
|
const Action = @import("action.zig").Action;
|
||||||
|
|
||||||
|
// Note that this options struct doesn't implement the `help` decl like
|
||||||
|
// other actions. That is because the help command is special and wants to
|
||||||
|
// handle its own logic around help detection.
|
||||||
|
pub const Options = struct {
|
||||||
|
/// This must be registered so that it isn't an error to pass `--help`
|
||||||
|
help: bool = false,
|
||||||
|
|
||||||
|
pub fn deinit(self: Options) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The `help` command shows general help about Ghostty. You can also
|
||||||
|
/// specify `--help` or `-h` along with any action such as `+list-themes`
|
||||||
|
/// to see help for a specific action.
|
||||||
|
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.writeAll(
|
||||||
|
\\Usage: ghostty [+action] [options]
|
||||||
|
\\
|
||||||
|
\\Run the Ghostty terminal emulator or a specific helper action.
|
||||||
|
\\
|
||||||
|
\\If no `+action` is specified, run the Ghostty terminal emulator.
|
||||||
|
\\All configuration keys are available as command line options.
|
||||||
|
\\To specify a configuration key, use the `--<key>=<value>` syntax
|
||||||
|
\\where key and value are the same format you'd put into a configuration
|
||||||
|
\\file. For example, `--font-size=12` or `--font-family="Fira Code"`.
|
||||||
|
\\
|
||||||
|
\\To see a list of all available configuration options, please see
|
||||||
|
\\the `src/config/Config.zig` file. A future update will allow seeing
|
||||||
|
\\the list of configuration options from the command line.
|
||||||
|
\\
|
||||||
|
\\A special command line argument `-e <command>` can be used to run
|
||||||
|
\\the specific command inside the terminal emulator. For example,
|
||||||
|
\\`ghostty -e top` will run the `top` command inside the terminal.
|
||||||
|
\\
|
||||||
|
\\On macOS, launching the terminal emulator from the CLI is not
|
||||||
|
\\supported and only actions are supported.
|
||||||
|
\\
|
||||||
|
\\Available actions:
|
||||||
|
\\
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
|
||||||
|
inline for (@typeInfo(Action).Enum.fields) |field| {
|
||||||
|
try stdout.print(" +{s}\n", .{field.name});
|
||||||
|
}
|
||||||
|
|
||||||
|
try stdout.writeAll(
|
||||||
|
\\
|
||||||
|
\\Specify `+<action> --help` to see the help for a specific action,
|
||||||
|
\\where `<action>` is one of actions listed below.
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Action = @import("action.zig").Action;
|
||||||
const args = @import("args.zig");
|
const args = @import("args.zig");
|
||||||
const x11_color = @import("../terminal/main.zig").x11_color;
|
const x11_color = @import("../terminal/main.zig").x11_color;
|
||||||
|
|
||||||
@ -6,6 +7,12 @@ pub const Options = struct {
|
|||||||
pub fn deinit(self: Options) void {
|
pub fn deinit(self: Options) void {
|
||||||
_ = self;
|
_ = 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
|
/// The "list-colors" command is used to list all the named RGB colors in
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
const Action = @import("action.zig").Action;
|
||||||
const args = @import("args.zig");
|
const args = @import("args.zig");
|
||||||
const font = @import("../font/main.zig");
|
const font = @import("../font/main.zig");
|
||||||
|
|
||||||
@ -26,6 +27,12 @@ pub const Config = struct {
|
|||||||
if (self._arena) |arena| arena.deinit();
|
if (self._arena) |arena| arena.deinit();
|
||||||
self.* = undefined;
|
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.
|
/// The list-fonts command is used to list all the available fonts for Ghostty.
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const inputpkg = @import("../input.zig");
|
const inputpkg = @import("../input.zig");
|
||||||
const args = @import("args.zig");
|
const args = @import("args.zig");
|
||||||
|
const Action = @import("action.zig").Action;
|
||||||
const Arena = std.heap.ArenaAllocator;
|
const Arena = std.heap.ArenaAllocator;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const Config = @import("../config/Config.zig");
|
const Config = @import("../config/Config.zig");
|
||||||
@ -13,6 +14,12 @@ pub const Options = struct {
|
|||||||
pub fn deinit(self: Options) void {
|
pub fn deinit(self: Options) void {
|
||||||
_ = self;
|
_ = 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
|
/// The "list-keybinds" command is used to list all the available keybinds
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const inputpkg = @import("../input.zig");
|
const inputpkg = @import("../input.zig");
|
||||||
const args = @import("args.zig");
|
const args = @import("args.zig");
|
||||||
|
const Action = @import("action.zig").Action;
|
||||||
const Arena = std.heap.ArenaAllocator;
|
const Arena = std.heap.ArenaAllocator;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const Config = @import("../config/Config.zig");
|
const Config = @import("../config/Config.zig");
|
||||||
@ -10,6 +11,12 @@ pub const Options = struct {
|
|||||||
pub fn deinit(self: Options) void {
|
pub fn deinit(self: Options) void {
|
||||||
_ = self;
|
_ = 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
|
/// The "list-themes" command is used to list all the available themes
|
||||||
|
@ -1,10 +1,36 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const build_config = @import("../build_config.zig");
|
const build_config = @import("../build_config.zig");
|
||||||
const xev = @import("xev");
|
const xev = @import("xev");
|
||||||
const renderer = @import("../renderer.zig");
|
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(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);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run() !u8 {
|
|
||||||
const stdout = std.io.getStdOut().writer();
|
const stdout = std.io.getStdOut().writer();
|
||||||
try stdout.print("Ghostty {s}\n\n", .{build_config.version_string});
|
try stdout.print("Ghostty {s}\n\n", .{build_config.version_string});
|
||||||
try stdout.print("Build Config\n", .{});
|
try stdout.print("Build Config\n", .{});
|
||||||
|
167
src/helpgen.zig
Normal file
167
src/helpgen.zig
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
//! This program is used to generate the help strings from the configuration
|
||||||
|
//! file and CLI actions for Ghostty. These can then be used to generate
|
||||||
|
//! help, docs, website, etc.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const ziglyph = @import("ziglyph");
|
||||||
|
const Config = @import("config/Config.zig");
|
||||||
|
const Action = @import("cli/action.zig").Action;
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
const alloc = gpa.allocator();
|
||||||
|
|
||||||
|
const stdout = std.io.getStdOut().writer();
|
||||||
|
try stdout.writeAll(
|
||||||
|
\\// THIS FILE IS AUTO GENERATED
|
||||||
|
\\
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
|
||||||
|
try genConfig(alloc, stdout);
|
||||||
|
try genActions(alloc, stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genConfig(alloc: std.mem.Allocator, writer: anytype) !void {
|
||||||
|
var ast = try std.zig.Ast.parse(alloc, @embedFile("config/Config.zig"), .zig);
|
||||||
|
defer ast.deinit(alloc);
|
||||||
|
|
||||||
|
try writer.writeAll(
|
||||||
|
\\/// Configuration help
|
||||||
|
\\pub const Config = struct {
|
||||||
|
\\
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
|
||||||
|
inline for (@typeInfo(Config).Struct.fields) |field| {
|
||||||
|
if (field.name[0] == '_') continue;
|
||||||
|
try genConfigField(alloc, writer, ast, field.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeAll("};\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genConfigField(
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
writer: anytype,
|
||||||
|
ast: std.zig.Ast,
|
||||||
|
comptime field: []const u8,
|
||||||
|
) !void {
|
||||||
|
const tokens = ast.tokens.items(.tag);
|
||||||
|
for (tokens, 0..) |token, i| {
|
||||||
|
// We only care about identifiers that are preceded by doc comments.
|
||||||
|
if (token != .identifier) continue;
|
||||||
|
if (tokens[i - 1] != .doc_comment) continue;
|
||||||
|
|
||||||
|
// Identifier may have @"" so we strip that.
|
||||||
|
const name = ast.tokenSlice(@intCast(i));
|
||||||
|
const key = if (name[0] == '@') name[2 .. name.len - 1] else name;
|
||||||
|
if (!std.mem.eql(u8, key, field)) continue;
|
||||||
|
|
||||||
|
const comment = try extractDocComments(alloc, ast, @intCast(i - 1), tokens);
|
||||||
|
try writer.writeAll("pub const ");
|
||||||
|
try writer.writeAll(name);
|
||||||
|
try writer.writeAll(": [:0]const u8 = \n");
|
||||||
|
try writer.writeAll(comment);
|
||||||
|
try writer.writeAll("\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genActions(alloc: std.mem.Allocator, writer: anytype) !void {
|
||||||
|
try writer.writeAll(
|
||||||
|
\\
|
||||||
|
\\/// Actions help
|
||||||
|
\\pub const Action = struct {
|
||||||
|
\\
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
|
||||||
|
inline for (@typeInfo(Action).Enum.fields) |field| {
|
||||||
|
const action_file = comptime action_file: {
|
||||||
|
const action = @field(Action, field.name);
|
||||||
|
break :action_file action.file();
|
||||||
|
};
|
||||||
|
|
||||||
|
var ast = try std.zig.Ast.parse(alloc, @embedFile(action_file), .zig);
|
||||||
|
defer ast.deinit(alloc);
|
||||||
|
const tokens: []std.zig.Token.Tag = ast.tokens.items(.tag);
|
||||||
|
|
||||||
|
for (tokens, 0..) |token, i| {
|
||||||
|
// We're looking for a function named "run".
|
||||||
|
if (token != .keyword_fn) continue;
|
||||||
|
if (!std.mem.eql(u8, ast.tokenSlice(@intCast(i + 1)), "run")) continue;
|
||||||
|
|
||||||
|
// The function must be preceded by a doc comment.
|
||||||
|
if (tokens[i - 2] != .doc_comment) {
|
||||||
|
std.debug.print(
|
||||||
|
"doc comment must be present on run function of the {s} action!",
|
||||||
|
.{field.name},
|
||||||
|
);
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const comment = try extractDocComments(alloc, ast, @intCast(i - 2), tokens);
|
||||||
|
try writer.writeAll("pub const @\"");
|
||||||
|
try writer.writeAll(field.name);
|
||||||
|
try writer.writeAll("\" = \n");
|
||||||
|
try writer.writeAll(comment);
|
||||||
|
try writer.writeAll("\n\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try writer.writeAll("};\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extractDocComments(
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
ast: std.zig.Ast,
|
||||||
|
index: std.zig.Ast.TokenIndex,
|
||||||
|
tokens: []std.zig.Token.Tag,
|
||||||
|
) ![]const u8 {
|
||||||
|
// Find the first index of the doc comments. The doc comments are
|
||||||
|
// always stacked on top of each other so we can just go backwards.
|
||||||
|
const start_idx: usize = start_idx: for (0..index) |i| {
|
||||||
|
const reverse_i = index - i - 1;
|
||||||
|
const token = tokens[reverse_i];
|
||||||
|
if (token != .doc_comment) break :start_idx reverse_i + 1;
|
||||||
|
} else unreachable;
|
||||||
|
|
||||||
|
// Go through and build up the lines.
|
||||||
|
var lines = std.ArrayList([]const u8).init(alloc);
|
||||||
|
defer lines.deinit();
|
||||||
|
for (start_idx..index + 1) |i| {
|
||||||
|
const token = tokens[i];
|
||||||
|
if (token != .doc_comment) break;
|
||||||
|
try lines.append(ast.tokenSlice(@intCast(i))[3..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the lines to a multiline string.
|
||||||
|
var buffer = std.ArrayList(u8).init(alloc);
|
||||||
|
const writer = buffer.writer();
|
||||||
|
const prefix = findCommonPrefix(lines);
|
||||||
|
for (lines.items) |line| {
|
||||||
|
try writer.writeAll(" \\\\");
|
||||||
|
try writer.writeAll(line[@min(prefix, line.len)..]);
|
||||||
|
try writer.writeAll("\n");
|
||||||
|
}
|
||||||
|
try writer.writeAll(";\n");
|
||||||
|
|
||||||
|
return buffer.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findCommonPrefix(lines: std.ArrayList([]const u8)) usize {
|
||||||
|
var m: usize = std.math.maxInt(usize);
|
||||||
|
for (lines.items) |line| {
|
||||||
|
var n: usize = std.math.maxInt(usize);
|
||||||
|
for (line, 0..) |c, i| {
|
||||||
|
if (c != ' ') {
|
||||||
|
n = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m = @min(m, n);
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
Reference in New Issue
Block a user