From 65fe00b7addfb53a7e7ac35b9f178e615cf357a6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 28 Jul 2021 17:26:19 +0000 Subject: [PATCH] Auto-generate shell completions for solana-cli (issue #8879 and #14005) (#18931) (#18945) * Auto-generate shell completions for solana-cli (issue #8879 and #14005) Implement `completion` SubCommand for solana-cli, which outputs completion script to stdout and exits the process. The script generation handled completely by clap. In order to implement the generation, one minor design change was necessary regarding the creation of clap `App`. Previously: One part of App initialization was in the `app` function, and some other arguments and subcommands were added later directly in the `main` function. Now: The whole construction of App was moved to `get_clap_app` function. P.S. I wasn't sure if constructing App separately had visual importance, so both constructing parts are still separate in `base_clap_app` and `final_clap_app` functions. But they sure could be in one single function. * Dereplicode match expr, fix clippy warning. * Move clap App construction into separate module Also join two parts of the construction into a single function * Fix tests * Apply rustfmt lints (cherry picked from commit 9d0a937a0559f04bb8929ab8181f891189402d1d) Co-authored-by: theonekeyg <34949189+theonekeyg@users.noreply.github.com> --- cli/src/clap_app.rs | 457 ++++++++++++++++++++++++++++++++++++++ cli/src/cli.rs | 311 +++----------------------- cli/src/cluster_query.rs | 4 +- cli/src/lib.rs | 1 + cli/src/main.rs | 186 +--------------- cli/src/nonce.rs | 4 +- cli/src/program.rs | 17 +- cli/src/stake.rs | 4 +- cli/src/validator_info.rs | 4 +- cli/src/vote.rs | 4 +- 10 files changed, 514 insertions(+), 478 deletions(-) create mode 100644 cli/src/clap_app.rs diff --git a/cli/src/clap_app.rs b/cli/src/clap_app.rs new file mode 100644 index 0000000000..44109c3029 --- /dev/null +++ b/cli/src/clap_app.rs @@ -0,0 +1,457 @@ +use crate::{ + cli::*, cluster_query::*, feature::*, inflation::*, nonce::*, program::*, stake::*, + validator_info::*, vote::*, +}; +use clap::{App, AppSettings, Arg, ArgGroup, SubCommand}; +use solana_clap_utils::{ + self, fee_payer::fee_payer_arg, input_validators::*, keypair::*, memo::memo_arg, nonce::*, + offline::*, +}; +use solana_cli_config::CONFIG_FILE; + +pub fn get_clap_app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, 'v> { + App::new(name) + .about(about) + .version(version) + .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand( + SubCommand::with_name("address") + .about("Get your public key") + .arg( + Arg::with_name("confirm_key") + .long("confirm-key") + .takes_value(false) + .help("Confirm key on device; only relevant if using remote wallet"), + ), + ) + .cluster_query_subcommands() + .feature_subcommands() + .inflation_subcommands() + .nonce_subcommands() + .program_subcommands() + .stake_subcommands() + .subcommand( + SubCommand::with_name("airdrop") + .about("Request SOL from a faucet") + .arg( + Arg::with_name("amount") + .index(1) + .value_name("AMOUNT") + .takes_value(true) + .validator(is_amount) + .required(true) + .help("The airdrop amount to request, in SOL"), + ) + .arg( + pubkey!(Arg::with_name("to") + .index(2) + .value_name("RECIPIENT_ADDRESS"), + "The account address of airdrop recipient. "), + ), + ) + .subcommand( + SubCommand::with_name("balance") + .about("Get your balance") + .arg( + pubkey!(Arg::with_name("pubkey") + .index(1) + .value_name("ACCOUNT_ADDRESS"), + "The account address of the balance to check. ") + ) + .arg( + Arg::with_name("lamports") + .long("lamports") + .takes_value(false) + .help("Display balance in lamports instead of SOL"), + ), + ) + .subcommand( + SubCommand::with_name("confirm") + .about("Confirm transaction by signature") + .arg( + Arg::with_name("signature") + .index(1) + .value_name("TRANSACTION_SIGNATURE") + .takes_value(true) + .required(true) + .help("The transaction signature to confirm"), + ) + .after_help(// Formatted specifically for the manually-indented heredoc string + "Note: This will show more detailed information for finalized transactions with verbose mode (-v/--verbose).\ + \n\ + \nAccount modes:\ + \n |srwx|\ + \n s: signed\ + \n r: readable (always true)\ + \n w: writable\ + \n x: program account (inner instructions excluded)\ + " + ), + ) + .subcommand( + SubCommand::with_name("decode-transaction") + .about("Decode a serialized transaction") + .arg( + Arg::with_name("transaction") + .index(1) + .value_name("TRANSACTION") + .takes_value(true) + .required(true) + .help("transaction to decode"), + ) + .arg( + Arg::with_name("encoding") + .index(2) + .value_name("ENCODING") + .possible_values(&["base58", "base64"]) // Subset of `UiTransactionEncoding` enum + .default_value("base58") + .takes_value(true) + .required(true) + .help("transaction encoding"), + ), + ) + .subcommand( + SubCommand::with_name("create-address-with-seed") + .about("Generate a derived account address with a seed") + .arg( + Arg::with_name("seed") + .index(1) + .value_name("SEED_STRING") + .takes_value(true) + .required(true) + .validator(is_derived_address_seed) + .help("The seed. Must not take more than 32 bytes to encode as utf-8"), + ) + .arg( + Arg::with_name("program_id") + .index(2) + .value_name("PROGRAM_ID") + .takes_value(true) + .required(true) + .help( + "The program_id that the address will ultimately be used for, \n\ + or one of NONCE, STAKE, and VOTE keywords", + ), + ) + .arg( + pubkey!(Arg::with_name("from") + .long("from") + .value_name("FROM_PUBKEY") + .required(false), + "From (base) key, [default: cli config keypair]. "), + ), + ) + .subcommand( + SubCommand::with_name("deploy") + .about("Deploy a program") + .arg( + Arg::with_name("program_location") + .index(1) + .value_name("PROGRAM_FILEPATH") + .takes_value(true) + .required(true) + .help("/path/to/program.o"), + ) + .arg( + Arg::with_name("address_signer") + .index(2) + .value_name("PROGRAM_ADDRESS_SIGNER") + .takes_value(true) + .validator(is_valid_signer) + .help("The signer for the desired address of the program [default: new random address]") + ) + .arg( + Arg::with_name("use_deprecated_loader") + .long("use-deprecated-loader") + .takes_value(false) + .hidden(true) // Don't document this argument to discourage its use + .help("Use the deprecated BPF loader") + ) + .arg( + Arg::with_name("allow_excessive_balance") + .long("allow-excessive-deploy-account-balance") + .takes_value(false) + .help("Use the designated program id, even if the account already holds a large balance of SOL") + ), + ) + .subcommand( + SubCommand::with_name("resolve-signer") + .about("Checks that a signer is valid, and returns its specific path; useful for signers that may be specified generally, eg. usb://ledger") + .arg( + Arg::with_name("signer") + .index(1) + .value_name("SIGNER_KEYPAIR") + .takes_value(true) + .required(true) + .validator(is_valid_signer) + .help("The signer path to resolve") + ) + ) + .subcommand( + SubCommand::with_name("transfer") + .about("Transfer funds between system accounts") + .alias("pay") + .arg( + pubkey!(Arg::with_name("to") + .index(1) + .value_name("RECIPIENT_ADDRESS") + .required(true), + "The account address of recipient. "), + ) + .arg( + Arg::with_name("amount") + .index(2) + .value_name("AMOUNT") + .takes_value(true) + .validator(is_amount_or_all) + .required(true) + .help("The amount to send, in SOL; accepts keyword ALL"), + ) + .arg( + pubkey!(Arg::with_name("from") + .long("from") + .value_name("FROM_ADDRESS"), + "Source account of funds (if different from client local account). "), + ) + .arg( + Arg::with_name("no_wait") + .long("no-wait") + .takes_value(false) + .help("Return signature immediately after submitting the transaction, instead of waiting for confirmations"), + ) + .arg( + Arg::with_name("derived_address_seed") + .long("derived-address-seed") + .takes_value(true) + .value_name("SEED_STRING") + .requires("derived_address_program_id") + .validator(is_derived_address_seed) + .hidden(true) + ) + .arg( + Arg::with_name("derived_address_program_id") + .long("derived-address-program-id") + .takes_value(true) + .value_name("PROGRAM_ID") + .requires("derived_address_seed") + .hidden(true) + ) + .arg( + Arg::with_name("allow_unfunded_recipient") + .long("allow-unfunded-recipient") + .takes_value(false) + .help("Complete the transfer even if the recipient address is not funded") + ) + .offline_args() + .nonce_args(false) + .arg(memo_arg()) + .arg(fee_payer_arg()), + ) + .subcommand( + SubCommand::with_name("account") + .about("Show the contents of an account") + .alias("account") + .arg( + pubkey!(Arg::with_name("account_pubkey") + .index(1) + .value_name("ACCOUNT_ADDRESS") + .required(true), + "Account key URI. ") + ) + .arg( + Arg::with_name("output_file") + .long("output-file") + .short("o") + .value_name("FILEPATH") + .takes_value(true) + .help("Write the account data to this file"), + ) + .arg( + Arg::with_name("lamports") + .long("lamports") + .takes_value(false) + .help("Display balance in lamports instead of SOL"), + ), + ) + .validator_info_subcommands() + .vote_subcommands() + .arg({ + let arg = Arg::with_name("config_file") + .short("C") + .long("config") + .value_name("FILEPATH") + .takes_value(true) + .global(true) + .help("Configuration file to use"); + if let Some(ref config_file) = *CONFIG_FILE { + arg.default_value(config_file) + } else { + arg + } + }) + .arg( + Arg::with_name("json_rpc_url") + .short("u") + .long("url") + .value_name("URL_OR_MONIKER") + .takes_value(true) + .global(true) + .validator(is_url_or_moniker) + .help( + "URL for Solana's JSON RPC or moniker (or their first letter): \ + [mainnet-beta, testnet, devnet, localhost]", + ), + ) + .arg( + Arg::with_name("websocket_url") + .long("ws") + .value_name("URL") + .takes_value(true) + .global(true) + .validator(is_url) + .help("WebSocket URL for the solana cluster"), + ) + .arg( + Arg::with_name("keypair") + .short("k") + .long("keypair") + .value_name("KEYPAIR") + .global(true) + .takes_value(true) + .help("Filepath or URL to a keypair"), + ) + .arg( + Arg::with_name("commitment") + .long("commitment") + .takes_value(true) + .possible_values(&[ + "processed", + "confirmed", + "finalized", + "recent", // Deprecated as of v1.5.5 + "single", // Deprecated as of v1.5.5 + "singleGossip", // Deprecated as of v1.5.5 + "root", // Deprecated as of v1.5.5 + "max", // Deprecated as of v1.5.5 + ]) + .value_name("COMMITMENT_LEVEL") + .hide_possible_values(true) + .global(true) + .help("Return information at the selected commitment level [possible values: processed, confirmed, finalized]"), + ) + .arg( + Arg::with_name("verbose") + .long("verbose") + .short("v") + .global(true) + .help("Show additional information"), + ) + .arg( + Arg::with_name("no_address_labels") + .long("no-address-labels") + .global(true) + .help("Do not use address labels in the output"), + ) + .arg( + Arg::with_name("output_format") + .long("output") + .value_name("FORMAT") + .global(true) + .takes_value(true) + .possible_values(&["json", "json-compact"]) + .help("Return information in specified output format"), + ) + .arg( + Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name) + .long(SKIP_SEED_PHRASE_VALIDATION_ARG.long) + .global(true) + .help(SKIP_SEED_PHRASE_VALIDATION_ARG.help), + ) + .arg( + Arg::with_name("rpc_timeout") + .long("rpc-timeout") + .value_name("SECONDS") + .takes_value(true) + .default_value(DEFAULT_RPC_TIMEOUT_SECONDS) + .global(true) + .hidden(true) + .help("Timeout value for RPC requests"), + ) + .arg( + Arg::with_name("confirm_transaction_initial_timeout") + .long("confirm-timeout") + .value_name("SECONDS") + .takes_value(true) + .default_value(DEFAULT_CONFIRM_TX_TIMEOUT_SECONDS) + .global(true) + .hidden(true) + .help("Timeout value for initial transaction status"), + ) + .subcommand( + SubCommand::with_name("config") + .about("Solana command-line tool configuration settings") + .aliases(&["get", "set"]) + .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand( + SubCommand::with_name("get") + .about("Get current config settings") + .arg( + Arg::with_name("specific_setting") + .index(1) + .value_name("CONFIG_FIELD") + .takes_value(true) + .possible_values(&[ + "json_rpc_url", + "websocket_url", + "keypair", + "commitment", + ]) + .help("Return a specific config setting"), + ), + ) + .subcommand( + SubCommand::with_name("set") + .about("Set a config setting") + .group( + ArgGroup::with_name("config_settings") + .args(&["json_rpc_url", "websocket_url", "keypair", "commitment"]) + .multiple(true) + .required(true), + ), + ) + .subcommand( + SubCommand::with_name("import-address-labels") + .about("Import a list of address labels") + .arg( + Arg::with_name("filename") + .index(1) + .value_name("FILENAME") + .takes_value(true) + .help("YAML file of address labels"), + ), + ) + .subcommand( + SubCommand::with_name("export-address-labels") + .about("Export the current address labels") + .arg( + Arg::with_name("filename") + .index(1) + .value_name("FILENAME") + .takes_value(true) + .help("YAML file to receive the current address labels"), + ), + ), + ) + .subcommand( + SubCommand::with_name("completion") + .about("Generate completion scripts for various shells") + .arg( + Arg::with_name("shell") + .long("shell") + .short("s") + .takes_value(true) + .possible_values(&["bash", "fish", "zsh", "powershell", "elvish"]) + .default_value("bash") + ) + ) +} diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 6d0c71cf25..10108eb67d 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -1,21 +1,15 @@ use crate::{ - cluster_query::*, feature::*, inflation::*, memo::*, nonce::*, program::*, spend_utils::*, - stake::*, validator_info::*, vote::*, + clap_app::*, cluster_query::*, feature::*, inflation::*, memo::*, nonce::*, program::*, + spend_utils::*, stake::*, validator_info::*, vote::*, }; -use clap::{value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand}; +use clap::{crate_description, crate_name, value_t_or_exit, ArgMatches, Shell}; use log::*; use num_traits::FromPrimitive; use serde_json::{self, Value}; use solana_account_decoder::{UiAccount, UiAccountEncoding}; use solana_clap_utils::{ - self, - fee_payer::{fee_payer_arg, FEE_PAYER_ARG}, - input_parsers::*, - input_validators::*, - keypair::*, - memo::{memo_arg, MEMO_ARG}, - nonce::*, - offline::*, + self, fee_payer::FEE_PAYER_ARG, input_parsers::*, input_validators::*, keypair::*, + memo::MEMO_ARG, nonce::*, offline::*, }; use solana_cli_output::{ display::{build_balance_message, println_name_value}, @@ -52,8 +46,8 @@ use solana_sdk::{ use solana_transaction_status::{EncodedTransaction, UiTransactionEncoding}; use solana_vote_program::vote_state::VoteAuthorize; use std::{ - collections::HashMap, error, fmt::Write as FmtWrite, fs::File, io::Write, str::FromStr, - sync::Arc, time::Duration, + collections::HashMap, error, fmt::Write as FmtWrite, fs::File, io::stdout, io::Write, + str::FromStr, sync::Arc, time::Duration, }; use thiserror::Error; @@ -948,6 +942,25 @@ pub fn parse_command( signers: vec![], }) } + ("completion", Some(matches)) => { + let shell_choice = match matches.value_of("shell") { + Some("bash") => Shell::Bash, + Some("fish") => Shell::Fish, + Some("zsh") => Shell::Zsh, + Some("powershell") => Shell::PowerShell, + Some("elvish") => Shell::Elvish, + // This is safe, since we assign default_value and possible_values + // are restricted + _ => unreachable!(), + }; + get_clap_app( + crate_name!(), + crate_description!(), + solana_version::version!(), + ) + .gen_completions_to("solana", shell_choice, &mut stdout()); + std::process::exit(0); + } // ("", None) => { eprintln!("{}", matches.usage()); @@ -2037,274 +2050,6 @@ where } } -pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, 'v> { - App::new(name) - .about(about) - .version(version) - .setting(AppSettings::SubcommandRequiredElseHelp) - .subcommand( - SubCommand::with_name("address") - .about("Get your public key") - .arg( - Arg::with_name("confirm_key") - .long("confirm-key") - .takes_value(false) - .help("Confirm key on device; only relevant if using remote wallet"), - ), - ) - .cluster_query_subcommands() - .feature_subcommands() - .inflation_subcommands() - .nonce_subcommands() - .program_subcommands() - .stake_subcommands() - .subcommand( - SubCommand::with_name("airdrop") - .about("Request SOL from a faucet") - .arg( - Arg::with_name("amount") - .index(1) - .value_name("AMOUNT") - .takes_value(true) - .validator(is_amount) - .required(true) - .help("The airdrop amount to request, in SOL"), - ) - .arg( - pubkey!(Arg::with_name("to") - .index(2) - .value_name("RECIPIENT_ADDRESS"), - "The account address of airdrop recipient. "), - ), - ) - .subcommand( - SubCommand::with_name("balance") - .about("Get your balance") - .arg( - pubkey!(Arg::with_name("pubkey") - .index(1) - .value_name("ACCOUNT_ADDRESS"), - "The account address of the balance to check. ") - ) - .arg( - Arg::with_name("lamports") - .long("lamports") - .takes_value(false) - .help("Display balance in lamports instead of SOL"), - ), - ) - .subcommand( - SubCommand::with_name("confirm") - .about("Confirm transaction by signature") - .arg( - Arg::with_name("signature") - .index(1) - .value_name("TRANSACTION_SIGNATURE") - .takes_value(true) - .required(true) - .help("The transaction signature to confirm"), - ) - .after_help(// Formatted specifically for the manually-indented heredoc string - "Note: This will show more detailed information for finalized transactions with verbose mode (-v/--verbose).\ - \n\ - \nAccount modes:\ - \n |srwx|\ - \n s: signed\ - \n r: readable (always true)\ - \n w: writable\ - \n x: program account (inner instructions excluded)\ - " - ), - ) - .subcommand( - SubCommand::with_name("decode-transaction") - .about("Decode a serialized transaction") - .arg( - Arg::with_name("transaction") - .index(1) - .value_name("TRANSACTION") - .takes_value(true) - .required(true) - .help("transaction to decode"), - ) - .arg( - Arg::with_name("encoding") - .index(2) - .value_name("ENCODING") - .possible_values(&["base58", "base64"]) // Subset of `UiTransactionEncoding` enum - .default_value("base58") - .takes_value(true) - .required(true) - .help("transaction encoding"), - ), - ) - .subcommand( - SubCommand::with_name("create-address-with-seed") - .about("Generate a derived account address with a seed") - .arg( - Arg::with_name("seed") - .index(1) - .value_name("SEED_STRING") - .takes_value(true) - .required(true) - .validator(is_derived_address_seed) - .help("The seed. Must not take more than 32 bytes to encode as utf-8"), - ) - .arg( - Arg::with_name("program_id") - .index(2) - .value_name("PROGRAM_ID") - .takes_value(true) - .required(true) - .help( - "The program_id that the address will ultimately be used for, \n\ - or one of NONCE, STAKE, and VOTE keywords", - ), - ) - .arg( - pubkey!(Arg::with_name("from") - .long("from") - .value_name("FROM_PUBKEY") - .required(false), - "From (base) key, [default: cli config keypair]. "), - ), - ) - .subcommand( - SubCommand::with_name("deploy") - .about("Deploy a program") - .arg( - Arg::with_name("program_location") - .index(1) - .value_name("PROGRAM_FILEPATH") - .takes_value(true) - .required(true) - .help("/path/to/program.o"), - ) - .arg( - Arg::with_name("address_signer") - .index(2) - .value_name("PROGRAM_ADDRESS_SIGNER") - .takes_value(true) - .validator(is_valid_signer) - .help("The signer for the desired address of the program [default: new random address]") - ) - .arg( - Arg::with_name("use_deprecated_loader") - .long("use-deprecated-loader") - .takes_value(false) - .hidden(true) // Don't document this argument to discourage its use - .help("Use the deprecated BPF loader") - ) - .arg( - Arg::with_name("allow_excessive_balance") - .long("allow-excessive-deploy-account-balance") - .takes_value(false) - .help("Use the designated program id, even if the account already holds a large balance of SOL") - ), - ) - .subcommand( - SubCommand::with_name("resolve-signer") - .about("Checks that a signer is valid, and returns its specific path; useful for signers that may be specified generally, eg. usb://ledger") - .arg( - Arg::with_name("signer") - .index(1) - .value_name("SIGNER_KEYPAIR") - .takes_value(true) - .required(true) - .validator(is_valid_signer) - .help("The signer path to resolve") - ) - ) - .subcommand( - SubCommand::with_name("transfer") - .about("Transfer funds between system accounts") - .alias("pay") - .arg( - pubkey!(Arg::with_name("to") - .index(1) - .value_name("RECIPIENT_ADDRESS") - .required(true), - "The account address of recipient. "), - ) - .arg( - Arg::with_name("amount") - .index(2) - .value_name("AMOUNT") - .takes_value(true) - .validator(is_amount_or_all) - .required(true) - .help("The amount to send, in SOL; accepts keyword ALL"), - ) - .arg( - pubkey!(Arg::with_name("from") - .long("from") - .value_name("FROM_ADDRESS"), - "Source account of funds (if different from client local account). "), - ) - .arg( - Arg::with_name("no_wait") - .long("no-wait") - .takes_value(false) - .help("Return signature immediately after submitting the transaction, instead of waiting for confirmations"), - ) - .arg( - Arg::with_name("derived_address_seed") - .long("derived-address-seed") - .takes_value(true) - .value_name("SEED_STRING") - .requires("derived_address_program_id") - .validator(is_derived_address_seed) - .hidden(true) - ) - .arg( - Arg::with_name("derived_address_program_id") - .long("derived-address-program-id") - .takes_value(true) - .value_name("PROGRAM_ID") - .requires("derived_address_seed") - .hidden(true) - ) - .arg( - Arg::with_name("allow_unfunded_recipient") - .long("allow-unfunded-recipient") - .takes_value(false) - .help("Complete the transfer even if the recipient address is not funded") - ) - .offline_args() - .nonce_args(false) - .arg(memo_arg()) - .arg(fee_payer_arg()), - ) - .subcommand( - SubCommand::with_name("account") - .about("Show the contents of an account") - .alias("account") - .arg( - pubkey!(Arg::with_name("account_pubkey") - .index(1) - .value_name("ACCOUNT_ADDRESS") - .required(true), - "Account key URI. ") - ) - .arg( - Arg::with_name("output_file") - .long("output-file") - .short("o") - .value_name("FILEPATH") - .takes_value(true) - .help("Write the account data to this file"), - ) - .arg( - Arg::with_name("lamports") - .long("lamports") - .takes_value(false) - .help("Display balance in lamports instead of SOL"), - ), - ) - .validator_info_subcommands() - .vote_subcommands() -} - #[cfg(test)] mod tests { use super::*; @@ -2414,7 +2159,7 @@ mod tests { #[test] #[allow(clippy::cognitive_complexity)] fn test_cli_parse_command() { - let test_commands = app("test", "desc", "version"); + let test_commands = get_clap_app("test", "desc", "version"); let pubkey = solana_sdk::pubkey::new_rand(); let pubkey_string = format!("{}", pubkey); @@ -2951,7 +2696,7 @@ mod tests { #[test] fn test_parse_transfer_subcommand() { - let test_commands = app("test", "desc", "version"); + let test_commands = get_clap_app("test", "desc", "version"); let default_keypair = Keypair::new(); let default_keypair_file = make_tmp_path("keypair_file"); diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index 99a01c5ebd..47a6149568 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -2146,7 +2146,7 @@ pub fn process_calculate_rent( #[cfg(test)] mod tests { use super::*; - use crate::cli::{app, parse_command}; + use crate::{clap_app::get_clap_app, cli::parse_command}; use solana_sdk::signature::{write_keypair, Keypair}; use std::str::FromStr; use tempfile::NamedTempFile; @@ -2158,7 +2158,7 @@ mod tests { #[test] fn test_parse_command() { - let test_commands = app("test", "desc", "version"); + let test_commands = get_clap_app("test", "desc", "version"); let default_keypair = Keypair::new(); let (default_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 252b152850..824eeb1147 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -24,6 +24,7 @@ extern crate const_format; extern crate serde_derive; pub mod checks; +pub mod clap_app; pub mod cli; pub mod cluster_query; pub mod feature; diff --git a/cli/src/main.rs b/cli/src/main.rs index a4d29def28..4b1d03ce7a 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,18 +1,15 @@ -use clap::{ - crate_description, crate_name, value_t_or_exit, AppSettings, Arg, ArgGroup, ArgMatches, - SubCommand, -}; +use clap::{crate_description, crate_name, value_t_or_exit, ArgMatches}; use console::style; use solana_clap_utils::{ - input_validators::{is_url, is_url_or_moniker, normalize_to_url_if_moniker}, - keypair::{CliSigners, DefaultSigner, SKIP_SEED_PHRASE_VALIDATION_ARG}, + input_validators::normalize_to_url_if_moniker, + keypair::{CliSigners, DefaultSigner}, DisplayError, }; -use solana_cli::cli::{ - app, parse_command, process_command, CliCommandInfo, CliConfig, SettingType, - DEFAULT_CONFIRM_TX_TIMEOUT_SECONDS, DEFAULT_RPC_TIMEOUT_SECONDS, +use solana_cli::{ + clap_app::get_clap_app, + cli::{parse_command, process_command, CliCommandInfo, CliConfig, SettingType}, }; -use solana_cli_config::{Config, CONFIG_FILE}; +use solana_cli_config::Config; use solana_cli_output::{display::println_name_value, OutputFormat}; use solana_client::rpc_config::RpcSendTransactionConfig; use solana_remote_wallet::remote_wallet::RemoteWalletManager; @@ -238,178 +235,11 @@ pub fn parse_args<'a>( fn main() -> Result<(), Box> { solana_logger::setup_with_default("off"); - let matches = app( + let matches = get_clap_app( crate_name!(), crate_description!(), solana_version::version!(), ) - .arg({ - let arg = Arg::with_name("config_file") - .short("C") - .long("config") - .value_name("FILEPATH") - .takes_value(true) - .global(true) - .help("Configuration file to use"); - if let Some(ref config_file) = *CONFIG_FILE { - arg.default_value(config_file) - } else { - arg - } - }) - .arg( - Arg::with_name("json_rpc_url") - .short("u") - .long("url") - .value_name("URL_OR_MONIKER") - .takes_value(true) - .global(true) - .validator(is_url_or_moniker) - .help( - "URL for Solana's JSON RPC or moniker (or their first letter): \ - [mainnet-beta, testnet, devnet, localhost]", - ), - ) - .arg( - Arg::with_name("websocket_url") - .long("ws") - .value_name("URL") - .takes_value(true) - .global(true) - .validator(is_url) - .help("WebSocket URL for the solana cluster"), - ) - .arg( - Arg::with_name("keypair") - .short("k") - .long("keypair") - .value_name("KEYPAIR") - .global(true) - .takes_value(true) - .help("Filepath or URL to a keypair"), - ) - .arg( - Arg::with_name("commitment") - .long("commitment") - .takes_value(true) - .possible_values(&[ - "processed", - "confirmed", - "finalized", - "recent", // Deprecated as of v1.5.5 - "single", // Deprecated as of v1.5.5 - "singleGossip", // Deprecated as of v1.5.5 - "root", // Deprecated as of v1.5.5 - "max", // Deprecated as of v1.5.5 - ]) - .value_name("COMMITMENT_LEVEL") - .hide_possible_values(true) - .global(true) - .help("Return information at the selected commitment level [possible values: processed, confirmed, finalized]"), - ) - .arg( - Arg::with_name("verbose") - .long("verbose") - .short("v") - .global(true) - .help("Show additional information"), - ) - .arg( - Arg::with_name("no_address_labels") - .long("no-address-labels") - .global(true) - .help("Do not use address labels in the output"), - ) - .arg( - Arg::with_name("output_format") - .long("output") - .value_name("FORMAT") - .global(true) - .takes_value(true) - .possible_values(&["json", "json-compact"]) - .help("Return information in specified output format"), - ) - .arg( - Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name) - .long(SKIP_SEED_PHRASE_VALIDATION_ARG.long) - .global(true) - .help(SKIP_SEED_PHRASE_VALIDATION_ARG.help), - ) - .arg( - Arg::with_name("rpc_timeout") - .long("rpc-timeout") - .value_name("SECONDS") - .takes_value(true) - .default_value(DEFAULT_RPC_TIMEOUT_SECONDS) - .global(true) - .hidden(true) - .help("Timeout value for RPC requests"), - ) - .arg( - Arg::with_name("confirm_transaction_initial_timeout") - .long("confirm-timeout") - .value_name("SECONDS") - .takes_value(true) - .default_value(DEFAULT_CONFIRM_TX_TIMEOUT_SECONDS) - .global(true) - .hidden(true) - .help("Timeout value for initial transaction status"), - ) - .subcommand( - SubCommand::with_name("config") - .about("Solana command-line tool configuration settings") - .aliases(&["get", "set"]) - .setting(AppSettings::SubcommandRequiredElseHelp) - .subcommand( - SubCommand::with_name("get") - .about("Get current config settings") - .arg( - Arg::with_name("specific_setting") - .index(1) - .value_name("CONFIG_FIELD") - .takes_value(true) - .possible_values(&[ - "json_rpc_url", - "websocket_url", - "keypair", - "commitment", - ]) - .help("Return a specific config setting"), - ), - ) - .subcommand( - SubCommand::with_name("set") - .about("Set a config setting") - .group( - ArgGroup::with_name("config_settings") - .args(&["json_rpc_url", "websocket_url", "keypair", "commitment"]) - .multiple(true) - .required(true), - ), - ) - .subcommand( - SubCommand::with_name("import-address-labels") - .about("Import a list of address labels") - .arg( - Arg::with_name("filename") - .index(1) - .value_name("FILENAME") - .takes_value(true) - .help("YAML file of address labels"), - ), - ) - .subcommand( - SubCommand::with_name("export-address-labels") - .about("Export the current address labels") - .arg( - Arg::with_name("filename") - .index(1) - .value_name("FILENAME") - .takes_value(true) - .help("YAML file to receive the current address labels"), - ), - ), - ) .get_matches(); do_main(&matches).map_err(|err| DisplayError::new_as_boxed(err).into()) diff --git a/cli/src/nonce.rs b/cli/src/nonce.rs index 9e83e94877..90e53be793 100644 --- a/cli/src/nonce.rs +++ b/cli/src/nonce.rs @@ -651,7 +651,7 @@ pub fn process_withdraw_from_nonce_account( #[cfg(test)] mod tests { use super::*; - use crate::cli::{app, parse_command}; + use crate::{clap_app::get_clap_app, cli::parse_command}; use solana_sdk::{ account::Account, account_utils::StateMut, @@ -671,7 +671,7 @@ mod tests { #[test] fn test_parse_command() { - let test_commands = app("test", "desc", "version"); + let test_commands = get_clap_app("test", "desc", "version"); let default_keypair = Keypair::new(); let (default_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); diff --git a/cli/src/program.rs b/cli/src/program.rs index 0c557c9563..b46c4e8e11 100644 --- a/cli/src/program.rs +++ b/cli/src/program.rs @@ -2109,7 +2109,10 @@ fn send_and_confirm_transactions_with_spinner( #[cfg(test)] mod tests { use super::*; - use crate::cli::{app, parse_command, process_command}; + use crate::{ + clap_app::get_clap_app, + cli::{parse_command, process_command}, + }; use serde_json::Value; use solana_cli_output::OutputFormat; use solana_sdk::signature::write_keypair_file; @@ -2131,7 +2134,7 @@ mod tests { #[test] #[allow(clippy::cognitive_complexity)] fn test_cli_parse_deploy() { - let test_commands = app("test", "desc", "version"); + let test_commands = get_clap_app("test", "desc", "version"); let default_keypair = Keypair::new(); let keypair_file = make_tmp_path("keypair_file"); @@ -2339,7 +2342,7 @@ mod tests { #[test] #[allow(clippy::cognitive_complexity)] fn test_cli_parse_write_buffer() { - let test_commands = app("test", "desc", "version"); + let test_commands = get_clap_app("test", "desc", "version"); let default_keypair = Keypair::new(); let keypair_file = make_tmp_path("keypair_file"); @@ -2487,7 +2490,7 @@ mod tests { #[test] #[allow(clippy::cognitive_complexity)] fn test_cli_parse_set_upgrade_authority() { - let test_commands = app("test", "desc", "version"); + let test_commands = get_clap_app("test", "desc", "version"); let default_keypair = Keypair::new(); let keypair_file = make_tmp_path("keypair_file"); @@ -2595,7 +2598,7 @@ mod tests { #[test] #[allow(clippy::cognitive_complexity)] fn test_cli_parse_set_buffer_authority() { - let test_commands = app("test", "desc", "version"); + let test_commands = get_clap_app("test", "desc", "version"); let default_keypair = Keypair::new(); let keypair_file = make_tmp_path("keypair_file"); @@ -2652,7 +2655,7 @@ mod tests { #[test] #[allow(clippy::cognitive_complexity)] fn test_cli_parse_show() { - let test_commands = app("test", "desc", "version"); + let test_commands = get_clap_app("test", "desc", "version"); let default_keypair = Keypair::new(); let keypair_file = make_tmp_path("keypair_file"); @@ -2751,7 +2754,7 @@ mod tests { #[test] #[allow(clippy::cognitive_complexity)] fn test_cli_parse_close() { - let test_commands = app("test", "desc", "version"); + let test_commands = get_clap_app("test", "desc", "version"); let default_keypair = Keypair::new(); let keypair_file = make_tmp_path("keypair_file"); diff --git a/cli/src/stake.rs b/cli/src/stake.rs index e28597e876..04e8d1d015 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -2358,7 +2358,7 @@ pub fn is_stake_program_v2_enabled( #[cfg(test)] mod tests { use super::*; - use crate::cli::{app, parse_command}; + use crate::{clap_app::get_clap_app, cli::parse_command}; use solana_client::blockhash_query; use solana_sdk::{ hash::Hash, @@ -2376,7 +2376,7 @@ mod tests { #[test] #[allow(clippy::cognitive_complexity)] fn test_parse_command() { - let test_commands = app("test", "desc", "version"); + let test_commands = get_clap_app("test", "desc", "version"); let default_keypair = Keypair::new(); let (default_keypair_file, mut tmp_file) = make_tmp_file(); write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap(); diff --git a/cli/src/validator_info.rs b/cli/src/validator_info.rs index 6ac5f239b9..61054a3e98 100644 --- a/cli/src/validator_info.rs +++ b/cli/src/validator_info.rs @@ -408,7 +408,7 @@ pub fn process_get_validator_info( #[cfg(test)] mod tests { use super::*; - use crate::cli::app; + use crate::clap_app::get_clap_app; use bincode::{serialize, serialized_size}; use serde_json::json; @@ -432,7 +432,7 @@ mod tests { #[test] fn test_parse_args() { - let matches = app("test", "desc", "version").get_matches_from(vec![ + let matches = get_clap_app("test", "desc", "version").get_matches_from(vec![ "test", "validator-info", "publish", diff --git a/cli/src/vote.rs b/cli/src/vote.rs index ce27c55cf1..ee81929d07 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -880,7 +880,7 @@ pub fn process_withdraw_from_vote_account( #[cfg(test)] mod tests { use super::*; - use crate::cli::{app, parse_command}; + use crate::{clap_app::get_clap_app, cli::parse_command}; use solana_sdk::signature::{read_keypair_file, write_keypair, Keypair, Signer}; use tempfile::NamedTempFile; @@ -891,7 +891,7 @@ mod tests { #[test] fn test_parse_command() { - let test_commands = app("test", "desc", "version"); + let test_commands = get_clap_app("test", "desc", "version"); let keypair = Keypair::new(); let pubkey = keypair.pubkey(); let pubkey_string = pubkey.to_string();