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 9d0a937a05)

Co-authored-by: theonekeyg <34949189+theonekeyg@users.noreply.github.com>
This commit is contained in:
mergify[bot]
2021-07-28 17:26:19 +00:00
committed by GitHub
parent 02c509390a
commit 65fe00b7ad
10 changed files with 514 additions and 478 deletions

457
cli/src/clap_app.rs Normal file
View File

@ -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")
)
)
}

View File

@ -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");

View File

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

View File

@ -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;

View File

@ -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<dyn error::Error>> {
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())

View File

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

View File

@ -2109,7 +2109,10 @@ fn send_and_confirm_transactions_with_spinner<T: Signers>(
#[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");

View File

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

View File

@ -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",

View File

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