pacak / bpaf

Command line parser with applicative interface
337 stars 17 forks source link

Add support for env-only args for derive #344

Open vallentin opened 5 months ago

vallentin commented 5 months ago

(Thanks for bpaf it's the only command-line parser that I tested supporting adjacent commands)

This is already supported by the combinatoric API. However, there is an implicit short or long (as documented) for #[bpaf(env("VAR"))]. That behavior can obviously not be changed, I'm asking for some way to express #[bpaf(env_only("VAR"))].


I essentially have a struct EnvVars, with a bunch of env vars inside the main struct Args.

I would like bpaf derive to:


My current workaround is to mix combinatoric and derive.

Here's an example. I know it isn't pretty. But it successfully parses everything using bpaf, and all fields within EnvVars are hidden from the help text:

use bpaf::{Bpaf, Parser};

fn main() {
    let args = args().run();
    println!("{:#?}", args);
}

#[derive(Bpaf, Clone, Debug)]
#[bpaf(options)]
pub struct Args {
    #[bpaf(external(env_vars))]
    vars: EnvVars,
}

#[derive(Bpaf, Clone, Debug)]
struct EnvVars {
    #[bpaf(external(var::<0>))]
    a: Option<String>,
    #[bpaf(external(var::<1>))]
    b: Option<String>,
    #[bpaf(external(var::<2>))]
    c: Option<String>,
}

fn var<const NAME: usize>() -> impl bpaf::Parser<Option<String>> {
    const NAMES: [&str; 3] = ["A", "B", "C"];
    let name = NAMES[NAME];

    bpaf::env(name).argument(name).optional()
}

While I could manually use std::env::var() or use the envy crate, I would love if I could keep everything streamlined using bpaf.

pacak commented 5 months ago

At this point I'd implement it something like this, with or without an optional helper inside env_vars

#[derive(Clone, Debug)]
struct EnvVars {
    a: Option<String>,
    b: Option<String>,
    c: Option<String>,
}

fn env_vars() -> impl bpaf::Parser<EnvVars> {
    let a = bpaf::env("A").argument("A").optional();
    let b = bpaf::env("B").argument("B").optional();
    let c = bpaf::env("C").argument("C").optional();
    bpaf::construct!(EnvVars { a, b, c })
}

But that's a workaround as well. I need to think a bit about a better approach. Maybe special case long("") into "don't generate a named argument".


#[derive(Bpaf, Clone, Debug)]
struct EnvVars {
    #[bpaf(env, long(""))]
    a: Option<String>,
    #[bpaf(env, long(""))]
    b: Option<String>,
    #[bpaf(env, long(""))]
    c: Option<String>,
}
vallentin commented 5 months ago

I like that approach more, but yeah, like I said, it definitely wasn't pretty.

Personally, I approve of long(""), but I could definitely also see someone being confused by it initially.