From 5f82dd60176e8a1378a8b9d068a38f988e5f4370 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 1 Jan 2021 21:16:20 -0600 Subject: [PATCH] fix: Arg diff reports immediately --- src/checks.rs | 125 +++++++++++++++++++++++++++++++++++++++++++++++++ src/diff.rs | 93 ------------------------------------ src/lib.rs | 2 - src/main.rs | 16 ++----- src/replace.rs | 103 ---------------------------------------- 5 files changed, 130 insertions(+), 209 deletions(-) delete mode 100644 src/diff.rs delete mode 100644 src/replace.rs diff --git a/src/checks.rs b/src/checks.rs index 7053d24..9349437 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -58,6 +58,14 @@ impl TyposSettings { } } + pub fn build_diff_typos(&self) -> DiffTypos { + DiffTypos { + check_filenames: self.check_filenames, + check_files: self.check_files, + binary: self.binary, + } + } + pub fn build_identifier_parser(&self) -> Identifiers { Identifiers { check_filenames: self.check_filenames, @@ -238,6 +246,123 @@ impl Check for FixTypos { } } +#[derive(Debug, Clone)] +pub struct DiffTypos { + check_filenames: bool, + check_files: bool, + binary: bool, +} + +impl Check for DiffTypos { + fn check_file( + &self, + path: &std::path::Path, + explicit: bool, + tokenizer: &tokens::Tokenizer, + dictionary: &dyn Dictionary, + reporter: &dyn report::Report, + ) -> Result<(), std::io::Error> { + let parser = typos::ParserBuilder::new() + .tokenizer(tokenizer) + .dictionary(dictionary) + .typos(); + + let mut content = Vec::new(); + let mut new_content = Vec::new(); + if self.check_files { + let (buffer, content_type) = read_file(path, reporter)?; + if !explicit && !self.binary && content_type.is_binary() { + let msg = report::BinaryFile { path }; + reporter.report(msg.into())?; + } else { + let mut fixes = Vec::new(); + let mut accum_line_num = AccumulateLineNum::new(); + for typo in parser.parse_bytes(&buffer) { + if is_fixable(&typo) { + fixes.push(typo.into_owned()); + } else { + let line_num = accum_line_num.line_num(&buffer, typo.byte_offset); + let (line, line_offset) = extract_line(&buffer, typo.byte_offset); + let msg = report::Typo { + context: Some(report::FileContext { path, line_num }.into()), + buffer: std::borrow::Cow::Borrowed(line), + byte_offset: line_offset, + typo: typo.typo.as_ref(), + corrections: typo.corrections, + }; + reporter.report(msg.into())?; + } + } + if !fixes.is_empty() { + new_content = fix_buffer(buffer.clone(), fixes.into_iter()); + content = buffer + } + } + } + + // Match FixTypos ordering for easy diffing. + let mut new_path = None; + if self.check_filenames { + if let Some(file_name) = path.file_name().and_then(|s| s.to_str()) { + let mut fixes = Vec::new(); + for typo in parser.parse_str(file_name) { + if is_fixable(&typo) { + fixes.push(typo.into_owned()); + } else { + let msg = report::Typo { + context: Some(report::PathContext { path }.into()), + buffer: std::borrow::Cow::Borrowed(file_name.as_bytes()), + byte_offset: typo.byte_offset, + typo: typo.typo.as_ref(), + corrections: typo.corrections, + }; + reporter.report(msg.into())?; + } + } + if !fixes.is_empty() { + let file_name = file_name.to_owned().into_bytes(); + let new_name = fix_buffer(file_name, fixes.into_iter()); + let new_name = + String::from_utf8(new_name).expect("corrections are valid utf-8"); + new_path = Some(path.with_file_name(new_name)); + } + } + } + + if new_path.is_some() || !content.is_empty() { + let original_path = path.display().to_string(); + let fixed_path = new_path + .as_ref() + .map(|p| p.as_path()) + .unwrap_or(path) + .display() + .to_string(); + let original_content: Vec<_> = content + .lines_with_terminator() + .map(|s| String::from_utf8_lossy(s).into_owned()) + .collect(); + let fixed_content: Vec<_> = new_content + .lines_with_terminator() + .map(|s| String::from_utf8_lossy(s).into_owned()) + .collect(); + let diff = difflib::unified_diff( + &original_content, + &fixed_content, + original_path.as_str(), + fixed_path.as_str(), + "original", + "fixed", + 0, + ); + for line in diff { + print!("{}", line); + } + } + + Ok(()) + } +} + #[derive(Debug, Clone)] pub struct Identifiers { check_filenames: bool, diff --git a/src/diff.rs b/src/diff.rs deleted file mode 100644 index 8457588..0000000 --- a/src/diff.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::collections::BTreeMap; -use std::sync; - -use bstr::ByteSlice; - -pub struct Diff<'r> { - reporter: &'r dyn crate::report::Report, - deferred: sync::Mutex, -} - -impl<'r> Diff<'r> { - pub fn new(reporter: &'r dyn crate::report::Report) -> Self { - Self { - reporter, - deferred: sync::Mutex::new(crate::replace::Deferred::default()), - } - } - - pub fn show(&self) -> Result<(), std::io::Error> { - let deferred = self.deferred.lock().unwrap(); - - for (path, corrections) in deferred.content.iter() { - let buffer = std::fs::read(path)?; - - let mut original = Vec::new(); - let mut corrected = Vec::new(); - for (line_idx, line) in buffer.lines_with_terminator().enumerate() { - original.push(String::from_utf8_lossy(line).into_owned()); - - let line_num = line_idx + 1; - let line = if let Some(corrections) = corrections.get(&line_num) { - let line = line.to_vec(); - crate::replace::correct(line, &corrections) - } else { - line.to_owned() - }; - corrected.push(String::from_utf8_lossy(&line).into_owned()) - } - - let display_path = path.display().to_string(); - let diff = difflib::unified_diff( - &original, - &corrected, - display_path.as_str(), - display_path.as_str(), - "original", - "corrected", - 0, - ); - for line in diff { - print!("{}", line); - } - } - - Ok(()) - } -} - -impl<'r> crate::report::Report for Diff<'r> { - fn report(&self, msg: crate::report::Message<'_>) -> Result<(), std::io::Error> { - let typo = match &msg { - crate::report::Message::Typo(typo) => typo, - _ => return self.reporter.report(msg), - }; - - let corrections = match &typo.corrections { - typos::Status::Corrections(corrections) if corrections.len() == 1 => corrections, - _ => return self.reporter.report(msg), - }; - - match &typo.context { - Some(crate::report::Context::File(file)) => { - let path = file.path.to_owned(); - let line_num = file.line_num; - let correction = crate::replace::Correction::new( - typo.byte_offset, - typo.typo, - corrections[0].as_ref(), - ); - let mut deferred = self.deferred.lock().unwrap(); - let content = deferred - .content - .entry(path) - .or_insert_with(BTreeMap::new) - .entry(line_num) - .or_insert_with(Vec::new); - content.push(correction); - Ok(()) - } - _ => self.reporter.report(msg), - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 2584359..4d0e01e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,4 @@ pub mod checks; pub mod config; pub mod dict; -pub mod diff; -pub(crate) mod replace; pub mod report; diff --git a/src/main.rs b/src/main.rs index 5e33fd5..ade1fee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,6 @@ mod args; use typos_cli::checks; use typos_cli::config; use typos_cli::dict; -use typos_cli::diff; use typos_cli::report; use proc_exit::WithCodeResultExt; @@ -99,13 +98,9 @@ fn run() -> proc_exit::ExitResult { args.format.reporter() }; let status_reporter = report::MessageStatus::new(output_reporter); - let mut reporter: &dyn report::Report = &status_reporter; - let diff_reporter = diff::Diff::new(reporter); - if args.diff { - reporter = &diff_reporter; - } + let reporter: &dyn report::Report = &status_reporter; - let (files, identifier_parser, word_parser, checks, fixer); + let (files, identifier_parser, word_parser, checks, fixer, differ); let selected_checks: &dyn checks::Check = if args.files { files = settings.build_files(); &files @@ -118,6 +113,9 @@ fn run() -> proc_exit::ExitResult { } else if args.write_changes { fixer = settings.build_fix_typos(); &fixer + } else if args.diff { + differ = settings.build_diff_typos(); + &differ } else { checks = settings.build_typos(); &checks @@ -152,10 +150,6 @@ fn run() -> proc_exit::ExitResult { if status_reporter.errors_found() { errors_found = true; } - - if args.diff { - diff_reporter.show().with_code(proc_exit::Code::FAILURE)?; - } } if errors_found { diff --git a/src/replace.rs b/src/replace.rs deleted file mode 100644 index 78f2a17..0000000 --- a/src/replace.rs +++ /dev/null @@ -1,103 +0,0 @@ -use std::collections::BTreeMap; -use std::path; - -#[derive(Clone, Debug, Default)] -pub(crate) struct Deferred { - pub(crate) content: BTreeMap>>, - pub(crate) paths: BTreeMap>, -} - -#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] -pub(crate) struct Correction { - pub(crate) byte_offset: usize, - pub(crate) typo: Vec, - pub(crate) correction: Vec, -} - -impl Correction { - pub(crate) fn new(byte_offset: usize, typo: &str, correction: &str) -> Self { - Self { - byte_offset, - typo: typo.as_bytes().to_vec(), - correction: correction.as_bytes().to_vec(), - } - } -} - -pub(crate) fn correct(mut line: Vec, corrections: &[Correction]) -> Vec { - let mut corrections: Vec<_> = corrections.iter().collect(); - corrections.sort_unstable(); - corrections.reverse(); - - for correction in corrections { - let start = correction.byte_offset; - let end = start + correction.typo.len(); - line.splice(start..end, correction.correction.iter().copied()); - } - - line -} - -#[cfg(test)] -mod test { - use super::*; - - fn simple_correct(line: &str, corrections: Vec<(usize, &str, &str)>) -> String { - let line = line.as_bytes().to_vec(); - let corrections: Vec<_> = corrections - .into_iter() - .map(|(byte_offset, typo, correction)| Correction { - byte_offset, - typo: typo.as_bytes().to_vec(), - correction: correction.as_bytes().to_vec(), - }) - .collect(); - let actual = correct(line, &corrections); - String::from_utf8(actual).unwrap() - } - - #[test] - fn test_correct_single() { - let actual = simple_correct("foo foo foo", vec![(4, "foo", "bar")]); - assert_eq!(actual, "foo bar foo"); - } - - #[test] - fn test_correct_single_grow() { - let actual = simple_correct("foo foo foo", vec![(4, "foo", "happy")]); - assert_eq!(actual, "foo happy foo"); - } - - #[test] - fn test_correct_single_shrink() { - let actual = simple_correct("foo foo foo", vec![(4, "foo", "if")]); - assert_eq!(actual, "foo if foo"); - } - - #[test] - fn test_correct_start() { - let actual = simple_correct("foo foo foo", vec![(0, "foo", "bar")]); - assert_eq!(actual, "bar foo foo"); - } - - #[test] - fn test_correct_end() { - let actual = simple_correct("foo foo foo", vec![(8, "foo", "bar")]); - assert_eq!(actual, "foo foo bar"); - } - - #[test] - fn test_correct_end_grow() { - let actual = simple_correct("foo foo foo", vec![(8, "foo", "happy")]); - assert_eq!(actual, "foo foo happy"); - } - - #[test] - fn test_correct_multiple() { - let actual = simple_correct( - "foo foo foo", - vec![(4, "foo", "happy"), (8, "foo", "world")], - ); - assert_eq!(actual, "foo happy world"); - } -}