00JCIV00 / cova

Commands, Options, Values, Arguments. A simple yet robust cross-platform command line argument parsing library for Zig.
https://00jciv00.github.io/cova/
MIT License
110 stars 5 forks source link

I don't know what I'm doing wrong here and the error message is not helpful: #34

Closed p7r0x7 closed 1 year ago

p7r0x7 commented 1 year ago
const std = @import("std");
const cova = @import("cova");
const io = @import("std").io;
const os = @import("std").os;
const mem = @import("std").mem;
const ascii = @import("std").ascii;
pub usingnamespace @import("cova"); // Forward namespaces from the original module

const blurple = "\x1b[38;5;111m";
const butter = "\x1b[38;5;230m";
const zero = "\x1b[0m";

/// Cova configuration type identity
pub const CommandT = cova.Command.Custom(.{
    .indent_fmt = "    ",
    .subcmds_help_fmt = "{s}:\t" ++ butter ++ "{s}" ++ zero,
    .opt_config = .{
        .help_fn = struct {
            fn help(self: anytype, writer: anytype, _: mem.Allocator) !void {
                try self.usage(writer);
                try writer.print("\n{?s}{?s}{s}", .{
                    @TypeOf(self.*).indent_fmt,
                    @TypeOf(self.*).indent_fmt,
                    self.description,
                });
            }
        }.help,
        .usage_fn = struct {
            fn usage(self: anytype, writer: anytype, _: mem.Allocator) !void {
                const long_prefix = @TypeOf(self.*).long_prefix;
                try writer.print(
                    "{s}{?s} {s}\"{s}({s})\"{s} {s}",
                    .{ long_prefix, self.long_name, butter, self.val.name(), @TypeOf(self.*).val.valType(), zero, zero },
                );
            }
        }.usage,
        .allow_abbreviated_long_opts = false,
        .allow_opt_val_no_space = true,
        .opt_val_seps = "=:",
        .short_prefix = null,
        .long_prefix = "-",
    },
    .val_config = .{
        .vals_help_fmt = "{s} ({s}):\t" ++ butter ++ "{s}" ++ zero,
        .set_behavior = .Last,
        .arg_delims = ",;",
    },
});

///
pub const vpxl_cmd = command(
    "vpxl",
    "a VP9 encoder by Matt R Bonnette", 
    &.{
        command("xpsnr", "calculate XPSNR score", null, null),
        command("fssim", "calculate FastSSIM score", null, null),
    },
    &.{ 
        pathOption("mkv", "input_path", ""),
        pathOption("y4m", "input_path", ""),
        pathOption("yuv", "input_path", ""),
        pathOption("webm", "output_path", ""),
        pathOption("ivf", "output_path", ""),
        boolOption("resume", true, "Don't be dummy and disable this, this is necessary for thine happiness <3."),
    },
);

fn command(comptime name: []const u8, comptime description: []const u8, comptime sub_cmds: ?[]CommandT, comptime opts: ?[]CommandT.OptionT) CommandT {
    return .{ .name = name, .description = blurple ++ description ++ zero, .sub_cmds = sub_cmds, .opts = opts };
}

fn boolOption(comptime name: []const u8, comptime default: ?bool, comptime description: []const u8) CommandT.OptionT {
    return .{
        .name = name,
        .long_name = name,
        .description = blurple ++ description ++ zero,
        .val = CommandT.ValueT.ofType(bool, .{
            .name = "",
            .default_val = default,
            .parse_fn = struct {
                pub fn parseBool(arg: []const u8, _: mem.Allocator) !bool {
                    const T = [_][]const u8{ "1", "true", "t", "yes", "y" };
                    const F = [_][]const u8{ "0", "false", "f", "no", "n" };
                    for (T) |str| if (ascii.eqlIgnoreCase(str, arg)) return true;
                    for (F) |str| if (ascii.eqlIgnoreCase(str, arg)) return false;
                    return error.InvalidBooleanValue;
                }
            }.parseBool,
        }),
    };
}

