Skip to content

Commit

Permalink
Merge pull request #75 from tertsdiepraam/error-type-with-exit-code
Browse files Browse the repository at this point in the history
Add exit code to the error type
  • Loading branch information
cakebaker authored Dec 17, 2023
2 parents d4b4e3c + affc4b6 commit 932a471
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 39 deletions.
15 changes: 9 additions & 6 deletions derive/src/argument.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ pub fn short_handling(args: &[Argument]) -> (TokenStream, Vec<char>) {
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())),
}
)))
);
Expand Down Expand Up @@ -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()
))
Expand Down Expand Up @@ -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),*], "")
));
Expand Down Expand Up @@ -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::<Vec<String>>()
))
Err(uutils_args::Error {
exit_code: Self::EXIT_CODE,
kind: uutils_args::ErrorKind::MissingPositionalArguments(
missing.iter().map(ToString::to_string).collect::<Vec<String>>()
)
})
} else {
Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<uutils_args::Argument<Self>>, uutils_args::Error> {
) -> Result<Option<::uutils_args::Argument<Self>>, ::uutils_args::ErrorKind> {
use uutils_args::{Value, lexopt, Error, Argument};

#free
Expand Down
41 changes: 26 additions & 15 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
Expand Down Expand Up @@ -51,53 +56,59 @@ pub enum Error {
IoError(std::io::Error),
}

impl From<std::io::Error> for Error {
impl From<std::io::Error> 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,
Expand All @@ -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:"
Expand All @@ -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<lexopt::Error> for Error {
fn from(other: lexopt::Error) -> Error {
impl From<lexopt::Error> 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()),
Expand Down
13 changes: 7 additions & 6 deletions src/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -64,8 +65,8 @@ pub fn parse_prefix<T: Value>(parser: &mut lexopt::Parser, prefix: &'static str)
}

/// Parse a value and wrap the error into an `Error::ParsingFailed`
pub fn parse_value_for_option<T: Value>(opt: &str, v: &OsStr) -> Result<T, Error> {
T::from_value(v).map_err(|e| Error::ParsingFailed {
pub fn parse_value_for_option<T: Value>(opt: &str, v: &OsStr) -> Result<T, ErrorKind> {
T::from_value(v).map_err(|e| ErrorKind::ParsingFailed {
option: opt.into(),
value: v.to_string_lossy().to_string(),
error: e,
Expand All @@ -76,7 +77,7 @@ pub fn parse_value_for_option<T: Value>(opt: &str, v: &OsStr) -> Result<T, Error
pub fn infer_long_option<'a>(
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 {
Expand All @@ -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(),
}),
Expand Down
21 changes: 13 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -24,12 +24,12 @@ pub enum Argument<T: Arguments> {
Custom(T),
}

fn exit_if_err<T>(res: Result<T, Error>, exit_code: i32) -> T {
fn exit_if_err<T>(res: Result<T, Error>) -> T {
match res {
Ok(v) => v,
Err(err) => {
eprintln!("{err}");
std::process::exit(exit_code);
std::process::exit(err.exit_code);
}
}
}
Expand Down Expand Up @@ -62,7 +62,7 @@ pub trait Arguments: Sized {
fn next_arg(
parser: &mut lexopt::Parser,
positional_idx: &mut usize,
) -> Result<Option<Argument<Self>>, Error>;
) -> Result<Option<Argument<Self>>, ErrorKind>;

/// Check for any required arguments that have not been found.
///
Expand All @@ -89,7 +89,7 @@ pub trait Arguments: Sized {
I: IntoIterator,
I::Item: Into<OsString>,
{
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.
Expand Down Expand Up @@ -135,10 +135,15 @@ impl<T: Arguments> ArgumentIter<T> {
}

pub fn next_arg(&mut self) -> Result<Option<T>, 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 => {
Expand Down Expand Up @@ -184,7 +189,7 @@ pub trait Options<Arg: Arguments>: Sized {
I: IntoIterator,
I::Item: Into<OsString>,
{
exit_if_err(self.try_parse(args), Arg::EXIT_CODE)
exit_if_err(self.try_parse(args))
}

#[allow(unused_mut)]
Expand Down
10 changes: 7 additions & 3 deletions src/value.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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<Self>;

Expand Down Expand Up @@ -81,7 +81,11 @@ impl Value for String {
fn from_value(value: &OsStr) -> ValueResult<Self> {
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()),
}
}
}
Expand Down

0 comments on commit 932a471

Please sign in to comment.