Add cli-output helpers (#18933)

* Add OutputFormat helper to reduce copy-pasta

* Add CliSignOnlyData constructor
This commit is contained in:
Tyera Eulberg 2021-07-27 22:21:23 -06:00 committed by GitHub
parent 14f0ce850d
commit 467c18e0d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 110 additions and 31 deletions

1
Cargo.lock generated
View File

@ -4354,6 +4354,7 @@ dependencies = [
"Inflector", "Inflector",
"base64 0.13.0", "base64 0.13.0",
"chrono", "chrono",
"clap 2.33.3",
"console", "console",
"humantime", "humantime",
"indicatif", "indicatif",

View File

@ -12,6 +12,7 @@ documentation = "https://docs.rs/solana-cli-output"
[dependencies] [dependencies]
base64 = "0.13.0" base64 = "0.13.0"
chrono = { version = "0.4.11", features = ["serde"] } chrono = { version = "0.4.11", features = ["serde"] }
clap = "2.33.0"
console = "0.14.1" console = "0.14.1"
humantime = "2.0.1" humantime = "2.0.1"
Inflector = "0.11.4" Inflector = "0.11.4"

View File

@ -8,6 +8,7 @@ use {
QuietDisplay, VerboseDisplay, QuietDisplay, VerboseDisplay,
}, },
chrono::{Local, TimeZone}, chrono::{Local, TimeZone},
clap::ArgMatches,
console::{style, Emoji}, console::{style, Emoji},
inflector::cases::titlecase::to_title_case, inflector::cases::titlecase::to_title_case,
serde::{Deserialize, Serialize}, serde::{Deserialize, Serialize},
@ -47,7 +48,7 @@ use {
static WARNING: Emoji = Emoji("⚠️", "!"); static WARNING: Emoji = Emoji("⚠️", "!");
#[derive(PartialEq)] #[derive(PartialEq, Debug)]
pub enum OutputFormat { pub enum OutputFormat {
Display, Display,
Json, Json,
@ -77,6 +78,21 @@ impl OutputFormat {
OutputFormat::JsonCompact => serde_json::to_value(item).unwrap().to_string(), OutputFormat::JsonCompact => serde_json::to_value(item).unwrap().to_string(),
} }
} }
pub fn from_matches(matches: &ArgMatches<'_>, output_name: &str, verbose: bool) -> Self {
matches
.value_of(output_name)
.map(|value| match value {
"json" => OutputFormat::Json,
"json-compact" => OutputFormat::JsonCompact,
_ => unreachable!(),
})
.unwrap_or(if verbose {
OutputFormat::DisplayVerbose
} else {
OutputFormat::Display
})
}
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -1572,7 +1588,7 @@ impl fmt::Display for CliInflation {
} }
} }
#[derive(Serialize, Deserialize, Default)] #[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CliSignOnlyData { pub struct CliSignOnlyData {
pub blockhash: String, pub blockhash: String,
@ -2024,6 +2040,11 @@ pub fn return_signers_with_config(
output_format: &OutputFormat, output_format: &OutputFormat,
config: &ReturnSignersConfig, config: &ReturnSignersConfig,
) -> Result<String, Box<dyn std::error::Error>> { ) -> Result<String, Box<dyn std::error::Error>> {
let cli_command = return_signers_data(tx, config);
Ok(output_format.formatted_string(&cli_command))
}
pub fn return_signers_data(tx: &Transaction, config: &ReturnSignersConfig) -> CliSignOnlyData {
let verify_results = tx.verify_with_results(); let verify_results = tx.verify_with_results();
let mut signers = Vec::new(); let mut signers = Vec::new();
let mut absent = Vec::new(); let mut absent = Vec::new();
@ -2048,15 +2069,13 @@ pub fn return_signers_with_config(
None None
}; };
let cli_command = CliSignOnlyData { CliSignOnlyData {
blockhash: tx.message.recent_blockhash.to_string(), blockhash: tx.message.recent_blockhash.to_string(),
message, message,
signers, signers,
absent, absent,
bad_sig, bad_sig,
}; }
Ok(output_format.formatted_string(&cli_command))
} }
pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly { pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
@ -2438,6 +2457,7 @@ impl VerboseDisplay for CliGossipNodes {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use clap::{App, Arg};
use solana_sdk::{ use solana_sdk::{
message::Message, message::Message,
pubkey::Pubkey, pubkey::Pubkey,
@ -2498,6 +2518,22 @@ mod tests {
assert_eq!(sign_only.absent_signers[0], absent.pubkey()); assert_eq!(sign_only.absent_signers[0], absent.pubkey());
assert_eq!(sign_only.bad_signers[0], bad.pubkey()); assert_eq!(sign_only.bad_signers[0], bad.pubkey());
let res_data = return_signers_data(&tx, &ReturnSignersConfig::default());
assert_eq!(
res_data,
CliSignOnlyData {
blockhash: blockhash.to_string(),
message: None,
signers: vec![format!(
"{}={}",
present.pubkey().to_string(),
tx.signatures[1]
)],
absent: vec![absent.pubkey().to_string()],
bad_sig: vec![bad.pubkey().to_string()],
}
);
let expected_msg = "AwECBwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgTl3Dqh9\ let expected_msg = "AwECBwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgTl3Dqh9\
F19Wo1Rmw0x+zMuNipG07jeiXfYPW4/Js5QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE\ F19Wo1Rmw0x+zMuNipG07jeiXfYPW4/Js5QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE\
BAQEBAYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBQUFBQUFBQUFBQUFBQUFBQUF\ BAQEBAYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBQUFBQUFBQUFBQUFBQUFBQUF\
@ -2511,10 +2547,26 @@ mod tests {
let res = return_signers_with_config(&tx, &OutputFormat::JsonCompact, &config).unwrap(); let res = return_signers_with_config(&tx, &OutputFormat::JsonCompact, &config).unwrap();
let sign_only = parse_sign_only_reply_string(&res); let sign_only = parse_sign_only_reply_string(&res);
assert_eq!(sign_only.blockhash, blockhash); assert_eq!(sign_only.blockhash, blockhash);
assert_eq!(sign_only.message, Some(expected_msg)); assert_eq!(sign_only.message, Some(expected_msg.clone()));
assert_eq!(sign_only.present_signers[0].0, present.pubkey()); assert_eq!(sign_only.present_signers[0].0, present.pubkey());
assert_eq!(sign_only.absent_signers[0], absent.pubkey()); assert_eq!(sign_only.absent_signers[0], absent.pubkey());
assert_eq!(sign_only.bad_signers[0], bad.pubkey()); assert_eq!(sign_only.bad_signers[0], bad.pubkey());
let res_data = return_signers_data(&tx, &config);
assert_eq!(
res_data,
CliSignOnlyData {
blockhash: blockhash.to_string(),
message: Some(expected_msg),
signers: vec![format!(
"{}={}",
present.pubkey().to_string(),
tx.signatures[1]
)],
absent: vec![absent.pubkey().to_string()],
bad_sig: vec![bad.pubkey().to_string()],
}
);
} }
#[test] #[test]
@ -2563,4 +2615,50 @@ mod tests {
"verbose" "verbose"
); );
} }
#[test]
fn test_output_format_from_matches() {
let app = App::new("test").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"),
);
let matches = app
.clone()
.get_matches_from(vec!["test", "--output", "json"]);
assert_eq!(
OutputFormat::from_matches(&matches, "output_format", false),
OutputFormat::Json
);
assert_eq!(
OutputFormat::from_matches(&matches, "output_format", true),
OutputFormat::Json
);
let matches = app
.clone()
.get_matches_from(vec!["test", "--output", "json-compact"]);
assert_eq!(
OutputFormat::from_matches(&matches, "output_format", false),
OutputFormat::JsonCompact
);
assert_eq!(
OutputFormat::from_matches(&matches, "output_format", true),
OutputFormat::JsonCompact
);
let matches = app.clone().get_matches_from(vec!["test"]);
assert_eq!(
OutputFormat::from_matches(&matches, "output_format", false),
OutputFormat::Display
);
assert_eq!(
OutputFormat::from_matches(&matches, "output_format", true),
OutputFormat::DisplayVerbose
);
}
} }

