mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
config: overhaul help string generation
This commit is contained in:
@ -8,6 +8,7 @@ const builtin = @import("builtin");
|
||||
const apprt = @import("../apprt.zig");
|
||||
const font = @import("../font/main.zig");
|
||||
const renderer = @import("../renderer.zig");
|
||||
const configpkg = @import("../config.zig");
|
||||
const Command = @import("../Command.zig");
|
||||
const WasmTarget = @import("../os/wasm/target.zig").Target;
|
||||
|
||||
@ -396,7 +397,7 @@ pub fn addOptions(self: *const Config, step: *std.Build.Step.Options) !void {
|
||||
.{self.version},
|
||||
));
|
||||
step.addOption(
|
||||
ReleaseChannel,
|
||||
configpkg.Config.ReleaseChannel,
|
||||
"release_channel",
|
||||
channel: {
|
||||
const pre = self.version.pre orelse break :channel .stable;
|
||||
@ -492,12 +493,3 @@ pub const ExeEntrypoint = enum {
|
||||
bench_grapheme_break,
|
||||
bench_page_init,
|
||||
};
|
||||
|
||||
/// The release channel for the build.
|
||||
pub const ReleaseChannel = enum {
|
||||
/// Unstable builds on every commit.
|
||||
tip,
|
||||
|
||||
/// Stable tagged releases.
|
||||
stable,
|
||||
};
|
||||
|
@ -34,6 +34,7 @@ pub fn genConfig(writer: anytype, cli: bool) !void {
|
||||
if (cli) try writer.writeAll("--");
|
||||
try writer.writeAll(field.name);
|
||||
try writer.writeAll("`**\n\n");
|
||||
|
||||
if (@hasDecl(help_strings.Config, field.name)) {
|
||||
var iter = std.mem.splitScalar(u8, @field(help_strings.Config, field.name), '\n');
|
||||
var first = true;
|
||||
|
@ -1,6 +1,7 @@
|
||||
const std = @import("std");
|
||||
const Config = @import("../../config/Config.zig");
|
||||
const help_strings = @import("help_strings");
|
||||
const formatter = @import("../../config/formatter.zig");
|
||||
|
||||
pub fn main() !void {
|
||||
const output = std.io.getStdOut().writer();
|
||||
@ -77,10 +78,16 @@ pub fn genConfig(writer: anytype) !void {
|
||||
callout_note,
|
||||
callout_warning,
|
||||
} = null;
|
||||
var block_indent: usize = 0;
|
||||
|
||||
while (iter.next()) |line| {
|
||||
var indent: usize = 0;
|
||||
while (indent < line.len and line[indent] == ' ') : (indent += 1) {}
|
||||
const s = line[indent..];
|
||||
|
||||
while (iter.next()) |s| {
|
||||
// Empty line resets our block
|
||||
if (std.mem.eql(u8, s, "")) {
|
||||
try writer.writeByteNTimes(' ', block_indent);
|
||||
try endBlock(writer, block);
|
||||
block = null;
|
||||
|
||||
@ -90,30 +97,35 @@ pub fn genConfig(writer: anytype) !void {
|
||||
|
||||
// If we don't have a block figure out our type.
|
||||
const first: bool = block == null;
|
||||
if (block == null) {
|
||||
if (std.mem.startsWith(u8, s, " ")) {
|
||||
if (block == null) block: {
|
||||
if (indent == 4) {
|
||||
block = .code;
|
||||
try writer.writeAll("```\n");
|
||||
} else if (std.ascii.startsWithIgnoreCase(s, "note:")) {
|
||||
break :block;
|
||||
}
|
||||
|
||||
try writer.writeByteNTimes(' ', indent);
|
||||
block_indent = indent;
|
||||
|
||||
if (std.ascii.startsWithIgnoreCase(s, "note:")) {
|
||||
block = .callout_note;
|
||||
try writer.writeAll("<Note>\n");
|
||||
try writer.writeByteNTimes(' ', indent);
|
||||
} else if (std.ascii.startsWithIgnoreCase(s, "warning:")) {
|
||||
block = .callout_warning;
|
||||
try writer.writeAll("<Warning>\n");
|
||||
try writer.writeByteNTimes(' ', indent);
|
||||
} else {
|
||||
block = .text;
|
||||
}
|
||||
} else if (block != .code) {
|
||||
try writer.writeByteNTimes(' ', indent);
|
||||
}
|
||||
|
||||
try writer.writeAll(switch (block.?) {
|
||||
.text => s,
|
||||
.callout_note => if (first) s["note:".len..] else s,
|
||||
.callout_warning => if (first) s["warning:".len..] else s,
|
||||
|
||||
.code => if (std.mem.startsWith(u8, s, " "))
|
||||
s[4..]
|
||||
else
|
||||
s,
|
||||
.text, .code => s,
|
||||
.callout_note => std.mem.trim(u8, if (first) s["note:".len..] else s, " "),
|
||||
.callout_warning => std.mem.trim(u8, if (first) s["warning:".len..] else s, " "),
|
||||
});
|
||||
try writer.writeAll("\n");
|
||||
}
|
||||
|
@ -11,15 +11,14 @@ const font = @import("font/main.zig");
|
||||
const rendererpkg = @import("renderer.zig");
|
||||
const WasmTarget = @import("os/wasm/target.zig").Target;
|
||||
const BuildConfig = @import("build/Config.zig");
|
||||
|
||||
pub const ReleaseChannel = BuildConfig.ReleaseChannel;
|
||||
const Config = @import("config/Config.zig");
|
||||
|
||||
/// The semantic version of this build.
|
||||
pub const version = options.app_version;
|
||||
pub const version_string = options.app_version_string;
|
||||
|
||||
/// The release channel for this build.
|
||||
pub const release_channel = std.meta.stringToEnum(ReleaseChannel, @tagName(options.release_channel)).?;
|
||||
pub const release_channel = std.meta.stringToEnum(Config.ReleaseChannel, @tagName(options.release_channel)).?;
|
||||
|
||||
/// The optimization mode as a string.
|
||||
pub const mode_string = mode: {
|
||||
|
@ -121,12 +121,12 @@ pub const Action = enum {
|
||||
// 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;
|
||||
};
|
||||
// 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;
|
||||
}
|
||||
|
@ -41,10 +41,10 @@ pub fn run(alloc: Allocator) !u8 {
|
||||
try stdout.print("{s}", .{field.name});
|
||||
if (opts.docs) {
|
||||
try stdout.print(":\n", .{});
|
||||
var iter = std.mem.splitScalar(u8, std.mem.trimRight(u8, @field(help_strings.KeybindAction, field.name), &std.ascii.whitespace), '\n');
|
||||
while (iter.next()) |line| {
|
||||
try stdout.print(" {s}\n", .{line});
|
||||
}
|
||||
// var iter = std.mem.splitScalar(u8, std.mem.trimRight(u8, @field(help_strings.KeybindAction, field.name), &std.ascii.whitespace), '\n');
|
||||
// while (iter.next()) |line| {
|
||||
// try stdout.print(" {s}\n", .{line});
|
||||
// }
|
||||
} else {
|
||||
try stdout.print("\n", .{});
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
||||
const Metrics = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Config = @import("../config/Config.zig");
|
||||
|
||||
/// Recommended cell width and height for a monospace grid using this font.
|
||||
cell_width: u32,
|
||||
@ -257,130 +258,7 @@ fn clamp(self: *Metrics) void {
|
||||
/// little space as possible.
|
||||
pub const ModifierSet = std.AutoHashMapUnmanaged(Key, Modifier);
|
||||
|
||||
/// A modifier to apply to a metrics value. The modifier value represents
|
||||
/// a delta, so percent is a percentage to change, not a percentage of.
|
||||
/// For example, "20%" is 20% larger, not 20% of the value. Likewise,
|
||||
/// an absolute value of "20" is 20 larger, not literally 20.
|
||||
pub const Modifier = union(enum) {
|
||||
percent: f64,
|
||||
absolute: i32,
|
||||
|
||||
/// Parses the modifier value. If the value ends in "%" it is assumed
|
||||
/// to be a percent, otherwise the value is parsed as an integer.
|
||||
pub fn parse(input: []const u8) !Modifier {
|
||||
if (input.len == 0) return error.InvalidFormat;
|
||||
|
||||
if (input[input.len - 1] == '%') {
|
||||
var percent = std.fmt.parseFloat(
|
||||
f64,
|
||||
input[0 .. input.len - 1],
|
||||
) catch return error.InvalidFormat;
|
||||
percent /= 100;
|
||||
|
||||
if (percent <= -1) return .{ .percent = 0 };
|
||||
if (percent < 0) return .{ .percent = 1 + percent };
|
||||
return .{ .percent = 1 + percent };
|
||||
}
|
||||
|
||||
return .{
|
||||
.absolute = std.fmt.parseInt(i32, input, 10) catch
|
||||
return error.InvalidFormat,
|
||||
};
|
||||
}
|
||||
|
||||
/// So it works with the config framework.
|
||||
pub fn parseCLI(input: ?[]const u8) !Modifier {
|
||||
return try parse(input orelse return error.ValueRequired);
|
||||
}
|
||||
|
||||
/// Used by config formatter
|
||||
pub fn formatEntry(self: Modifier, formatter: anytype) !void {
|
||||
var buf: [1024]u8 = undefined;
|
||||
switch (self) {
|
||||
.percent => |v| {
|
||||
try formatter.formatEntry(
|
||||
[]const u8,
|
||||
std.fmt.bufPrint(
|
||||
&buf,
|
||||
"{d}%",
|
||||
.{(v - 1) * 100},
|
||||
) catch return error.OutOfMemory,
|
||||
);
|
||||
},
|
||||
|
||||
.absolute => |v| {
|
||||
try formatter.formatEntry(
|
||||
[]const u8,
|
||||
std.fmt.bufPrint(
|
||||
&buf,
|
||||
"{d}",
|
||||
.{v},
|
||||
) catch return error.OutOfMemory,
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a modifier to a numeric value.
|
||||
pub fn apply(self: Modifier, v: anytype) @TypeOf(v) {
|
||||
const T = @TypeOf(v);
|
||||
const signed = @typeInfo(T).Int.signedness == .signed;
|
||||
return switch (self) {
|
||||
.percent => |p| percent: {
|
||||
const p_clamped: f64 = @max(0, p);
|
||||
const v_f64: f64 = @floatFromInt(v);
|
||||
const applied_f64: f64 = @round(v_f64 * p_clamped);
|
||||
const applied_T: T = @intFromFloat(applied_f64);
|
||||
break :percent applied_T;
|
||||
},
|
||||
|
||||
.absolute => |abs| absolute: {
|
||||
const v_i64: i64 = @intCast(v);
|
||||
const abs_i64: i64 = @intCast(abs);
|
||||
const applied_i64: i64 = v_i64 +| abs_i64;
|
||||
const clamped_i64: i64 = if (signed) applied_i64 else @max(0, applied_i64);
|
||||
const applied_T: T = std.math.cast(T, clamped_i64) orelse
|
||||
std.math.maxInt(T) * @as(T, @intCast(std.math.sign(clamped_i64)));
|
||||
break :absolute applied_T;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// Hash using the hasher.
|
||||
pub fn hash(self: Modifier, hasher: anytype) void {
|
||||
const autoHash = std.hash.autoHash;
|
||||
autoHash(hasher, std.meta.activeTag(self));
|
||||
switch (self) {
|
||||
// floats can't be hashed directly so we bitcast to i64.
|
||||
// for the purpose of what we're trying to do this seems
|
||||
// good enough but I would prefer value hashing.
|
||||
.percent => |v| autoHash(hasher, @as(i64, @bitCast(v))),
|
||||
.absolute => |v| autoHash(hasher, v),
|
||||
}
|
||||
}
|
||||
|
||||
test "formatConfig percent" {
|
||||
const configpkg = @import("../config.zig");
|
||||
const testing = std.testing;
|
||||
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||
defer buf.deinit();
|
||||
|
||||
const p = try parseCLI("24%");
|
||||
try p.formatEntry(configpkg.entryFormatter("a", buf.writer()));
|
||||
try std.testing.expectEqualSlices(u8, "a = 24%\n", buf.items);
|
||||
}
|
||||
|
||||
test "formatConfig absolute" {
|
||||
const configpkg = @import("../config.zig");
|
||||
const testing = std.testing;
|
||||
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||
defer buf.deinit();
|
||||
|
||||
const p = try parseCLI("-30");
|
||||
try p.formatEntry(configpkg.entryFormatter("a", buf.writer()));
|
||||
try std.testing.expectEqualSlices(u8, "a = -30\n", buf.items);
|
||||
}
|
||||
};
|
||||
pub const Modifier = Config.MetricModifier;
|
||||
|
||||
/// Key is an enum of all the available metrics keys.
|
||||
pub const Key = key: {
|
||||
|
390
src/helpgen.zig
390
src/helpgen.zig
@ -27,46 +27,15 @@ 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 {
|
||||
\\
|
||||
\\
|
||||
try genStringsStruct(
|
||||
alloc,
|
||||
writer,
|
||||
ast,
|
||||
"Config",
|
||||
0,
|
||||
ast.rootDecls(),
|
||||
0,
|
||||
);
|
||||
|
||||
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 {
|
||||
@ -86,6 +55,7 @@ fn genActions(alloc: std.mem.Allocator, writer: anytype) !void {
|
||||
|
||||
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| {
|
||||
@ -102,12 +72,18 @@ fn genActions(alloc: std.mem.Allocator, writer: anytype) !void {
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
const comment = try extractDocComments(alloc, ast, @intCast(i - 2), tokens);
|
||||
const comment = try extractDocComments(
|
||||
alloc,
|
||||
ast,
|
||||
@intCast(i - 2),
|
||||
0,
|
||||
) orelse continue;
|
||||
|
||||
try writer.writeAll("pub const @\"");
|
||||
try writer.writeAll(field.name);
|
||||
try writer.writeAll("\" = \n");
|
||||
try writer.writeAll(comment);
|
||||
try writer.writeAll("\n\n");
|
||||
try writer.writeAll(";\n\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -120,26 +96,337 @@ fn genKeybindActions(alloc: std.mem.Allocator, writer: anytype) !void {
|
||||
defer ast.deinit(alloc);
|
||||
|
||||
try writer.writeAll(
|
||||
\\/// keybind actions help
|
||||
\\pub const KeybindAction = struct {
|
||||
\\
|
||||
\\};
|
||||
);
|
||||
|
||||
// for (ast.rootDecls()) |decl| {
|
||||
// if (ast.fullVarDecl(decl)) |var_decl| {
|
||||
// var buf: [2]std.zig.Ast.TokenIndex = undefined;
|
||||
// const decl_container = ast.fullContainerDecl(&buf, var_decl.ast.init_node) orelse continue;
|
||||
// const name = ast.tokenSlice(var_decl.ast.mut_token + 1);
|
||||
|
||||
// if (!std.mem.eql(u8, name, "Action")) continue;
|
||||
|
||||
// _ = decl_container;
|
||||
// _ = writer;
|
||||
|
||||
// try genStringsStruct(
|
||||
// alloc,
|
||||
// writer,
|
||||
// ast,
|
||||
// "KeybindAction",
|
||||
// decl_container.ast.members,
|
||||
// var_decl.firstToken() - 1,
|
||||
// );
|
||||
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
fn genStringsStruct(
|
||||
alloc: std.mem.Allocator,
|
||||
writer: anytype,
|
||||
ast: std.zig.Ast,
|
||||
name: []const u8,
|
||||
main_token: std.zig.Ast.TokenIndex,
|
||||
members: []const std.zig.Ast.Node.Index,
|
||||
doc_comment_token: ?std.zig.Ast.TokenIndex,
|
||||
) !void {
|
||||
var fields: std.ArrayListUnmanaged(std.zig.Ast.full.ContainerField) = .{};
|
||||
defer fields.deinit(alloc);
|
||||
|
||||
var decls: std.ArrayListUnmanaged(std.zig.Ast.full.VarDecl) = .{};
|
||||
defer decls.deinit(alloc);
|
||||
|
||||
try writer.print("pub const {s} = struct {{\n", .{name});
|
||||
|
||||
for (members) |member| {
|
||||
if (ast.fullContainerField(member)) |field| {
|
||||
try fields.append(alloc, field);
|
||||
} else if (ast.fullVarDecl(member)) |var_decl| {
|
||||
// Is it defining a subtype?
|
||||
try decls.append(alloc, var_decl);
|
||||
}
|
||||
}
|
||||
|
||||
for (fields.items) |field| {
|
||||
try genConfigField(alloc, writer, ast, field, decls.items);
|
||||
}
|
||||
|
||||
for (decls.items) |var_decl| {
|
||||
var buf: [2]std.zig.Ast.TokenIndex = undefined;
|
||||
const decl_container = ast.fullContainerDecl(&buf, var_decl.ast.init_node) orelse continue;
|
||||
|
||||
try genStringsStruct(
|
||||
alloc,
|
||||
writer,
|
||||
ast,
|
||||
ast.tokenSlice(var_decl.ast.mut_token + 1),
|
||||
decl_container.ast.main_token,
|
||||
decl_container.ast.members,
|
||||
var_decl.firstToken() - 1,
|
||||
);
|
||||
}
|
||||
|
||||
try writer.writeAll(
|
||||
\\pub const @"DOC-COMMENT": []const u8 =
|
||||
\\
|
||||
);
|
||||
|
||||
inline for (@typeInfo(KeybindAction).Union.fields) |field| {
|
||||
if (field.name[0] == '_') continue;
|
||||
try genConfigField(alloc, writer, ast, field.name);
|
||||
if (doc_comment_token) |token| {
|
||||
if (try extractDocComments(
|
||||
alloc,
|
||||
ast,
|
||||
@intCast(token),
|
||||
0,
|
||||
)) |comment| {
|
||||
try writer.writeAll(comment);
|
||||
}
|
||||
}
|
||||
|
||||
try writer.writeAll("};\n");
|
||||
try genValidValues(
|
||||
alloc,
|
||||
writer,
|
||||
ast,
|
||||
main_token,
|
||||
members,
|
||||
);
|
||||
|
||||
try writer.writeAll(
|
||||
\\ \\
|
||||
\\;
|
||||
\\};
|
||||
);
|
||||
}
|
||||
|
||||
fn genValidValues(
|
||||
alloc: std.mem.Allocator,
|
||||
writer: anytype,
|
||||
ast: std.zig.Ast,
|
||||
main_token: std.zig.Ast.TokenIndex,
|
||||
members: []const std.zig.Ast.Node.Index,
|
||||
) !void {
|
||||
const token_tags = ast.tokens.items(.tag);
|
||||
|
||||
const ContainerType = enum {
|
||||
@"enum",
|
||||
@"union",
|
||||
bitfield,
|
||||
};
|
||||
|
||||
const container_type: ContainerType = switch (token_tags[main_token]) {
|
||||
.keyword_enum => .@"enum",
|
||||
.keyword_union => .@"union",
|
||||
.keyword_struct => switch (token_tags[main_token - 1]) {
|
||||
.keyword_packed => .bitfield,
|
||||
else => return,
|
||||
},
|
||||
else => return,
|
||||
};
|
||||
|
||||
try writer.writeAll(
|
||||
\\\\
|
||||
\\\\Valid values:
|
||||
\\\\
|
||||
\\
|
||||
);
|
||||
|
||||
for (members) |member| {
|
||||
const field = ast.fullContainerField(member) orelse continue;
|
||||
var field_name = ast.tokenSlice(field.ast.main_token);
|
||||
|
||||
if (std.mem.startsWith(u8, field_name, "@\"")) {
|
||||
field_name = field_name[2..][0 .. field_name.len - 3];
|
||||
}
|
||||
|
||||
try writer.writeAll(
|
||||
\\\\ -
|
||||
);
|
||||
|
||||
switch (container_type) {
|
||||
.@"enum" => try writer.print(" `{s}`\n", .{field_name}),
|
||||
.@"union" => {
|
||||
const field_type = ast.getNodeSource(field.ast.type_expr);
|
||||
|
||||
// Only generate the field name if the field is "enum-variant-like":
|
||||
// type is void or nonexistent.
|
||||
if (field.ast.main_token == ast.firstToken(field.ast.type_expr) or
|
||||
std.mem.eql(u8, field_type, "void"))
|
||||
{
|
||||
try writer.print(" `{s}`\n", .{field_name});
|
||||
}
|
||||
},
|
||||
.bitfield => {
|
||||
const default_value = ast.tokenSlice(ast.firstToken(field.ast.value_expr));
|
||||
const is_default = std.mem.eql(u8, default_value, "true");
|
||||
|
||||
if (is_default) {
|
||||
try writer.print(" [x] `{s}` (Enabled by default)\n", .{field_name});
|
||||
} else {
|
||||
try writer.print(" [ ] `{s}`\n", .{field_name});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
try writer.writeAll(
|
||||
\\\\
|
||||
\\
|
||||
);
|
||||
|
||||
if (try extractDocComments(
|
||||
alloc,
|
||||
ast,
|
||||
field.firstToken() - 1,
|
||||
3, // 4 indents would be an indented code block
|
||||
)) |comment| {
|
||||
try writer.writeAll(comment);
|
||||
try writer.writeAll(
|
||||
\\\\
|
||||
\\
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn genConfigField(
|
||||
alloc: std.mem.Allocator,
|
||||
writer: anytype,
|
||||
ast: std.zig.Ast,
|
||||
field: std.zig.Ast.full.ContainerField,
|
||||
decls: []std.zig.Ast.full.VarDecl,
|
||||
) !void {
|
||||
const name = ast.tokenSlice(field.ast.main_token);
|
||||
if (name[0] == '_') return;
|
||||
|
||||
// Escape special identifiers that are valid as enum variants but not as field names
|
||||
const special_identifiers = &[_][]const u8{ "true", "false", "null" };
|
||||
|
||||
const is_special = for (special_identifiers) |special| {
|
||||
if (std.mem.eql(u8, name, special)) break true;
|
||||
} else false;
|
||||
|
||||
const comment = try extractDocComments(
|
||||
alloc,
|
||||
ast,
|
||||
field.firstToken() - 1,
|
||||
0,
|
||||
) orelse return;
|
||||
|
||||
try writer.writeAll("pub const ");
|
||||
if (is_special) try writer.writeAll("@\"");
|
||||
try writer.writeAll(name);
|
||||
if (is_special) try writer.writeAll("\"");
|
||||
try writer.writeAll(": [:0]const u8 = \n");
|
||||
try writer.writeAll(comment);
|
||||
|
||||
const type_name = ast.tokenSlice(ast.lastToken(field.ast.type_expr));
|
||||
|
||||
for (decls) |decl| {
|
||||
if (std.mem.eql(u8, type_name, ast.tokenSlice(decl.ast.mut_token + 1))) {
|
||||
try writer.writeAll(
|
||||
\\\\
|
||||
\\\\
|
||||
\\++
|
||||
\\
|
||||
);
|
||||
try writer.writeAll(type_name);
|
||||
try writer.writeAll(
|
||||
\\.@"DOC-COMMENT"
|
||||
\\
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try genDefaultValue(writer, ast, field);
|
||||
|
||||
try writer.writeAll(";\n");
|
||||
}
|
||||
|
||||
fn genDefaultValue(
|
||||
writer: anytype,
|
||||
ast: std.zig.Ast,
|
||||
field: std.zig.Ast.full.ContainerField,
|
||||
) !void {
|
||||
const value = ast.nodes.get(field.ast.value_expr);
|
||||
|
||||
switch (value.tag) {
|
||||
.number_literal, .string_literal => {
|
||||
try writer.writeAll(
|
||||
\\++
|
||||
\\\\
|
||||
\\\\Defaults to `{s}`.
|
||||
, ast.getNodeSource(field.ast.value_expr));
|
||||
},
|
||||
.identifier, .number_literal, .string_literal, .enum_literal => {
|
||||
try writer.writeAll(
|
||||
\\++
|
||||
\\\\
|
||||
\\\\
|
||||
\\\\
|
||||
);
|
||||
|
||||
const default = switch (value.tag) {
|
||||
.enum_literal, .identifier => id: {
|
||||
// Escape @"blah"
|
||||
const slice = ast.tokenSlice(value.main_token);
|
||||
break :id if (std.mem.startsWith(u8, slice, "@\""))
|
||||
slice[2..][0 .. slice.len - 3]
|
||||
else
|
||||
slice;
|
||||
},
|
||||
.number_literal => ast.tokenSlice(value.main_token),
|
||||
// We really don't know. Guess.
|
||||
else => ast.getNodeSource(field.ast.value_expr),
|
||||
};
|
||||
|
||||
// var default = ast.getNodeSource(field.ast.value_expr);
|
||||
// if (default[0] == '.') {
|
||||
// default = default[1..];
|
||||
// }
|
||||
|
||||
if (std.mem.eql(u8, default, "null")) {
|
||||
try writer.writeAll("Unset by default.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const default_type_node = ast.nodes.get(field.ast.type_expr);
|
||||
// ?bool is still semantically boolean
|
||||
const default_type = if (default_type_node.tag == .optional_type)
|
||||
ast.getNodeSource(default_type_node.data.lhs)
|
||||
else
|
||||
ast.getNodeSource(field.ast.type_expr);
|
||||
|
||||
// There are some enums/tagged unions with variants called `true`
|
||||
// or `false`, and it's not accurate to call them enabled or
|
||||
// disabled in some circumstances.
|
||||
// Thus we only consider booleans here.
|
||||
if (std.mem.eql(u8, default_type, "bool")) {
|
||||
if (std.mem.eql(u8, default, "true")) {
|
||||
try writer.writeAll("Enabled by default.\n");
|
||||
} else if (std.mem.eql(u8, default, "false")) {
|
||||
try writer.writeAll("Disabled by default.\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try writer.print("Defaults to `{s}`.\n", .{default});
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn extractDocComments(
|
||||
alloc: std.mem.Allocator,
|
||||
ast: std.zig.Ast,
|
||||
index: std.zig.Ast.TokenIndex,
|
||||
tokens: []std.zig.Token.Tag,
|
||||
) ![]const u8 {
|
||||
comptime indent: usize,
|
||||
) !?[]const u8 {
|
||||
if (index == 0) return null;
|
||||
const tokens = ast.tokens.items(.tag);
|
||||
|
||||
// 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| {
|
||||
@ -161,14 +448,15 @@ fn extractDocComments(
|
||||
var buffer = std.ArrayList(u8).init(alloc);
|
||||
const writer = buffer.writer();
|
||||
const prefix = findCommonPrefix(lines);
|
||||
|
||||
if (lines.items.len == 0) return null;
|
||||
for (lines.items) |line| {
|
||||
try writer.writeAll(" \\\\");
|
||||
try writer.writeAll(" \\\\" ++ " " ** indent);
|
||||
try writer.writeAll(line[@min(prefix, line.len)..]);
|
||||
try writer.writeAll("\n");
|
||||
}
|
||||
try writer.writeAll(";\n");
|
||||
|
||||
return buffer.toOwnedSlice();
|
||||
return try buffer.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn findCommonPrefix(lines: std.ArrayList([]const u8)) usize {
|
||||
|
Reference in New Issue
Block a user