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();