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:
Mitchell Hashimoto
2024-01-19 22:22:29 -08:00
parent f9ac37cdf7
commit 203b38fdac
3 changed files with 55 additions and 193 deletions

View File

@ -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,

View File

@ -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" {

View File

@ -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);
}