Skip to content

Commit

Permalink
refactor: positonals type now depend on the number of values taken
Browse files Browse the repository at this point in the history
For `Values.one` positionals will be of type `?T`

For `Values.many` positonals will be of type `[]const T`
  • Loading branch information
Hejsil committed Oct 22, 2024
1 parent e73b56a commit 94fe309
Showing 1 changed file with 94 additions and 35 deletions.
129 changes: 94 additions & 35 deletions clap.zig
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,7 @@ pub fn Result(
) type {
return struct {
args: Arguments(Id, params, value_parsers, .slice),
positionals: []const FindPositionalType(Id, params, value_parsers),
positionals: Positionals(Id, params, value_parsers, .slice),
exe_arg: ?[]const u8,
arena: std.heap.ArenaAllocator,

Expand Down Expand Up @@ -718,11 +718,13 @@ pub fn parseEx(
opt: ParseOptions,
) !ResultEx(Id, params, value_parsers) {
const allocator = opt.allocator;
const Positional = FindPositionalType(Id, params, value_parsers);

var positionals = std.ArrayList(Positional).init(allocator);
var positional_count: usize = 0;
var positionals = initPositionals(Positionals(Id, params, value_parsers, .list));
errdefer deinitPositionals(&positionals, allocator);

var arguments = Arguments(Id, params, value_parsers, .list){};
errdefer deinitArgs(Id, params, allocator, &arguments);
errdefer deinitArgs(&arguments, allocator);

var stream = streaming.Clap(Id, std.meta.Child(@TypeOf(iter))){
.params = params,
Expand Down Expand Up @@ -753,9 +755,14 @@ pub fn parseEx(
},
},
.positional => {
try positionals.append(try parser(arg.value.?));
if (opt.terminating_positional <= positionals.items.len - 1)
switch (@typeInfo(@TypeOf(positionals))) {
.optional => positionals = try parser(arg.value.?),
else => try positionals.append(allocator, try parser(arg.value.?)),
}
if (opt.terminating_positional <= positional_count)
break :arg_loop;

positional_count += 1;
},
}
}
Expand All @@ -775,9 +782,15 @@ pub fn parseEx(
}
}

// We are done parsing, but our positionals are stored in lists, and not slices.
const result_positionals = switch (@typeInfo(@TypeOf(positionals))) {
.optional => positionals,
else => try positionals.toOwnedSlice(allocator),
};

return ResultEx(Id, params, value_parsers){
.args = result_args,
.positionals = try positionals.toOwnedSlice(),
.positionals = result_positionals,
.allocator = allocator,
};
}
Expand All @@ -790,23 +803,46 @@ pub fn ResultEx(
) type {
return struct {
args: Arguments(Id, params, value_parsers, .slice),
positionals: []const FindPositionalType(Id, params, value_parsers),
positionals: Positionals(Id, params, value_parsers, .slice),
allocator: std.mem.Allocator,

pub fn deinit(result: *@This()) void {
deinitArgs(Id, params, result.allocator, &result.args);
result.allocator.free(result.positionals);
deinitArgs(&result.args, result.allocator);
deinitPositionals(&result.positionals, result.allocator);
}
};
}

fn FindPositionalType(
fn Positionals(
comptime Id: type,
comptime params: []const Param(Id),
comptime value_parsers: anytype,
comptime multi_arg_kind: MultiArgKind,
) type {
const pos = findPositional(Id, params) orelse return []const u8;
return ParamType(Id, pos, value_parsers);
const pos = findPositional(Id, params) orelse return ?void;
const T = ParamType(Id, pos, value_parsers);
if (pos.takes_value == .many)
return switch (multi_arg_kind) {
.slice => []const T,
.list => std.ArrayListUnmanaged(T),
};

return ?T;
}

fn initPositionals(comptime T: type) T {
return switch (@typeInfo(T)) {
.optional => null,
else => .{},
};
}

fn deinitPositionals(positionals: anytype, allocator: std.mem.Allocator) void {
switch (@typeInfo(@TypeOf(positionals.*))) {
.optional => {},
.@"struct" => positionals.deinit(allocator),
else => allocator.free(positionals.*),
}
}

fn findPositional(comptime Id: type, params: []const Param(Id)) ?Param(Id) {
Expand Down Expand Up @@ -835,26 +871,12 @@ fn ParamType(

/// Deinitializes a struct of type `Argument`. Since the `Argument` type is generated, and we
/// cannot add the deinit declaration to it, we declare it here instead.
fn deinitArgs(
comptime Id: type,
comptime params: []const Param(Id),
allocator: std.mem.Allocator,
arguments: anytype,
) void {
inline for (params) |param| {
const longest = comptime param.names.longest();
if (longest.kind == .positional)
continue;
if (param.takes_value != .many)
continue;

const field = @field(arguments, longest.name);

// If the multi value field is a struct, we know it is a list and should be deinited.
// Otherwise, it is a slice that should be freed.
switch (@typeInfo(@TypeOf(field))) {
.@"struct" => @field(arguments, longest.name).deinit(allocator),
else => allocator.free(@field(arguments, longest.name)),
fn deinitArgs(arguments: anytype, allocator: std.mem.Allocator) void {
inline for (@typeInfo(@TypeOf(arguments.*)).@"struct".fields) |field| {
switch (@typeInfo(field.type)) {
.int, .optional => {},
.@"struct" => @field(arguments, field.name).deinit(allocator),
else => allocator.free(@field(arguments, field.name)),
}
}
}
Expand Down Expand Up @@ -948,14 +970,51 @@ test "different assignment separators" {
try std.testing.expectEqualSlices(usize, &.{ 0, 1, 2, 3 }, res.args.aa);
}

test "single positional" {
const params = comptime parseParamsComptime(
\\<str>
\\
);

{
var iter = args.SliceIterator{ .args = &.{} };
var res = try parseEx(Help, &params, parsers.default, &iter, .{
.allocator = std.testing.allocator,
});
defer res.deinit();

try std.testing.expect(res.positionals == null);
}

{
var iter = args.SliceIterator{ .args = &.{"a"} };
var res = try parseEx(Help, &params, parsers.default, &iter, .{
.allocator = std.testing.allocator,
});
defer res.deinit();

try std.testing.expectEqualStrings("a", res.positionals.?);
}

{
var iter = args.SliceIterator{ .args = &.{ "a", "b" } };
var res = try parseEx(Help, &params, parsers.default, &iter, .{
.allocator = std.testing.allocator,
});
defer res.deinit();

try std.testing.expectEqualStrings("b", res.positionals.?);
}
}

test "everything" {
const params = comptime parseParamsComptime(
\\-a, --aa
\\-b, --bb
\\-c, --cc <str>
\\-d, --dd <usize>...
\\-h
\\<str>
\\<str>...
\\
);

Expand Down Expand Up @@ -984,7 +1043,7 @@ test "terminating positional" {
\\-c, --cc <str>
\\-d, --dd <usize>...
\\-h
\\<str>
\\<str>...
\\
);

Expand Down

0 comments on commit 94fe309

Please sign in to comment.