From 4ddbdcf5dd85cf6c779052a977aa1fb83b1565ad Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 14 Nov 2020 19:53:51 -0600 Subject: [PATCH] fix(cli): Define an error code policy The main goal is to make spelling errors differentiated from other errors. Fixes #170 --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + src/exit.rs | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 45 +++++++++++++++++++++++++++++-------------- 4 files changed, 97 insertions(+), 14 deletions(-) create mode 100644 src/exit.rs diff --git a/Cargo.lock b/Cargo.lock index 696db77..4681580 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -973,6 +973,15 @@ dependencies = [ "unicode-xid 0.2.1", ] +[[package]] +name = "sysexit" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26991bb0e6ef939ac9a8d6b57979efa59921928a3aa0334d818fe6e458aa6a9" +dependencies = [ + "libc", +] + [[package]] name = "tap" version = "1.0.0" @@ -1092,6 +1101,7 @@ dependencies = [ "predicates", "serde", "structopt", + "sysexit", "toml", "typos", "typos-dict", diff --git a/Cargo.toml b/Cargo.toml index f87e14c..fbe3fbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ env_logger = "0.8" bstr = "0.2" ahash = "0.5.8" difflib = "0.4" +sysexit = "0.2" [dev-dependencies] assert_fs = "1.0" diff --git a/src/exit.rs b/src/exit.rs new file mode 100644 index 0000000..ddbf1ea --- /dev/null +++ b/src/exit.rs @@ -0,0 +1,55 @@ +pub struct ExitCode { + pub code: sysexit::Code, + pub error: Option, +} + +impl ExitCode { + pub fn code(code: sysexit::Code) -> Self { + Self { code, error: None } + } + + pub fn error(mut self, error: anyhow::Error) -> Self { + self.error = Some(error); + self + } +} + +impl From for ExitCode { + fn from(code: sysexit::Code) -> Self { + Self::code(code) + } +} + +pub trait ChainCodeExt { + fn error(self) -> ExitCode; + fn chain(self, error: anyhow::Error) -> ExitCode; +} + +impl ChainCodeExt for sysexit::Code { + fn error(self) -> ExitCode { + ExitCode::code(self) + } + fn chain(self, error: anyhow::Error) -> ExitCode { + ExitCode::code(self).error(error) + } +} + +pub trait ExitCodeResultErrorExt { + fn code(self, code: sysexit::Code) -> Result; +} + +impl ExitCodeResultErrorExt for Result { + fn code(self, code: sysexit::Code) -> Result { + self.map_err(|e| ExitCode::code(code).error(e.into())) + } +} + +pub trait ExitCodeResultAnyhowExt { + fn code(self, code: sysexit::Code) -> Result; +} + +impl ExitCodeResultAnyhowExt for Result { + fn code(self, code: sysexit::Code) -> Result { + self.map_err(|e| ExitCode::code(code).error(e)) + } +} diff --git a/src/main.rs b/src/main.rs index 00ce2e5..2bef956 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,26 +11,43 @@ mod checks; mod config; mod dict; mod diff; +mod exit; mod replace; +use exit::ChainCodeExt; +use exit::ExitCodeResultAnyhowExt; +use exit::ExitCodeResultErrorExt; + fn main() { let code = match run() { - Ok(code) => code, + Ok(()) => sysexit::Code::Success, Err(err) => { - eprintln!("{}", err); - 1 + if let Some(error) = err.error { + eprintln!("{}", error); + } + err.code } }; - std::process::exit(code); + std::process::exit(code as i32); } -fn run() -> Result { - let args = args::Args::from_args(); +fn run() -> Result<(), exit::ExitCode> { + // clap's `get_matches` uses Failure rather than Usage, so bypass it for `get_matches_safe`. + let args = match args::Args::from_args_safe() { + Ok(args) => args, + Err(e) if e.use_stderr() => { + return Err(sysexit::Code::Usage.chain(e.into())); + } + Err(e) => { + println!("{}", e); + return Ok(()); + } + }; init_logging(args.verbose.log_level()); let config = if let Some(path) = args.custom_config.as_ref() { - config::Config::from_file(path)? + config::Config::from_file(path).code(sysexit::Code::Config)? } else { config::Config::default() }; @@ -38,7 +55,7 @@ fn run() -> Result { let mut typos_found = false; let mut errors_found = false; for path in args.path.iter() { - let path = path.canonicalize()?; + let path = path.canonicalize().code(sysexit::Code::Usage)?; let cwd = if path.is_file() { path.parent().unwrap() } else { @@ -47,7 +64,7 @@ fn run() -> Result { let mut config = config.clone(); if !args.isolated { - let derived = config::Config::derive(cwd)?; + let derived = config::Config::derive(cwd).code(sysexit::Code::Config)?; config.update(&derived); } config.update(&args.config); @@ -134,18 +151,18 @@ fn run() -> Result { } if args.diff { - diff_reporter.show()?; + diff_reporter.show().code(sysexit::Code::Unknown)?; } else if args.write_changes { - replace_reporter.write()?; + replace_reporter.write().code(sysexit::Code::Unknown)?; } } if errors_found { - Ok(2) + Err(sysexit::Code::Failure.error()) } else if typos_found { - Ok(1) + Err(sysexit::Code::DataErr.error()) } else { - Ok(0) + Ok(()) } }