From 9e29032f5b17445b56da541b7bbded30429e6584 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 26 Sep 2022 00:43:33 +0200 Subject: [PATCH] chore: upgrade deprecated clap usage (#3346) --- cli/src/cmd/cast/call.rs | 6 +++--- cli/src/cmd/cast/estimate.rs | 6 +++--- cli/src/cmd/cast/send.rs | 2 +- cli/src/cmd/cast/wallet/vanity.rs | 35 ++++++++++++++++++++++-------- cli/src/cmd/forge/cache.rs | 14 ++++++------ cli/src/cmd/forge/fmt.rs | 2 +- cli/src/cmd/forge/geiger/mod.rs | 2 +- cli/src/cmd/forge/script/mod.rs | 2 +- cli/src/cmd/forge/snapshot.rs | 6 +++--- cli/src/cmd/forge/test/mod.rs | 2 +- cli/src/cmd/forge/watch.rs | 6 +++--- cli/src/cmd/retry.rs | 7 +++--- cli/src/cmd/utils.rs | 11 ---------- cli/src/opts/cast.rs | 36 +++++++++++++++---------------- cli/src/opts/chain.rs | 5 ++--- cli/src/opts/transaction.rs | 10 ++++----- common/src/evm.rs | 4 ++-- 17 files changed, 81 insertions(+), 75 deletions(-) diff --git a/cli/src/cmd/cast/call.rs b/cli/src/cmd/cast/call.rs index d61383fdf9b4..6400ad03ff7b 100644 --- a/cli/src/cmd/cast/call.rs +++ b/cli/src/cmd/cast/call.rs @@ -17,7 +17,7 @@ use foundry_config::{Chain, Config}; #[derive(Debug, Parser)] pub struct CallArgs { - #[clap(help = "The destination of the transaction.", parse(try_from_str = parse_name_or_address), value_name = "TO")] + #[clap(help = "The destination of the transaction.", value_parser = parse_name_or_address, value_name = "TO")] to: Option, #[clap(help = "The signature of the function to call.", value_name = "SIG")] sig: Option, @@ -28,7 +28,7 @@ pub struct CallArgs { #[clap(flatten)] // TODO: We only need RPC URL and Etherscan API key here. eth: EthereumOpts, - #[clap(long, short, help = "the block you want to query, can also be earliest/latest/pending", parse(try_from_str = parse_block_id), value_name = "BLOCK")] + #[clap(long, short, help = "the block you want to query, can also be earliest/latest/pending", value_parser = parse_block_id, value_name = "BLOCK")] block: Option, #[clap(subcommand)] command: Option, @@ -50,7 +50,7 @@ pub enum CallSubcommands { long_help = r#"Ether to send in the transaction, either specified in wei, or as a string with a unit type. Examples: 1ether, 10gwei, 0.01ether"#, - parse(try_from_str = parse_ether_value), + value_parser = parse_ether_value, value_name = "VALUE" )] value: Option, diff --git a/cli/src/cmd/cast/estimate.rs b/cli/src/cmd/cast/estimate.rs index 3c83ec9b67ce..58f90bedebed 100644 --- a/cli/src/cmd/cast/estimate.rs +++ b/cli/src/cmd/cast/estimate.rs @@ -14,7 +14,7 @@ use foundry_config::{Chain, Config}; #[derive(Debug, Parser)] pub struct EstimateArgs { - #[clap(help = "The destination of the transaction.", parse(try_from_str = parse_name_or_address), value_name = "TO")] + #[clap(help = "The destination of the transaction.", value_parser = parse_name_or_address, value_name = "TO")] to: Option, #[clap(help = "The signature of the function to call.", value_name = "SIG")] sig: Option, @@ -26,7 +26,7 @@ pub struct EstimateArgs { long_help = r#"Ether to send in the transaction, either specified in wei, or as a string with a unit type. Examples: 1ether, 10gwei, 0.01ether"#, - parse(try_from_str = parse_ether_value), + value_parser = parse_ether_value, value_name = "VALUE" )] value: Option, @@ -53,7 +53,7 @@ pub enum EstimateSubcommands { long_help = r#"Ether to send in the transaction, either specified in wei, or as a string with a unit type. Examples: 1ether, 10gwei, 0.01ether"#, - parse(try_from_str = parse_ether_value), + value_parser = parse_ether_value, value_name = "VALUE" )] value: Option, diff --git a/cli/src/cmd/cast/send.rs b/cli/src/cmd/cast/send.rs index 1ab9f81d28b2..24a85dfe2b13 100644 --- a/cli/src/cmd/cast/send.rs +++ b/cli/src/cmd/cast/send.rs @@ -11,7 +11,7 @@ use std::sync::Arc; pub struct SendTxArgs { #[clap( help = "The destination of the transaction. If not provided, you must use cast send --create.", - parse(try_from_str = parse_name_or_address), + value_parser = parse_name_or_address, value_name = "TO" )] to: Option, diff --git a/cli/src/cmd/cast/wallet/vanity.rs b/cli/src/cmd/cast/wallet/vanity.rs index 3ebb1cfdfcea..6280f3ba0e68 100644 --- a/cli/src/cmd/cast/wallet/vanity.rs +++ b/cli/src/cmd/cast/wallet/vanity.rs @@ -2,7 +2,7 @@ use crate::cmd::Cmd; use cast::SimpleCast; -use clap::Parser; +use clap::{builder::TypedValueParser, Parser}; use ethers::{ core::{k256::ecdsa::SigningKey, rand::thread_rng}, prelude::{LocalWallet, Signer}, @@ -22,11 +22,11 @@ pub struct VanityArgs { long, help = "Prefix for the vanity address.", required_unless_present = "ends-with", - validator = hex_address_validator(), + value_parser = HexAddressValidator::default(), value_name = "HEX" )] pub starts_with: Option, - #[clap(long, help = "Suffix for the vanity address.", validator = hex_address_validator(), value_name = "HEX")] + #[clap(long, help = "Suffix for the vanity address.", value_parser = HexAddressValidator::default(), value_name = "HEX")] pub ends_with: Option, #[clap( long, @@ -280,13 +280,30 @@ impl VanityMatcher for RegexMatcher { } } -/// Validates an address from cli args. -pub fn hex_address_validator() -> impl FnMut(&str) -> eyre::Result<()> { - move |v: &str| -> eyre::Result<()> { - if v.len() > 40 { - eyre::bail!("vanity patterns length exceeded. cannot be more than 40 characters") +/// Parse 40 byte addresses +#[derive(Copy, Clone, Debug, Default)] +#[non_exhaustive] +pub struct HexAddressValidator; + +impl TypedValueParser for HexAddressValidator { + type Value = String; + + fn parse_ref( + &self, + _cmd: &clap::Command, + _arg: Option<&clap::Arg>, + value: &std::ffi::OsStr, + ) -> Result { + if value.len() > 40 { + return Err(clap::Error::raw( + clap::ErrorKind::InvalidValue, + "vanity patterns length exceeded. cannot be more than 40 characters", + )) } - Ok(()) + let value = value.to_str().ok_or_else(|| { + clap::Error::raw(clap::ErrorKind::InvalidUtf8, "address must be valid utf8") + })?; + Ok(value.to_string()) } } diff --git a/cli/src/cmd/forge/cache.rs b/cli/src/cmd/forge/cache.rs index fed693b1847f..dc10cef47f36 100644 --- a/cli/src/cmd/forge/cache.rs +++ b/cli/src/cmd/forge/cache.rs @@ -1,6 +1,6 @@ //! cache command -use clap::{Parser, Subcommand}; +use clap::{builder::PossibleValuesParser, Parser, Subcommand}; use std::str::FromStr; use strum::VariantNames; @@ -16,7 +16,7 @@ pub struct CacheArgs { pub sub: CacheSubcommands, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ChainOrAll { Chain(Chain), All, @@ -43,8 +43,7 @@ pub struct CleanArgs { #[clap( env = "CHAIN", default_value = "all", - possible_value = "all", - possible_values = Chain::VARIANTS, + value_parser = chain_value_parser(), value_name = "CHAINS" )] chains: Vec, @@ -70,8 +69,7 @@ pub struct LsArgs { #[clap( env = "CHAIN", default_value = "all", - possible_value = "all", - possible_values = Chain::VARIANTS, + value_parser = chain_value_parser(), value_name = "CHAINS" )] chains: Vec, @@ -146,3 +144,7 @@ fn clean_chain_cache( } Ok(()) } + +fn chain_value_parser() -> PossibleValuesParser { + Some(&"all").into_iter().chain(Chain::VARIANTS).into() +} diff --git a/cli/src/cmd/forge/fmt.rs b/cli/src/cmd/forge/fmt.rs index fdb205672b54..4b3b7f20979a 100644 --- a/cli/src/cmd/forge/fmt.rs +++ b/cli/src/cmd/forge/fmt.rs @@ -24,7 +24,7 @@ pub struct FmtArgs { conflicts_with = "root", value_hint = ValueHint::FilePath, value_name = "PATH", - multiple = true + multiple_values = true )] paths: Vec, #[clap( diff --git a/cli/src/cmd/forge/geiger/mod.rs b/cli/src/cmd/forge/geiger/mod.rs index b915341236f6..77acf2c6587b 100644 --- a/cli/src/cmd/forge/geiger/mod.rs +++ b/cli/src/cmd/forge/geiger/mod.rs @@ -21,7 +21,7 @@ pub struct GeigerArgs { conflicts_with = "root", value_hint = ValueHint::FilePath, value_name = "PATH", - multiple = true + multiple_values = true )] paths: Vec, #[clap( diff --git a/cli/src/cmd/forge/script/mod.rs b/cli/src/cmd/forge/script/mod.rs index 3b3421dbe673..68c3c090be1b 100644 --- a/cli/src/cmd/forge/script/mod.rs +++ b/cli/src/cmd/forge/script/mod.rs @@ -161,7 +161,7 @@ pub struct ScriptArgs { long, help = "Gas price for legacy transactions, or max fee per gas for EIP1559 transactions.", env = "ETH_GAS_PRICE", - parse(try_from_str = parse_ether_value), + value_parser = parse_ether_value, value_name = "PRICE" )] pub with_gas_price: Option, diff --git a/cli/src/cmd/forge/snapshot.rs b/cli/src/cmd/forge/snapshot.rs index 31c1a68652f6..37d22a2bd10d 100644 --- a/cli/src/cmd/forge/snapshot.rs +++ b/cli/src/cmd/forge/snapshot.rs @@ -6,11 +6,11 @@ use crate::{ test, test::{Test, TestOutcome}, }, - u32_validator, Cmd, + Cmd, }, utils::STATIC_FUZZ_SEED, }; -use clap::{Parser, ValueHint}; +use clap::{builder::RangedU64ValueParser, Parser, ValueHint}; use ethers::types::U256; use eyre::Context; use forge::result::TestKindReport; @@ -83,7 +83,7 @@ pub struct SnapshotArgs { #[clap( help = "Tolerates gas deviations up to the specified percentage.", long, - validator = u32_validator(0, 100), + value_parser = RangedU64ValueParser::::new().range(0..100), value_name = "SNAPSHOT_THRESHOLD" )] tolerance: Option, diff --git a/cli/src/cmd/forge/test/mod.rs b/cli/src/cmd/forge/test/mod.rs index 5f2dc5f57e40..716e860d0349 100644 --- a/cli/src/cmd/forge/test/mod.rs +++ b/cli/src/cmd/forge/test/mod.rs @@ -102,7 +102,7 @@ pub struct TestArgs { #[clap( long, help = "Set seed used to generate randomness during your fuzz runs", - parse(try_from_str = utils::parse_u256) + value_parser = utils::parse_u256 )] pub fuzz_seed: Option, } diff --git a/cli/src/cmd/forge/watch.rs b/cli/src/cmd/forge/watch.rs index 77e4d2a6cfa5..22a76efbd7d6 100644 --- a/cli/src/cmd/forge/watch.rs +++ b/cli/src/cmd/forge/watch.rs @@ -4,7 +4,7 @@ use crate::{ cmd::forge::{build::BuildArgs, snapshot::SnapshotArgs, test::TestArgs}, utils::{self, FoundryPathExt}, }; -use clap::Parser; +use clap::{ArgAction, Parser}; use foundry_config::Config; use std::{collections::HashSet, convert::Infallible, path::PathBuf, sync::Arc}; use tracing::trace; @@ -34,7 +34,7 @@ pub struct WatchArgs { /// /// When using --poll mode, you'll want a larger duration, or risk /// overloading disk I/O. - #[clap(long = "watch-delay", forbid_empty_values = true, value_name = "DELAY")] + #[clap(long = "watch-delay", value_parser = clap::builder::NonEmptyStringValueParser::default(), value_name = "DELAY")] pub watch_delay: Option, #[clap(long = "no-restart", help = "Do not restart the command while it's still running.")] @@ -55,7 +55,7 @@ pub struct WatchArgs { value_name = "PATH", min_values = 0, multiple_values = true, - multiple_occurrences = false, + action = ArgAction::Append, help = "Watches the given files or directories for changes. If no paths are provided, the source and test directories of the project are watched." )] pub watch: Option>, diff --git a/cli/src/cmd/retry.rs b/cli/src/cmd/retry.rs index c016ba099a58..61179e9ac54b 100644 --- a/cli/src/cmd/retry.rs +++ b/cli/src/cmd/retry.rs @@ -1,5 +1,4 @@ -use crate::cmd::u32_validator; -use clap::Parser; +use clap::{builder::RangedU64ValueParser, Parser}; use foundry_utils::Retry; /// Retry config used when waiting for verification @@ -15,7 +14,7 @@ pub struct RetryArgs { long, help = "Number of attempts for retrying verification", default_value = "5", - validator = u32_validator(1, 10), + value_parser = RangedU64ValueParser::::new().range(1..=10), value_name = "RETRIES" )] pub retries: u32, @@ -24,7 +23,7 @@ pub struct RetryArgs { long, help = "Optional delay to apply inbetween verification attempts in seconds.", default_value = "5", - validator = u32_validator(0, 30), + value_parser = RangedU64ValueParser::::new().range(0..=30), value_name = "DELAY" )] pub delay: u32, diff --git a/cli/src/cmd/utils.rs b/cli/src/cmd/utils.rs index 4b0b18ab4078..d3060dd1ff36 100644 --- a/cli/src/cmd/utils.rs +++ b/cli/src/cmd/utils.rs @@ -110,17 +110,6 @@ pub fn get_cached_entry_by_name( eyre::bail!(err) } -pub fn u32_validator(min: u32, max: u32) -> impl FnMut(&str) -> eyre::Result<()> { - move |v: &str| -> eyre::Result<()> { - let v = v.parse::()?; - if v >= min && v <= max { - Ok(()) - } else { - Err(eyre::eyre!("Expected between {} and {} inclusive.", min, max)) - } - } -} - /// Returns error if constructor has arguments. pub fn ensure_clean_constructor(abi: &Abi) -> eyre::Result<()> { if let Some(constructor) = &abi.constructor { diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index f7ff0977a4cd..b975e98ad6d1 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -223,7 +223,7 @@ Examples: #[clap(visible_aliases = &["ac", "acl"])] #[clap(about = "Create an access list for a transaction.")] AccessList { - #[clap(help = "The destination of the transaction.", parse(try_from_str = parse_name_or_address), value_name = "ADDRESS")] + #[clap(help = "The destination of the transaction.", value_parser = parse_name_or_address, value_name = "ADDRESS")] address: NameOrAddress, #[clap(help = "The signature of the function to call.", value_name = "SIG")] sig: String, @@ -234,7 +234,7 @@ Examples: short = 'B', help = "The block height you want to query at.", long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", - parse(try_from_str = parse_block_id), + value_parser = parse_block_id, value_name = "BLOCK" )] block: Option, @@ -251,7 +251,7 @@ Examples: #[clap( help = "The block height you want to query at.", long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", - parse(try_from_str = parse_block_id), + value_parser = parse_block_id, value_name = "BLOCK" )] block: BlockId, @@ -320,7 +320,7 @@ Examples: rpc_url: Option, #[clap(help = "The deployer address.", value_name = "ADDRESS")] address: String, - #[clap(long, help = "The nonce of the deployer address.", parse(try_from_str = parse_u256), value_name = "NONCE")] + #[clap(long, help = "The nonce of the deployer address.", value_parser = parse_u256, value_name = "NONCE")] nonce: Option, }, #[clap(name = "namehash")] @@ -516,7 +516,7 @@ Tries to decode the calldata using https://sig.eth.samczsun.com unless --offline short = 'B', help = "The block height you want to query at.", long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", - parse(try_from_str = parse_block_id), + value_parser = parse_block_id, value_name = "BLOCK" )] block: Option, @@ -532,11 +532,11 @@ Tries to decode the calldata using https://sig.eth.samczsun.com unless --offline short = 'B', help = "The block height you want to query at.", long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", - parse(try_from_str = parse_block_id), + value_parser = parse_block_id, value_name = "BLOCK" )] block: Option, - #[clap(help = "The account you want to query", parse(try_from_str = parse_name_or_address), value_name = "WHO")] + #[clap(help = "The account you want to query", value_parser = parse_name_or_address, value_name = "WHO")] who: NameOrAddress, #[clap(short, long, env = "ETH_RPC_URL", value_name = "URL")] rpc_url: Option, @@ -550,7 +550,7 @@ Tries to decode the calldata using https://sig.eth.samczsun.com unless --offline short = 'B', help = "The block height you want to query at.", long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", - parse(try_from_str = parse_block_id), + value_parser = parse_block_id, value_name = "BLOCK" )] block: Option, @@ -566,11 +566,11 @@ Tries to decode the calldata using https://sig.eth.samczsun.com unless --offline short = 'B', help = "The block height you want to query at.", long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", - parse(try_from_str = parse_block_id), + value_parser = parse_block_id, value_name = "BLOCK" )] block: Option, - #[clap(help = "The contract address.", parse(try_from_str = parse_name_or_address), value_name = "WHO")] + #[clap(help = "The contract address.", value_parser = parse_name_or_address, value_name = "WHO")] who: NameOrAddress, #[clap(short, long, env = "ETH_RPC_URL", value_name = "URL")] rpc_url: Option, @@ -621,9 +621,9 @@ Tries to decode the calldata using https://sig.eth.samczsun.com unless --offline about = "Get the raw value of a contract's storage slot." )] Storage { - #[clap(help = "The contract address.", parse(try_from_str = parse_name_or_address), value_name = "ADDRESS")] + #[clap(help = "The contract address.", value_parser = parse_name_or_address, value_name = "ADDRESS")] address: NameOrAddress, - #[clap(help = "The storage slot number (hex or decimal)", parse(try_from_str = parse_slot), value_name = "SLOT")] + #[clap(help = "The storage slot number (hex or decimal)", value_parser = parse_slot, value_name = "SLOT")] slot: H256, #[clap(short, long, env = "ETH_RPC_URL", value_name = "URL")] rpc_url: Option, @@ -632,7 +632,7 @@ Tries to decode the calldata using https://sig.eth.samczsun.com unless --offline short = 'B', help = "The block height you want to query at.", long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", - parse(try_from_str = parse_block_id), + value_parser = parse_block_id, value_name = "BLOCK" )] block: Option, @@ -643,9 +643,9 @@ Tries to decode the calldata using https://sig.eth.samczsun.com unless --offline about = "Generate a storage proof for a given storage slot." )] Proof { - #[clap(help = "The contract address.", parse(try_from_str = parse_name_or_address), value_name = "ADDRESS")] + #[clap(help = "The contract address.", value_parser = parse_name_or_address, value_name = "ADDRESS")] address: NameOrAddress, - #[clap(help = "The storage slot numbers (hex or decimal).", parse(try_from_str = parse_slot), value_name = "SLOTS")] + #[clap(help = "The storage slot numbers (hex or decimal).", value_parser = parse_slot, value_name = "SLOTS")] slots: Vec, #[clap(short, long, env = "ETH_RPC_URL", value_name = "URL")] rpc_url: Option, @@ -654,7 +654,7 @@ Tries to decode the calldata using https://sig.eth.samczsun.com unless --offline short = 'B', help = "The block height you want to query at.", long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", - parse(try_from_str = parse_block_id), + value_parser = parse_block_id, value_name = "BLOCK" )] block: Option, @@ -668,11 +668,11 @@ Tries to decode the calldata using https://sig.eth.samczsun.com unless --offline short = 'B', help = "The block height you want to query at.", long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", - parse(try_from_str = parse_block_id), + value_parser = parse_block_id, value_name = "BLOCK" )] block: Option, - #[clap(help = "The address you want to get the nonce for.", parse(try_from_str = parse_name_or_address), value_name = "WHO")] + #[clap(help = "The address you want to get the nonce for.", value_parser = parse_name_or_address, value_name = "WHO")] who: NameOrAddress, #[clap(short, long, env = "ETH_RPC_URL", value_name = "URL")] rpc_url: Option, diff --git a/cli/src/opts/chain.rs b/cli/src/opts/chain.rs index 8eb0e9df8391..d09b426ced50 100644 --- a/cli/src/opts/chain.rs +++ b/cli/src/opts/chain.rs @@ -1,4 +1,4 @@ -use clap::Parser; +use clap::{builder::PossibleValuesParser, Parser}; use ethers::types::Chain; use strum::VariantNames; @@ -11,8 +11,7 @@ pub struct ClapChain { long = "chain", env = "CHAIN", default_value = "mainnet", - // if Chain implemented ArgEnum, we'd get this for free - possible_values = Chain::VARIANTS, + value_parser = PossibleValuesParser::from(Chain::VARIANTS), value_name = "CHAIN" )] pub inner: Chain, diff --git a/cli/src/opts/transaction.rs b/cli/src/opts/transaction.rs index 32b16055631a..c0556f1e049b 100644 --- a/cli/src/opts/transaction.rs +++ b/cli/src/opts/transaction.rs @@ -9,7 +9,7 @@ pub struct TransactionOpts { long = "gas-limit", help = "Gas limit for the transaction.", env = "ETH_GAS_LIMIT", - parse(try_from_str = parse_u256), + value_parser = parse_u256, value_name = "GAS_LIMIT" )] pub gas_limit: Option, @@ -18,7 +18,7 @@ pub struct TransactionOpts { long = "gas-price", help = "Gas price for legacy transactions, or max fee per gas for EIP1559 transactions.", env = "ETH_GAS_PRICE", - parse(try_from_str = parse_ether_value), + value_parser = parse_ether_value, value_name = "PRICE" )] pub gas_price: Option, @@ -27,7 +27,7 @@ pub struct TransactionOpts { long = "priority-gas-price", help = "Max priority fee per gas for EIP1559 transactions.", env = "ETH_PRIORITY_GAS_PRICE", - parse(try_from_str = parse_ether_value), + value_parser = parse_ether_value, value_name = "PRICE" )] pub priority_gas_price: Option, @@ -38,7 +38,7 @@ pub struct TransactionOpts { long_help = r#"Ether to send in the transaction, either specified in wei, or as a string with a unit type. Examples: 1ether, 10gwei, 0.01ether"#, - parse(try_from_str = parse_ether_value), + value_parser = parse_ether_value, value_name = "VALUE" )] pub value: Option, @@ -46,7 +46,7 @@ Examples: 1ether, 10gwei, 0.01ether"#, #[clap( long, help = "Nonce for the transaction.", - parse(try_from_str = parse_u256), + value_parser = parse_u256, value_name = "NONCE" )] pub nonce: Option, diff --git a/common/src/evm.rs b/common/src/evm.rs index 4a7037c22a4a..4a5b63b1074e 100644 --- a/common/src/evm.rs +++ b/common/src/evm.rs @@ -1,5 +1,5 @@ //! cli arguments for configuring the evm settings -use clap::Parser; +use clap::{ArgAction, Parser}; use ethers_core::types::{Address, U256}; use foundry_config::{ figment::{ @@ -91,7 +91,7 @@ pub struct EvmArgs { /// - 3: Print execution traces for failing tests /// - 4: Print execution traces for all tests, and setup traces for failing tests /// - 5: Print execution and setup traces for all tests - #[clap(long, short, parse(from_occurrences), verbatim_doc_comment)] + #[clap(long, short, verbatim_doc_comment, action = ArgAction::Count)] #[serde(skip)] pub verbosity: u8,