From 5dcb7a4549a2901e4040641710c5fdef5cbd526d Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Thu, 30 Nov 2023 17:11:30 -0800 Subject: [PATCH] Move the CLI functionality into the library (#326) * Move the CLI functionality into the library * comments * fix * fix todo * pub root * todo * dont treat help as error * empty commit --- src/bin/stellar-xdr/main.rs | 81 +++----------------- src/{bin/stellar-xdr => cli}/decode.rs | 25 +++--- src/{bin/stellar-xdr => cli}/encode.rs | 75 +++++++++--------- src/{bin/stellar-xdr => cli}/guess.rs | 37 ++++----- src/cli/mod.rs | 89 ++++++++++++++++++++++ src/{bin/stellar-xdr => cli}/types.rs | 9 +-- src/{bin/stellar-xdr => cli}/types/list.rs | 14 ++-- src/{bin/stellar-xdr => cli}/version.rs | 4 +- src/lib.rs | 3 + 9 files changed, 177 insertions(+), 160 deletions(-) rename src/{bin/stellar-xdr => cli}/decode.rs (78%) rename src/{bin/stellar-xdr => cli}/encode.rs (56%) rename src/{bin/stellar-xdr => cli}/guess.rs (82%) create mode 100644 src/cli/mod.rs rename src/{bin/stellar-xdr => cli}/types.rs (61%) rename src/{bin/stellar-xdr => cli}/types/list.rs (74%) rename src/{bin/stellar-xdr => cli}/version.rs (85%) diff --git a/src/bin/stellar-xdr/main.rs b/src/bin/stellar-xdr/main.rs index dcc2b2ff..78ff30f3 100644 --- a/src/bin/stellar-xdr/main.rs +++ b/src/bin/stellar-xdr/main.rs @@ -1,75 +1,14 @@ -mod decode; -mod encode; -mod guess; -mod types; -mod version; - -use clap::{CommandFactory, Parser, Subcommand, ValueEnum}; -use std::{error::Error, fmt::Debug}; - -#[derive(Parser, Debug, Clone)] -#[command( - author, - version, - about, - long_about = None, - disable_help_subcommand = true, - disable_version_flag = true, - disable_colored_help = true, - infer_subcommands = true, -)] -struct Root { - /// Channel of XDR to operate on - #[arg(value_enum, default_value_t)] - channel: Channel, - #[command(subcommand)] - cmd: Cmd, -} - -#[derive(ValueEnum, Debug, Clone)] -pub enum Channel { - #[value(name = "+curr")] - Curr, - #[value(name = "+next")] - Next, -} - -impl Default for Channel { - fn default() -> Self { - Self::Curr - } -} - -#[derive(Subcommand, Debug, Clone)] -enum Cmd { - /// View information about types - Types(types::Cmd), - /// Guess the XDR type - Guess(guess::Cmd), - /// Decode XDR - Decode(decode::Cmd), - /// Encode XDR - Encode(encode::Cmd), - /// Print version information - Version, -} - -fn run() -> Result<(), Box> { - let root = Root::parse(); - match root.cmd { - Cmd::Types(c) => c.run(&root.channel)?, - Cmd::Guess(c) => c.run(&root.channel)?, - Cmd::Decode(c) => c.run(&root.channel)?, - Cmd::Encode(c) => c.run(&root.channel)?, - Cmd::Version => version::Cmd::run(), - } - Ok(()) -} +use clap::Error; +use std::env; +use stellar_xdr::cli; fn main() { - if let Err(e) = run() { - Root::command() - .error(clap::error::ErrorKind::ValueValidation, e) - .exit() + if let Err(e) = cli::run(env::args_os()) { + match e { + cli::Error::Clap(e) => e.exit(), + cli::Error::Guess(_) | cli::Error::Decode(_) | cli::Error::Encode(_) => { + Error::raw(clap::error::ErrorKind::ValueValidation, e).exit() + } + } } } diff --git a/src/bin/stellar-xdr/decode.rs b/src/cli/decode.rs similarity index 78% rename from src/bin/stellar-xdr/decode.rs rename to src/cli/decode.rs index c90f6fb9..965b3f87 100644 --- a/src/bin/stellar-xdr/decode.rs +++ b/src/cli/decode.rs @@ -8,16 +8,16 @@ use std::{ use clap::{Args, ValueEnum}; use serde::Serialize; -use crate::Channel; +use crate::cli::Channel; #[derive(thiserror::Error, Debug)] pub enum Error { #[error("unknown type {0}, choose one of {1:?}")] UnknownType(String, &'static [&'static str]), #[error("error decoding XDR: {0}")] - ReadXdrCurr(#[from] stellar_xdr::curr::Error), + ReadXdrCurr(#[from] crate::curr::Error), #[error("error decoding XDR: {0}")] - ReadXdrNext(#[from] stellar_xdr::next::Error), + ReadXdrNext(#[from] crate::next::Error), #[error("error reading file: {0}")] ReadFile(#[from] std::io::Error), #[error("error generating JSON: {0}")] @@ -75,35 +75,32 @@ macro_rules! run_x { ($f:ident, $m:ident) => { fn $f(&self) -> Result<(), Error> { let mut files = self.files()?; - let r#type = stellar_xdr::$m::TypeVariant::from_str(&self.r#type).map_err(|_| { - Error::UnknownType( - self.r#type.clone(), - &stellar_xdr::$m::TypeVariant::VARIANTS_STR, - ) + let r#type = crate::$m::TypeVariant::from_str(&self.r#type).map_err(|_| { + Error::UnknownType(self.r#type.clone(), &crate::$m::TypeVariant::VARIANTS_STR) })?; for f in &mut files { - let mut f = stellar_xdr::$m::Limited::new(f, stellar_xdr::$m::Limits::none()); + let mut f = crate::$m::Limited::new(f, crate::$m::Limits::none()); match self.input { InputFormat::Single => { - let t = stellar_xdr::$m::Type::read_xdr_to_end(r#type, &mut f)?; + let t = crate::$m::Type::read_xdr_to_end(r#type, &mut f)?; self.out(&t)?; } InputFormat::SingleBase64 => { - let t = stellar_xdr::$m::Type::read_xdr_base64_to_end(r#type, &mut f)?; + let t = crate::$m::Type::read_xdr_base64_to_end(r#type, &mut f)?; self.out(&t)?; } InputFormat::Stream => { - for t in stellar_xdr::$m::Type::read_xdr_iter(r#type, &mut f) { + for t in crate::$m::Type::read_xdr_iter(r#type, &mut f) { self.out(&t?)?; } } InputFormat::StreamBase64 => { - for t in stellar_xdr::$m::Type::read_xdr_base64_iter(r#type, &mut f) { + for t in crate::$m::Type::read_xdr_base64_iter(r#type, &mut f) { self.out(&t?)?; } } InputFormat::StreamFramed => { - for t in stellar_xdr::$m::Type::read_xdr_framed_iter(r#type, &mut f) { + for t in crate::$m::Type::read_xdr_framed_iter(r#type, &mut f) { self.out(&t?)?; } } diff --git a/src/bin/stellar-xdr/encode.rs b/src/cli/encode.rs similarity index 56% rename from src/bin/stellar-xdr/encode.rs rename to src/cli/encode.rs index b8dff346..ba588790 100644 --- a/src/bin/stellar-xdr/encode.rs +++ b/src/cli/encode.rs @@ -7,56 +7,56 @@ use std::{ use clap::{Args, ValueEnum}; -use crate::Channel; +use crate::cli::Channel; #[derive(thiserror::Error, Debug)] pub enum Error { #[error("unknown type {0}, choose one of {1:?}")] UnknownType(String, &'static [&'static str]), #[error("error decoding JSON: {0}")] - ReadJsonCurr(stellar_xdr::curr::Error), + ReadJsonCurr(crate::curr::Error), #[error("error decoding JSON: {0}")] - ReadJsonNext(stellar_xdr::next::Error), + ReadJsonNext(crate::next::Error), #[error("error reading file: {0}")] ReadFile(#[from] std::io::Error), #[error("error generating XDR: {0}")] - WriteXdrCurr(stellar_xdr::curr::Error), + WriteXdrCurr(crate::curr::Error), #[error("error generating XDR: {0}")] - WriteXdrNext(stellar_xdr::next::Error), + WriteXdrNext(crate::next::Error), } -impl From for Error { - fn from(e: stellar_xdr::curr::Error) -> Self { +impl From for Error { + fn from(e: crate::curr::Error) -> Self { match e { - stellar_xdr::curr::Error::Invalid - | stellar_xdr::curr::Error::Unsupported - | stellar_xdr::curr::Error::LengthExceedsMax - | stellar_xdr::curr::Error::LengthMismatch - | stellar_xdr::curr::Error::NonZeroPadding - | stellar_xdr::curr::Error::Utf8Error(_) - | stellar_xdr::curr::Error::InvalidHex - | stellar_xdr::curr::Error::Io(_) - | stellar_xdr::curr::Error::DepthLimitExceeded - | stellar_xdr::curr::Error::LengthLimitExceeded => Error::WriteXdrCurr(e), - stellar_xdr::curr::Error::Json(_) => Error::ReadJsonCurr(e), + crate::curr::Error::Invalid + | crate::curr::Error::Unsupported + | crate::curr::Error::LengthExceedsMax + | crate::curr::Error::LengthMismatch + | crate::curr::Error::NonZeroPadding + | crate::curr::Error::Utf8Error(_) + | crate::curr::Error::InvalidHex + | crate::curr::Error::Io(_) + | crate::curr::Error::DepthLimitExceeded + | crate::curr::Error::LengthLimitExceeded => Error::WriteXdrCurr(e), + crate::curr::Error::Json(_) => Error::ReadJsonCurr(e), } } } -impl From for Error { - fn from(e: stellar_xdr::next::Error) -> Self { +impl From for Error { + fn from(e: crate::next::Error) -> Self { match e { - stellar_xdr::next::Error::Invalid - | stellar_xdr::next::Error::Unsupported - | stellar_xdr::next::Error::LengthExceedsMax - | stellar_xdr::next::Error::LengthMismatch - | stellar_xdr::next::Error::NonZeroPadding - | stellar_xdr::next::Error::Utf8Error(_) - | stellar_xdr::next::Error::InvalidHex - | stellar_xdr::next::Error::Io(_) - | stellar_xdr::next::Error::DepthLimitExceeded - | stellar_xdr::next::Error::LengthLimitExceeded => Error::WriteXdrNext(e), - stellar_xdr::next::Error::Json(_) => Error::ReadJsonNext(e), + crate::next::Error::Invalid + | crate::next::Error::Unsupported + | crate::next::Error::LengthExceedsMax + | crate::next::Error::LengthMismatch + | crate::next::Error::NonZeroPadding + | crate::next::Error::Utf8Error(_) + | crate::next::Error::InvalidHex + | crate::next::Error::Io(_) + | crate::next::Error::DepthLimitExceeded + | crate::next::Error::LengthLimitExceeded => Error::WriteXdrNext(e), + crate::next::Error::Json(_) => Error::ReadJsonNext(e), } } } @@ -107,19 +107,16 @@ impl Default for OutputFormat { macro_rules! run_x { ($f:ident, $m:ident) => { fn $f(&self) -> Result<(), Error> { - use stellar_xdr::$m::WriteXdr; + use crate::$m::WriteXdr; let mut files = self.files()?; - let r#type = stellar_xdr::$m::TypeVariant::from_str(&self.r#type).map_err(|_| { - Error::UnknownType( - self.r#type.clone(), - &stellar_xdr::$m::TypeVariant::VARIANTS_STR, - ) + let r#type = crate::$m::TypeVariant::from_str(&self.r#type).map_err(|_| { + Error::UnknownType(self.r#type.clone(), &crate::$m::TypeVariant::VARIANTS_STR) })?; for f in &mut files { match self.input { InputFormat::Json => { - let t = stellar_xdr::$m::Type::read_json(r#type, f)?; - let l = stellar_xdr::$m::Limits::none(); + let t = crate::$m::Type::read_json(r#type, f)?; + let l = crate::$m::Limits::none(); match self.output { OutputFormat::Single => stdout().write_all(&t.to_xdr(l)?)?, diff --git a/src/bin/stellar-xdr/guess.rs b/src/cli/guess.rs similarity index 82% rename from src/bin/stellar-xdr/guess.rs rename to src/cli/guess.rs index 9ef9b574..4f48820b 100644 --- a/src/bin/stellar-xdr/guess.rs +++ b/src/cli/guess.rs @@ -7,15 +7,15 @@ use std::{ use clap::{Args, ValueEnum}; -use crate::Channel; +use crate::cli::Channel; #[derive(thiserror::Error, Debug)] #[allow(clippy::enum_variant_names)] pub enum Error { #[error("error decoding XDR: {0}")] - ReadXdrCurr(#[from] stellar_xdr::curr::Error), + ReadXdrCurr(#[from] crate::curr::Error), #[error("error decoding XDR: {0}")] - ReadXdrNext(#[from] stellar_xdr::next::Error), + ReadXdrNext(#[from] crate::next::Error), #[error("error reading file: {0}")] ReadFile(#[from] std::io::Error), } @@ -69,26 +69,21 @@ impl Default for OutputFormat { macro_rules! run_x { ($f:ident, $m:ident) => { fn $f(&self) -> Result<(), Error> { - let mut f = stellar_xdr::$m::Limited::new( - ResetRead::new(self.file()?), - stellar_xdr::$m::Limits::none(), - ); - 'variants: for v in stellar_xdr::$m::TypeVariant::VARIANTS { + let mut f = + crate::$m::Limited::new(ResetRead::new(self.file()?), crate::$m::Limits::none()); + 'variants: for v in crate::$m::TypeVariant::VARIANTS { f.inner.reset(); let count: usize = match self.input { - InputFormat::Single => stellar_xdr::$m::Type::read_xdr_to_end(v, &mut f) + InputFormat::Single => crate::$m::Type::read_xdr_to_end(v, &mut f) + .ok() + .map(|_| 1) + .unwrap_or_default(), + InputFormat::SingleBase64 => crate::$m::Type::read_xdr_base64_to_end(v, &mut f) .ok() .map(|_| 1) .unwrap_or_default(), - InputFormat::SingleBase64 => { - stellar_xdr::$m::Type::read_xdr_base64_to_end(v, &mut f) - .ok() - .map(|_| 1) - .unwrap_or_default() - } InputFormat::Stream => { - let iter = - stellar_xdr::$m::Type::read_xdr_iter(v, &mut f).take(self.certainty); + let iter = crate::$m::Type::read_xdr_iter(v, &mut f).take(self.certainty); let mut count = 0; for v in iter { match v { @@ -99,8 +94,8 @@ macro_rules! run_x { count } InputFormat::StreamBase64 => { - let iter = stellar_xdr::$m::Type::read_xdr_base64_iter(v, &mut f) - .take(self.certainty); + let iter = + crate::$m::Type::read_xdr_base64_iter(v, &mut f).take(self.certainty); let mut count = 0; for v in iter { match v { @@ -111,8 +106,8 @@ macro_rules! run_x { count } InputFormat::StreamFramed => { - let iter = stellar_xdr::$m::Type::read_xdr_framed_iter(v, &mut f) - .take(self.certainty); + let iter = + crate::$m::Type::read_xdr_framed_iter(v, &mut f).take(self.certainty); let mut count = 0; for v in iter { match v { diff --git a/src/cli/mod.rs b/src/cli/mod.rs new file mode 100644 index 00000000..c51bb95d --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1,89 @@ +mod decode; +mod encode; +mod guess; +mod types; +mod version; + +use clap::{Parser, Subcommand, ValueEnum}; +use std::{ffi::OsString, fmt::Debug}; + +#[derive(Parser, Debug, Clone)] +#[command( + author, + version, + about, + long_about = None, + disable_help_subcommand = true, + disable_version_flag = true, + disable_colored_help = true, + infer_subcommands = true, +)] +pub struct Root { + /// Channel of XDR to operate on + #[arg(value_enum, default_value_t)] + channel: Channel, + #[command(subcommand)] + cmd: Cmd, +} + +#[derive(ValueEnum, Debug, Clone)] +pub enum Channel { + #[value(name = "+curr")] + Curr, + #[value(name = "+next")] + Next, +} + +impl Default for Channel { + fn default() -> Self { + Self::Curr + } +} + +#[derive(Subcommand, Debug, Clone)] +pub enum Cmd { + /// View information about types + Types(types::Cmd), + /// Guess the XDR type + Guess(guess::Cmd), + /// Decode XDR + Decode(decode::Cmd), + /// Encode XDR + Encode(encode::Cmd), + /// Print version information + Version, +} + +#[derive(thiserror::Error, Debug)] +#[allow(clippy::enum_variant_names)] +pub enum Error { + #[error("{0}")] + Clap(#[from] clap::Error), + #[error("error decoding XDR: {0}")] + Guess(#[from] guess::Error), + #[error("error reading file: {0}")] + Decode(#[from] decode::Error), + #[error("error reading file: {0}")] + Encode(#[from] encode::Error), +} + +/// Run the CLI with the given args. +/// +/// ## Errors +/// +/// If the input cannot be parsed. +pub fn run(args: I) -> Result<(), Error> +where + I: IntoIterator, + T: Into + Clone, +{ + let root = Root::try_parse_from(args)?; + match root.cmd { + Cmd::Types(c) => c.run(&root.channel), + Cmd::Guess(c) => c.run(&root.channel)?, + Cmd::Decode(c) => c.run(&root.channel)?, + Cmd::Encode(c) => c.run(&root.channel)?, + Cmd::Version => version::Cmd::run(), + } + Ok(()) +} diff --git a/src/bin/stellar-xdr/types.rs b/src/cli/types.rs similarity index 61% rename from src/bin/stellar-xdr/types.rs rename to src/cli/types.rs index 911401b6..a0ad2790 100644 --- a/src/bin/stellar-xdr/types.rs +++ b/src/cli/types.rs @@ -1,10 +1,8 @@ mod list; -use std::error::Error; - use clap::{Args, Subcommand}; -use crate::Channel; +use crate::cli::Channel; #[derive(Args, Debug, Clone)] #[command()] @@ -19,10 +17,9 @@ pub enum Sub { } impl Cmd { - pub fn run(&self, channel: &Channel) -> Result<(), Box> { + pub fn run(&self, channel: &Channel) { match &self.sub { - Sub::List(c) => c.run(channel)?, + Sub::List(c) => c.run(channel), } - Ok(()) } } diff --git a/src/bin/stellar-xdr/types/list.rs b/src/cli/types/list.rs similarity index 74% rename from src/bin/stellar-xdr/types/list.rs rename to src/cli/types/list.rs index 0f6ac4b2..30054ac4 100644 --- a/src/bin/stellar-xdr/types/list.rs +++ b/src/cli/types/list.rs @@ -1,7 +1,6 @@ use clap::{Args, ValueEnum}; -use std::error::Error; -use crate::Channel; +use crate::cli::Channel; #[derive(Args, Debug, Clone)] #[command()] @@ -25,7 +24,7 @@ impl Default for OutputFormat { } impl Cmd { - pub fn run(&self, channel: &Channel) -> Result<(), Box> { + pub fn run(&self, channel: &Channel) { let types = Self::types(channel); match self.output { OutputFormat::Plain => { @@ -34,19 +33,18 @@ impl Cmd { } } OutputFormat::Json => { - println!("{}", serde_json::to_string(&types)?); + println!("{}", serde_json::to_string(&types).unwrap()); } OutputFormat::JsonFormatted => { - println!("{}", serde_json::to_string_pretty(&types)?); + println!("{}", serde_json::to_string_pretty(&types).unwrap()); } } - Ok(()) } fn types(channel: &Channel) -> Vec<&'static str> { let types: &[&str] = match channel { - Channel::Curr => &stellar_xdr::curr::TypeVariant::VARIANTS_STR, - Channel::Next => &stellar_xdr::next::TypeVariant::VARIANTS_STR, + Channel::Curr => &crate::curr::TypeVariant::VARIANTS_STR, + Channel::Next => &crate::next::TypeVariant::VARIANTS_STR, }; let mut types: Vec<&'static str> = types.to_vec(); types.sort_unstable(); diff --git a/src/bin/stellar-xdr/version.rs b/src/cli/version.rs similarity index 85% rename from src/bin/stellar-xdr/version.rs rename to src/cli/version.rs index 1b18cbe6..7a18c186 100644 --- a/src/bin/stellar-xdr/version.rs +++ b/src/cli/version.rs @@ -1,12 +1,14 @@ use clap::Parser; +use crate::VERSION; + #[derive(Parser, Debug, Clone)] #[command()] pub struct Cmd; impl Cmd { pub fn run() { - let v = stellar_xdr::VERSION; + let v = VERSION; println!( "stellar-xdr {} ({}) xdr (+curr): {} diff --git a/src/lib.rs b/src/lib.rs index ef1e3b65..475a715f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,3 +123,6 @@ pub mod curr; #[cfg(feature = "next")] pub mod next; + +#[cfg(feature = "cli")] +pub mod cli;