diff --git a/derive/src/argument.rs b/derive/src/argument.rs index a4d9e77..c6b2f2d 100644 --- a/derive/src/argument.rs +++ b/derive/src/argument.rs @@ -186,7 +186,7 @@ pub fn short_handling(args: &[Argument]) -> (TokenStream, Vec) { Ok(Some(Argument::Custom( match short { #(#match_arms)* - _ => return Err(::uutils_args::Error::UnexpectedOption(short.to_string(), Vec::new())), + _ => return Err(::uutils_args::ErrorKind::UnexpectedOption(short.to_string(), Vec::new())), } ))) ); @@ -233,7 +233,7 @@ pub fn long_handling(args: &[Argument], help_flags: &Flags) -> TokenStream { if options.is_empty() { return quote!( - return Err(::uutils_args::Error::UnexpectedOption( + return Err(::uutils_args::ErrorKind::UnexpectedOption( long.to_string(), Vec::new() )) @@ -321,7 +321,7 @@ pub fn free_handling(args: &[Argument]) -> TokenStream { if let Some((prefix, value)) = arg.split_once('=') { #(#dd_branches)* - return Err(::uutils_args::Error::UnexpectedOption( + return Err(::uutils_args::ErrorKind::UnexpectedOption( prefix.to_string(), ::uutils_args::internal::filter_suggestions(prefix, &[#(#dd_args),*], "") )); @@ -392,9 +392,12 @@ pub fn positional_handling(args: &[Argument]) -> (TokenStream, TokenStream) { let mut missing: Vec<&str> = vec![]; #(#missing_argument_checks)* if !missing.is_empty() { - Err(uutils_args::Error::MissingPositionalArguments( - missing.iter().map(ToString::to_string).collect::>() - )) + Err(uutils_args::Error { + exit_code: Self::EXIT_CODE, + kind: uutils_args::ErrorKind::MissingPositionalArguments( + missing.iter().map(ToString::to_string).collect::>() + ) + }) } else { Ok(()) } diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 5967c09..bfeb314 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -73,7 +73,7 @@ pub fn arguments(input: TokenStream) -> TokenStream { #[allow(unreachable_code)] fn next_arg( parser: &mut uutils_args::lexopt::Parser, positional_idx: &mut usize - ) -> Result>, uutils_args::Error> { + ) -> Result>, ::uutils_args::ErrorKind> { use uutils_args::{Value, lexopt, Error, Argument}; #free diff --git a/src/error.rs b/src/error.rs index e2c9faa..ff7a6f4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -7,8 +7,13 @@ use std::{ fmt::{Debug, Display}, }; +pub struct Error { + pub exit_code: i32, + pub kind: ErrorKind, +} + /// Errors that can occur while parsing arguments. -pub enum Error { +pub enum ErrorKind { /// There was an option that required an option, but none was given. MissingValue { option: Option, @@ -51,53 +56,59 @@ pub enum Error { IoError(std::io::Error), } -impl From for Error { +impl From for ErrorKind { fn from(value: std::io::Error) -> Self { - Error::IoError(value) + ErrorKind::IoError(value) } } impl StdError for Error {} +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.kind.fmt(f) + } +} + impl Debug for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self, f) } } -impl Display for Error { +impl Display for ErrorKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "error: ")?; match self { - Error::MissingValue { option } => match option { + ErrorKind::MissingValue { option } => match option { Some(option) => write!(f, "Missing value for '{option}'."), None => write!(f, "Missing value"), }, - Error::MissingPositionalArguments(args) => { + ErrorKind::MissingPositionalArguments(args) => { write!(f, "Missing values for the following positional arguments:")?; for arg in args { write!(f, " - {arg}")?; } Ok(()) } - Error::UnexpectedOption(opt, suggestions) => { + ErrorKind::UnexpectedOption(opt, suggestions) => { write!(f, "Found an invalid option '{opt}'.")?; if !suggestions.is_empty() { write!(f, "\nDid you mean: {}", suggestions.join(", "))?; } Ok(()) } - Error::UnexpectedArgument(arg) => { + ErrorKind::UnexpectedArgument(arg) => { write!(f, "Found an invalid argument '{}'.", arg.to_string_lossy()) } - Error::UnexpectedValue { option, value } => { + ErrorKind::UnexpectedValue { option, value } => { write!( f, "Got an unexpected value '{}' for option '{option}'.", value.to_string_lossy(), ) } - Error::ParsingFailed { + ErrorKind::ParsingFailed { option, value, error, @@ -110,7 +121,7 @@ impl Display for Error { write!(f, "Invalid value '{value}' for '{option}': {error}") } } - Error::AmbiguousOption { option, candidates } => { + ErrorKind::AmbiguousOption { option, candidates } => { write!( f, "Option '{option}' is ambiguous. The following candidates match:" @@ -120,16 +131,16 @@ impl Display for Error { } Ok(()) } - Error::NonUnicodeValue(x) => { + ErrorKind::NonUnicodeValue(x) => { write!(f, "Invalid unicode value found: {}", x.to_string_lossy()) } - Error::IoError(x) => std::fmt::Display::fmt(x, f), + ErrorKind::IoError(x) => std::fmt::Display::fmt(x, f), } } } -impl From for Error { - fn from(other: lexopt::Error) -> Error { +impl From for ErrorKind { + fn from(other: lexopt::Error) -> ErrorKind { match other { lexopt::Error::MissingValue { option } => Self::MissingValue { option }, lexopt::Error::UnexpectedOption(s) => Self::UnexpectedOption(s, Vec::new()), diff --git a/src/internal.rs b/src/internal.rs index 8a13d57..412c737 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -10,7 +10,8 @@ //! Yet, they should be properly documented to make macro-expanded code //! readable. -use super::{Error, Value}; +use crate::error::ErrorKind; +use crate::value::Value; use std::{ ffi::{OsStr, OsString}, io::Write, @@ -64,8 +65,8 @@ pub fn parse_prefix(parser: &mut lexopt::Parser, prefix: &'static str) } /// Parse a value and wrap the error into an `Error::ParsingFailed` -pub fn parse_value_for_option(opt: &str, v: &OsStr) -> Result { - T::from_value(v).map_err(|e| Error::ParsingFailed { +pub fn parse_value_for_option(opt: &str, v: &OsStr) -> Result { + T::from_value(v).map_err(|e| ErrorKind::ParsingFailed { option: opt.into(), value: v.to_string_lossy().to_string(), error: e, @@ -76,7 +77,7 @@ pub fn parse_value_for_option(opt: &str, v: &OsStr) -> Result( input: &'a str, long_options: &'a [&'a str], -) -> Result<&'a str, Error> { +) -> Result<&'a str, ErrorKind> { let mut candidates = Vec::new(); let mut exact_match = None; for opt in long_options { @@ -91,11 +92,11 @@ pub fn infer_long_option<'a>( match (exact_match, &candidates[..]) { (Some(opt), _) => Ok(*opt), (None, [opt]) => Ok(**opt), - (None, []) => Err(Error::UnexpectedOption( + (None, []) => Err(ErrorKind::UnexpectedOption( format!("--{input}"), filter_suggestions(input, long_options, "--"), )), - (None, _) => Err(Error::AmbiguousOption { + (None, _) => Err(ErrorKind::AmbiguousOption { option: input.to_string(), candidates: candidates.iter().map(|s| s.to_string()).collect(), }), diff --git a/src/lib.rs b/src/lib.rs index e098140..342bf9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ mod value; pub use lexopt; pub use uutils_args_derive::*; -pub use error::Error; +pub use error::{Error, ErrorKind}; pub use value::{Value, ValueError, ValueResult}; use std::{ffi::OsString, marker::PhantomData}; @@ -24,12 +24,12 @@ pub enum Argument { Custom(T), } -fn exit_if_err(res: Result, exit_code: i32) -> T { +fn exit_if_err(res: Result) -> T { match res { Ok(v) => v, Err(err) => { eprintln!("{err}"); - std::process::exit(exit_code); + std::process::exit(err.exit_code); } } } @@ -62,7 +62,7 @@ pub trait Arguments: Sized { fn next_arg( parser: &mut lexopt::Parser, positional_idx: &mut usize, - ) -> Result>, Error>; + ) -> Result>, ErrorKind>; /// Check for any required arguments that have not been found. /// @@ -89,7 +89,7 @@ pub trait Arguments: Sized { I: IntoIterator, I::Item: Into, { - exit_if_err(Self::try_check(args), Self::EXIT_CODE) + exit_if_err(Self::try_check(args)) } /// Check all arguments immediately and return any errors. @@ -135,10 +135,15 @@ impl ArgumentIter { } pub fn next_arg(&mut self) -> Result, Error> { - if let Some(arg) = T::next_arg(&mut self.parser, &mut self.positional_idx)? { + if let Some(arg) = + T::next_arg(&mut self.parser, &mut self.positional_idx).map_err(|kind| Error { + exit_code: T::EXIT_CODE, + kind, + })? + { match arg { Argument::Help => { - self.help()?; + self.help().unwrap(); std::process::exit(0); } Argument::Version => { @@ -184,7 +189,7 @@ pub trait Options: Sized { I: IntoIterator, I::Item: Into, { - exit_if_err(self.try_parse(args), Arg::EXIT_CODE) + exit_if_err(self.try_parse(args)) } #[allow(unused_mut)] diff --git a/src/value.rs b/src/value.rs index ae9e3f7..54cfbba 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,7 +1,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use crate::error::Error; +use crate::error::{Error, ErrorKind}; use std::{ ffi::{OsStr, OsString}, path::PathBuf, @@ -50,7 +50,7 @@ impl std::fmt::Display for ValueError { /// Defines how a type should be parsed from an argument. /// -/// If an error is returned, it will be wrapped in [`Error::ParsingFailed`] +/// If an error is returned, it will be wrapped in [`ErrorKind::ParsingFailed`] pub trait Value: Sized { fn from_value(value: &OsStr) -> ValueResult; @@ -81,7 +81,11 @@ impl Value for String { fn from_value(value: &OsStr) -> ValueResult { match value.to_str() { Some(s) => Ok(s.into()), - None => Err(Error::NonUnicodeValue(value.into()).into()), + None => Err(Error { + exit_code: 1, + kind: ErrorKind::NonUnicodeValue(value.into()), + } + .into()), } } }