fn pathOption(comptime name: []const u8, comptime val: []const u8, comptime description: []const u8) CommandT.OptionT {
    return .{
        .name = name,
        .long_name = name,
        .description = blurple ++ description ++ zero,
        .val = CommandT.ValueT.ofType([]const u8, .{
            .name = val ++ " ",
            .parse_fn = struct {
                pub fn parsePath(arg: []const u8, _: mem.Allocator) ![]const u8 {
                    os.access(arg, os.F_OK) catch |err| {
                        // Windows doesn't make stdin/out/err available via system path,
                        // so this will have to be handled outside Cova
                        if (mem.eql(u8, arg, "-")) return arg;
                        return err;
                    };
                    return arg;
                }
            }.parsePath,
        }),
    };
}
p7r0x7@Peroxide-2 ~/D/vpxl [2]> zig build -Drelease=true run -- -help
zig build-exe vpxl ReleaseSafe native: error: the following command failed with 1 compilation errors:
/Users/p7r0x7/zig/zig build-exe /Users/p7r0x7/Documents/vpxl/src/main.zig -I/opt/homebrew/Cellar/zimg/3.0.5/include -L/opt/homebrew/Cellar/zimg/3.0.5/lib -lzimg -OReleaseSafe --cache-dir /Users/p7r0x7/Documents/vpxl/zig-cache --global-cache-dir /Users/p7r0x7/.cache/zig --name vpxl --mod cova::/Users/p7r0x7/.cache/zig/p/122030184bbcae053f930edf0f30dd8482c4a7b6149e2fa20cb46e3a1214b6e354bf/src/cova.zig --deps cova --listen=- 
Build Summary: 0/5 steps succeeded; 1 failed (disable with --summary none)
run transitive failure
└─ run vpxl transitive failure
   ├─ zig build-exe vpxl ReleaseSafe native 1 errors
   └─ install transitive failure
      └─ install vpxl transitive failure
         └─ zig build-exe vpxl ReleaseSafe native (reused)
