From 869b916ca6d45e3fcec555217573f8c75ad7011a Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 16 Nov 2020 20:02:10 -0600 Subject: [PATCH] fix: Handle broken pipe --- Cargo.lock | 17 +++-- Cargo.toml | 2 +- crates/typos/src/checks.rs | 125 +++++++++++++++++-------------------- crates/typos/src/error.rs | 52 --------------- crates/typos/src/lib.rs | 2 - crates/typos/src/report.rs | 104 ++++++++++++++++++++---------- crates/typos/src/tokens.rs | 23 ++++--- src/args.rs | 8 +-- src/checks.rs | 51 ++++----------- src/diff.rs | 6 +- src/exit.rs | 55 ---------------- src/main.rs | 64 ++++++++++--------- src/replace.rs | 72 +++++++++++---------- 13 files changed, 241 insertions(+), 340 deletions(-) delete mode 100644 crates/typos/src/error.rs delete mode 100644 src/exit.rs diff --git a/Cargo.lock b/Cargo.lock index 4681580..773641d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -668,6 +668,12 @@ dependencies = [ "treeline", ] +[[package]] +name = "proc-exit" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac573c94c4d539e8c339d612c8fc9835d286989411f1381a84a304c0ac19b41" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -973,15 +979,6 @@ 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" @@ -1099,9 +1096,9 @@ dependencies = [ "log", "phf", "predicates", + "proc-exit", "serde", "structopt", - "sysexit", "toml", "typos", "typos-dict", diff --git a/Cargo.toml b/Cargo.toml index fbe3fbc..377a4bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ env_logger = "0.8" bstr = "0.2" ahash = "0.5.8" difflib = "0.4" -sysexit = "0.2" +proc-exit = "0.1" [dev-dependencies] assert_fs = "1.0" diff --git a/crates/typos/src/checks.rs b/crates/typos/src/checks.rs index 922fc9d..6040440 100644 --- a/crates/typos/src/checks.rs +++ b/crates/typos/src/checks.rs @@ -12,7 +12,7 @@ pub trait Check: Send + Sync { parser: &tokens::Parser, dictionary: &dyn Dictionary, reporter: &dyn report::Report, - ) -> Result; + ) -> Result<(), std::io::Error>; fn check_bytes( &self, @@ -20,7 +20,7 @@ pub trait Check: Send + Sync { parser: &tokens::Parser, dictionary: &dyn Dictionary, reporter: &dyn report::Report, - ) -> Result; + ) -> Result<(), std::io::Error>; fn check_filenames(&self) -> bool; @@ -34,11 +34,9 @@ pub trait Check: Send + Sync { parser: &tokens::Parser, dictionary: &dyn Dictionary, reporter: &dyn report::Report, - ) -> Result { - let mut typos_found = false; - + ) -> Result<(), std::io::Error> { if !self.check_filenames() { - return Ok(typos_found); + return Ok(()); } if let Some(file_name) = path.file_name().and_then(|s| s.to_str()) { @@ -46,10 +44,10 @@ pub trait Check: Send + Sync { reporter, context: report::PathContext { path }.into(), }; - typos_found |= self.check_str(file_name, parser, dictionary, &context_reporter)?; + self.check_str(file_name, parser, dictionary, &context_reporter)?; } - Ok(typos_found) + Ok(()) } fn check_file( @@ -59,19 +57,17 @@ pub trait Check: Send + Sync { parser: &tokens::Parser, dictionary: &dyn Dictionary, reporter: &dyn report::Report, - ) -> Result { - let mut typos_found = false; - + ) -> Result<(), std::io::Error> { if !self.check_files() { - return Ok(typos_found); + return Ok(()); } - let buffer = read_file(path)?; + let buffer = read_file(path, reporter)?; let (buffer, content_type) = massage_data(buffer)?; if !explicit && !self.binary() && content_type.is_binary() { let msg = report::BinaryFile { path }; - reporter.report(msg.into()); - return Ok(typos_found); + reporter.report(msg.into())?; + return Ok(()); } for (line_idx, line) in buffer.lines().enumerate() { @@ -80,10 +76,10 @@ pub trait Check: Send + Sync { reporter, context: report::FileContext { path, line_num }.into(), }; - typos_found |= self.check_bytes(line, parser, dictionary, &context_reporter)?; + self.check_bytes(line, parser, dictionary, &context_reporter)?; } - Ok(typos_found) + Ok(()) } } @@ -93,7 +89,7 @@ struct ReportContext<'m, 'r> { } impl<'m, 'r> report::Report for ReportContext<'m, 'r> { - fn report(&self, msg: report::Message) -> bool { + fn report(&self, msg: report::Message) -> Result<(), std::io::Error> { let msg = msg.context(Some(self.context.clone())); self.reporter.report(msg) } @@ -179,9 +175,7 @@ impl Check for Typos { parser: &tokens::Parser, dictionary: &dyn Dictionary, reporter: &dyn report::Report, - ) -> Result { - let mut typos_found = false; - + ) -> Result<(), std::io::Error> { for ident in parser.parse_str(buffer) { match dictionary.correct_ident(ident) { Some(Status::Valid) => {} @@ -194,7 +188,7 @@ impl Check for Typos { typo: ident.token(), corrections, }; - typos_found |= reporter.report(msg.into()); + reporter.report(msg.into())?; } None => { for word in ident.split() { @@ -209,7 +203,7 @@ impl Check for Typos { typo: word.token(), corrections, }; - typos_found |= reporter.report(msg.into()); + reporter.report(msg.into())?; } None => {} } @@ -217,8 +211,7 @@ impl Check for Typos { } } } - - Ok(typos_found) + Ok(()) } fn check_bytes( @@ -227,9 +220,7 @@ impl Check for Typos { parser: &tokens::Parser, dictionary: &dyn Dictionary, reporter: &dyn report::Report, - ) -> Result { - let mut typos_found = false; - + ) -> Result<(), std::io::Error> { for ident in parser.parse_bytes(buffer) { match dictionary.correct_ident(ident) { Some(Status::Valid) => {} @@ -242,7 +233,7 @@ impl Check for Typos { typo: ident.token(), corrections, }; - typos_found |= reporter.report(msg.into()); + reporter.report(msg.into())?; } None => { for word in ident.split() { @@ -257,7 +248,7 @@ impl Check for Typos { typo: word.token(), corrections, }; - typos_found |= reporter.report(msg.into()); + reporter.report(msg.into())?; } None => {} } @@ -266,7 +257,7 @@ impl Check for Typos { } } - Ok(typos_found) + Ok(()) } fn check_filenames(&self) -> bool { @@ -296,19 +287,17 @@ impl Check for ParseIdentifiers { parser: &tokens::Parser, _dictionary: &dyn Dictionary, reporter: &dyn report::Report, - ) -> Result { - let typos_found = false; - + ) -> Result<(), std::io::Error> { let msg = report::Parse { context: None, kind: report::ParseKind::Identifier, data: parser.parse_str(buffer).map(|i| i.token()).collect(), }; if !msg.data.is_empty() { - reporter.report(msg.into()); + reporter.report(msg.into())?; } - Ok(typos_found) + Ok(()) } fn check_bytes( @@ -317,19 +306,17 @@ impl Check for ParseIdentifiers { parser: &tokens::Parser, _dictionary: &dyn Dictionary, reporter: &dyn report::Report, - ) -> Result { - let typos_found = false; - + ) -> Result<(), std::io::Error> { let msg = report::Parse { context: None, kind: report::ParseKind::Identifier, data: parser.parse_bytes(buffer).map(|i| i.token()).collect(), }; if !msg.data.is_empty() { - reporter.report(msg.into()); + reporter.report(msg.into())?; } - Ok(typos_found) + Ok(()) } fn check_filenames(&self) -> bool { @@ -359,9 +346,7 @@ impl Check for ParseWords { parser: &tokens::Parser, _dictionary: &dyn Dictionary, reporter: &dyn report::Report, - ) -> Result { - let typos_found = false; - + ) -> Result<(), std::io::Error> { let msg = report::Parse { context: None, kind: report::ParseKind::Word, @@ -371,10 +356,10 @@ impl Check for ParseWords { .collect(), }; if !msg.data.is_empty() { - reporter.report(msg.into()); + reporter.report(msg.into())?; } - Ok(typos_found) + Ok(()) } fn check_bytes( @@ -383,9 +368,7 @@ impl Check for ParseWords { parser: &tokens::Parser, _dictionary: &dyn Dictionary, reporter: &dyn report::Report, - ) -> Result { - let typos_found = false; - + ) -> Result<(), std::io::Error> { let msg = report::Parse { context: None, kind: report::ParseKind::Word, @@ -395,10 +378,10 @@ impl Check for ParseWords { .collect(), }; if !msg.data.is_empty() { - reporter.report(msg.into()); + reporter.report(msg.into())?; } - Ok(typos_found) + Ok(()) } fn check_filenames(&self) -> bool { @@ -424,9 +407,8 @@ impl Check for Files { _parser: &tokens::Parser, _dictionary: &dyn Dictionary, _reporter: &dyn report::Report, - ) -> Result { - let typos_found = false; - Ok(typos_found) + ) -> Result<(), std::io::Error> { + Ok(()) } fn check_bytes( @@ -435,9 +417,8 @@ impl Check for Files { _parser: &tokens::Parser, _dictionary: &dyn Dictionary, _reporter: &dyn report::Report, - ) -> Result { - let typos_found = false; - Ok(typos_found) + ) -> Result<(), std::io::Error> { + Ok(()) } fn check_filenames(&self) -> bool { @@ -458,9 +439,8 @@ impl Check for Files { _parser: &tokens::Parser, _dictionary: &dyn Dictionary, _reporter: &dyn report::Report, - ) -> Result { - let typos_found = false; - Ok(typos_found) + ) -> Result<(), std::io::Error> { + Ok(()) } fn check_file( @@ -470,23 +450,32 @@ impl Check for Files { _parser: &tokens::Parser, _dictionary: &dyn Dictionary, reporter: &dyn report::Report, - ) -> Result { - let typos_found = false; - + ) -> Result<(), std::io::Error> { let msg = report::File::new(path); - reporter.report(msg.into()); + reporter.report(msg.into())?; - Ok(typos_found) + Ok(()) } } -fn read_file(path: &std::path::Path) -> Result, crate::Error> { - std::fs::read(path).map_err(|e| crate::ErrorKind::IoError.into_error().with_source(e)) +fn read_file( + path: &std::path::Path, + reporter: &dyn report::Report, +) -> Result, std::io::Error> { + let buffer = match std::fs::read(path) { + Ok(buffer) => buffer, + Err(err) => { + let msg = report::Error::new(err.to_string()); + reporter.report(msg.into())?; + Vec::new() + } + }; + Ok(buffer) } fn massage_data( buffer: Vec, -) -> Result<(Vec, content_inspector::ContentType), crate::Error> { +) -> Result<(Vec, content_inspector::ContentType), std::io::Error> { let mut content_type = content_inspector::inspect(&buffer); // HACK: We only support UTF-8 at the moment diff --git a/crates/typos/src/error.rs b/crates/typos/src/error.rs deleted file mode 100644 index 3d49ded..0000000 --- a/crates/typos/src/error.rs +++ /dev/null @@ -1,52 +0,0 @@ -#[derive(Debug, Clone, Copy, derive_more::Display)] -pub enum ErrorKind { - #[display(fmt = "Invalid word")] - InvalidWord, - #[display(fmt = "IO Error")] - IoError, -} - -impl ErrorKind { - pub fn into_error(self) -> Error { - Error { - kind: self, - msg: None, - source: None, - } - } -} - -#[derive(thiserror::Error, Debug)] -pub struct Error { - kind: ErrorKind, - msg: Option, - source: Option, -} - -impl Error { - pub fn with_message(mut self, msg: String) -> Self { - self.msg = Some(msg); - self - } - - pub fn with_source( - mut self, - source: E, - ) -> Self { - self.source = Some(source.into()); - self - } -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - if let Some(msg) = self.msg.as_ref() { - write!(f, "{}: {}", self.kind, msg)?; - } else if let Some(source) = self.source.as_ref() { - write!(f, "{}: {}", self.kind, source)?; - } else { - write!(f, "{}", self.kind)?; - } - Ok(()) - } -} diff --git a/crates/typos/src/lib.rs b/crates/typos/src/lib.rs index 58ce818..1cb77c9 100644 --- a/crates/typos/src/lib.rs +++ b/crates/typos/src/lib.rs @@ -1,9 +1,7 @@ mod dict; -mod error; pub mod checks; pub mod report; pub mod tokens; pub use crate::dict::*; -pub use crate::error::*; diff --git a/crates/typos/src/report.rs b/crates/typos/src/report.rs index bd45753..c9eb5d7 100644 --- a/crates/typos/src/report.rs +++ b/crates/typos/src/report.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::io::{self, Write}; +use std::sync::atomic; #[derive(Clone, Debug, serde::Serialize, derive_more::From)] #[serde(rename_all = "snake_case")] @@ -214,15 +215,49 @@ impl Default for Error { } pub trait Report: Send + Sync { - fn report(&self, msg: Message) -> bool; + fn report(&self, msg: Message) -> Result<(), std::io::Error>; } -#[derive(Copy, Clone, Debug)] +pub struct MessageStatus<'r> { + typos_found: atomic::AtomicBool, + errors_found: atomic::AtomicBool, + reporter: &'r dyn Report, +} + +impl<'r> MessageStatus<'r> { + pub fn new(reporter: &'r dyn Report) -> Self { + Self { + typos_found: atomic::AtomicBool::new(false), + errors_found: atomic::AtomicBool::new(false), + reporter, + } + } + + pub fn typos_found(&self) -> bool { + self.typos_found.load(atomic::Ordering::Relaxed) + } + + pub fn errors_found(&self) -> bool { + self.errors_found.load(atomic::Ordering::Relaxed) + } +} + +impl<'r> Report for MessageStatus<'r> { + fn report(&self, msg: Message) -> Result<(), std::io::Error> { + self.typos_found + .compare_and_swap(false, msg.is_correction(), atomic::Ordering::Relaxed); + self.errors_found + .compare_and_swap(false, msg.is_error(), atomic::Ordering::Relaxed); + self.reporter.report(msg) + } +} + +#[derive(Debug, Default)] pub struct PrintSilent; impl Report for PrintSilent { - fn report(&self, msg: Message) -> bool { - msg.is_correction() + fn report(&self, _msg: Message) -> Result<(), std::io::Error> { + Ok(()) } } @@ -230,17 +265,17 @@ impl Report for PrintSilent { pub struct PrintBrief; impl Report for PrintBrief { - fn report(&self, msg: Message) -> bool { + fn report(&self, msg: Message) -> Result<(), std::io::Error> { match &msg { Message::BinaryFile(msg) => { log::info!("{}", msg); } - Message::Typo(msg) => print_brief_correction(msg), + Message::Typo(msg) => print_brief_correction(msg)?, Message::File(msg) => { - println!("{}", msg.path.display()); + writeln!(io::stdout(), "{}", msg.path.display())?; } Message::Parse(msg) => { - println!("{}", itertools::join(msg.data.iter(), " ")); + writeln!(io::stdout(), "{}", itertools::join(msg.data.iter(), " "))?; } Message::PathError(msg) => { log::error!("{}: {}", msg.path.display(), msg.msg); @@ -249,7 +284,7 @@ impl Report for PrintBrief { log::error!("{}", msg.msg); } } - msg.is_correction() + Ok(()) } } @@ -257,17 +292,17 @@ impl Report for PrintBrief { pub struct PrintLong; impl Report for PrintLong { - fn report(&self, msg: Message) -> bool { + fn report(&self, msg: Message) -> Result<(), std::io::Error> { match &msg { Message::BinaryFile(msg) => { log::info!("{}", msg); } - Message::Typo(msg) => print_long_correction(msg), + Message::Typo(msg) => print_long_correction(msg)?, Message::File(msg) => { - println!("{}", msg.path.display()); + writeln!(io::stdout(), "{}", msg.path.display())?; } Message::Parse(msg) => { - println!("{}", itertools::join(msg.data.iter(), " ")); + writeln!(io::stdout(), "{}", itertools::join(msg.data.iter(), " "))?; } Message::PathError(msg) => { log::error!("{}: {}", msg.path.display(), msg.msg); @@ -276,34 +311,38 @@ impl Report for PrintLong { log::error!("{}", msg.msg); } } - msg.is_correction() + Ok(()) } } -fn print_brief_correction(msg: &Typo) { +fn print_brief_correction(msg: &Typo) -> Result<(), std::io::Error> { match &msg.corrections { crate::Status::Valid => {} crate::Status::Invalid => { - println!( + writeln!( + io::stdout(), "{}:{}: {} is disallowed", context_display(&msg.context), msg.byte_offset, msg.typo, - ); + )?; } crate::Status::Corrections(corrections) => { - println!( + writeln!( + io::stdout(), "{}:{}: {} -> {}", context_display(&msg.context), msg.byte_offset, msg.typo, itertools::join(corrections.iter(), ", ") - ); + )?; } } + + Ok(()) } -fn print_long_correction(msg: &Typo) { +fn print_long_correction(msg: &Typo) -> Result<(), std::io::Error> { let stdout = io::stdout(); let mut handle = stdout.lock(); match &msg.corrections { @@ -315,8 +354,7 @@ fn print_long_correction(msg: &Typo) { context_display(&msg.context), msg.byte_offset, msg.typo, - ) - .unwrap(); + )?; } crate::Status::Corrections(corrections) => { writeln!( @@ -324,8 +362,7 @@ fn print_long_correction(msg: &Typo) { "error: `{}` should be {}", msg.typo, itertools::join(corrections.iter(), ", ") - ) - .unwrap(); + )?; } } writeln!( @@ -333,8 +370,7 @@ fn print_long_correction(msg: &Typo) { " --> {}:{}", context_display(&msg.context), msg.byte_offset - ) - .unwrap(); + )?; if let Some(Context::File(context)) = &msg.context { let line_num = context.line_num.to_string(); @@ -345,11 +381,13 @@ fn print_long_correction(msg: &Typo) { let line = String::from_utf8_lossy(msg.buffer.as_ref()); let line = line.replace("\t", " "); - writeln!(handle, "{} |", line_indent).unwrap(); - writeln!(handle, "{} | {}", line_num, line.trim_end()).unwrap(); - writeln!(handle, "{} | {}{}", line_indent, hl_indent, hl).unwrap(); - writeln!(handle, "{} |", line_indent).unwrap(); + writeln!(handle, "{} |", line_indent)?; + writeln!(handle, "{} | {}", line_num, line.trim_end())?; + writeln!(handle, "{} | {}{}", line_indent, hl_indent, hl)?; + writeln!(handle, "{} |", line_indent)?; } + + Ok(()) } fn context_display<'c>(context: &'c Option>) -> &'c dyn std::fmt::Display { @@ -363,8 +401,8 @@ fn context_display<'c>(context: &'c Option>) -> &'c dyn std::fmt::Di pub struct PrintJson; impl Report for PrintJson { - fn report(&self, msg: Message) -> bool { - println!("{}", serde_json::to_string(&msg).unwrap()); - msg.is_correction() + fn report(&self, msg: Message) -> Result<(), std::io::Error> { + writeln!(io::stdout(), "{}", serde_json::to_string(&msg).unwrap())?; + Ok(()) } } diff --git a/crates/typos/src/tokens.rs b/crates/typos/src/tokens.rs index d796831..9f2728c 100644 --- a/crates/typos/src/tokens.rs +++ b/crates/typos/src/tokens.rs @@ -192,23 +192,26 @@ pub struct Word<'t> { } impl<'t> Word<'t> { - pub fn new(token: &'t str, offset: usize) -> Result { + pub fn new(token: &'t str, offset: usize) -> Result { let mut itr = split_ident(token, 0); let mut item = itr.next().ok_or_else(|| { - crate::ErrorKind::InvalidWord - .into_error() - .with_message(format!("{:?} is nothing", token)) + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("{:?} is nothing", token), + ) })?; if item.offset != 0 { - return Err(crate::ErrorKind::InvalidWord - .into_error() - .with_message(format!("{:?} has padding", token))); + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("{:?} has padding", token), + )); } item.offset += offset; if itr.next().is_some() { - return Err(crate::ErrorKind::InvalidWord - .into_error() - .with_message(format!("{:?} is multiple words", token))); + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("{:?} is multiple words", token), + )); } Ok(item) } diff --git a/src/args.rs b/src/args.rs index f9d2b0f..1b1d153 100644 --- a/src/args.rs +++ b/src/args.rs @@ -12,10 +12,10 @@ arg_enum! { } } -const PRINT_SILENT: typos::report::PrintSilent = typos::report::PrintSilent; -const PRINT_BRIEF: typos::report::PrintBrief = typos::report::PrintBrief; -const PRINT_LONG: typos::report::PrintLong = typos::report::PrintLong; -const PRINT_JSON: typos::report::PrintJson = typos::report::PrintJson; +pub const PRINT_SILENT: typos::report::PrintSilent = typos::report::PrintSilent; +pub const PRINT_BRIEF: typos::report::PrintBrief = typos::report::PrintBrief; +pub const PRINT_LONG: typos::report::PrintLong = typos::report::PrintLong; +pub const PRINT_JSON: typos::report::PrintJson = typos::report::PrintJson; impl Format { pub(crate) fn reporter(self) -> &'static dyn typos::report::Report { diff --git a/src/checks.rs b/src/checks.rs index 2896dbb..ff2a221 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -1,28 +1,14 @@ -use std::sync::atomic; - pub(crate) fn check_path( walk: ignore::Walk, checks: &dyn typos::checks::Check, parser: &typos::tokens::Parser, dictionary: &dyn typos::Dictionary, reporter: &dyn typos::report::Report, -) -> (bool, bool) { - let mut typos_found = false; - let mut errors_found = false; - +) -> Result<(), anyhow::Error> { for entry in walk { - match check_entry(entry, checks, parser, dictionary, reporter) { - Ok(true) => typos_found = true, - Err(err) => { - let msg = typos::report::Error::new(err.to_string()); - reporter.report(msg.into()); - errors_found = true - } - _ => (), - } + check_entry(entry, checks, parser, dictionary, reporter)?; } - - (typos_found, errors_found) + Ok(()) } pub(crate) fn check_path_parallel( @@ -31,26 +17,21 @@ pub(crate) fn check_path_parallel( parser: &typos::tokens::Parser, dictionary: &dyn typos::Dictionary, reporter: &dyn typos::report::Report, -) -> (bool, bool) { - let typos_found = atomic::AtomicBool::new(false); - let errors_found = atomic::AtomicBool::new(false); - +) -> Result<(), anyhow::Error> { + let error: std::sync::Mutex> = std::sync::Mutex::new(Ok(())); walk.run(|| { Box::new(|entry: Result| { match check_entry(entry, checks, parser, dictionary, reporter) { - Ok(true) => typos_found.store(true, atomic::Ordering::Relaxed), + Ok(()) => ignore::WalkState::Continue, Err(err) => { - let msg = typos::report::Error::new(err.to_string()); - reporter.report(msg.into()); - errors_found.store(true, atomic::Ordering::Relaxed); + *error.lock().unwrap() = Err(err); + ignore::WalkState::Quit } - _ => (), } - ignore::WalkState::Continue }) }); - (typos_found.into_inner(), errors_found.into_inner()) + error.into_inner().unwrap() } fn check_entry( @@ -59,19 +40,13 @@ fn check_entry( parser: &typos::tokens::Parser, dictionary: &dyn typos::Dictionary, reporter: &dyn typos::report::Report, -) -> Result { - let mut typos_found = false; - +) -> Result<(), anyhow::Error> { let entry = entry?; if entry.file_type().map(|t| t.is_file()).unwrap_or(true) { let explicit = entry.depth() == 0; - if checks.check_filename(entry.path(), parser, dictionary, reporter)? { - typos_found = true; - } - if checks.check_file(entry.path(), explicit, parser, dictionary, reporter)? { - typos_found = true; - } + checks.check_filename(entry.path(), parser, dictionary, reporter)?; + checks.check_file(entry.path(), explicit, parser, dictionary, reporter)?; } - Ok(typos_found) + Ok(()) } diff --git a/src/diff.rs b/src/diff.rs index a49ae13..c99ac4e 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -57,7 +57,7 @@ impl<'r> Diff<'r> { } impl<'r> typos::report::Report for Diff<'r> { - fn report(&self, msg: typos::report::Message<'_>) -> bool { + fn report(&self, msg: typos::report::Message<'_>) -> Result<(), std::io::Error> { let typo = match &msg { typos::report::Message::Typo(typo) => typo, _ => return self.reporter.report(msg), @@ -85,9 +85,9 @@ impl<'r> typos::report::Report for Diff<'r> { .entry(line_num) .or_insert_with(Vec::new); content.push(correction); - false + Ok(()) } - _ => msg.is_correction(), + _ => self.reporter.report(msg), } } } diff --git a/src/exit.rs b/src/exit.rs deleted file mode 100644 index ddbf1ea..0000000 --- a/src/exit.rs +++ /dev/null @@ -1,55 +0,0 @@ -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 2bef956..96429dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,35 +11,24 @@ mod checks; mod config; mod dict; mod diff; -mod exit; mod replace; -use exit::ChainCodeExt; -use exit::ExitCodeResultAnyhowExt; -use exit::ExitCodeResultErrorExt; +use proc_exit::ProcessExitResultExt; +use proc_exit::WithCodeResultExt; fn main() { - let code = match run() { - Ok(()) => sysexit::Code::Success, - Err(err) => { - if let Some(error) = err.error { - eprintln!("{}", error); - } - err.code - } - }; - std::process::exit(code as i32); + run().process_exit(); } -fn run() -> Result<(), exit::ExitCode> { +fn run() -> Result<(), proc_exit::Exit> { // 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())); + return Err(proc_exit::Code::USAGE_ERR.with_message(e)); } Err(e) => { - println!("{}", e); + writeln!(std::io::stdout(), "{}", e)?; return Ok(()); } }; @@ -47,7 +36,7 @@ fn run() -> Result<(), exit::ExitCode> { init_logging(args.verbose.log_level()); let config = if let Some(path) = args.custom_config.as_ref() { - config::Config::from_file(path).code(sysexit::Code::Config)? + config::Config::from_file(path).with_code(proc_exit::Code::CONFIG_ERR)? } else { config::Config::default() }; @@ -55,7 +44,7 @@ fn run() -> Result<(), exit::ExitCode> { let mut typos_found = false; let mut errors_found = false; for path in args.path.iter() { - let path = path.canonicalize().code(sysexit::Code::Usage)?; + let path = path.canonicalize().with_code(proc_exit::Code::USAGE_ERR)?; let cwd = if path.is_file() { path.parent().unwrap() } else { @@ -64,7 +53,7 @@ fn run() -> Result<(), exit::ExitCode> { let mut config = config.clone(); if !args.isolated { - let derived = config::Config::derive(cwd).code(sysexit::Code::Config)?; + let derived = config::Config::derive(cwd).with_code(proc_exit::Code::CONFIG_ERR)?; config.update(&derived); } config.update(&args.config); @@ -102,7 +91,14 @@ fn run() -> Result<(), exit::ExitCode> { .git_exclude(config.files.ignore_vcs()) .parents(config.files.ignore_parent()); - let mut reporter = args.format.reporter(); + // HACK: Diff doesn't handle mixing content + let output_reporter = if args.diff { + &args::PRINT_SILENT + } else { + args.format.reporter() + }; + let status_reporter = typos::report::MessageStatus::new(output_reporter); + let mut reporter: &dyn typos::report::Report = &status_reporter; let replace_reporter = replace::Replace::new(reporter); let diff_reporter = diff::Diff::new(reporter); if args.diff { @@ -126,7 +122,7 @@ fn run() -> Result<(), exit::ExitCode> { &checks }; - let (cur_typos, cur_errors) = if single_threaded { + if single_threaded { checks::check_path( walk.build(), selected_checks, @@ -134,6 +130,7 @@ fn run() -> Result<(), exit::ExitCode> { &dictionary, reporter, ) + .with_code(proc_exit::Code::FAILURE)?; } else { checks::check_path_parallel( walk.build_parallel(), @@ -142,27 +139,34 @@ fn run() -> Result<(), exit::ExitCode> { &dictionary, reporter, ) - }; - if cur_typos { + .with_code(proc_exit::Code::FAILURE)?; + } + if status_reporter.typos_found() { typos_found = true; } - if cur_errors { + if status_reporter.errors_found() { errors_found = true; } if args.diff { - diff_reporter.show().code(sysexit::Code::Unknown)?; + diff_reporter.show().with_code(proc_exit::Code::FAILURE)?; } else if args.write_changes { - replace_reporter.write().code(sysexit::Code::Unknown)?; + replace_reporter + .write() + .with_code(proc_exit::Code::FAILURE)?; } } if errors_found { - Err(sysexit::Code::Failure.error()) + proc_exit::Code::FAILURE.ok() } else if typos_found { - Err(sysexit::Code::DataErr.error()) + // Can;'t use `Failure` since its so prevalent, it could be easy to get a + // `Failure` from something else and get it mixed up with typos. + // + // Can't use DataErr or anything else an std::io::ErrorKind might map to. + proc_exit::Code::UNKNOWN.ok() } else { - Ok(()) + proc_exit::Code::SUCCESS.ok() } } diff --git a/src/replace.rs b/src/replace.rs index aef418a..1ac129a 100644 --- a/src/replace.rs +++ b/src/replace.rs @@ -55,7 +55,7 @@ impl<'r> Replace<'r> { } impl<'r> typos::report::Report for Replace<'r> { - fn report(&self, msg: typos::report::Message<'_>) -> bool { + fn report(&self, msg: typos::report::Message<'_>) -> Result<(), std::io::Error> { let typo = match &msg { typos::report::Message::Typo(typo) => typo, _ => return self.reporter.report(msg), @@ -80,7 +80,7 @@ impl<'r> typos::report::Report for Replace<'r> { .entry(line_num) .or_insert_with(Vec::new); content.push(correction); - false + Ok(()) } Some(typos::report::Context::Path(path)) => { let path = path.path.to_owned(); @@ -89,7 +89,7 @@ impl<'r> typos::report::Report for Replace<'r> { let mut deferred = self.deferred.lock().unwrap(); let content = deferred.paths.entry(path).or_insert_with(Vec::new); content.push(correction); - false + Ok(()) } _ => self.reporter.report(msg), } @@ -207,22 +207,24 @@ mod test { let primary = typos::report::PrintSilent; let replace = Replace::new(&primary); - replace.report( - typos::report::Typo::default() - .context(Some( - typos::report::FileContext::default() - .path(input_file.path()) - .line_num(1) - .into(), - )) - .buffer(std::borrow::Cow::Borrowed(b"1 foo 2\n3 4 5")) - .byte_offset(2) - .typo("foo") - .corrections(typos::Status::Corrections(vec![ - std::borrow::Cow::Borrowed("bar"), - ])) - .into(), - ); + replace + .report( + typos::report::Typo::default() + .context(Some( + typos::report::FileContext::default() + .path(input_file.path()) + .line_num(1) + .into(), + )) + .buffer(std::borrow::Cow::Borrowed(b"1 foo 2\n3 4 5")) + .byte_offset(2) + .typo("foo") + .corrections(typos::Status::Corrections(vec![ + std::borrow::Cow::Borrowed("bar"), + ])) + .into(), + ) + .unwrap(); replace.write().unwrap(); input_file.assert("1 bar 2\n3 4 5"); @@ -236,21 +238,23 @@ mod test { let primary = typos::report::PrintSilent; let replace = Replace::new(&primary); - replace.report( - typos::report::Typo::default() - .context(Some( - typos::report::PathContext::default() - .path(input_file.path()) - .into(), - )) - .buffer(std::borrow::Cow::Borrowed(b"foo.txt")) - .byte_offset(0) - .typo("foo") - .corrections(typos::Status::Corrections(vec![ - std::borrow::Cow::Borrowed("bar"), - ])) - .into(), - ); + replace + .report( + typos::report::Typo::default() + .context(Some( + typos::report::PathContext::default() + .path(input_file.path()) + .into(), + )) + .buffer(std::borrow::Cow::Borrowed(b"foo.txt")) + .byte_offset(0) + .typo("foo") + .corrections(typos::Status::Corrections(vec![ + std::borrow::Cow::Borrowed("bar"), + ])) + .into(), + ) + .unwrap(); replace.write().unwrap(); input_file.assert(predicates::path::missing());