-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Command with many + req_flag
and adjacent
still seems to conflict with top-level flags?
#325
Comments
There's a lot to unpack here, I'll go over each point at a slower pace and make corresponding tickets. What's your final goal you are trying to achieve? Add an optional command |
Yes. I'll have a github repo with the small project available soon, if you'd like I can link it here when ready.
For the enum related feature, each variant represents a different Linux capability set. I wanted the fn main() {
let args = args().run();
if let Some(Inspect { mut sets }) = args.inspect {
// Default fallback, strum feature used to get a collection of all variants
if sets.len() == 0 {
sets = CapSet::iter().collect();
}
// Call enum method to display the capabilities of each set (variant):
for set in sets {
set.list();
}
}
} $ cargo run -- inspect -bep
Bounding: ["CAP_AUDIT_WRITE", "CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_FSETID", "CAP_KILL", "CAP_MKNOD", "CAP_NET_BIND_SERVICE", "CAP_NET_RAW", "CAP_SETFCAP", "CAP_SETGID", "CAP_SETPCAP", "CAP_SETUID", "CAP_SYS_CHROOT"]
Effective: []
Permitted: [] The features of I have chosen to avoid short usage with If I want the shorts to work, I'll refactor to a command enum for |
Off-topic
|
The way I think about those attributes is that they are chained sequentially: Here's my go at implementing inspect command //! Add an optional command inspect (-a | -b | -e | -i | -p)... that takes some extra optional flags and collects them into a set
use bpaf::*;
use std::collections::BTreeSet;
// start by making a set of arguments parser will be used for capability set
#[derive(Debug, Bpaf, Clone, Eq, PartialEq, PartialOrd, Ord)]
enum CapSet {
#[bpaf(short, long)]
Ambient,
#[bpaf(short, long)]
Bounding,
#[bpaf(short, long)]
Effective,
#[bpaf(short, long)]
Inheritable,
#[bpaf(short, long)]
Permitted,
}
// Inspect command refers to capabilities using `external` - parses a single flag, then runs it as
// for as long as inner parser (here - cap_set) keeps producing values, collects results into a set
//
// Parser contains command annotation on top to make it into a subcommand parser
#[derive(Bpaf, Debug, Clone)]
#[bpaf(command)]
struct Inspect {
#[bpaf(external, collect)]
cap_set: BTreeSet<CapSet>,
}
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
struct Options {
// regular flag :)
#[bpaf(fallback(8080))]
port: usize,
// inspect gets parsed from an external command, here - inspect()
// extracts cap_set fields with `map`
// and makes the whole thing optional if it fails
#[bpaf(external, map(|f| f.cap_set), optional)]
inspect: Option<BTreeSet<CapSet>>,
}
fn main() {
println!("{:?}", options().run());
} |
The only thing it doesn't do is populating a set with all the capabilities when none passed. Ideally it should be done with a polymorphic variation of But you can perform this operation either when extracting |
This was a bug, fixed.
Getting terminal width, especially in a cross-platform way requires a bunch of external dependencies and doesn't add a lot of values so I'm assuming terminal width to be 100 and use that. Linebreak you see comes from the error message being adjusted to fit into that width. |
|
This is similar to #178 and suffers from the same problem, though not immediately. I'm already using
I might get away with adding something like |
You could use a feature gate if that was a concern? Then just document what the rough overhead in release binary weight is or build time if that is worth drawing attention to? (like done here for CLI arg parsing crates)
Oh, that was kinda weird then. I guess it could be better communicated within the context of As the command docs I referenced describe consuming everything to the right, that was my expectation. I added
Fair enough, I don't have a Is it possible to emit a warning if |
Similar with usage here, I'm overriding the implicit default. So another way to look at it might be a way to configure the defaults? For the derive API, perhaps that could even be a struct that the top-level derived struct could refer to by annotation? 🤷♂️ I'm not sure how the internals work, so if that's too much trouble no worries 👍 When I explored a refactor by using an enum to represent two separate commands, it looked a bit more "noisy", but I'm not sure if you could improve on that much either?: #[derive(Bpaf)]
pub enum SubCommands {
/// Bind to a port
#[bpaf(command)]
Bind(
#[bpaf(external(bind_args))]
BindArgs
),
/// Show the capabilities available to the process
#[bpaf(command)]
Inspect(
#[bpaf(external(cap_set), collect, parse(with_fallback))]
HashSet<CapSet>
),
} I know I can collapse the inner variant value with annotation before it into a single line. I don't think that helps with readability though 😅 When it's only needing annotation for the direct inner value perhaps something like this could be viable? (for #[derive(Bpaf)]
pub enum SubCommands {
#[bpaf(
command,
inner(external(bind_args))
)]
/// Bind to a port
Bind(BindArgs),
#[bpaf(
command,
inner(external(cap_set), collect, parse(with_fallback))
)]
/// Show the capabilities available to the process
Inspect(HashSet<CapSet>),
} and with the command annotation hoisting: #[derive(Bpaf)]
#[bpaf(all(command))]
pub enum SubCommands {
#[bpaf( inner(external(bind_args)) )]
/// Bind to a port
Bind(BindArgs),
#[bpaf( inner(external(cap_set), collect, parse(with_fallback)) )]
/// Show the capabilities available to the process
Inspect(HashSet<CapSet>),
}
I think that looks nicer. For the enum group of switches ( enum CapSet {
#[bpaf(short, long)]
Ambient,
#[bpaf(short, long)]
Bounding,
#[bpaf(short, long)]
Effective,
#[bpaf(short, long)]
Inheritable,
#[bpaf(short, long)]
Permitted,
} it'd also look much nicer: #[bpaf(all(short, long))]
enum CapSet {
Ambient,
Bounding,
Effective,
Inheritable,
Permitted,
} If that's adding too much complexity for |
It's about compilation time. You need to pull at least libc and more. I don't think there's any other cli parser crates out there that do it, at least cargo happily wraps the lines if your terminal is slightly narrower than its output.
It should be possible, but having different flags to use the same name is a feature. I started working on
This doesn't add too much complexity to the derive macro itself, but makes API I must teach users to use more complex and introduces a bunch of corner cases I'm trying to avoid... |
I thought that was the purpose of cargo feature flags? To only install / compile deps when the related opt-in features are enabled? I don't mind it, I just found it a bit odd when my terminal had plenty of width at runtime, or too short of width that the text was always split to a new line at a fixed length (with the terminal wrapping to multi-line if it got too short during a resize afterwards, yet the fixed length split making that awkward regardless of terminal width). If it wasn't clear in my example, there is a enforced linebreak after "individual", even when I run the command with enough width to fit all on a single line.
Except when you get the experience like my example of a subcommand using the flag and it triggering the flag intended only before the subcommand? 🤔
In that case they're still using By conflicting I meant like the Perhaps it's less of an issue with combinatoric API, but seems easy enough to run into with Derive API?
I would disagree, it's already complex enough that it feels like guess work for where to place the annotation at times? I don't think the docs call out:
Given that, I think the
Here's a Clap example:
See how the enum of subcommands is much nicer?
That's completely fair 👍 This sort of project is not simple, I really appreciate your efforts! ❤️ If |
I've tried to navigate the docs and look for other examples of how to approach this, or if it's valid to do at all.
adjacent
annotation is meant to prevent the conflict by establishing a parsing scope where the unrelated flags cannot overlap? (docs seem to suggest this is implicit for commands to consume everything afterwards too)Reproduction
My approach with an enum AFAIK implicitly uses the
NamedArg::req_flag
"Required Flag" which seemed appropriate for this functionality. I have a group of enum variants to collect from that are treated as switches.Initial failure
Seems to be some formatting issues with the error:
supports
options (-e -b ..)
line break seems unintentional? Doesn't seem to matter what terminal width is (WSL2 Terminal)If I follow the error advice, it is parsing the
-p
forinspect
as the global/shared flag-p
/--port
, instead of--permitted
:As a workaround, I can avoid the short for
--port
, or introduce a 2nd subcommand (as--port
+--aware
are for the default command and have nothing to do with myinspect
subcommand).I get the impression that these
command
docs (OptionParser
) might be trying to indicate the issue I'm running into and how to avoid it, similar to the commonOutput
example I've seen elsewhere in the docs that suggests usingadjacent
.New observations
inspect
field inArgs
to the top, theinspect
command works without an error, but--aware
must come afterwards, while--port
cannot be used as-p
before/afterinspect
regardless (ambiguous context).--aware
is declared afterinspect
,-p
(--port
) can follow as it breaks the ambiguity?Using
adjacent
makes no difference to that, and I don't appear to be able to annotateadjacent
anywhere else (fails to compile).adjacent
with subcommandinspect
at the top ofArgs
adjacent
with subcommandinspect
at the bottom ofArgs
Interestingly,
port: u16
may have something to do with the behaviour/errors due to thefallback
instead of an Option type?I noticed that if I give
--aware
a short of-a
, this is always being treated as--aware
, even forinspect -a
(unless-a
was set prior already):That sort of behaviour with
adjacent
seems worse?port
/aware
are still grabbed eagerly despite being defined inArgs
earlier?Due to
aware
being a switch, it is more subtle than the failure I had withport
(which while optional, assumes a value when specified)Help output
If helpful for context :)
It seems the annotation is working like thisadjacent
(ParseArgument
) instead of thisadjacent
(ParseCon
)?The intent was to allow the subcommand to map a set of switches to unique enum variants that will be iterated through.
The solution I came up with above is a bit awkward looking, so it might be the wrong way about it? I could try it with
bool
switches and add some logic to create theHashSet
with enum variants manually, but I'm not sure if that'd look or work any better 😅The text was updated successfully, but these errors were encountered: