Skip to content

Commit

Permalink
Add usage expression generation
Browse files Browse the repository at this point in the history
  • Loading branch information
n0s4 committed Aug 14, 2024
1 parent 70b7068 commit c265220
Showing 1 changed file with 108 additions and 5 deletions.
113 changes: 108 additions & 5 deletions src/help.zig
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,7 @@ pub fn generate(
comptime command_seq: []const u8,
) []const u8 {
// TODO: generate usage
comptime var help: []const u8 = std.fmt.comptimePrint(
"Usage: {s} [options]\n",
.{command_seq},
);
comptime var help: []const u8 = generateUsage(Flags, info, command_seq);

if (@hasDecl(Flags, "description")) {
const description: []const u8 = Flags.description; // must be a string
Expand Down Expand Up @@ -117,7 +114,7 @@ fn getDescriptions(comptime S: type) Descriptions(S) {
const D = @TypeOf(S.descriptions);
if (@typeInfo(D) != .Struct) meta.compileError(
"descriptions is not a struct value: {s}",
.{@typeName(Descriptions)},
.{@typeName(D)},
);

for (@typeInfo(D).Struct.fields) |desc| {
Expand All @@ -135,6 +132,112 @@ fn getDescriptions(comptime S: type) Descriptions(S) {
return descriptions;
}

const Usage = struct {
items: []const []const u8 = &.{},

fn add(self: *Usage, item: []const u8) void {
self.items = self.items ++ .{item};
}

pub fn render(self: Usage, comptime command_seq: []const u8) []const u8 {
var usage: []const u8 = "Usage: " ++ command_seq;

const indent_len = usage.len;
const max_line_len = 80;
var len_prev_lines = 0;

for (self.items) |item| {
if (usage.len + " ".len + item.len - len_prev_lines > max_line_len) {
len_prev_lines = usage.len;
usage = usage ++ "\n" ++ " " ** indent_len;
}
usage = usage ++ " " ++ item;
}

return usage;
}
};

fn generateUsage(
comptime Flags: type,
comptime info: meta.FlagsInfo,
comptime command_seq: []const u8,
) []const u8 {
var usage = Usage{};

const flag_formats = getFormats(Flags);
for (info.flags) |flag| {
var flag_usage: []const u8 = "";

if (flag.switch_char) |switch_char| {
flag_usage = flag_usage ++ std.fmt.comptimePrint("-{c} | ", .{switch_char});
}

flag_usage = flag_usage ++ flag.flag_name;

if (flag.type != bool) {
const format = @field(flag_formats, flag.field_name) orelse
"<" ++ flag.flag_name[2..] ++ ">"; // chop off the leading "--"

flag_usage = flag_usage ++ " " ++ format;
}

if (flag.type == bool or @typeInfo(flag.type) == .Optional or flag.default_value != null) {
flag_usage = "[" ++ flag_usage ++ "]";
}

usage.add(flag_usage);
}

for (info.positionals) |arg| {
const arg_usage = if (@typeInfo(arg.type) == .Optional or arg.default_value != null)
"[" ++ arg.arg_name ++ "]"
else
arg.arg_name;

usage.add(arg_usage);
}

if (info.subcommands.len > 0) {
usage.add("<command>");
}

return usage.render(command_seq) ++ "\n";
}

fn Formats(comptime T: type) type {
return std.enums.EnumFieldStruct(
std.meta.FieldEnum(T),
?[]const u8,
@as(?[]const u8, null),
);
}

fn getFormats(comptime S: type) Formats(S) {
var formats: Formats(S) = .{};

if (@hasDecl(S, "formats")) {
const F = @TypeOf(S.formats);
if (@typeInfo(F) != .Struct) meta.compileError(
"formats is not a struct value: {s}",
.{@typeName(F)},
);

for (@typeInfo(F).Struct.fields) |format| {
if (!@hasField(S, format.name)) meta.compileError(
"format name does not match any field: {s}",
.{format.name},
);

const format_val = @field(S.formats, format.name);
@field(formats, format.name) =
@as([]const u8, format_val); // format must be a string
}
}

return formats;
}

test generate {
const Flags = struct {
pub const description =
Expand Down

0 comments on commit c265220

Please sign in to comment.