Skip to content

Commit

Permalink
Implemented Empty Option handling
Browse files Browse the repository at this point in the history
- Added `allow_empty` to Options, enabling any Option to be passed without an associated Value regardless of the Value's Child Type.
- Updated `cova.parse` and `Value` to properly handle the new Empty state.
- Closed #64.
  • Loading branch information
00JCIV00 committed Dec 3, 2024
1 parent dbb9c4b commit 4e082bf
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 5 deletions.
7 changes: 6 additions & 1 deletion examples/covademo.zig
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ pub const setup_cmd: CommandT = .{
.short_name = 's',
.long_name = "string",
.alias_long_names = &.{ "text" },
.allow_empty = true,
.val = ValueT.ofType([]const u8, .{
.name = "string_val",
.description = "A string value.",
Expand Down Expand Up @@ -627,7 +628,11 @@ pub fn main() !void {
}
);
var main_opts = try main_cmd.getOpts(.{});
if (main_opts.get("string_opt")) |str_opt| {
if (main_opts.get("string_opt")) |str_opt| strOpt: {
if (str_opt.val.isEmpty()) {
log.debug("This Option was set, but intentionally left empty.", .{});
break :strOpt;
}
const opt_strs = try str_opt.val.getAllAs([]const u8);
log.debug("Option Strings (--string): {d}", .{ opt_strs.len });
for (opt_strs, 0..) |str, idx| log.debug(" {d}. {s}", .{ idx, str });
Expand Down
4 changes: 3 additions & 1 deletion src/Option.zig
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ pub fn Custom(comptime config: Config) type {
name: []const u8,
/// The Description of this Option for Usage/Help messages.
description: []const u8 = "",
/// Hide thie Command from Usage/Help messages.
/// Hide this Command from Usage/Help messages.
hidden: bool = false,

/// During parsing, mandate that THIS Option must be used in a case-sensitive manner when called by its Long Name.
Expand All @@ -236,6 +236,8 @@ pub fn Custom(comptime config: Config) type {
/// During parsing, mandate that this Option is given.
/// This will do nothing if this Option's Value has a default value.
mandatory: bool = false,
/// Allow this Option's Value to Empty even if it's not a Boolean.
allow_empty: bool = false,

// (WIP) TODO: Figure out if this is possible
///// A custom Help function to override the default `help()` function for this custom Option INSTANCE.
Expand Down
31 changes: 29 additions & 2 deletions src/Value.zig
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,14 @@ pub fn Typed(comptime SetT: type, comptime config: Config) type {
///
/// **Internal Use.**
_entry_idx: u7 = 0,
/// The Max number of Raw Argument Entries that can be provided.
/// The Max number of Raw Argument Entries that can be provided.
/// This must be between 1 to the value of `config.max_children`.
max_entries: u7 = 1,
/// Flag to determine if this Value is at max capacity for Raw Arguments.
///
/// *This should be Read-Only for library users.*
is_maxed: bool = false,
/// Delimiter Characters that can be used to split up Multi-Values or Multi-Options.
/// Delimiter Characters that can be used to split up Multi-Values or Multi-Options.
/// This is only applicable if `set_behavior = .Multi`.
arg_delims: []const u8 = config.global_arg_delims,
/// Set Behavior for this Value.
Expand All @@ -236,6 +236,11 @@ pub fn Typed(comptime SetT: type, comptime config: Config) type {
///
/// *This should be Read-Only for library users.*
is_set: bool = false,
/// Flag to determine if this Value has been set to Empty.
/// This is intended to be used w/ Options.
///
/// *This should be Read-Only for library users.*
is_empty: bool = true,

/// A Parsing Function to be used in place of the normal `parse()` for Argument Parsing for this specific Value.
/// This will be used FIRST, before `type_parse_fn` then the normal `parse()` functions are tried.
Expand Down Expand Up @@ -336,10 +341,19 @@ pub fn Typed(comptime SetT: type, comptime config: Config) type {
}
}
@constCast(self).is_maxed = self._entry_idx == self.max_entries;
@constCast(self).is_empty = false;
}
else return error.InvalidValue;
}

/// Set this Value without actual data so that it's "empty".
/// This is intended to be used with Options.
pub fn setEmpty(self: *const @This()) !void {
if (!self.is_empty) return error.NotEmpty;
if (@constCast(self).default_val) |_| @constCast(self).default_val = null;
@constCast(self).is_set = true;
}

/// Get the first Parsed and Validated value of this Value.
/// This will pull the first value from `_set_args` and should be used with the `First` or `Last` Set Behaviors.
pub fn get(self: *const @This()) !ChildT {
Expand Down Expand Up @@ -682,6 +696,13 @@ pub fn Custom(comptime config: Config) type {
inline else => |tag| try @field(self.*.generic, @tagName(tag)).set(arg),
}
}
/// Set the inner Typed Value without data so that it is "empty".
/// This is meant to be used with Options
pub fn setEmpty(self: *const @This()) !void {
switch (meta.activeTag(self.*.generic)) {
inline else => |tag| try @field(self.*.generic, @tagName(tag)).setEmpty(),
}
}

/// Set a new Argument Index for this Value.
pub fn setArgIdx(self: *const @This(), arg_idx: u8) !void {
Expand Down Expand Up @@ -774,6 +795,12 @@ pub fn Custom(comptime config: Config) type {
inline else => |tag| @field(self.*.generic, @tagName(tag)).is_set,
};
}
/// Check if the inner Typed Value is Empty.
pub fn isEmpty(self: *const @This()) bool {
return switch (meta.activeTag(self.*.generic)) {
inline else => |tag| @field(self.*.generic, @tagName(tag)).is_empty,
};
}
/// Check if the inner Typed Value has a default value.
pub fn hasDefault(self: *const @This()) bool {
return switch (meta.activeTag(self.*.generic)) {
Expand Down
11 changes: 10 additions & 1 deletion src/cova.zig
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,16 @@ fn parseOpt(args: *ArgIteratorGeneric, comptime OptionType: type, opt: *const Op
const peek_arg = args.peek();
const set_arg =
if (peek_arg == null or peek_arg.?[0] == '-') setArg: {
if (!(mem.eql(u8, opt.val.childType(), "bool") and !opt.val.hasCustomParseFn())) return error.EmptyArgumentProvidedToOption;
if (!(mem.eql(u8, opt.val.childType(), "bool"))) {
if (opt.allow_empty) {
opt.val.setEmpty() catch
log.err("The Option '{s}' has already been set.", .{ opt.name });
_ = args.next();
return;
}
else if (!opt.val.hasCustomParseFn())
return error.EmptyArgumentProvidedToOption;
}
_ = args.next();
break :setArg "true";
}
Expand Down

0 comments on commit 4e082bf

Please sign in to comment.