View File

@ -200,18 +200,7 @@ pub fn parse_args<'a>(
} }
let verbose = matches.is_present("verbose"); let verbose = matches.is_present("verbose");
let output_format = matches let output_format = OutputFormat::from_matches(matches, "output_format", verbose);
.value_of("output_format")
.map(|value| match value {
"json" => OutputFormat::Json,
"json-compact" => OutputFormat::JsonCompact,
_ => unreachable!(),
})
.unwrap_or(if verbose {
OutputFormat::DisplayVerbose
} else {
OutputFormat::Display
});
let (_, commitment) = CliConfig::compute_commitment_config( let (_, commitment) = CliConfig::compute_commitment_config(
matches.value_of("commitment").unwrap_or(""), matches.value_of("commitment").unwrap_or(""),

View File

@ -385,18 +385,7 @@ pub fn bigtable_process_command(ledger_path: &Path, matches: &ArgMatches<'_>) {
let runtime = tokio::runtime::Runtime::new().unwrap(); let runtime = tokio::runtime::Runtime::new().unwrap();
let verbose = matches.is_present("verbose"); let verbose = matches.is_present("verbose");
let output_format = matches let output_format = OutputFormat::from_matches(matches, "output_format", verbose);
.value_of("output_format")
.map(|value| match value {
"json" => OutputFormat::Json,
"json-compact" => OutputFormat::JsonCompact,
_ => unreachable!(),
})
.unwrap_or(if verbose {
OutputFormat::DisplayVerbose
} else {
OutputFormat::Display
});
let future = match matches.subcommand() { let future = match matches.subcommand() {
("upload", Some(arg_matches)) => { ("upload", Some(arg_matches)) => {

View File

@ -2807,6 +2807,7 @@ dependencies = [
"Inflector", "Inflector",
"base64 0.13.0", "base64 0.13.0",
"chrono", "chrono",
"clap",
"console", "console",
"humantime", "humantime",
"indicatif", "indicatif",