diff --git a/benches/checks.rs b/benches/checks.rs index 810f339..c55c93d 100644 --- a/benches/checks.rs +++ b/benches/checks.rs @@ -26,7 +26,7 @@ fn bench_checks(c: &mut Criterion) { sample_path.path(), true, &policy, - &typos_cli::report::PrintSilent, + &PrintSilent, ) }); }); @@ -36,28 +36,18 @@ fn bench_checks(c: &mut Criterion) { sample_path.path(), true, &policy, - &typos_cli::report::PrintSilent, + &PrintSilent, ) }); }); group.bench_with_input(BenchmarkId::new("Words", name), &len, |b, _| { b.iter(|| { - typos_cli::file::Words.check_file( - sample_path.path(), - true, - &policy, - &typos_cli::report::PrintSilent, - ) + typos_cli::file::Words.check_file(sample_path.path(), true, &policy, &PrintSilent) }); }); group.bench_with_input(BenchmarkId::new("Typos", name), &len, |b, _| { b.iter(|| { - typos_cli::file::Typos.check_file( - sample_path.path(), - true, - &policy, - &typos_cli::report::PrintSilent, - ) + typos_cli::file::Typos.check_file(sample_path.path(), true, &policy, &PrintSilent) }); }); } @@ -66,5 +56,14 @@ fn bench_checks(c: &mut Criterion) { temp.close().unwrap(); } +#[derive(Debug, Default)] +pub struct PrintSilent; + +impl typos_cli::report::Report for PrintSilent { + fn report(&self, _msg: typos_cli::report::Message) -> Result<(), std::io::Error> { + Ok(()) + } +} + criterion_group!(benches, bench_checks,); criterion_main!(benches); diff --git a/src/bin/typos-cli/args.rs b/src/bin/typos-cli/args.rs index 53f96ac..fd6c9f4 100644 --- a/src/bin/typos-cli/args.rs +++ b/src/bin/typos-cli/args.rs @@ -1,6 +1,6 @@ use structopt::StructOpt; -use crate::config; +use typos_cli::config; arg_enum! { #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -12,10 +12,10 @@ arg_enum! { } } -pub const PRINT_SILENT: typos_cli::report::PrintSilent = typos_cli::report::PrintSilent; -pub const PRINT_BRIEF: typos_cli::report::PrintBrief = typos_cli::report::PrintBrief; -pub const PRINT_LONG: typos_cli::report::PrintLong = typos_cli::report::PrintLong; -pub const PRINT_JSON: typos_cli::report::PrintJson = typos_cli::report::PrintJson; +pub const PRINT_SILENT: crate::report::PrintSilent = crate::report::PrintSilent; +pub const PRINT_BRIEF: crate::report::PrintBrief = crate::report::PrintBrief; +pub const PRINT_LONG: crate::report::PrintLong = crate::report::PrintLong; +pub const PRINT_JSON: crate::report::PrintJson = crate::report::PrintJson; impl Format { pub(crate) fn reporter(self) -> &'static dyn typos_cli::report::Report { diff --git a/src/bin/typos-cli/main.rs b/src/bin/typos-cli/main.rs index 0d9139d..eba486b 100644 --- a/src/bin/typos-cli/main.rs +++ b/src/bin/typos-cli/main.rs @@ -7,8 +7,7 @@ use std::io::Write; use structopt::StructOpt; mod args; -use typos_cli::config; -use typos_cli::report; +mod report; use proc_exit::WithCodeResultExt; @@ -63,9 +62,10 @@ fn run_dump_config(args: &args::Args, output_path: &std::path::Path) -> proc_exi let mut engine = typos_cli::policy::ConfigEngine::new(&storage); engine.set_isolated(args.isolated); - let mut overrides = config::Config::default(); + let mut overrides = typos_cli::config::Config::default(); if let Some(path) = args.custom_config.as_ref() { - let custom = config::Config::from_file(path).with_code(proc_exit::Code::CONFIG_ERR)?; + let custom = + typos_cli::config::Config::from_file(path).with_code(proc_exit::Code::CONFIG_ERR)?; overrides.update(&custom); } overrides.update(&args.config.to_config()); @@ -75,7 +75,7 @@ fn run_dump_config(args: &args::Args, output_path: &std::path::Path) -> proc_exi .load_config(cwd) .with_code(proc_exit::Code::CONFIG_ERR)?; - let mut defaulted_config = config::Config::from_defaults(); + let mut defaulted_config = typos_cli::config::Config::from_defaults(); defaulted_config.update(&config); let output = toml::to_string_pretty(&defaulted_config).with_code(proc_exit::Code::FAILURE)?; if output_path == std::path::Path::new("-") { @@ -108,9 +108,10 @@ fn run_type_list(args: &args::Args) -> proc_exit::ExitResult { let mut engine = typos_cli::policy::ConfigEngine::new(&storage); engine.set_isolated(args.isolated); - let mut overrides = config::Config::default(); + let mut overrides = typos_cli::config::Config::default(); if let Some(path) = args.custom_config.as_ref() { - let custom = config::Config::from_file(path).with_code(proc_exit::Code::CONFIG_ERR)?; + let custom = + typos_cli::config::Config::from_file(path).with_code(proc_exit::Code::CONFIG_ERR)?; overrides.update(&custom); } overrides.update(&args.config.to_config()); @@ -142,9 +143,10 @@ fn run_checks(args: &args::Args) -> proc_exit::ExitResult { let mut engine = typos_cli::policy::ConfigEngine::new(&storage); engine.set_isolated(args.isolated); - let mut overrides = config::Config::default(); + let mut overrides = typos_cli::config::Config::default(); if let Some(path) = args.custom_config.as_ref() { - let custom = config::Config::from_file(path).with_code(proc_exit::Code::CONFIG_ERR)?; + let custom = + typos_cli::config::Config::from_file(path).with_code(proc_exit::Code::CONFIG_ERR)?; overrides.update(&custom); } overrides.update(&args.config.to_config()); @@ -190,7 +192,7 @@ fn run_checks(args: &args::Args) -> proc_exit::ExitResult { args.format.reporter() }; let status_reporter = report::MessageStatus::new(output_reporter); - let reporter: &dyn report::Report = &status_reporter; + let reporter: &dyn typos_cli::report::Report = &status_reporter; let selected_checks: &dyn typos_cli::file::FileChecker = if args.files { &typos_cli::file::FoundFiles diff --git a/src/bin/typos-cli/report.rs b/src/bin/typos-cli/report.rs new file mode 100644 index 0000000..afaad55 --- /dev/null +++ b/src/bin/typos-cli/report.rs @@ -0,0 +1,204 @@ +#![allow(clippy::needless_update)] + +use std::io::{self, Write}; +use std::sync::atomic; + +use typos_cli::report::{Context, Message, Report, Typo}; + +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> { + let _ = self.typos_found.compare_exchange( + false, + msg.is_correction(), + atomic::Ordering::Relaxed, + atomic::Ordering::Relaxed, + ); + let _ = self + .errors_found + .compare_exchange( + false, + msg.is_error(), + atomic::Ordering::Relaxed, + atomic::Ordering::Relaxed, + ) + .unwrap(); + self.reporter.report(msg) + } +} + +#[derive(Debug, Default)] +pub struct PrintSilent; + +impl Report for PrintSilent { + fn report(&self, _msg: Message) -> Result<(), std::io::Error> { + Ok(()) + } +} + +#[derive(Copy, Clone, Debug)] +pub struct PrintBrief; + +impl Report for PrintBrief { + 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::File(msg) => { + writeln!(io::stdout(), "{}", msg.path.display())?; + } + Message::Parse(msg) => { + writeln!(io::stdout(), "{}", msg.data)?; + } + Message::Error(msg) => { + log::error!("{}: {}", context_display(&msg.context), msg.msg); + } + _ => unimplemented!("New message {:?}", msg), + } + Ok(()) + } +} + +#[derive(Copy, Clone, Debug)] +pub struct PrintLong; + +impl Report for PrintLong { + 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::File(msg) => { + writeln!(io::stdout(), "{}", msg.path.display())?; + } + Message::Parse(msg) => { + writeln!(io::stdout(), "{}", msg.data)?; + } + Message::Error(msg) => { + log::error!("{}: {}", context_display(&msg.context), msg.msg); + } + _ => unimplemented!("New message {:?}", msg), + } + Ok(()) + } +} + +fn print_brief_correction(msg: &Typo) -> Result<(), std::io::Error> { + let line = String::from_utf8_lossy(msg.buffer.as_ref()); + let line = line.replace("\t", " "); + let column = unicode_segmentation::UnicodeSegmentation::graphemes( + line.get(0..msg.byte_offset).unwrap(), + true, + ) + .count(); + match &msg.corrections { + typos::Status::Valid => {} + typos::Status::Invalid => { + writeln!( + io::stdout(), + "{}:{}: `{}` is disallowed", + context_display(&msg.context), + column, + msg.typo, + )?; + } + typos::Status::Corrections(corrections) => { + writeln!( + io::stdout(), + "{}:{}: `{}` -> {}", + context_display(&msg.context), + column, + msg.typo, + itertools::join(corrections.iter().map(|s| format!("`{}`", s)), ", ") + )?; + } + } + + Ok(()) +} + +fn print_long_correction(msg: &Typo) -> Result<(), std::io::Error> { + let stdout = io::stdout(); + let mut handle = stdout.lock(); + + let line = String::from_utf8_lossy(msg.buffer.as_ref()); + let line = line.replace("\t", " "); + let column = unicode_segmentation::UnicodeSegmentation::graphemes( + line.get(0..msg.byte_offset).unwrap(), + true, + ) + .count(); + match &msg.corrections { + typos::Status::Valid => {} + typos::Status::Invalid => { + writeln!(handle, "error: `{}` is disallowed`", msg.typo,)?; + } + typos::Status::Corrections(corrections) => { + writeln!( + handle, + "error: `{}` should be {}", + msg.typo, + itertools::join(corrections.iter().map(|s| format!("`{}`", s)), ", ") + )?; + } + } + writeln!(handle, " --> {}:{}", context_display(&msg.context), column)?; + + if let Some(Context::File(context)) = &msg.context { + let line_num = context.line_num.to_string(); + let line_indent: String = itertools::repeat_n(" ", line_num.len()).collect(); + + let hl_indent: String = itertools::repeat_n(" ", column).collect(); + let hl: String = itertools::repeat_n("^", msg.typo.len()).collect(); + + 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 { + context + .as_ref() + .map(|c| c as &dyn std::fmt::Display) + .unwrap_or(&"") +} + +#[derive(Copy, Clone, Debug)] +pub struct PrintJson; + +impl Report for PrintJson { + fn report(&self, msg: Message) -> Result<(), std::io::Error> { + writeln!(io::stdout(), "{}", serde_json::to_string(&msg).unwrap())?; + Ok(()) + } +} diff --git a/src/report.rs b/src/report.rs index 08b2f28..871accb 100644 --- a/src/report.rs +++ b/src/report.rs @@ -1,8 +1,10 @@ #![allow(clippy::needless_update)] use std::borrow::Cow; -use std::io::{self, Write}; -use std::sync::atomic; + +pub trait Report: Send + Sync { + fn report(&self, msg: Message) -> Result<(), std::io::Error>; +} #[derive(Clone, Debug, serde::Serialize, derive_more::From)] #[serde(rename_all = "snake_case")] @@ -203,203 +205,3 @@ impl<'m> Default for Error<'m> { } } } - -pub trait Report: Send + Sync { - fn report(&self, msg: Message) -> Result<(), std::io::Error>; -} - -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> { - let _ = self.typos_found.compare_exchange( - false, - msg.is_correction(), - atomic::Ordering::Relaxed, - atomic::Ordering::Relaxed, - ); - let _ = self - .errors_found - .compare_exchange( - false, - msg.is_error(), - atomic::Ordering::Relaxed, - atomic::Ordering::Relaxed, - ) - .unwrap(); - self.reporter.report(msg) - } -} - -#[derive(Debug, Default)] -pub struct PrintSilent; - -impl Report for PrintSilent { - fn report(&self, _msg: Message) -> Result<(), std::io::Error> { - Ok(()) - } -} - -#[derive(Copy, Clone, Debug)] -pub struct PrintBrief; - -impl Report for PrintBrief { - 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::File(msg) => { - writeln!(io::stdout(), "{}", msg.path.display())?; - } - Message::Parse(msg) => { - writeln!(io::stdout(), "{}", msg.data)?; - } - Message::Error(msg) => { - log::error!("{}: {}", context_display(&msg.context), msg.msg); - } - } - Ok(()) - } -} - -#[derive(Copy, Clone, Debug)] -pub struct PrintLong; - -impl Report for PrintLong { - 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::File(msg) => { - writeln!(io::stdout(), "{}", msg.path.display())?; - } - Message::Parse(msg) => { - writeln!(io::stdout(), "{}", msg.data)?; - } - Message::Error(msg) => { - log::error!("{}: {}", context_display(&msg.context), msg.msg); - } - } - Ok(()) - } -} - -fn print_brief_correction(msg: &Typo) -> Result<(), std::io::Error> { - let line = String::from_utf8_lossy(msg.buffer.as_ref()); - let line = line.replace("\t", " "); - let column = unicode_segmentation::UnicodeSegmentation::graphemes( - line.get(0..msg.byte_offset).unwrap(), - true, - ) - .count(); - match &msg.corrections { - typos::Status::Valid => {} - typos::Status::Invalid => { - writeln!( - io::stdout(), - "{}:{}: `{}` is disallowed", - context_display(&msg.context), - column, - msg.typo, - )?; - } - typos::Status::Corrections(corrections) => { - writeln!( - io::stdout(), - "{}:{}: `{}` -> {}", - context_display(&msg.context), - column, - msg.typo, - itertools::join(corrections.iter().map(|s| format!("`{}`", s)), ", ") - )?; - } - } - - Ok(()) -} - -fn print_long_correction(msg: &Typo) -> Result<(), std::io::Error> { - let stdout = io::stdout(); - let mut handle = stdout.lock(); - - let line = String::from_utf8_lossy(msg.buffer.as_ref()); - let line = line.replace("\t", " "); - let column = unicode_segmentation::UnicodeSegmentation::graphemes( - line.get(0..msg.byte_offset).unwrap(), - true, - ) - .count(); - match &msg.corrections { - typos::Status::Valid => {} - typos::Status::Invalid => { - writeln!(handle, "error: `{}` is disallowed`", msg.typo,)?; - } - typos::Status::Corrections(corrections) => { - writeln!( - handle, - "error: `{}` should be {}", - msg.typo, - itertools::join(corrections.iter().map(|s| format!("`{}`", s)), ", ") - )?; - } - } - writeln!(handle, " --> {}:{}", context_display(&msg.context), column)?; - - if let Some(Context::File(context)) = &msg.context { - let line_num = context.line_num.to_string(); - let line_indent: String = itertools::repeat_n(" ", line_num.len()).collect(); - - let hl_indent: String = itertools::repeat_n(" ", column).collect(); - let hl: String = itertools::repeat_n("^", msg.typo.len()).collect(); - - 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 { - context - .as_ref() - .map(|c| c as &dyn std::fmt::Display) - .unwrap_or(&"") -} - -#[derive(Copy, Clone, Debug)] -pub struct PrintJson; - -impl Report for PrintJson { - fn report(&self, msg: Message) -> Result<(), std::io::Error> { - writeln!(io::stdout(), "{}", serde_json::to_string(&msg).unwrap())?; - Ok(()) - } -}