Closed GigaGrunch closed 2 months ago
This is something that Tigerbeetle's lib does quite nicely:
pos: struct { flag: bool = false, positional: struct {
p1: []const u8,
p2: []const u8,
p3: ?u32 = null,
p4: ?u32 = null,
} },
This seems like a good solution.
I think I should have done this in the first place since the user can still iterate over the remaining arguments after parsing, so there's no need to collect them into a buffer like we do...
Thanks for your interest. I should be able to do this pretty easily when I get round to it. I've been neglecting this library a bit though so I might get sidetracked by chores :sweat_smile:.
I think I should have done this in the first place since the user can still iterate over the remaining arguments after parsing, so there's no need to collect them into a buffer like we do...
On second thought, if we have an optional positional then we can't tell if a a given positional will match its type until after it is consumed, then the user could not access it via the iterator after parsing.
This could be an error case, but what if you want to allow an unknown number of positionals afterwards (e.g file paths)?
The positional struct could have a special optional 'rest' field of [][]u8
, so once all the required positionals have been matched and the optional positionals stop being matched, the rest are stored in this field if it exists, otherwise it's an error.
const Flags = struct {
flag: bool,
positionals: struct {
// At least 1 positional is required and the first will be stored here:
required: []const u8,
// If there is a second positional argument and it can be parsed to an `i32`, it will be stored here,
// otherwise it will be appended to `rest`
opt_num: ?i32,
// If there is a third positional argument _and_ `opt_num` was successfully parsed to an `i32`
// it will be stored here, otherwise it will be appended to `rest`
third: ?[]const u8
rest: [][]u8,
},
};
Thoughts?
... You could also have
positionals: [][]u8
If you don't need any "defined" positionals.
In any case I want to replace the current "chuck all the positional args back to the user" approach to give more control.
Thanks for looking into this! For my current use case, the tigerbeetle way would definitely be sufficient, but I see that you still want to have that "rest" list for stuff like a list of files. I really like the idea that the rest is optional so that I can just not have it to define an exact list of positionals. It wouldn't even need to be bound to that specific name, right? As soon as its a [][]u8
, nothing can come after it.
Yes, the user should be able to pick a name.
I'm going to write a post on Ziggit to work out my ideas and request suggestions/feedback from other users.
FYI: This is the project where I am planning to use flags in. Right now, I went with zli because it has the positional argument feature that I need, but I would still switch to flags when possible. I also have a branch where flags is integrated. https://github.com/GigaGrunch/trecker
I've been working on handling positional arguments more flexibly, and what I've come up with is basically passing an interface (similar to std.io.AnyWriter
or something) to the parse function which tells it how to handle them. This facilitates collecting positionals via buffer, allocator and causing an error if you don't want to handle them - and there are also wrapper functions for each of these cases. You can check out the latest examples (particularly positionals.zig
). The API ideas above are a bit idealistic but something like it could be worked towards, I have some ideas.
This doesn't directly address your use case though so that's what I'm going to implement next.
Cool! I'm happy with this and will be using it in my project now 👍 I'll open a separate issue for improved help texts for positional arguments.
Currently, positional arguments are just a list of strings and it's up to the user to deal with those. It would be nice to be able to define required positional arguments with names and types.