mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
rewrite generate_help for personal style
- Output to stdin instead of a file - Less nesting - Utilize ranged for loops instead of while loops - Eliminate unnecessary state tracking - Put help in a struct
This commit is contained in:
68
build.zig
68
build.zig
@ -114,6 +114,12 @@ pub fn build(b: *std.Build) !void {
|
||||
"Build and install the benchmark executables.",
|
||||
) 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
|
||||
// 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
|
||||
@ -177,6 +183,10 @@ pub fn build(b: *std.Build) !void {
|
||||
// We can use wasmtime to test wasm
|
||||
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
|
||||
try benchSteps(b, target, optimize, config, emit_bench);
|
||||
|
||||
@ -197,8 +207,6 @@ pub fn build(b: *std.Build) !void {
|
||||
.{version},
|
||||
));
|
||||
|
||||
createHelp(b);
|
||||
|
||||
// Exe
|
||||
if (exe_) |exe| {
|
||||
exe.root_module.addOptions("build_options", exe_options);
|
||||
@ -477,7 +485,10 @@ pub fn build(b: *std.Build) !void {
|
||||
}
|
||||
|
||||
// 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.
|
||||
const macos_lib_step, const macos_lib_path = try createMacOSLib(
|
||||
b,
|
||||
@ -1100,42 +1111,43 @@ fn addDeps(
|
||||
}
|
||||
}
|
||||
|
||||
addHelp(step);
|
||||
addHelp(b, step);
|
||||
|
||||
return static_libs;
|
||||
}
|
||||
|
||||
var generate_help_step: *std.Build.Step.Run = undefined;
|
||||
var help_strings: std.Build.LazyPath = undefined;
|
||||
|
||||
/// Generate help files
|
||||
fn createHelp(b: *std.Build) void {
|
||||
const generate_help = b.addExecutable(.{
|
||||
.name = "generate_help",
|
||||
.root_source_file = .{ .path = "src/generate_help_strings.zig" },
|
||||
.target = b.host,
|
||||
});
|
||||
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;
|
||||
};
|
||||
|
||||
generate_help_step = b.addRunArtifact(generate_help);
|
||||
generate_help_step.step.dependOn(&generate_help.step);
|
||||
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);
|
||||
|
||||
help_strings = generate_help_step.addOutputFileArg("help_strings.zig");
|
||||
const help_run = b.addRunArtifact(help_exe);
|
||||
HelpState.generated = help_run.captureStdOut();
|
||||
break :strings HelpState.generated.?;
|
||||
};
|
||||
|
||||
if (builtin.target.isDarwin()) {
|
||||
const generated = b.option([]const u8, "help_strings", "generated help file") orelse "help_strings";
|
||||
const write_file = b.addWriteFiles();
|
||||
help_strings = write_file.addCopyFile(help_strings, generated);
|
||||
if (step_) |step| {
|
||||
help_output.addStepDependencies(&step.step);
|
||||
step.root_module.addAnonymousImport("help_strings", .{
|
||||
.root_source_file = help_output,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Add the generated help files to the build.
|
||||
fn addHelp(step: *std.Build.Step.Compile) void {
|
||||
step.step.dependOn(&generate_help_step.step);
|
||||
step.root_module.addAnonymousImport("help_strings", .{
|
||||
.root_source_file = help_strings,
|
||||
});
|
||||
}
|
||||
|
||||
fn benchSteps(
|
||||
b: *std.Build,
|
||||
target: std.Build.ResolvedTarget,
|
||||
|
@ -69,6 +69,21 @@ pub const Action = enum {
|
||||
.@"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" {
|
||||
|
@ -1,165 +0,0 @@
|
||||
const std = @import("std");
|
||||
const ziglyph = @import("ziglyph");
|
||||
const Action = @import("cli/action.zig").Action;
|
||||
const Config = @import("config/Config.zig");
|
||||
|
||||
pub fn searchConfigAst(alloc: std.mem.Allocator, output: std.fs.File) !void {
|
||||
var ast = try std.zig.Ast.parse(alloc, @embedFile("config/Config.zig"), .zig);
|
||||
defer ast.deinit(alloc);
|
||||
|
||||
const config: Config = .{};
|
||||
|
||||
const tokens = ast.tokens.items(.tag);
|
||||
|
||||
var set = std.StringHashMap(bool).init(alloc);
|
||||
defer set.deinit();
|
||||
|
||||
try output.writeAll(
|
||||
\\//THIS FILE IS AUTO GENERATED
|
||||
\\//DO NOT MAKE ANY CHANGES TO THIS FILE!
|
||||
);
|
||||
|
||||
try output.writeAll("\n\n");
|
||||
|
||||
inline for (@typeInfo(@TypeOf(config)).Struct.fields) |field| {
|
||||
if (field.name[0] != '_') try set.put(field.name, false);
|
||||
}
|
||||
|
||||
var index: u32 = 0;
|
||||
while (true) : (index += 1) {
|
||||
if (index >= tokens.len) break;
|
||||
const token = tokens[index];
|
||||
|
||||
if (token == .identifier) {
|
||||
const slice = ast.tokenSlice(index);
|
||||
// We need this check because the ast grabs the identifier with @"" in case it's used.
|
||||
const key = if (slice[0] == '@') slice[2 .. slice.len - 1] else slice;
|
||||
|
||||
if (key[0] == '_') continue;
|
||||
|
||||
if (set.get(key)) |value| {
|
||||
if (value) continue;
|
||||
if (tokens[index - 1] != .doc_comment) continue;
|
||||
|
||||
const comment = try consumeDocComments(alloc, ast, index - 1, &tokens);
|
||||
const prop_type = ": " ++ "[:0]const u8 " ++ "= " ++ "\n";
|
||||
|
||||
try output.writeAll(slice);
|
||||
try output.writeAll(prop_type);
|
||||
// const concat = try std.mem.concat(self.alloc, u8, &.{ slice, prop_type });
|
||||
// try output.writeAll(concat);
|
||||
try output.writeAll(comment);
|
||||
try output.writeAll("\n\n");
|
||||
|
||||
try set.put(key, true);
|
||||
}
|
||||
}
|
||||
if (token == .eof) break;
|
||||
}
|
||||
}
|
||||
|
||||
fn actionPath(comptime action: Action) []const u8 {
|
||||
return switch (action) {
|
||||
.version => "cli/version.zig",
|
||||
.@"list-fonts" => "cli/list_fonts.zig",
|
||||
.@"list-keybinds" => "cli/list_keybinds.zig",
|
||||
.@"list-themes" => "cli/list_themes.zig",
|
||||
.@"list-colors" => "cli/list_colors.zig",
|
||||
};
|
||||
}
|
||||
|
||||
pub fn searchActionsAst(alloc: std.mem.Allocator, output: std.fs.File) !void {
|
||||
inline for (@typeInfo(Action).Enum.fields) |field| {
|
||||
const action = comptime std.meta.stringToEnum(Action, field.name).?;
|
||||
|
||||
var ast = try std.zig.Ast.parse(alloc, @embedFile(comptime actionPath(action)), .zig);
|
||||
const tokens = ast.tokens.items(.tag);
|
||||
|
||||
var index: u32 = 0;
|
||||
while (true) : (index += 1) {
|
||||
if (tokens[index] == .keyword_fn) {
|
||||
if (std.mem.eql(u8, ast.tokenSlice(index + 1), "run")) {
|
||||
if (tokens[index - 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 consumeDocComments(alloc, ast, index - 2, &tokens);
|
||||
const prop_type = "@\"+" ++ field.name ++ "\"" ++ ": " ++ "[:0]const u8 " ++ "= " ++ "\n";
|
||||
|
||||
try output.writeAll(prop_type);
|
||||
try output.writeAll(comment);
|
||||
try output.writeAll("\n\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn consumeDocComments(alloc: std.mem.Allocator, ast: std.zig.Ast, index: std.zig.Ast.TokenIndex, toks: anytype) ![]const u8 {
|
||||
var lines = std.ArrayList([]const u8).init(alloc);
|
||||
defer lines.deinit();
|
||||
|
||||
const tokens = toks.*;
|
||||
var current_idx = index;
|
||||
|
||||
// We iterate backwards because the doc_comment tokens should be on top of each other in case there are any.
|
||||
while (true) : (current_idx -= 1) {
|
||||
const token = tokens[current_idx];
|
||||
|
||||
if (token != .doc_comment) break;
|
||||
// Insert at 0 so that we don't have the text in reverse.
|
||||
try lines.insert(0, ast.tokenSlice(current_idx)[3..]);
|
||||
}
|
||||
|
||||
const prefix = findCommonPrefix(lines);
|
||||
|
||||
var buffer = std.ArrayList(u8).init(alloc);
|
||||
const writer = buffer.writer();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const args = try std.process.argsAlloc(alloc);
|
||||
defer std.process.argsFree(alloc, args);
|
||||
|
||||
if (args.len != 2) {
|
||||
std.debug.print("invalid number of arguments provided!", .{});
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
const path = args[1];
|
||||
|
||||
var output = try std.fs.cwd().createFile(path, .{});
|
||||
defer output.close();
|
||||
|
||||
try searchConfigAst(alloc, output);
|
||||
try searchActionsAst(alloc, output);
|
||||
}
|
Reference in New Issue
Block a user