Skip to content

Commit

Permalink
Replace structopt with clap for CLI (#188, #155)
Browse files Browse the repository at this point in the history
Co-authored-by: Kai Ren <tyranron@gmail.com>
  • Loading branch information
theredfish and tyranron authored Dec 21, 2021
1 parent 58dd096 commit aa5dd48
Show file tree
Hide file tree
Showing 11 changed files with 78 additions and 88 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ All user visible changes to `cucumber` crate will be documented in this file. Th
- Bump up [MSRV] to 1.57 for better error reporting in `const` assertions. ([cef3d480])
- Switch to [`gherkin`] crate instead of [`gherkin_rust`]. ([rev])
- Renamed `@allow_skipped` built-in tag to `@allow.skipped`. ([#181])
- Switched CLI to `clap` from `structopt`. ([#188])

### Added

Expand Down Expand Up @@ -60,6 +61,7 @@ All user visible changes to `cucumber` crate will be documented in this file. Th
[#178]: /../../pull/178
[#181]: /../../pull/181
[#182]: /../../pull/182
[#188]: /../../pull/188
[cef3d480]: /../../commit/cef3d480579190425461ddb04a1248675248351e
[rev]: /../../commit/rev-full
[0110-1]: https://llg.cubic.org/docs/junit
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ timestamps = []
[dependencies]
async-trait = "0.1.40"
atty = "0.2.14"
clap = { version = "3.0.0-rc.7", features = ["derive"] }
console = "0.15"
derive_more = { version = "0.99.17", features = ["as_ref", "deref", "deref_mut", "display", "error", "from", "into"], default_features = false }
either = "1.6"
Expand All @@ -50,7 +51,6 @@ linked-hash-map = "0.5.3"
once_cell = { version = "1.8", features = ["parking_lot"] }
regex = "1.5"
sealed = "0.3"
structopt = "0.3.25"

# "macros" feature dependencies.
cucumber-codegen = { version = "0.11.0-dev", path = "./codegen", optional = true }
Expand Down
88 changes: 48 additions & 40 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@
use gherkin::tagexpr::TagOperation;
use regex::Regex;
use structopt::StructOpt;

use crate::writer::Coloring;

pub use clap::{Args, Parser};

// Workaround for overwritten doc-comments.
// https://github.com/TeXitoi/structopt/issues/333#issuecomment-712265332
#[cfg_attr(
Expand All @@ -51,7 +52,6 @@ and may be extended with custom CLI options additionally.
# use async_trait::async_trait;
# use cucumber::{cli, WorldInit};
# use futures::FutureExt as _;
# use structopt::StructOpt;
# use tokio::time;
#
# #[derive(Debug, WorldInit)]
Expand All @@ -68,17 +68,17 @@ and may be extended with custom CLI options additionally.
#
# #[tokio::main(flavor = "current_thread")]
# async fn main() {
#[derive(StructOpt)]
#[derive(clap::Args)]
struct CustomOpts {
/// Additional time to wait in before hook.
#[structopt(
#[clap(
long,
parse(try_from_str = humantime::parse_duration)
)]
pre_pause: Option<Duration>,
}
let opts = cli::Opts::<_, _, _, CustomOpts>::from_args();
let opts = cli::Opts::<_, _, _, CustomOpts>::parsed();
let pre_pause = opts.custom.pre_pause.unwrap_or_default();
MyWorld::cucumber()
Expand All @@ -96,18 +96,18 @@ MyWorld::cucumber()
"#
)]
#[cfg_attr(not(doc), doc = "Run the tests, pet a dog!.")]
#[derive(Debug, Clone, StructOpt)]
#[structopt(name = "cucumber", about = "Run the tests, pet a dog!.")]
#[derive(Debug, Clone, clap::Parser)]
#[clap(name = "cucumber", about = "Run the tests, pet a dog!.")]
pub struct Opts<Parser, Runner, Writer, Custom = Empty>
where
Parser: StructOpt,
Runner: StructOpt,
Writer: StructOpt,
Custom: StructOpt,
Parser: Args,
Runner: Args,
Writer: Args,
Custom: Args,
{
/// Regex to filter scenarios by their name.
#[structopt(
short = "n",
#[clap(
short = 'n',
long = "name",
name = "regex",
visible_alias = "scenario-name"
Expand All @@ -118,8 +118,8 @@ where
///
/// Note: Tags from Feature, Rule and Scenario are merged together on
/// filtering, so be careful about conflicting tags on different levels.
#[structopt(
short = "t",
#[clap(
short = 't',
long = "tags",
name = "tagexpr",
conflicts_with = "regex"
Expand All @@ -129,26 +129,41 @@ where
/// [`Parser`] CLI options.
///
/// [`Parser`]: crate::Parser
#[structopt(flatten)]
#[clap(flatten)]
pub parser: Parser,

/// [`Runner`] CLI options.
///
/// [`Runner`]: crate::Runner
#[structopt(flatten)]
#[clap(flatten)]
pub runner: Runner,

/// [`Writer`] CLI options.
///
/// [`Writer`]: crate::Writer
#[structopt(flatten)]
#[clap(flatten)]
pub writer: Writer,

/// Additional custom CLI options.
#[structopt(flatten)]
#[clap(flatten)]
pub custom: Custom,
}

impl<Parser, Runner, Writer, Custom> Opts<Parser, Runner, Writer, Custom>
where
Parser: Args,
Runner: Args,
Writer: Args,
Custom: Args,
{
/// Shortcut for [`clap::Parser::parse()`], which doesn't require the trait
/// being imported.
#[must_use]
pub fn parsed() -> Self {
<Self as clap::Parser>::parse()
}
}

/// Indication whether a [`Writer`] using CLI options supports colored output.
///
/// [`Writer`]: crate::Writer
Expand All @@ -170,14 +185,8 @@ pub trait Colored {
not(doc),
allow(missing_docs, clippy::missing_docs_in_private_items)
)]
#[derive(Clone, Copy, Debug, StructOpt)]
pub struct Empty {
/// This field exists only because [`StructOpt`] derive macro doesn't
/// support unit structs.
#[allow(dead_code)]
#[structopt(skip)]
skipped: (),
}
#[derive(Args, Clone, Copy, Debug)]
pub struct Empty;

impl Colored for Empty {}

Expand All @@ -186,7 +195,7 @@ impl Colored for Empty {}
#[cfg_attr(
doc,
doc = r#"
Composes two [`StructOpt`] derivers together.
Composes two [`clap::Args`] derivers together.
# Example
Expand All @@ -195,13 +204,12 @@ another one:
```rust
# use async_trait::async_trait;
# use cucumber::{cli, event, parser, writer, Event, World, Writer};
# use structopt::StructOpt;
#
struct CustomWriter<Wr>(Wr);
#[derive(StructOpt)]
#[derive(clap::Args)]
struct Cli {
#[structopt(long)]
#[clap(long)]
custom_option: Option<String>,
}
Expand Down Expand Up @@ -277,18 +285,18 @@ impl<Wr: writer::NonTransforming> writer::NonTransforming
not(doc),
allow(missing_docs, clippy::missing_docs_in_private_items)
)]
#[derive(Debug, StructOpt)]
pub struct Compose<L: StructOpt, R: StructOpt> {
/// Left [`StructOpt`] deriver.
#[structopt(flatten)]
#[derive(Args, Debug)]
pub struct Compose<L: Args, R: Args> {
/// Left [`clap::Args`] deriver.
#[clap(flatten)]
pub left: L,

/// Right [`StructOpt`] deriver.
#[structopt(flatten)]
/// Right [`clap::Args`] deriver.
#[clap(flatten)]
pub right: R,
}

impl<L: StructOpt, R: StructOpt> Compose<L, R> {
impl<L: Args, R: Args> Compose<L, R> {
/// Unpacks this [`Compose`] into the underlying CLIs.
#[must_use]
pub fn into_inner(self) -> (L, R) {
Expand All @@ -299,8 +307,8 @@ impl<L: StructOpt, R: StructOpt> Compose<L, R> {

impl<L, R> Colored for Compose<L, R>
where
L: Colored + StructOpt,
R: Colored + StructOpt,
L: Args + Colored,
R: Args + Colored,
{
fn coloring(&self) -> Coloring {
// Basically, founds "maximum" `Coloring` of CLI options.
Expand Down
30 changes: 14 additions & 16 deletions src/cucumber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ use std::{

use futures::{future::LocalBoxFuture, StreamExt as _};
use regex::Regex;
use structopt::{StructOpt, StructOptInternal};

use crate::{
cli, event, parser, runner, step, tag::Ext as _, writer, Event, Parser,
Expand Down Expand Up @@ -54,7 +53,7 @@ where
P: Parser<I>,
R: Runner<W>,
Wr: Writer<W>,
Cli: StructOpt,
Cli: clap::Args,
{
/// [`Parser`] sourcing [`Feature`]s for execution.
///
Expand Down Expand Up @@ -88,7 +87,7 @@ where
P: Parser<I>,
R: Runner<W>,
Wr: Writer<W>,
Cli: StructOpt,
Cli: clap::Args,
{
/// Creates a custom [`Cucumber`] executor with the provided [`Parser`],
/// [`Runner`] and [`Writer`].
Expand Down Expand Up @@ -437,7 +436,7 @@ where
P: Parser<I>,
R: Runner<W>,
Wr: Writer<W> + for<'val> writer::Arbitrary<'val, W, String>,
Cli: StructOpt,
Cli: clap::Args,
{
/// Consider [`Skipped`] steps as [`Failed`] if their [`Scenario`] isn't
/// marked with `@allow.skipped` tag.
Expand Down Expand Up @@ -627,7 +626,7 @@ where
P: Parser<I>,
R: Runner<W>,
Wr: Writer<W> + writer::Normalized,
Cli: StructOpt + StructOptInternal,
Cli: clap::Args,
{
/// Runs [`Cucumber`].
///
Expand All @@ -645,7 +644,7 @@ where
/// using them inside [`Cucumber`].
///
/// Also, any additional custom CLI options may be specified as a
/// [`StructOpt`] deriving type, used as the last type parameter of
/// [`clap::Args`] deriving type, used as the last type parameter of
/// [`cli::Opts`].
///
/// > ⚠️ __WARNING__: Any CLI options of [`Parser`], [`Runner`], [`Writer`]
Expand All @@ -660,7 +659,6 @@ where
/// # use async_trait::async_trait;
/// # use cucumber::{cli, WorldInit};
/// # use futures::FutureExt as _;
/// # use structopt::StructOpt;
/// # use tokio::time;
/// #
/// # #[derive(Debug, WorldInit)]
Expand All @@ -677,17 +675,17 @@ where
/// #
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// #[derive(StructOpt)]
/// #[derive(clap::Args)]
/// struct CustomCli {
/// /// Additional time to wait in a before hook.
/// #[structopt(
/// #[clap(
/// long,
/// parse(try_from_str = humantime::parse_duration)
/// )]
/// before_time: Option<Duration>,
/// }
///
/// let cli = cli::Opts::<_, _, _, CustomCli>::from_args();
/// let cli = cli::Opts::<_, _, _, CustomCli>::parsed();
/// let time = cli.custom.before_time.unwrap_or_default();
///
/// MyWorld::cucumber()
Expand Down Expand Up @@ -719,7 +717,7 @@ where
cli: cli::Opts<P::Cli, R::Cli, Wr::Cli, CustomCli>,
) -> Cucumber<W, P, I, R, Wr, CustomCli>
where
CustomCli: StructOpt,
CustomCli: clap::Args,
{
let Cucumber {
parser,
Expand Down Expand Up @@ -811,7 +809,7 @@ where
runner: runner_cli,
writer: writer_cli,
..
} = self.cli.unwrap_or_else(cli::Opts::<_, _, _, _>::from_args);
} = self.cli.unwrap_or_else(cli::Opts::<_, _, _, _>::parsed);

let filter = move |feat: &gherkin::Feature,
rule: Option<&gherkin::Rule>,
Expand Down Expand Up @@ -884,7 +882,7 @@ where
P: Debug + Parser<I>,
R: Debug + Runner<W>,
Wr: Debug + Writer<W>,
Cli: StructOpt,
Cli: clap::Args,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Cucumber")
Expand Down Expand Up @@ -954,7 +952,7 @@ where
W: World,
R: Runner<W>,
Wr: Writer<W>,
Cli: StructOpt,
Cli: clap::Args,
I: AsRef<Path>,
{
/// Sets the provided language of [`gherkin`] files.
Expand All @@ -977,7 +975,7 @@ where
W: World,
P: Parser<I>,
Wr: Writer<W>,
Cli: StructOpt,
Cli: clap::Args,
F: Fn(
&gherkin::Feature,
Option<&gherkin::Rule>,
Expand Down Expand Up @@ -1176,7 +1174,7 @@ where
P: Parser<I>,
R: Runner<W>,
Wr: writer::Failure<W> + writer::Normalized,
Cli: StructOpt + StructOptInternal,
Cli: clap::Args,
{
/// Runs [`Cucumber`].
///
Expand Down
5 changes: 2 additions & 3 deletions src/parser/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ use futures::stream;
use gherkin::GherkinEnv;
use globwalk::{GlobWalker, GlobWalkerBuilder};
use itertools::Itertools as _;
use structopt::StructOpt;

use crate::feature::Ext as _;

Expand All @@ -36,11 +35,11 @@ use super::{Error as ParseError, Parser};
not(doc),
allow(missing_docs, clippy::missing_docs_in_private_items)
)]
#[derive(Debug, StructOpt)]
#[derive(Debug, clap::Parser)]
pub struct Cli {
/// Glob pattern to look for feature files with. By default, looks for
/// `*.feature`s in the path configured tests runner.
#[structopt(long = "input", short = "i", name = "glob")]
#[clap(long = "input", short = 'i', name = "glob")]
pub features: Option<Walker>,
}

Expand Down
Loading

0 comments on commit aa5dd48

Please sign in to comment.