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

Expanding valid boolean value types. #29

Closed p7r0x7 closed 1 year ago

p7r0x7 commented 1 year ago

I believe that boolean flags should support any of 1, true, "yes", 0, false, or "no" as values, just as the character between flags and their values can be any of "", " ", or "=".

What do you think?

00JCIV00 commented 1 year ago

This is currently supported with custom parsing functions! There's even a specific Builder Function for that specific feature under Value.ParsingFunctions.Builder.altTrue() (API docs here).

Using that function, you can build a function to parse any set of words as true. There's an example in the covademo example that I've extracted below:

    ValueT.ofType(bool, .{
            .name = "cmd_bool",
            .description = "A boolean value for the command.",
            .parse_fn = Value.ParsingFns.Builder.altTrue(&.{ "true", "t", "yes", "y", "1" }),
    }),
p7r0x7 commented 1 year ago

Wow this is consistently a far more complex library than I expect. Thanks! I'll close once I've tested this.

p7r0x7 commented 1 year ago

What about alternate true and false simultaneously, is that currently accessible in the library?

00JCIV00 commented 1 year ago

What about alternate false, is that currently accessible in the library?

There's not a direct Builder Function for that, but it's completely possible by creating your own function that wraps altTrue() for the parse_fn field. Rough example would be something like:

// This can be declared directly at the parse_fn field, but declaring it before the Command setup allows you to reuse it for multiple boolean Values easily.
fn altBool(arg: []const u8) !bool {
    const altTrue = Value.ParsingFns.Builder.altTrue(&.{ "true", "strings", "list" });
    const altFalse = Value.ParsingFns.Builder.altTrue(&.{ "false", "strings", "list" });

    // Check for your true words
    if (try altTrue(arg)) return true;
    // Check for your false words
    else if (try altFalse(arg)) return false;
    // Error if the arg didn't match either list
    return error.InvalidBooleanValue;
}

// Within your Command -> Values setup
...
   ValueT.ofType(bool, .{
        .name = "cmd_bool",
        .description = "A bool that only takes specific words.",
        .parse_fn = &altBool,
    })
...
p7r0x7 commented 1 year ago
const std = @import("std");
const cova = @import("cova");
const ascii = @import("std").ascii;
pub usingnamespace @import("cova");

pub const CommandT = cova.Command.Custom(.{
    .subcmds_help_fmt = "{s}:\t" ++ blue ++ "{s}" ++ zero,
    .val_config = .{
        .vals_help_fmt = "{s} ({s}):\t" ++ blue ++ "{s}" ++ zero,
        .set_behavior = .Last,
    },
    .opt_config = .{
        .help_fn = struct {
            fn help(self: anytype, writer: anytype) !void {
                try self.usage(writer);
                try writer.print("\n{?s}{?s}{s}", .{ CommandT.indent_fmt, CommandT.indent_fmt, self.description });
            }
        }.help,
        .usage_fmt = "{c}{?c}{s}{?s} " ++ yell ++ "\"{s}({s})\"" ++ zero,
        .allow_abbreviated_long_opts = false,
        .short_prefix = null,
        .long_prefix = "-",
    },
    .indent_fmt = "    ",
});

const blue = "\x1b[34m";
const yell = "\x1b[93m";
const zero = "\x1b[0m";

pub const setup_cmd: CommandT = .{
    .name = "vpxl",
    .description = "a VP9 encoder by Matt R Bonnette",
    .opts = &.{
        .{
            .name = "mkv",
            .long_name = "mkv",
            .val = CommandT.ValueT.ofType([]const u8, .{ .name = "input_path " }),
        },
        .{
            .name = "y4m",
            .long_name = "y4m",
            .val = CommandT.ValueT.ofType([]const u8, .{ .name = "input_path " }),
        },
        .{
            .name = "yuv",
            .long_name = "yuv",
            .val = CommandT.ValueT.ofType([]const u8, .{ .name = "input_path " }),
        },
        .{
            .name = "webm",
            .long_name = "webm",
            .val = CommandT.ValueT.ofType([]const u8, .{ .name = "output_path " }),
        },
        .{
            .name = "ivf",
            .long_name = "ivf",
            .val = CommandT.ValueT.ofType([]const u8, .{ .name = "output_path " }),
        },
        .{
            .name = "resume",
            .long_name = "resume",
            .description = "A boolean value for the command.\n",
            .val = CommandT.ValueT.ofType(bool, .{
                .name = "",
                .parse_fn = &parseBool,
            }),
        },
    },
};

pub fn parseBool(arg: []const u8) !bool {
    const T = [_][]const u8{ "1", "true", "t", "yes", "y" };
    const F = [_][]const u8{ "0", "false", "f", "no", "n" };
    for (T) |value| if (ascii.eqlIgnoreCase(value, arg)) return true;
    for (F) |value| if (ascii.eqlIgnoreCase(value, arg)) return false;
    return error.InvalidBooleanValue;
}

So I need to create a custom value type for this, because your builtin bool type cannot accept values for some reason. I can probably figure this out, but I thought I should show you what I was trying to do more clearly.

00JCIV00 commented 1 year ago

"because your builtin bool type cannot accept values for some reason"

This is definitely a bug. Good find! I've fixed it and I'm pushing the commit now.

The new behavior will allow Boolean Options to accept a value if they have a custom parse_fn.

p7r0x7 commented 1 year ago

Wonderful!