From dd8c11655395dc33853bd057a64823fe9824bf78 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Mon, 22 Nov 2021 22:31:24 +0200 Subject: [PATCH] Manually implement clap::Parser for listen command This is cumbersome, but it's the only way to support multiple occurrences of --event flag as of clap 3.0.0-beta.5. Should be fixed by the clap 3.0.0 release. --- relayer-cli/src/commands/listen.rs | 73 +++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 6 deletions(-) diff --git a/relayer-cli/src/commands/listen.rs b/relayer-cli/src/commands/listen.rs index 03e595b5d5..b6fcf9f84c 100644 --- a/relayer-cli/src/commands/listen.rs +++ b/relayer-cli/src/commands/listen.rs @@ -2,7 +2,8 @@ use alloc::sync::Arc; use core::{fmt, ops::Deref, str::FromStr}; use std::thread; -use abscissa_core::{application::fatal_error, Clap, Command, Runnable}; +use abscissa_core::{application::fatal_error, Runnable}; +use clap::{App, Arg, ArgMatches, Args, FromArgMatches}; use itertools::Itertools; use tokio::runtime::Runtime as TokioRuntime; use tracing::{error, info}; @@ -57,15 +58,12 @@ impl FromStr for EventFilter { } } -#[derive(Command, Debug, Clap)] +#[derive(Debug)] pub struct ListenCmd { /// Identifier of the chain to listen for events from chain_id: ChainId, - /// Add an event type to listen for, can be repeated. Listen for all events by default (available: Tx, NewBlock) - // FIXME: Vec is derived as multiple_values(true), not multiple_occurrences(true). - // See https://github.com/clap-rs/clap/issues/1772 - #[clap(short = 'e', long = "event", value_name = "EVENT")] + /// Event types to listen for events: Vec, } @@ -87,6 +85,69 @@ impl ListenCmd { } } +// Can't derive Clap: a Vec struct field is translated by clap_derive to an +// arg with multiple_values(true), not multiple_occurrences(true). +// Implement all the necessary traits manually instead. +// See https://github.com/clap-rs/clap/issues/1772 + +impl Args for ListenCmd { + fn augment_args(app: App<'_>) -> App<'_> { + augment_args(app, true) + } + + fn augment_args_for_update(app: App<'_>) -> App<'_> { + augment_args(app, false) + } +} + +fn augment_args(app: App<'_>, required: bool) -> App<'_> { + app.arg( + Arg::new("chain_id") + .required(required) + .about("Identifier of the chain to listen for events from") + .validator(ChainId::from_str), + ) + .arg( + Arg::new("events") + .multiple_occurrences(true) + .short('e') + .long("event") + .value_name("EVENT") + .about( + "Add an event type to listen for, can be repeated.\n\ + Listen for all events by default (available: Tx, NewBlock)", + ) + .validator(EventFilter::from_str), + ) +} + +impl FromArgMatches for ListenCmd { + fn from_arg_matches(matches: &ArgMatches) -> Option { + let chain_id = parse_chain_id(matches).expect("the required argument should be present"); + let events = parse_event_filters(matches).unwrap_or_default(); + Some(ListenCmd { chain_id, events }) + } + + fn update_from_arg_matches(&mut self, matches: &ArgMatches) { + if let Some(chain_id) = parse_chain_id(matches) { + self.chain_id = chain_id; + } + if let Some(events) = parse_event_filters(matches) { + self.events = events; + } + } +} + +fn parse_chain_id(matches: &ArgMatches) -> Option { + let val = matches.value_of("chain_id")?; + Some(ChainId::from_str(val).unwrap()) +} + +fn parse_event_filters(matches: &ArgMatches) -> Option> { + let vals = matches.values_of("events")?; + Some(vals.map(|s| EventFilter::from_str(s).unwrap()).collect()) +} + impl Runnable for ListenCmd { fn run(&self) { self.cmd()