-
Hello, I'm trying to use bpaf for a pipeline-type CLI (think imagemagick), meaning that (1) commands are chained and (2) command maybe abstracted as "stage" that can be "executed" on some "state". I anticipate I will have a rather large number of commands, so instead of building one large enum, I'd rather have individual, self-contained command implementation. What is a good pattern for this? To make this more concrete, here is a toy example where the state is just a single 2D point, and various commands can be chained to manipulate (translate, scale, etc) this point: Original code and compiler error (see below for corrected code)use bpaf::{construct, Bpaf, OptionParser, Parser};
use std::fmt::Debug;
/// the state that is manipulated by some pipeline stages
struct State {
x: f64,
y: f64,
}
/// when "executed", manipulate the state in some way
pub trait Stage: Debug + Clone {
fn execute(&self, state: &mut State);
}
// ====================
// some command/stage
#[derive(Clone, Debug, Bpaf)]
struct Translate {
#[bpaf(positional("TX"))]
tx: f64,
#[bpaf(positional("TY"))]
ty: f64,
}
impl Stage for Translate {
fn execute(&self, state: &mut State) {
state.x += self.tx;
state.y += self.ty;
}
}
// ====================
// some other command/stage
#[derive(Clone, Debug, Bpaf)]
struct Scale {
#[bpaf(positional("FACTOR"))]
factor: f64,
}
impl Stage for Scale {
fn execute(&self, state: &mut State) {
state.x *= self.factor;
state.y *= self.factor;
}
}
// ====================
#[derive(Debug, Clone)]
struct Options {
stages: Vec<Box<dyn Stage>>,
}
// parse a single stage
fn stage() -> impl Parser<Box<dyn Stage>> {
// ideally, there would be some place were all commands are globally registered
// then the registry content would be used here
let translate = translate().map(|t| Box::new(t) as Box<dyn Stage>);
let scale = scale().map(|s| Box::new(s) as Box<dyn Stage>);
construct!([translate, scale])
}
// parse all stages
fn options() -> OptionParser<Options> {
let stages = stage().many();
construct![Options { stages }]
}
pub fn main() {
let options = options().run();
// execute the pipeline
let mut state = State { x: 0.0, y: 0.0 };
println!("Init: x: {}, y: {}", state.x, state.y);
for stage in options.stages {
stage.execute(&mut state);
println!("Stage: x: {}, y: {}", state.x, state.y);
}
} Unfortunately, this code doesn't compile and generate some obscure (to me) error:
Is this approach somehow viable? If so, any hint as to how to fix this code? Otherwise, is there a good alternative for a similar pattern? Edit: here is the working, corrected code for reference, thanks to @pacak: use bpaf::{construct, Bpaf, OptionParser, Parser};
use std::fmt::Debug;
struct State {
x: f64,
y: f64,
}
trait Stage {
fn execute(&self, state: &mut State);
}
// ====================
#[derive(Clone, Debug, Bpaf)]
#[bpaf(command, adjacent)]
struct Translate {
#[bpaf(positional("TX"))]
tx: f64,
#[bpaf(positional("TY"))]
ty: f64,
}
impl Stage for Translate {
fn execute(&self, state: &mut State) {
state.x += self.tx;
state.y += self.ty;
}
}
// ====================
#[derive(Clone, Debug, Bpaf)]
#[bpaf(command, adjacent)]
struct Scale {
#[bpaf(positional("FACTOR"))]
factor: f64,
}
impl Stage for Scale {
fn execute(&self, state: &mut State) {
state.x *= self.factor;
state.y *= self.factor;
}
}
// ====================
struct Options {
stages: Vec<Box<dyn Stage>>,
}
fn stage() -> impl Parser<Box<dyn Stage>> {
let translate = translate().map(|t| Box::new(t) as Box<dyn Stage>);
let scale = scale().map(|s| Box::new(s) as Box<dyn Stage>);
construct!([translate, scale])
}
fn options() -> OptionParser<Options> {
let stages = stage().many();
construct!(Options { stages }).to_options()
}
pub fn main() {
let options = options().run();
let mut state = State { x: 0.0, y: 0.0 };
println!("Init: x: {}, y: {}", state.x, state.y);
for stage in options.stages {
stage.execute(&mut state);
println!("Stage: x: {}, y: {}", state.x, state.y);
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 4 replies
-
So you want a sequence of commands to operate on the same state giving you effectively |
Beta Was this translation helpful? Give feedback.
Removing
Debug + Clone
constraint fromStage
makes it trait object safe, removing#[derive(Debug, Clone)]
fromOptions
deals with unsatisfied requirements and finally thisconstruct!(Options { stages }).to_options()
makes it compile. You'll need to changeTranslate
andScale
into actual commands with#[bpaf(command)]
but that's easy.Seem to work.