-
Notifications
You must be signed in to change notification settings - Fork 71
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
CLI redesign (#58, #134, #140) #144
Conversation
# Conflicts: # src/parser/basic.rs # src/writer/basic.rs # src/writer/normalized.rs
@tyranron I've finished implementing composable
let (custom_cli, cucumber) = World::cucumber().cli::<CustomCli>();
cucumber.filter_run_and_exit(input, |_, _, _| {
// reuse custom_cli
})
.await;
let (custom_cli, fut) = World::cucumber()
.filter_run_and_exit_with_additional_cli::<CustomCli>(input, |_, _, _| {
// no way to reuse custom_cli
});
fut.await;
let cli::Compose { left: custom_cli, right: cucumber_cli } =
cli::Compose<CustomCli, cli::Opts<_, _, _>>::from_args();
World::cucumber()
// additional setup
.filter_run_and_exit_with_cli(cucumber_cli, input, |_, _, _| {
// reuse custom_cli
})
.await; I would go with this third one, as the situation, where you would like to expand |
@ilslv why cannot we parametrize So, the Regarding the interfaces, I imagine something like this: World::cucumber()
.extra_cli::<MyCustomCli>()
.filter_run_and_exit(input, |_, _, _, cli| {
// use cli
})
.await; |
@tyranron this can be problematic, when users want to use those options outside |
@ilslv can you provide a code example? |
#[derive(StructOpt)]
struct MaxThreads {
#[structopt(long)]
max_threads: Option<usize>,
}
fn main() {
let (custom, cli) =
cli::Compose::<MaxThreads, cli::Opts<_, _, _>>::from_args()
.into_inner();
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.worker_threads(custom.max_thread.unwrap_or(10))
.build()
.unwrap()
.block_on(async {
World::cucumber()
.run_and_exit_with_cli(cli, "features")
.await;
})
} |
@ilslv what about something like this? #[derive(StructOpt)]
struct MaxThreads {
#[structopt(default_value = "10")]
max_threads: usize,
}
fn main() {
let opts = cli::Opts<MaxThreads, _, _, _>>::from_args();
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.worker_threads(opts.extra.max_thread)
.build()
.unwrap()
.block_on(async move {
World::cucumber()
.with_cli(opts)
.run_and_exit("features/")
.await;
})
} |
@tyranron I don't quite understand why we would use that api, as it can introduce redundant #[derive(StructOpt)]
struct MaxThreads {
#[structopt(default_value = "10")]
max_threads: usize,
#[structopt(default_value = "thread")]
thread_name: String,
}
fn main() {
let opts = cli::Opts<MaxThreads, _, _, _>>::from_args();
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.worker_threads(opts.extra.max_thread)
.thread_name(opts.thread_name.clone())
// ^ redundant
.build()
.unwrap()
.block_on(async move {
World::cucumber()
.with_cli(opts)
// ^ no way to access `max_threads` or `thread_name`
.run_and_exit("features/")
.await;
})
} |
@ilslv I don't take that as an argument, because despite your example, an user may want to use it both outside and inside And I don't see problems with cloning on setup. This is not a hot path. Trying to zerocopying here is kinda overcarring 😅 |
FCM
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ilslv design is neat and good 👍 Polishing and bikeshedding is still required, though.
tests/wait.rs
Outdated
@@ -18,7 +21,7 @@ async fn main() { | |||
.after(|_, _, _, _| { | |||
time::sleep(Duration::from_millis(10)).boxed_local() | |||
}) | |||
.run_and_exit("tests/features/wait"); | |||
.run_and_exit_with_cli(cli, "tests/features/wait"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait, why do we do it here? This should'nt be required. .run_and_exit("tests/features/wait");
shoud work just fine without a need to explicitly specify cli::Empty
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tyranron wait
test just became some kind of proving ground for me, where we can toss all available features, whether we need them or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ilslv ok, but let's roll out something custom then, instead of cli::Empty
.
src/cucumber.rs
Outdated
/// [`Step`]: gherkin::Step | ||
pub async fn run_and_exit_with_cli<Cli>( | ||
self, | ||
cli: cli::Opts<Cli, P::CLI, R::CLI, Wr::CLI>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We better place it inside Cucumber
struct, so instead of introducing the whole new set of *run*_with_cli()
methods, we will need the only .with_cli()
when building and before running.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've described problem with this approach in point 1 here. We can't just use cli::Opts<Cli, P::CLI, R::CLI, Wr::CLI>
, because Cucumber
generic parameters are bound inside impls, not directly on struct. This allows us to have impls like
impl<W> Cucumber<W, (), (), (), ()> {
/// Creates an empty [`Cucumber`] executor.
///
/// Use [`Cucumber::with_parser()`], [`Cucumber::with_runner()`] and
/// [`Cucumber::with_writer()`] to be able to [`Cucumber::run()`] it.
#[must_use]
pub const fn custom() -> Self {
Self {
parser: (),
runner: (),
writer: (),
_world: PhantomData,
_parser_input: PhantomData,
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ilslv I don't see any problems to bound them in a struct, though. In this case it would be quite handy.
src/parser/basic.rs
Outdated
#[cfg_attr(doc, doc = "CLI options of [`Basic`].")] | ||
#[allow(missing_debug_implementations)] | ||
#[derive(StructOpt)] | ||
pub struct CLI { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Abbrevs still do better have PascalCase
style, not UPPERCASE
.
src/parser/basic.rs
Outdated
pub struct CLI { | ||
/// Feature-files glob pattern. | ||
#[structopt(long, short, name = "glob")] | ||
pub features: Option<Walker>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should mention here default value/behavior.
src/cli.rs
Outdated
#[derive(clap::Parser, Debug)] | ||
pub struct Opts { | ||
#[derive(Debug, StructOpt)] | ||
pub struct Opts<Custom, Parser, Runner, Writer> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we still need to specify all the parameters, I think it's better to move Custom
to the last position and default it to cli::Empty
.
visible_alias = "scenario-tags", | ||
conflicts_with = "regex" | ||
)] | ||
pub tags_filter: Option<TagOperation>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This option and the one above have the same descriptions, which is quite confusing.
src/cli.rs
Outdated
)] | ||
#[cfg_attr(doc, doc = "Composes two [`StructOpt`] derivers together.")] | ||
#[derive(Debug, StructOpt)] | ||
pub struct Compose<L, R> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need it anymore.
If it's still may be useful in some way for library users, it should have an example in its documentation (or on a module level), describing the case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left it because it can be very useful for custom Writer
wrappers. They can store their own cli options in addition to inner Writer
s.
src/cli.rs
Outdated
@@ -10,29 +10,106 @@ | |||
|
|||
//! CLI options. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to have a whole CLI idea description/overview on module level docs.
@tyranron should we upgrade to |
Not in terms of this PR. I plan to do it separately after merging this. There will be quite a stuff to do. |
# Conflicts: # codegen/src/lib.rs # src/lib.rs
tests/wait.rs
Outdated
async move { | ||
w.0 = 0; | ||
time::sleep(Duration::from_millis(10)).await; | ||
time::sleep(time).await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Disjoint capture really helped here 🎉
pub struct Cli { | ||
/// `.feature` files glob pattern. | ||
#[structopt(long, short, name = "glob")] | ||
pub features: Option<Walker>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolves #58, #134, #140
Synopsis
In
v0.10.0
CLI
was preserved as is with error messages for unsupported options. But we want to rework it for more flexibility.Solution
Redesign
CLI
and make it composable.Checklist
Draft:
prefixDraft:
prefix is removed