Closed aDogCalledSpot closed 1 year ago
When using bpaf
with combinatoric API you start by defining a consumer - let's say a positional and apply extra transformations, fallbacks, help, etc on top of that:
fn jobs() -> impl Parser<usize> {
positional("JOBS")
.help("number of jobs to use to compile the project")
.fallback(10)
}
In derive API those parsers are defined inline based on field type, if name is present, etc, so this derives a combination from 3 different flags: --project-a
.. --project-c
:
#[derive(Debug, Clone, Bpaf)]
pub enum Project {
ProjectA,
ProjectB,
ProjectC,
}
Generated code is something like
fn project() -> impl Parser<Project> {
let proj_a = long("project_a").req_flag(Project::ProjectA);
let proj_b = long("project_b").req_flag(Project::ProjectB);
let proj_c = long("project_c").req_flag(Project::ProjectC);
construct!([proj_a, proj_b, proj_c]))
}
external
lets you to to start a chain by calling a function instead of using derived or specified consumer, it's more obvious when you have a struct:
struct Foo {
field_1: usize,
#[bpaf(external(get_field_2))]
field_2: usize,
}
generates something like this:
fn foo() -> impl Parser<Foo> {
let field_1 = long("field-1").argument("ARG");
let field_2 = get_field_2(); // <- comes from external attribute
constuct!(Foo { field_1, field_2 }))
}
And you can't really mix several consumers or consumers with external since they need to start this chain and either external
or one of the consumers (positional
, argument
, etc) needs to be first.
Now back to your problem. If you want to consume project name positionally all you need to do is to define a FromStr
instance for it. You can do it either by hand or using EnumString
macro in strum
crate and the magic of positional
will take care of the rest.
// projects.rs
use strum::EnumString
#[derive(Debug, Clone, EnumString)]
pub enum Project {
ProjectA,
ProjectB,
ProjectC,
}
// build.rs
use bpaf::*;
use crate::projects::Project;
#[derive(Debug, Clone, Bpaf)]
pub struct Args {
#[bpaf(positional("PROJECT")]
project: Project,
}
Wow! Thanks for the detailed answer and quick reply! I already had the FromStr
since I needed it for the combinatoric approach anyway. This cleans everything up a lot.
I'm currently building a project manager. I have multiple operations that are defined for the same set of projects. For example,
build
andclean
(amongst many others) which all take a project as a positional argument.I would like to save the possible projects as an enum in a top-level file so I can access it in both my
build.rs
andclean.rs
.This currently does not work because external always has to be first but positional, being part of "consumer" needs to come before external.
It can be worked around using the combinatoric API, but it would be great to see this limitation removed in the future.