Skip to content

bpaf_derive: allow arbitrary expression in external#450

Open
vergenzt wants to merge 1 commit into
pacak:masterfrom
vergenzt:gh-270-bpaf-derive-allow-external-expressions
Open

bpaf_derive: allow arbitrary expression in external#450
vergenzt wants to merge 1 commit into
pacak:masterfrom
vergenzt:gh-270-bpaf-derive-allow-external-expressions

Conversation

@vergenzt
Copy link
Copy Markdown

Resolves #270

Does this seem like the right approach? I'm learning Rust, found myself also wanting the feature from #270, so decided to see if I could implement it myself. 🙂

Let me know what you think!

@pacak
Copy link
Copy Markdown
Owner

pacak commented Apr 15, 2026

Yeah, this looks similar to what I'd write myself.

I ended up not implementing it since I don't really like to encourage writing more code in the non-rust portion of the language, but maybe it's not that bad...

To get this merged we'll need to update the documentation as well.

Can you explain your use case a bit more?

@vergenzt
Copy link
Copy Markdown
Author

vergenzt commented Apr 15, 2026

Yeah, this looks similar to what I'd write myself.

I ended up not implementing it since I don't really like to encourage writing more code in the non-rust portion of the language, but maybe it's not that bad...

To get this merged we'll need to update the documentation as well.

Can you explain your use case a bit more?

My use case is that I wanted a boolean parser with --<opt> and --no-<opt> flags both available; I found bpaf::batteries::toggle_flag, and it'd be nice to be able to invoke that inline without having to make a separate named function for it.

E.g.

#[bpaf(external(toggle_flag(long("<opt>"), true, long("no-<opt>"), false)))]
my_opt: bool,

As an aside... it'd be even better to have a shorthand boolean-only version of toggle_flag (in the batteries module I presume)! 🙂

E.g.

pub fn boolean_toggle(name_long: &str) -> impl Parser<Option<bool>> {
    toggle_flag(long(name_long), true, long(format!("no-{name_long}")), false)
}

Then my use case could just be

#[bpaf(external(boolean_toggle_long("<opt>")))]
my_opt: bool,

! 😄

@pacak
Copy link
Copy Markdown
Owner

pacak commented Apr 15, 2026

#[bpaf(external(boolean_toggle_long("<opt>")))]
my_opt: bool,

So here my_opt and "<opt>" are the same or can be derived from each other then?

I was thinking about the alternative attribute, external_with, that works something like this:

struct Options {
    #[bpaf(external_with(make_explicit_not))]
    /// to foo or not to foo
    foo: bool
}

fn make_explicit_not(name: &'static str, help: Option<'static str>) -> impl Parser<bool> {
    // do whatever
}

Would it work better in your case?

@vergenzt
Copy link
Copy Markdown
Author


#[bpaf(external(boolean_toggle_long("<opt>")))]

my_opt: bool,

So here my_opt and "<opt>" are the same or can be derived from each other then?

I was thinking about the alternative attribute, external_with, that works something like this:

struct Options {

    #[bpaf(external_with(make_explicit_not))]

    /// to foo or not to foo

    foo: bool

}



fn make_explicit_not(name: &'static str, help: Option<'static str>) -> impl Parser<bool> {

    // do whatever

}

Would it work better in your case?

That could work!

My only hesitation is the limitation on signature of the external function. (Why would help be needed by the callee? Could it just take name instead?) But it's not too much of an issue, more of a nitpick.

@pacak
Copy link
Copy Markdown
Owner

pacak commented Apr 22, 2026

Say I'm trying to reimplement a parser similar to dd's. (dd if=/dev/sda of=/dev/null bs=10M). With combinatoric API I'd add a function to define the dd style argument and compose enough of them to cover everything. I'll obviously want to have a unique help message, metavar, etc.

fn dd_style(name: &'static str, metavar: &'static str, help: &'static str) -> impl Parser<String> {
    ...
}

For derive implementation - I'd want to have help messages as well and this should be different help for each argument. If help lives inside of the callee - I'll need a function per agument. Not ideal.

On the other hand I already have help as a doc comment on the Options struct. This opens two approaches - helps gets attached in the callee - this limits what callee could return, so no complex structures, no combinations.

Now that I'm thinking of it - it should probably accept additional parameters, similar to what you did to pass the metavar, if needed.

Does this makes sense to you?

From your example - this won't work in the current version. long requires a static str reference.

pub fn boolean_toggle(name_long: &str) -> impl Parser<Option<bool>> {
    toggle_flag(long(name_long), true, long(format!("no-{name_long}")), false)
}

Would you be interested in adding support for this extended variant as well, plus some docs? Should be close to what you have already, just inserts implicit name and help or index and help (for tuple structs) parameters in addition to what's present.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

support function calls inside external in bpaf_derive

2 participants