Merge pull request #174 from epage/pipe

fix: Handle broken pipe
This commit is contained in:
Ed Page 2020-11-21 22:09:12 -06:00 committed by GitHub
commit 8ecffe79e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 241 additions and 340 deletions

17
Cargo.lock generated
View file

@ -668,6 +668,12 @@ dependencies = [
"treeline", "treeline",
] ]
[[package]]
name = "proc-exit"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ac573c94c4d539e8c339d612c8fc9835d286989411f1381a84a304c0ac19b41"
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"
@ -973,15 +979,6 @@ dependencies = [
"unicode-xid 0.2.1", "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]] [[package]]
name = "tap" name = "tap"
version = "1.0.0" version = "1.0.0"
@ -1099,9 +1096,9 @@ dependencies = [
"log", "log",
"phf", "phf",
"predicates", "predicates",
"proc-exit",
"serde", "serde",
"structopt", "structopt",
"sysexit",
"toml", "toml",
"typos", "typos",
"typos-dict", "typos-dict",

View file

@ -48,7 +48,7 @@ env_logger = "0.8"
bstr = "0.2" bstr = "0.2"
ahash = "0.5.8" ahash = "0.5.8"
difflib = "0.4" difflib = "0.4"
sysexit = "0.2" proc-exit = "0.1"
[dev-dependencies] [dev-dependencies]
assert_fs = "1.0" assert_fs = "1.0"

View file

@ -12,7 +12,7 @@ pub trait Check: Send + Sync {
parser: &tokens::Parser, parser: &tokens::Parser,
dictionary: &dyn Dictionary, dictionary: &dyn Dictionary,
reporter: &dyn report::Report, reporter: &dyn report::Report,
) -> Result<bool, crate::Error>; ) -> Result<(), std::io::Error>;
fn check_bytes( fn check_bytes(
&self, &self,
@ -20,7 +20,7 @@ pub trait Check: Send + Sync {
parser: &tokens::Parser, parser: &tokens::Parser,
dictionary: &dyn Dictionary, dictionary: &dyn Dictionary,
reporter: &dyn report::Report, reporter: &dyn report::Report,
) -> Result<bool, crate::Error>; ) -> Result<(), std::io::Error>;
fn check_filenames(&self) -> bool; fn check_filenames(&self) -> bool;
@ -34,11 +34,9 @@ pub trait Check: Send + Sync {
parser: &tokens::Parser, parser: &tokens::Parser,
dictionary: &dyn Dictionary, dictionary: &dyn Dictionary,
reporter: &dyn report::Report, reporter: &dyn report::Report,
) -> Result<bool, crate::Error> { ) -> Result<(), std::io::Error> {
let mut typos_found = false;
if !self.check_filenames() { if !self.check_filenames() {
return Ok(typos_found); return Ok(());
} }
if let Some(file_name) = path.file_name().and_then(|s| s.to_str()) { if let Some(file_name) = path.file_name().and_then(|s| s.to_str()) {
@ -46,10 +44,10 @@ pub trait Check: Send + Sync {
reporter, reporter,
context: report::PathContext { path }.into(), 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( fn check_file(
@ -59,19 +57,17 @@ pub trait Check: Send + Sync {
parser: &tokens::Parser, parser: &tokens::Parser,
dictionary: &dyn Dictionary, dictionary: &dyn Dictionary,
reporter: &dyn report::Report, reporter: &dyn report::Report,
) -> Result<bool, crate::Error> { ) -> Result<(), std::io::Error> {
let mut typos_found = false;
if !self.check_files() { 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)?; let (buffer, content_type) = massage_data(buffer)?;
if !explicit && !self.binary() && content_type.is_binary() { if !explicit && !self.binary() && content_type.is_binary() {
let msg = report::BinaryFile { path }; let msg = report::BinaryFile { path };
reporter.report(msg.into()); reporter.report(msg.into())?;
return Ok(typos_found); return Ok(());
} }
for (line_idx, line) in buffer.lines().enumerate() { for (line_idx, line) in buffer.lines().enumerate() {
@ -80,10 +76,10 @@ pub trait Check: Send + Sync {
reporter, reporter,
context: report::FileContext { path, line_num }.into(), 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> { 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())); let msg = msg.context(Some(self.context.clone()));
self.reporter.report(msg) self.reporter.report(msg)
} }
@ -179,9 +175,7 @@ impl Check for Typos {
parser: &tokens::Parser, parser: &tokens::Parser,
dictionary: &dyn Dictionary, dictionary: &dyn Dictionary,
reporter: &dyn report::Report, reporter: &dyn report::Report,
) -> Result<bool, crate::Error> { ) -> Result<(), std::io::Error> {
let mut typos_found = false;
for ident in parser.parse_str(buffer) { for ident in parser.parse_str(buffer) {
match dictionary.correct_ident(ident) { match dictionary.correct_ident(ident) {
Some(Status::Valid) => {} Some(Status::Valid) => {}
@ -194,7 +188,7 @@ impl Check for Typos {
typo: ident.token(), typo: ident.token(),
corrections, corrections,
}; };
typos_found |= reporter.report(msg.into()); reporter.report(msg.into())?;
} }
None => { None => {
for word in ident.split() { for word in ident.split() {
@ -209,7 +203,7 @@ impl Check for Typos {
typo: word.token(), typo: word.token(),
corrections, corrections,
}; };
typos_found |= reporter.report(msg.into()); reporter.report(msg.into())?;
} }
None => {} None => {}
} }
@ -217,8 +211,7 @@ impl Check for Typos {
} }
} }
} }
Ok(())
Ok(typos_found)
} }
fn check_bytes( fn check_bytes(
@ -227,9 +220,7 @@ impl Check for Typos {
parser: &tokens::Parser, parser: &tokens::Parser,
dictionary: &dyn Dictionary, dictionary: &dyn Dictionary,
reporter: &dyn report::Report, reporter: &dyn report::Report,
) -> Result<bool, crate::Error> { ) -> Result<(), std::io::Error> {
let mut typos_found = false;
for ident in parser.parse_bytes(buffer) { for ident in parser.parse_bytes(buffer) {
match dictionary.correct_ident(ident) { match dictionary.correct_ident(ident) {
Some(Status::Valid) => {} Some(Status::Valid) => {}
@ -242,7 +233,7 @@ impl Check for Typos {
typo: ident.token(), typo: ident.token(),
corrections, corrections,
}; };
typos_found |= reporter.report(msg.into()); reporter.report(msg.into())?;
} }
None => { None => {
for word in ident.split() { for word in ident.split() {
@ -257,7 +248,7 @@ impl Check for Typos {
typo: word.token(), typo: word.token(),
corrections, corrections,
}; };
typos_found |= reporter.report(msg.into()); reporter.report(msg.into())?;
} }
None => {} None => {}
} }
@ -266,7 +257,7 @@ impl Check for Typos {
} }
} }
Ok(typos_found) Ok(())
} }
fn check_filenames(&self) -> bool { fn check_filenames(&self) -> bool {
@ -296,19 +287,17 @@ impl Check for ParseIdentifiers {
parser: &tokens::Parser, parser: &tokens::Parser,
_dictionary: &dyn Dictionary, _dictionary: &dyn Dictionary,
reporter: &dyn report::Report, reporter: &dyn report::Report,
) -> Result<bool, crate::Error> { ) -> Result<(), std::io::Error> {
let typos_found = false;
let msg = report::Parse { let msg = report::Parse {
context: None, context: None,
kind: report::ParseKind::Identifier, kind: report::ParseKind::Identifier,
data: parser.parse_str(buffer).map(|i| i.token()).collect(), data: parser.parse_str(buffer).map(|i| i.token()).collect(),
}; };
if !msg.data.is_empty() { if !msg.data.is_empty() {
reporter.report(msg.into()); reporter.report(msg.into())?;
} }
Ok(typos_found) Ok(())
} }
fn check_bytes( fn check_bytes(
@ -317,19 +306,17 @@ impl Check for ParseIdentifiers {
parser: &tokens::Parser, parser: &tokens::Parser,
_dictionary: &dyn Dictionary, _dictionary: &dyn Dictionary,
reporter: &dyn report::Report, reporter: &dyn report::Report,
) -> Result<bool, crate::Error> { ) -> Result<(), std::io::Error> {
let typos_found = false;
let msg = report::Parse { let msg = report::Parse {
context: None, context: None,
kind: report::ParseKind::Identifier, kind: report::ParseKind::Identifier,
data: parser.parse_bytes(buffer).map(|i| i.token()).collect(), data: parser.parse_bytes(buffer).map(|i| i.token()).collect(),
}; };
if !msg.data.is_empty() { if !msg.data.is_empty() {
reporter.report(msg.into()); reporter.report(msg.into())?;
} }
Ok(typos_found) Ok(())
} }
fn check_filenames(&self) -> bool { fn check_filenames(&self) -> bool {
@ -359,9 +346,7 @@ impl Check for ParseWords {
parser: &tokens::Parser, parser: &tokens::Parser,
_dictionary: &dyn Dictionary, _dictionary: &dyn Dictionary,
reporter: &dyn report::Report, reporter: &dyn report::Report,
) -> Result<bool, crate::Error> { ) -> Result<(), std::io::Error> {
let typos_found = false;
let msg = report::Parse { let msg = report::Parse {
context: None, context: None,
kind: report::ParseKind::Word, kind: report::ParseKind::Word,
@ -371,10 +356,10 @@ impl Check for ParseWords {
.collect(), .collect(),
}; };
if !msg.data.is_empty() { if !msg.data.is_empty() {
reporter.report(msg.into()); reporter.report(msg.into())?;
} }
Ok(typos_found) Ok(())
} }
fn check_bytes( fn check_bytes(
@ -383,9 +368,7 @@ impl Check for ParseWords {
parser: &tokens::Parser, parser: &tokens::Parser,
_dictionary: &dyn Dictionary, _dictionary: &dyn Dictionary,
reporter: &dyn report::Report, reporter: &dyn report::Report,
) -> Result<bool, crate::Error> { ) -> Result<(), std::io::Error> {
let typos_found = false;
let msg = report::Parse { let msg = report::Parse {
context: None, context: None,
kind: report::ParseKind::Word, kind: report::ParseKind::Word,
@ -395,10 +378,10 @@ impl Check for ParseWords {
.collect(), .collect(),
}; };
if !msg.data.is_empty() { if !msg.data.is_empty() {
reporter.report(msg.into()); reporter.report(msg.into())?;
} }
Ok(typos_found) Ok(())
} }
fn check_filenames(&self) -> bool { fn check_filenames(&self) -> bool {
@ -424,9 +407,8 @@ impl Check for Files {
_parser: &tokens::Parser, _parser: &tokens::Parser,
_dictionary: &dyn Dictionary, _dictionary: &dyn Dictionary,
_reporter: &dyn report::Report, _reporter: &dyn report::Report,
) -> Result<bool, crate::Error> { ) -> Result<(), std::io::Error> {
let typos_found = false; Ok(())
Ok(typos_found)
} }
fn check_bytes( fn check_bytes(
@ -435,9 +417,8 @@ impl Check for Files {
_parser: &tokens::Parser, _parser: &tokens::Parser,
_dictionary: &dyn Dictionary, _dictionary: &dyn Dictionary,
_reporter: &dyn report::Report, _reporter: &dyn report::Report,
) -> Result<bool, crate::Error> { ) -> Result<(), std::io::Error> {
let typos_found = false; Ok(())
Ok(typos_found)
} }
fn check_filenames(&self) -> bool { fn check_filenames(&self) -> bool {
@ -458,9 +439,8 @@ impl Check for Files {
_parser: &tokens::Parser, _parser: &tokens::Parser,
_dictionary: &dyn Dictionary, _dictionary: &dyn Dictionary,
_reporter: &dyn report::Report, _reporter: &dyn report::Report,
) -> Result<bool, crate::Error> { ) -> Result<(), std::io::Error> {
let typos_found = false; Ok(())
Ok(typos_found)
} }
fn check_file( fn check_file(
@ -470,23 +450,32 @@ impl Check for Files {
_parser: &tokens::Parser, _parser: &tokens::Parser,
_dictionary: &dyn Dictionary, _dictionary: &dyn Dictionary,
reporter: &dyn report::Report, reporter: &dyn report::Report,
) -> Result<bool, crate::Error> { ) -> Result<(), std::io::Error> {
let typos_found = false;
let msg = report::File::new(path); 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<Vec<u8>, crate::Error> { fn read_file(
std::fs::read(path).map_err(|e| crate::ErrorKind::IoError.into_error().with_source(e)) path: &std::path::Path,
reporter: &dyn report::Report,
) -> Result<Vec<u8>, 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( fn massage_data(
buffer: Vec<u8>, buffer: Vec<u8>,
) -> Result<(Vec<u8>, content_inspector::ContentType), crate::Error> { ) -> Result<(Vec<u8>, content_inspector::ContentType), std::io::Error> {
let mut content_type = content_inspector::inspect(&buffer); let mut content_type = content_inspector::inspect(&buffer);
// HACK: We only support UTF-8 at the moment // HACK: We only support UTF-8 at the moment

View file

@ -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<String>,
source: Option<anyhow::Error>,
}
impl Error {
pub fn with_message(mut self, msg: String) -> Self {
self.msg = Some(msg);
self
}
pub fn with_source<E: std::error::Error + std::fmt::Debug + Send + Sync + 'static>(
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(())
}
}

View file

@ -1,9 +1,7 @@
mod dict; mod dict;
mod error;
pub mod checks; pub mod checks;
pub mod report; pub mod report;
pub mod tokens; pub mod tokens;
pub use crate::dict::*; pub use crate::dict::*;
pub use crate::error::*;

View file

@ -2,6 +2,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::io::{self, Write}; use std::io::{self, Write};
use std::sync::atomic;
#[derive(Clone, Debug, serde::Serialize, derive_more::From)] #[derive(Clone, Debug, serde::Serialize, derive_more::From)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
@ -214,15 +215,49 @@ impl Default for Error {
} }
pub trait Report: Send + Sync { 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; pub struct PrintSilent;
impl Report for PrintSilent { impl Report for PrintSilent {
fn report(&self, msg: Message) -> bool { fn report(&self, _msg: Message) -> Result<(), std::io::Error> {
msg.is_correction() Ok(())
} }
} }
@ -230,17 +265,17 @@ impl Report for PrintSilent {
pub struct PrintBrief; pub struct PrintBrief;
impl Report for PrintBrief { impl Report for PrintBrief {
fn report(&self, msg: Message) -> bool { fn report(&self, msg: Message) -> Result<(), std::io::Error> {
match &msg { match &msg {
Message::BinaryFile(msg) => { Message::BinaryFile(msg) => {
log::info!("{}", msg); log::info!("{}", msg);
} }
Message::Typo(msg) => print_brief_correction(msg), Message::Typo(msg) => print_brief_correction(msg)?,
Message::File(msg) => { Message::File(msg) => {
println!("{}", msg.path.display()); writeln!(io::stdout(), "{}", msg.path.display())?;
} }
Message::Parse(msg) => { Message::Parse(msg) => {
println!("{}", itertools::join(msg.data.iter(), " ")); writeln!(io::stdout(), "{}", itertools::join(msg.data.iter(), " "))?;
} }
Message::PathError(msg) => { Message::PathError(msg) => {
log::error!("{}: {}", msg.path.display(), msg.msg); log::error!("{}: {}", msg.path.display(), msg.msg);
@ -249,7 +284,7 @@ impl Report for PrintBrief {
log::error!("{}", msg.msg); log::error!("{}", msg.msg);
} }
} }
msg.is_correction() Ok(())
} }
} }
@ -257,17 +292,17 @@ impl Report for PrintBrief {
pub struct PrintLong; pub struct PrintLong;
impl Report for PrintLong { impl Report for PrintLong {
fn report(&self, msg: Message) -> bool { fn report(&self, msg: Message) -> Result<(), std::io::Error> {
match &msg { match &msg {
Message::BinaryFile(msg) => { Message::BinaryFile(msg) => {
log::info!("{}", msg); log::info!("{}", msg);
} }
Message::Typo(msg) => print_long_correction(msg), Message::Typo(msg) => print_long_correction(msg)?,
Message::File(msg) => { Message::File(msg) => {
println!("{}", msg.path.display()); writeln!(io::stdout(), "{}", msg.path.display())?;
} }
Message::Parse(msg) => { Message::Parse(msg) => {
println!("{}", itertools::join(msg.data.iter(), " ")); writeln!(io::stdout(), "{}", itertools::join(msg.data.iter(), " "))?;
} }
Message::PathError(msg) => { Message::PathError(msg) => {
log::error!("{}: {}", msg.path.display(), msg.msg); log::error!("{}: {}", msg.path.display(), msg.msg);
@ -276,34 +311,38 @@ impl Report for PrintLong {
log::error!("{}", msg.msg); 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 { match &msg.corrections {
crate::Status::Valid => {} crate::Status::Valid => {}
crate::Status::Invalid => { crate::Status::Invalid => {
println!( writeln!(
io::stdout(),
"{}:{}: {} is disallowed", "{}:{}: {} is disallowed",
context_display(&msg.context), context_display(&msg.context),
msg.byte_offset, msg.byte_offset,
msg.typo, msg.typo,
); )?;
} }
crate::Status::Corrections(corrections) => { crate::Status::Corrections(corrections) => {
println!( writeln!(
io::stdout(),
"{}:{}: {} -> {}", "{}:{}: {} -> {}",
context_display(&msg.context), context_display(&msg.context),
msg.byte_offset, msg.byte_offset,
msg.typo, msg.typo,
itertools::join(corrections.iter(), ", ") 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 stdout = io::stdout();
let mut handle = stdout.lock(); let mut handle = stdout.lock();
match &msg.corrections { match &msg.corrections {
@ -315,8 +354,7 @@ fn print_long_correction(msg: &Typo) {
context_display(&msg.context), context_display(&msg.context),
msg.byte_offset, msg.byte_offset,
msg.typo, msg.typo,
) )?;
.unwrap();
} }
crate::Status::Corrections(corrections) => { crate::Status::Corrections(corrections) => {
writeln!( writeln!(
@ -324,8 +362,7 @@ fn print_long_correction(msg: &Typo) {
"error: `{}` should be {}", "error: `{}` should be {}",
msg.typo, msg.typo,
itertools::join(corrections.iter(), ", ") itertools::join(corrections.iter(), ", ")
) )?;
.unwrap();
} }
} }
writeln!( writeln!(
@ -333,8 +370,7 @@ fn print_long_correction(msg: &Typo) {
" --> {}:{}", " --> {}:{}",
context_display(&msg.context), context_display(&msg.context),
msg.byte_offset msg.byte_offset
) )?;
.unwrap();
if let Some(Context::File(context)) = &msg.context { if let Some(Context::File(context)) = &msg.context {
let line_num = context.line_num.to_string(); 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 = String::from_utf8_lossy(msg.buffer.as_ref());
let line = line.replace("\t", " "); let line = line.replace("\t", " ");
writeln!(handle, "{} |", line_indent).unwrap(); writeln!(handle, "{} |", line_indent)?;
writeln!(handle, "{} | {}", line_num, line.trim_end()).unwrap(); writeln!(handle, "{} | {}", line_num, line.trim_end())?;
writeln!(handle, "{} | {}{}", line_indent, hl_indent, hl).unwrap(); writeln!(handle, "{} | {}{}", line_indent, hl_indent, hl)?;
writeln!(handle, "{} |", line_indent).unwrap(); writeln!(handle, "{} |", line_indent)?;
} }
Ok(())
} }
fn context_display<'c>(context: &'c Option<Context<'c>>) -> &'c dyn std::fmt::Display { fn context_display<'c>(context: &'c Option<Context<'c>>) -> &'c dyn std::fmt::Display {
@ -363,8 +401,8 @@ fn context_display<'c>(context: &'c Option<Context<'c>>) -> &'c dyn std::fmt::Di
pub struct PrintJson; pub struct PrintJson;
impl Report for PrintJson { impl Report for PrintJson {
fn report(&self, msg: Message) -> bool { fn report(&self, msg: Message) -> Result<(), std::io::Error> {
println!("{}", serde_json::to_string(&msg).unwrap()); writeln!(io::stdout(), "{}", serde_json::to_string(&msg).unwrap())?;
msg.is_correction() Ok(())
} }
} }

View file

@ -192,23 +192,26 @@ pub struct Word<'t> {
} }
impl<'t> Word<'t> { impl<'t> Word<'t> {
pub fn new(token: &'t str, offset: usize) -> Result<Self, crate::Error> { pub fn new(token: &'t str, offset: usize) -> Result<Self, std::io::Error> {
let mut itr = split_ident(token, 0); let mut itr = split_ident(token, 0);
let mut item = itr.next().ok_or_else(|| { let mut item = itr.next().ok_or_else(|| {
crate::ErrorKind::InvalidWord std::io::Error::new(
.into_error() std::io::ErrorKind::InvalidInput,
.with_message(format!("{:?} is nothing", token)) format!("{:?} is nothing", token),
)
})?; })?;
if item.offset != 0 { if item.offset != 0 {
return Err(crate::ErrorKind::InvalidWord return Err(std::io::Error::new(
.into_error() std::io::ErrorKind::InvalidInput,
.with_message(format!("{:?} has padding", token))); format!("{:?} has padding", token),
));
} }
item.offset += offset; item.offset += offset;
if itr.next().is_some() { if itr.next().is_some() {
return Err(crate::ErrorKind::InvalidWord return Err(std::io::Error::new(
.into_error() std::io::ErrorKind::InvalidInput,
.with_message(format!("{:?} is multiple words", token))); format!("{:?} is multiple words", token),
));
} }
Ok(item) Ok(item)
} }

View file

@ -12,10 +12,10 @@ arg_enum! {
} }
} }
const PRINT_SILENT: typos::report::PrintSilent = typos::report::PrintSilent; pub const PRINT_SILENT: typos::report::PrintSilent = typos::report::PrintSilent;
const PRINT_BRIEF: typos::report::PrintBrief = typos::report::PrintBrief; pub const PRINT_BRIEF: typos::report::PrintBrief = typos::report::PrintBrief;
const PRINT_LONG: typos::report::PrintLong = typos::report::PrintLong; pub const PRINT_LONG: typos::report::PrintLong = typos::report::PrintLong;
const PRINT_JSON: typos::report::PrintJson = typos::report::PrintJson; pub const PRINT_JSON: typos::report::PrintJson = typos::report::PrintJson;
impl Format { impl Format {
pub(crate) fn reporter(self) -> &'static dyn typos::report::Report { pub(crate) fn reporter(self) -> &'static dyn typos::report::Report {

View file

@ -1,28 +1,14 @@
use std::sync::atomic;
pub(crate) fn check_path( pub(crate) fn check_path(
walk: ignore::Walk, walk: ignore::Walk,
checks: &dyn typos::checks::Check, checks: &dyn typos::checks::Check,
parser: &typos::tokens::Parser, parser: &typos::tokens::Parser,
dictionary: &dyn typos::Dictionary, dictionary: &dyn typos::Dictionary,
reporter: &dyn typos::report::Report, reporter: &dyn typos::report::Report,
) -> (bool, bool) { ) -> Result<(), anyhow::Error> {
let mut typos_found = false;
let mut errors_found = false;
for entry in walk { for entry in walk {
match check_entry(entry, checks, parser, dictionary, reporter) { 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
}
_ => (),
}
} }
Ok(())
(typos_found, errors_found)
} }
pub(crate) fn check_path_parallel( pub(crate) fn check_path_parallel(
@ -31,26 +17,21 @@ pub(crate) fn check_path_parallel(
parser: &typos::tokens::Parser, parser: &typos::tokens::Parser,
dictionary: &dyn typos::Dictionary, dictionary: &dyn typos::Dictionary,
reporter: &dyn typos::report::Report, reporter: &dyn typos::report::Report,
) -> (bool, bool) { ) -> Result<(), anyhow::Error> {
let typos_found = atomic::AtomicBool::new(false); let error: std::sync::Mutex<Result<(), anyhow::Error>> = std::sync::Mutex::new(Ok(()));
let errors_found = atomic::AtomicBool::new(false);
walk.run(|| { walk.run(|| {
Box::new(|entry: Result<ignore::DirEntry, ignore::Error>| { Box::new(|entry: Result<ignore::DirEntry, ignore::Error>| {
match check_entry(entry, checks, parser, dictionary, reporter) { match check_entry(entry, checks, parser, dictionary, reporter) {
Ok(true) => typos_found.store(true, atomic::Ordering::Relaxed), Ok(()) => ignore::WalkState::Continue,
Err(err) => { Err(err) => {
let msg = typos::report::Error::new(err.to_string()); *error.lock().unwrap() = Err(err);
reporter.report(msg.into()); ignore::WalkState::Quit
errors_found.store(true, atomic::Ordering::Relaxed);
} }
_ => (),
} }
ignore::WalkState::Continue
}) })
}); });
(typos_found.into_inner(), errors_found.into_inner()) error.into_inner().unwrap()
} }
fn check_entry( fn check_entry(
@ -59,19 +40,13 @@ fn check_entry(
parser: &typos::tokens::Parser, parser: &typos::tokens::Parser,
dictionary: &dyn typos::Dictionary, dictionary: &dyn typos::Dictionary,
reporter: &dyn typos::report::Report, reporter: &dyn typos::report::Report,
) -> Result<bool, anyhow::Error> { ) -> Result<(), anyhow::Error> {
let mut typos_found = false;
let entry = entry?; let entry = entry?;
if entry.file_type().map(|t| t.is_file()).unwrap_or(true) { if entry.file_type().map(|t| t.is_file()).unwrap_or(true) {
let explicit = entry.depth() == 0; let explicit = entry.depth() == 0;
if checks.check_filename(entry.path(), parser, dictionary, reporter)? { checks.check_filename(entry.path(), parser, dictionary, reporter)?;
typos_found = true; checks.check_file(entry.path(), explicit, parser, dictionary, reporter)?;
}
if checks.check_file(entry.path(), explicit, parser, dictionary, reporter)? {
typos_found = true;
}
} }
Ok(typos_found) Ok(())
} }

View file

@ -57,7 +57,7 @@ impl<'r> Diff<'r> {
} }
impl<'r> typos::report::Report for 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 { let typo = match &msg {
typos::report::Message::Typo(typo) => typo, typos::report::Message::Typo(typo) => typo,
_ => return self.reporter.report(msg), _ => return self.reporter.report(msg),
@ -85,9 +85,9 @@ impl<'r> typos::report::Report for Diff<'r> {
.entry(line_num) .entry(line_num)
.or_insert_with(Vec::new); .or_insert_with(Vec::new);
content.push(correction); content.push(correction);
false Ok(())
} }
_ => msg.is_correction(), _ => self.reporter.report(msg),
} }
} }
} }

View file

@ -1,55 +0,0 @@
pub struct ExitCode {
pub code: sysexit::Code,
pub error: Option<anyhow::Error>,
}
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<sysexit::Code> 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<T> {
fn code(self, code: sysexit::Code) -> Result<T, ExitCode>;
}
impl<T, E: std::error::Error + Send + Sync + 'static> ExitCodeResultErrorExt<T> for Result<T, E> {
fn code(self, code: sysexit::Code) -> Result<T, ExitCode> {
self.map_err(|e| ExitCode::code(code).error(e.into()))
}
}
pub trait ExitCodeResultAnyhowExt<T> {
fn code(self, code: sysexit::Code) -> Result<T, ExitCode>;
}
impl<T> ExitCodeResultAnyhowExt<T> for Result<T, anyhow::Error> {
fn code(self, code: sysexit::Code) -> Result<T, ExitCode> {
self.map_err(|e| ExitCode::code(code).error(e))
}
}

View file

@ -11,35 +11,24 @@ mod checks;
mod config; mod config;
mod dict; mod dict;
mod diff; mod diff;
mod exit;
mod replace; mod replace;
use exit::ChainCodeExt; use proc_exit::ProcessExitResultExt;
use exit::ExitCodeResultAnyhowExt; use proc_exit::WithCodeResultExt;
use exit::ExitCodeResultErrorExt;
fn main() { fn main() {
let code = match run() { run().process_exit();
Ok(()) => sysexit::Code::Success,
Err(err) => {
if let Some(error) = err.error {
eprintln!("{}", error);
}
err.code
}
};
std::process::exit(code as i32);
} }
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`. // clap's `get_matches` uses Failure rather than Usage, so bypass it for `get_matches_safe`.
let args = match args::Args::from_args_safe() { let args = match args::Args::from_args_safe() {
Ok(args) => args, Ok(args) => args,
Err(e) if e.use_stderr() => { 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) => { Err(e) => {
println!("{}", e); writeln!(std::io::stdout(), "{}", e)?;
return Ok(()); return Ok(());
} }
}; };
@ -47,7 +36,7 @@ fn run() -> Result<(), exit::ExitCode> {
init_logging(args.verbose.log_level()); init_logging(args.verbose.log_level());
let config = if let Some(path) = args.custom_config.as_ref() { 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 { } else {
config::Config::default() config::Config::default()
}; };
@ -55,7 +44,7 @@ fn run() -> Result<(), exit::ExitCode> {
let mut typos_found = false; let mut typos_found = false;
let mut errors_found = false; let mut errors_found = false;
for path in args.path.iter() { 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() { let cwd = if path.is_file() {
path.parent().unwrap() path.parent().unwrap()
} else { } else {
@ -64,7 +53,7 @@ fn run() -> Result<(), exit::ExitCode> {
let mut config = config.clone(); let mut config = config.clone();
if !args.isolated { 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(&derived);
} }
config.update(&args.config); config.update(&args.config);
@ -102,7 +91,14 @@ fn run() -> Result<(), exit::ExitCode> {
.git_exclude(config.files.ignore_vcs()) .git_exclude(config.files.ignore_vcs())
.parents(config.files.ignore_parent()); .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 replace_reporter = replace::Replace::new(reporter);
let diff_reporter = diff::Diff::new(reporter); let diff_reporter = diff::Diff::new(reporter);
if args.diff { if args.diff {
@ -126,7 +122,7 @@ fn run() -> Result<(), exit::ExitCode> {
&checks &checks
}; };
let (cur_typos, cur_errors) = if single_threaded { if single_threaded {
checks::check_path( checks::check_path(
walk.build(), walk.build(),
selected_checks, selected_checks,
@ -134,6 +130,7 @@ fn run() -> Result<(), exit::ExitCode> {
&dictionary, &dictionary,
reporter, reporter,
) )
.with_code(proc_exit::Code::FAILURE)?;
} else { } else {
checks::check_path_parallel( checks::check_path_parallel(
walk.build_parallel(), walk.build_parallel(),
@ -142,27 +139,34 @@ fn run() -> Result<(), exit::ExitCode> {
&dictionary, &dictionary,
reporter, reporter,
) )
}; .with_code(proc_exit::Code::FAILURE)?;
if cur_typos { }
if status_reporter.typos_found() {
typos_found = true; typos_found = true;
} }
if cur_errors { if status_reporter.errors_found() {
errors_found = true; errors_found = true;
} }
if args.diff { if args.diff {
diff_reporter.show().code(sysexit::Code::Unknown)?; diff_reporter.show().with_code(proc_exit::Code::FAILURE)?;
} else if args.write_changes { } else if args.write_changes {
replace_reporter.write().code(sysexit::Code::Unknown)?; replace_reporter
.write()
.with_code(proc_exit::Code::FAILURE)?;
} }
} }
if errors_found { if errors_found {
Err(sysexit::Code::Failure.error()) proc_exit::Code::FAILURE.ok()
} else if typos_found { } 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 { } else {
Ok(()) proc_exit::Code::SUCCESS.ok()
} }
} }

View file

@ -55,7 +55,7 @@ impl<'r> Replace<'r> {
} }
impl<'r> typos::report::Report for 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 { let typo = match &msg {
typos::report::Message::Typo(typo) => typo, typos::report::Message::Typo(typo) => typo,
_ => return self.reporter.report(msg), _ => return self.reporter.report(msg),
@ -80,7 +80,7 @@ impl<'r> typos::report::Report for Replace<'r> {
.entry(line_num) .entry(line_num)
.or_insert_with(Vec::new); .or_insert_with(Vec::new);
content.push(correction); content.push(correction);
false Ok(())
} }
Some(typos::report::Context::Path(path)) => { Some(typos::report::Context::Path(path)) => {
let path = path.path.to_owned(); 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 mut deferred = self.deferred.lock().unwrap();
let content = deferred.paths.entry(path).or_insert_with(Vec::new); let content = deferred.paths.entry(path).or_insert_with(Vec::new);
content.push(correction); content.push(correction);
false Ok(())
} }
_ => self.reporter.report(msg), _ => self.reporter.report(msg),
} }
@ -207,22 +207,24 @@ mod test {
let primary = typos::report::PrintSilent; let primary = typos::report::PrintSilent;
let replace = Replace::new(&primary); let replace = Replace::new(&primary);
replace.report( replace
typos::report::Typo::default() .report(
.context(Some( typos::report::Typo::default()
typos::report::FileContext::default() .context(Some(
.path(input_file.path()) typos::report::FileContext::default()
.line_num(1) .path(input_file.path())
.into(), .line_num(1)
)) .into(),
.buffer(std::borrow::Cow::Borrowed(b"1 foo 2\n3 4 5")) ))
.byte_offset(2) .buffer(std::borrow::Cow::Borrowed(b"1 foo 2\n3 4 5"))
.typo("foo") .byte_offset(2)
.corrections(typos::Status::Corrections(vec![ .typo("foo")
std::borrow::Cow::Borrowed("bar"), .corrections(typos::Status::Corrections(vec![
])) std::borrow::Cow::Borrowed("bar"),
.into(), ]))
); .into(),
)
.unwrap();
replace.write().unwrap(); replace.write().unwrap();
input_file.assert("1 bar 2\n3 4 5"); input_file.assert("1 bar 2\n3 4 5");
@ -236,21 +238,23 @@ mod test {
let primary = typos::report::PrintSilent; let primary = typos::report::PrintSilent;
let replace = Replace::new(&primary); let replace = Replace::new(&primary);
replace.report( replace
typos::report::Typo::default() .report(
.context(Some( typos::report::Typo::default()
typos::report::PathContext::default() .context(Some(
.path(input_file.path()) typos::report::PathContext::default()
.into(), .path(input_file.path())
)) .into(),
.buffer(std::borrow::Cow::Borrowed(b"foo.txt")) ))
.byte_offset(0) .buffer(std::borrow::Cow::Borrowed(b"foo.txt"))
.typo("foo") .byte_offset(0)
.corrections(typos::Status::Corrections(vec![ .typo("foo")
std::borrow::Cow::Borrowed("bar"), .corrections(typos::Status::Corrections(vec![
])) std::borrow::Cow::Borrowed("bar"),
.into(), ]))
); .into(),
)
.unwrap();
replace.write().unwrap(); replace.write().unwrap();
input_file.assert(predicates::path::missing()); input_file.assert(predicates::path::missing());