src/cova.zig:54:5: error: expected type '?[]Command.Custom(.{.opt_config = .{.val_config = .{.set_behavior = (enum), .arg_delims = .{ ... }, .custom_types = .{ ... }, .custom_parse_fns = null, .use_custom_bit_width_range = false, .min_int_bit_width = 1, .max_int_bit_width = 256, .help_fn = null, .usage_fn = null, .indent_fmt = null, .vals_usage_fmt = .{ ... }, .vals_help_fmt = .{ ... }}, .help_fn = (function 'help'), .usage_fn = (function 'usage'), .indent_fmt = null, .help_fmt = null, .usage_fmt = "[{c}{?c},{s}{?s} \"{s} ({s})\"]", .short_prefix = null, .long_prefix = "-", .allow_opt_val_no_space = true, .opt_val_seps = "=:", .allow_abbreviated_long_opts = false}, .val_config = .{.set_behavior = .Last, .arg_delims = ",;", .custom_types = .{  }, .custom_parse_fns = null, .use_custom_bit_width_range = false, .min_int_bit_width = 1, .max_int_bit_width = 256, .help_fn = null, .usage_fn = null, .indent_fmt = null, .vals_usage_fmt = "\"{s} ({s})\"", .vals_help_fmt = "{s} ({s}):\t\x1b[38;5;230m{s}\x1b[0m"}, .global_help_prefix = "", .help_fn = null, .usage_fn = null, .indent_fmt = "    ", .group_title_fmt = " {s}|{s}|\n", .group_sep_fmt = "{s}{s}\n", .help_header_fmt = "HELP:\n{s}COMMAND: {s}\n\n{s}DESCRIPTION: {s}\n\n", .cmd_alias_fmt = "{s}ALIAS(ES): {s}\n\n", .usage_header_fmt = "USAGE: {s} ", .subcmds_help_title_fmt = "{s}SUBCOMMANDS:\n", .opts_help_title_fmt = "{s}OPTIONS:\n", .vals_help_title_fmt = "{s}VALUES:\n", .subcmds_help_fmt = "{s}:\t\x1b[38;5;230m{s}\x1b[0m", .subcmds_usage_fmt = "'{s}'", .subcmd_alias_fmt = "[alias(es): {s}]", .max_args = 25, .sub_cmds_mandatory = true, .vals_mandatory = true})', found '*const [2]Command.Custom(.{.opt_config = .{.val_config = .{.set_behavior = (enum), .arg_delims = .{ ... }, .custom_types = .{ ... }, .custom_parse_fns = null, .use_custom_bit_width_range = false, .min_int_bit_width = 1, .max_int_bit_width = 256, .help_fn = null, .usage_fn = null, .indent_fmt = null, .vals_usage_fmt = .{ ... }, .vals_help_fmt = .{ ... }}, .help_fn = (function 'help'), .usage_fn = (function 'usage'), .indent_fmt = null, .help_fmt = null, .usage_fmt = "[{c}{?c},{s}{?s} \"{s} ({s})\"]", .short_prefix = null, .long_prefix = "-", .allow_opt_val_no_space = true, .opt_val_seps = "=:", .allow_abbreviated_long_opts = false}, .val_config = .{.set_behavior = .Last, .arg_delims = ",;", .custom_types = .{  }, .custom_parse_fns = null, .use_custom_bit_width_range = false, .min_int_bit_width = 1, .max_int_bit_width = 256, .help_fn = null, .usage_fn = null, .indent_fmt = null, .vals_usage_fmt = "\"{s} ({s})\"", .vals_help_fmt = "{s} ({s}):\t\x1b[38;5;230m{s}\x1b[0m"}, .global_help_prefix = "", .help_fn = null, .usage_fn = null, .indent_fmt = "    ", .group_title_fmt = " {s}|{s}|\n", .group_sep_fmt = "{s}{s}\n", .help_header_fmt = "HELP:\n{s}COMMAND: {s}\n\n{s}DESCRIPTION: {s}\n\n", .cmd_alias_fmt = "{s}ALIAS(ES): {s}\n\n", .usage_header_fmt = "USAGE: {s} ", .subcmds_help_title_fmt = "{s}SUBCOMMANDS:\n", .opts_help_title_fmt = "{s}OPTIONS:\n", .vals_help_title_fmt = "{s}VALUES:\n", .subcmds_help_fmt = "{s}:\t\x1b[38;5;230m{s}\x1b[0m", .subcmds_usage_fmt = "'{s}'", .subcmd_alias_fmt = "[alias(es): {s}]", .max_args = 25, .sub_cmds_mandatory = true, .vals_mandatory = true})'
    &.{
    ^
src/cova.zig:68:92: note: parameter type declared here
fn command(comptime name: []const u8, comptime description: []const u8, comptime sub_cmds: ?[]CommandT, comptime opts: ?[]CommandT.OptionT) CommandT {
                                                                                           ^~~~~~~~~~~
referenced by:
    main: src/main.zig:12:32
    callMain: /Users/p7r0x7/zig/lib/std/start.zig:583:32
    remaining reference traces hidden; use '-freference-trace' to see all reference traces
p7r0x7@Peroxide-2 ~/D/vpxl [2]>
00JCIV00 commented 1 year ago

Looks like a "constness" issue from a glance. Breaking down that error to something more human readable we get: expected type '?[]Command.Custom(...) found '*const [2]Command.Custom(...)

While a slice can be implicitly coerced from an array pointer, the constness of the inner type must be consistent between the two. When you do the &.{ command(...), command(...) } it creates two CommandT instances, but I think since they're being passed as part of a parameter, they ends up being const CommandT.

You could try changing the sub_cmds parameter to type []const CommandT and the same for opts. However, that might lead into other const issues.

The use of const in Zig is something I've had to learn over time and it may require changes in the Cova library down the road. Not sure that that affects you directly here though, as this seems to be a conflict with Zig's rules directly.

When you get a chance, try the above fix and let me know how it works out please.

p7r0x7 commented 1 year ago

It did work; I tried so many type combinations including const (but I think I tried const with a pointer that time), so I wish the error was easier decipher.

Separate confusion: valType and default_val aren't a thing, can you help me find the correct values for those?

pub fn usage(self: anytype, writer: anytype, _: mem.Allocator) !void {
    const long_prefix = @TypeOf(self.*).long_prefix;
    try writer.print(
        "{?s}{?s} {s}\"{s}({s})\"{s} {s}",
        .{ long_prefix, self.long_name, butter, self.val.name(), self.valType, zero, self.default_val },
    );
}
p7r0x7 commented 1 year ago

I'm working on implementing stuff for #27

p7r0x7 commented 1 year ago

I'm very confused as to why some values require @TypeOf(self.*). instead of just self. and I don't know enough about Zig to properly read your library.

00JCIV00 commented 1 year ago

valType() was changed to childType() recently for consistency in the Value code. Also note that it must be called as a function. default_val is not as easily exposed to the Value.Custom wrapper. You can only get the default value if you know the child Type. If so, you would do val.generic.[typename_here]. default_val (unless it's a []const u8 in which case put string). Tomorrow I can add an abstractions so that it's simply val.getDefaultAs(Type), but that will still require the child Type. I've tried fairly extensively to make this easier, but I haven't had much luck yet outside of comptime.

Values are a little weird under the hood because they have to be generic wrappers for any number of underlying child Types that are all treated as one Type. That's why the common Argument Type fields from Command and Option are all function calls in Value. Unfortunately, I haven't found a way to make a function with a dynamic return Type, which is also why childType only returns the Type Name and not the Type itself.

00JCIV00 commented 1 year ago

I'm very confused as to why some values require @TypeOf(self.*). instead of just self. and I don't know enough about Zig to properly read your library.

Would you mind sending an example? I'm guessing it has a little to do with what I said above, but I'd like to see where exactly you mean to give a better answer.

p7r0x7 commented 1 year ago

valType() was changed to childType() recently for consistency in the Value code. Also note that it must be called as a function. default_val is not as easily exposed to the Value.Custom wrapper. You can only get the default value if you know the child Type. If so, you would do val.generic.[typename_here]. default_val (unless it's a []const u8 in which case put string). Tomorrow I can add an abstractions so that it's simply val.getDefaultAs(Type), but that will still require the child Type. I've tried fairly extensively to make this easier, but I haven't had much luck yet outside of comptime.

Values are a little weird under the hood because they have to be generic wrappers for any number of underlying child Types that are all treated as one Type. That's why the common Argument Type fields from Command and Option are all function calls in Value. Unfortunately, I haven't found a way to make a function with a dynamic return Type, which is also why childType only returns the Type Name and not the Type itself.

Damn, so I need to programmatically generate usage funcs for each option, ugh...

p7r0x7 commented 1 year ago

I'm very confused as to why some values require @TypeOf(self.*). instead of just self. and I don't know enough about Zig to properly read your library.

Would you mind sending an example? I'm guessing it has a little to do with what I said above, but I'd like to see where exactly you mean to give a better answer.

pub fn usage(self: anytype, writer: anytype, _: mem.Allocator) !void {
    const long_prefix = @TypeOf(self.*).long_prefix;
    _ = long_prefix;
    try writer.print(
        "{?s}{?s} {s}\"{s}({s})\"{s} {s}",
        .{ self.long_prefix, self.long_name, butter, self.val.name(), self.val.childType(), zero, zero },
    );
}

This no longer works; removing the self. from long_prefix would fix it.

p7r0x7 commented 1 year ago

Note how this is written this way as well:

pub fn help(self: anytype, writer: anytype, _: mem.Allocator) !void {
    try self.usage(writer);
    try writer.print(
        "\n{?s}{?s}{s}",
        .{ @TypeOf(self.*).indent_fmt, @TypeOf(self.*).indent_fmt, self.description },
    );
}
p7r0x7 commented 1 year ago
const std = @import("std");
const cova = @import("cova");
const io = @import("std").io;
const os = @import("std").os;
const mem = @import("std").mem;
const ascii = @import("std").ascii;
pub usingnamespace @import("cova"); // Forward namespaces from the original module

const blurple = "\x1b[38;5;111m";
const butter = "\x1b[38;5;230m";
const zero = "\x1b[0m";

/// Cova configuration type identity
pub const CommandT = cova.Command.Custom(.{
    .indent_fmt = "    ",
    .subcmds_help_fmt = "{s}:\t" ++ butter ++ "{s}" ++ zero,
    .opt_config = .{
        .help_fn = struct {
            pub fn help(self: anytype, writer: anytype, _: mem.Allocator) !void {
                try self.usage(writer);
                try writer.print(
                    "\n{?s}{?s}{s}",
                    .{ @TypeOf(self.*).indent_fmt, @TypeOf(self.*).indent_fmt, self.description },
                );
            }
        }.help,
        .usage_fn = struct {
            pub fn usage(self: anytype, writer: anytype, _: mem.Allocator) !void {
                try writer.print(
                    "{?s}{?s} {s}\"{s}({s})\"{s}",
                    .{ @TypeOf(self.*).long_prefix, self.long_name, butter, self.val.name(), self.val.childType(), zero},
                );
            }
        }.usage,
        .allow_abbreviated_long_opts = false,
        .allow_opt_val_no_space = true,
        .opt_val_seps = "=:",
        .short_prefix = null,
        .long_prefix = "-",
    },
    .val_config = .{
        .vals_help_fmt = "{s} ({s}):\t" ++ butter ++ "{s}" ++ zero,
        .set_behavior = .Last,
        .arg_delims = ",;",
    },
});

///
pub const vpxl_cmd = command(
    "vpxl",
    "a VP9 encoder by Matt R Bonnette",
    &.{
        command("xpsnr", "calculate XPSNR score", null, &.{
            pathOption("mkv", "input_path", ""),
            pathOption("y4m", "input_path", ""),
            pathOption("yuv", "input_path", ""),
        }),
        command("fssim", "calculate FastSSIM score", null, &.{
            pathOption("mkv", "input_path", ""),
            pathOption("y4m", "input_path", ""),
            pathOption("yuv", "input_path", ""),
        }),
    },
    &.{
        pathOption("mkv", "input_path", ""),
        pathOption("y4m", "input_path", ""),
        pathOption("yuv", "input_path", ""),
        pathOption("webm", "output_path", ""),
        pathOption("ivf", "output_path", ""),
        boolOption("resume", true, "Don't be dummy and disable this, this is necessary for thine happiness <3."),
    },
);

fn command(name: []const u8, description: []const u8, sub_cmds: ?[]const CommandT, opts: ?[]const CommandT.OptionT) CommandT {
    return .{ .name = name, .description = blurple ++ description ++ zero, .sub_cmds = sub_cmds, .opts = opts };
}

fn boolOption(name: []const u8, default: bool, description: []const u8) CommandT.OptionT {
    return .{
        .name = name,
        .long_name = name,
        .description = blurple ++ description ++ zero,
        .val = CommandT.ValueT.ofType(bool, .{
            .name = "",
            .default_val = default,
            .parse_fn = struct {
                pub fn parseBool(arg: []const u8, _: mem.Allocator) !bool {
                    const T = [_][]const u8{ "1", "true", "t", "yes", "y" };
                    const F = [_][]const u8{ "0", "false", "f", "no", "n" };
                    for (T) |str| if (ascii.eqlIgnoreCase(str, arg)) return true;
                    for (F) |str| if (ascii.eqlIgnoreCase(str, arg)) return false;
                    return error.InvalidBooleanValue;
                }
            }.parseBool,
        }),
        .usage_fn = struct {
            pub fn usage(self: anytype, writer: anytype, _: mem.Allocator) !void {
                try writer.print(
                    "{?s}{?s} {s}\"{s}({s})\"{s} default: {s}",
                    .{ @TypeOf(self.*).long_prefix, self.long_name, butter, self.val.name(), self.val.childType(), zero, self.val.generic.bool.default_val },
                );
            }
        }.usage,
    };
}

fn pathOption(name: []const u8, val: []const u8, description: []const u8) CommandT.OptionT {
    return .{
        .name = name,
        .long_name = name,
        .description = blurple ++ description ++ zero,
        .val = CommandT.ValueT.ofType([]const u8, .{
            .name = val ++ " ",
            .parse_fn = struct {
                pub fn parsePath(arg: []const u8, _: mem.Allocator) ![]const u8 {
                    os.access(arg, os.F_OK) catch |err| {
                        // Windows doesn't make stdin/out/err available via system path,
                        // so this will have to be handled outside Cova
                        if (mem.eql(u8, arg, "-")) return arg;
                        return err;
                    };
                    return arg;
                }
            }.parsePath,
        }),
    };
}
p7r0x7@Peroxide-2 ~/D/vpxl [2]> zig build -Drelease=true run -- -help
zig build-exe vpxl ReleaseSafe native: error: the following command failed with 1 compilation errors:
/Users/p7r0x7/zig/zig build-exe /Users/p7r0x7/Documents/vpxl/src/main.zig -I/opt/homebrew/Cellar/zimg/3.0.5/include -L/opt/homebrew/Cellar/zimg/3.0.5/lib -lzimg -OReleaseSafe --cache-dir /Users/p7r0x7/Documents/vpxl/zig-cache --global-cache-dir /Users/p7r0x7/.cache/zig --name vpxl --mod cova::/Users/p7r0x7/.cache/zig/p/1220775674b8a20bc2919e1a9181a4dfe69b4263d74740d7d85a807874cb3fd790da/src/cova.zig --deps cova --listen=- 
Build Summary: 0/5 steps succeeded; 1 failed (disable with --summary none)
run transitive failure
└─ run vpxl transitive failure
   ├─ zig build-exe vpxl ReleaseSafe native 1 errors
   └─ install transitive failure
      └─ install vpxl transitive failure
         └─ zig build-exe vpxl ReleaseSafe native (reused)
src/cova.zig:103:11: error: no field named 'usage_fn' in struct 'Option.Custom(.{.val_config = .{.set_behavior = .Last, .arg_delims = ",;", .custom_types = .{  }, .custom_parse_fns = null, .use_custom_bit_width_range = false, .min_int_bit_width = 1, .max_int_bit_width = 256, .help_fn = null, .usage_fn = null, .indent_fmt = "    ", .vals_usage_fmt = "\"{s} ({s})\"", .vals_help_fmt = "{s} ({s}):\t\x1b[38;5;230m{s}\x1b[0m"}, .help_fn = (function 'help'), .usage_fn = (function 'usage'), .indent_fmt = "    ", .help_fmt = null, .usage_fmt = "[{c}{?c},{s}{?s} \"{s} ({s})\"]", .short_prefix = null, .long_prefix = "-", .allow_opt_val_no_space = true, .opt_val_seps = "=:", .allow_abbreviated_long_opts = false})'
        }.usage,
          ^~~~~
/Users/p7r0x7/.cache/zig/p/1220775674b8a20bc2919e1a9181a4dfe69b4263d74740d7d85a807874cb3fd790da/src/Option.zig:85:12: note: struct declared here
    return struct {
           ^~~~~~
src/cova.zig:70:19: note: called from here
        boolOption("resume", true, "Don't be dummy and disable this, this is necessary for thine happiness <3."),
        ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
referenced by:
    main: src/main.zig:12:32
    callMain: /Users/p7r0x7/zig/lib/std/start.zig:583:32
    remaining reference traces hidden; use '-freference-trace' to see all reference traces
p7r0x7@Peroxide-2 ~/D/vpxl [2]>

Here I'm trying to implement a custom usage_fn for every option, as you expressed was necessary.

p7r0x7 commented 1 year ago

cli.zig

const std = @import("std");
const cova = @import("cova");
const io = @import("std").io;
const os = @import("std").os;
const mem = @import("std").mem;
const ascii = @import("std").ascii;
pub usingnamespace @import("cova"); // Forward namespaces from the original module

const blurple = "\x1b[38;5;111m";
const butter = "\x1b[38;5;230m";
const zero = "\x1b[0m";

/// Cova configuration type identity
pub const CommandT = cova.Command.Custom(.{
    .indent_fmt = "    ",
    .subcmds_help_fmt = "{s}:\t" ++ butter ++ "{s}" ++ zero,
    .opt_config = .{
        .help_fn = struct {
            pub fn help(self: anytype, writer: anytype, _: mem.Allocator) !void {
                try self.usage(writer);
                try writer.print(
                    "\n{?s}{?s}{s}",
                    .{ @TypeOf(self.*).indent_fmt, @TypeOf(self.*).indent_fmt, self.description },
                );
            }
        }.help,
        .usage_fn = struct {
            pub fn usage(self: anytype, writer: anytype, _: mem.Allocator) !void {
                try writer.print(
                    @TypeOf(self.*).long_prefix ++ self.long_name ++ " " ++ butter ++ "\"{s} ({s})\"" ++ zero,
                    .{ self.val.name(), self.val.childType() },
                );
            }
        }.usage,
        .allow_abbreviated_long_opts = false,
        .allow_opt_val_no_space = true,
        .opt_val_seps = "=:",
        .short_prefix = null,
        .long_prefix = "-",
    },
    .val_config = .{
        .vals_help_fmt = "{s} ({s}):\t" ++ butter ++ "{s}" ++ zero,
        .set_behavior = .Last,
        .arg_delims = ",;",
    },
});

fn command(opt: []const u8, desc: []const u8, sub_cmds: ?[]const CommandT, opts: ?[]const CommandT.OptionT) CommandT {
    return .{ .name = opt, .description = blurple ++ desc ++ zero, .sub_cmds = sub_cmds, .opts = opts };
}

fn boolOption(opt: []const u8, default: bool, desc: []const u8) CommandT.OptionT {
    return .{
        .name = opt,
        .long_name = opt,
        .description = blurple ++ desc ++ zero,
        .val = CommandT.ValueT.ofType(bool, .{
            .name = "",
            .default_val = default,
            .parse_fn = struct {
                pub fn parseBool(arg: []const u8, _: mem.Allocator) !bool {
                    const T = [_][]const u8{ "1", "true", "t", "yes", "y" };
                    const F = [_][]const u8{ "0", "false", "f", "no", "n" };
                    for (T) |str| if (ascii.eqlIgnoreCase(str, arg)) return true;
                    for (F) |str| if (ascii.eqlIgnoreCase(str, arg)) return false;
                    return error.InvalidBooleanValue;
                }
            }.parseBool,
        }),
        //.usage_fn = struct {
        //    pub fn usage(self: anytype, writer: anytype, _: mem.Allocator) !void {
        //        try writer.write(@TypeOf(self.*).long_prefix ++ self.long_name ++ " " ++
        //        butter ++ "\"(" ++ self.val.childType() ++ ")\"" ++ zero ++ self.val.generic.bool.default_val);
        //    }
        //}.usage,
    };
}

fn pathOption(opt: []const u8, val: []const u8, desc: []const u8) CommandT.OptionT {
    return .{
        .name = opt,
        .long_name = opt,
        .description = desc,
        .val = CommandT.ValueT.ofType([]const u8, .{
            .name = val ++ "_path",
            .parse_fn = struct {
                pub fn parsePath(arg: []const u8, _: mem.Allocator) ![]const u8 {
                    os.access(arg, os.F_OK) catch |err| {
                        // Windows doesn't make stdin/out/err available via system path,
                        // so this will have to be handled outside Cova
                        if (mem.eql(u8, arg, "-")) return arg;
                        return err;
                    };
                    return arg;
                }
            }.parsePath,
        }),
        //.usage_fn = struct {
        //    pub fn usage(self: anytype, writer: anytype, _: mem.Allocator) !void {
        //        try writer.print(
        //            @TypeOf(self.*).long_prefix ++ self.long_name ++ " " ++ butter ++ "\"{s} ({s})\"" ++ zero,
        //            .{ self.val.name(), self.val.childType() },
        //        );
        //    }
        //}.usage,
    };
}

pub fn runVPXL(writer: anytype, ally: mem.Allocator) !void {
    const vpxl_cmd = comptime command(
        "vpxl",
        "a VP9 encoder by Matt R Bonnette",
        &.{
            command("xpsnr", "calculate XPSNR score", null, &.{
                pathOption("mkv", "input", ""),
                pathOption("y4m", "input", ""),
                pathOption("yuv", "input", ""),
            }),
            command("fssim", "calculate FastSSIM score", null, &.{
                pathOption("mkv", "input", ""),
                pathOption("y4m", "input", ""),
                pathOption("yuv", "input", ""),
            }),
        },
        &.{
            pathOption("mkv", "input", ""),
            pathOption("y4m", "input", ""),
            pathOption("yuv", "input", ""),
            pathOption("webm", "output", ""),
            pathOption("ivf", "output", ""),
            boolOption("resume", true, "Don't be dummy and disable this, this is necessary for thine happiness <3."),
        },
    );

    const vpxl_cli = &(try vpxl_cmd.init(ally, .{}));
    defer vpxl_cli.deinit();

    var arg_it = try cova.ArgIteratorGeneric.init(ally);
    defer arg_it.deinit();

    cova.parseArgs(&arg_it, CommandT, vpxl_cli, writer, .{}) catch |err| switch (err) {
        error.UsageHelpCalled,
        error.TooManyValues,
        error.UnrecognizedArgument,
        error.UnexpectedArgument,
        error.CouldNotParseOption,
        => {},
        else => return err,
    };

    const in_fmt = try vpxl_cli.matchOpts(&.{ "mkv", "y4m", "yuv" }, .{ .logic = .XOR });
    _ = in_fmt;
    const out_fmt = try vpxl_cli.matchOpts(&.{ "webm", "ivf" }, .{ .logic = .XOR });
    _ = out_fmt;

    // Handle in_fmt and out_fmt
    //if (builtin.mode == .Debug) try stderr.print("{s}\n{s}\n", .{ in_fmt[0].name, out_fmt[0].name });
    //if (builtin.mode == .Debug) try cli.utils.displayCmdInfo(cova.CommandT, vpxl_cli, ally, stderr);
}

main.zig

const std = @import("std");
const io = @import("std").io;
const cli = @import("cli.zig");
const mem = @import("std").mem;
const heap = @import("std").heap;
const builtin = @import("builtin");

pub fn main() !void {
    // Heapspace Initialization
    const ally: mem.Allocator = ally: {
        if (builtin.mode == .Debug) {
            var gpa = heap.GeneralPurposeAllocator(.{}){};
            break :ally heap.ArenaAllocator.init(gpa.allocator()).allocator();
            // arena of gpa
        } else {
            var arena = heap.ArenaAllocator.init(heap.page_allocator);
            var sfa = heap.stackFallback(8 << 20, arena.allocator());
            break :ally sfa.get();
            // stack then arena of page
        }
    };
    defer ally.deinit();

    // Connect to pipes & run VPXL's CLI
    var se = io.bufferedWriter(io.getStdErr().writer());
    const stderr = se.writer();
    try cli.runVPXL(stderr, ally);
}

Edit, please pay more attention the where I should be adding the custom usage funcs for each option than to anything else, I fixed an error from before.

00JCIV00 commented 1 year ago

Ok, I see the issue here, and it's something I could try and improve the documentation for. The usage_fn for Option.Config (along with all fields in that struct) are meant for attributes that apply to ALL instances of the generated Option Type OptionT. Because of this, the function cannot be applied to individual instances of an OptionT as you're trying above. This was the change I had mentioned making in #32 here.

Conversely, the parse_fn you're using in .val = CommandT.ValueT.ofType([]const u8, .{...}); and similar are applied directly to an instance of ValueT, so there's no need for @TypeOf(self) because it's handled by that instance directly.

Two solutions here. One, you can do a conditional statement in the function that covers the ValueT child Types you're trying to cover. Two, I can implement the change above.

p7r0x7 commented 1 year ago

I see what you're saying, but I don't know the best solution for make the default values for each option available in its usage_fn

Edit: as stated in #32, I'm going to attempt to use a global function with a well-written switch statement; frankly, I feel silly not thinking of doing that sooner (especially because you've already suggested it, I just kept failing to consider it).

00JCIV00 commented 1 year ago

No worries! I'm interested to see how your solution works out.

p7r0x7 commented 1 year ago

Closing this unconventional issue.