Skip to content

Commit

Permalink
fix: Arg write-changes reports immediately
Browse files Browse the repository at this point in the history
  • Loading branch information
Ed Page committed Jan 2, 2021
1 parent 48112a4 commit c900e48
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 195 deletions.
161 changes: 145 additions & 16 deletions src/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ impl TyposSettings {
}
}

pub fn build_fix_typos(&self) -> FixTypos {
FixTypos {
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,
Expand Down Expand Up @@ -120,8 +128,7 @@ impl Check for Typos {
}

if self.check_files {
let buffer = read_file(path, reporter)?;
let (buffer, content_type) = massage_data(buffer)?;
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())?;
Expand All @@ -146,6 +153,91 @@ impl Check for Typos {
}
}

#[derive(Debug, Clone)]
pub struct FixTypos {
check_filenames: bool,
check_files: bool,
binary: bool,
}

impl Check for FixTypos {
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();

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() {
let buffer = fix_buffer(buffer, fixes.into_iter());
write_file(path, content_type, &buffer, reporter)?;
}
}
}

// Ensure the above write can happen before renaming the file.
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");
let new_path = path.with_file_name(new_name);
std::fs::rename(path, new_path)?;
}
}
}

Ok(())
}
}

#[derive(Debug, Clone)]
pub struct Identifiers {
check_filenames: bool,
Expand Down Expand Up @@ -180,8 +272,7 @@ impl Check for Identifiers {
}

if self.check_files {
let buffer = read_file(path, reporter)?;
let (buffer, content_type) = massage_data(buffer)?;
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())?;
Expand Down Expand Up @@ -237,8 +328,7 @@ impl Check for Words {
}

if self.check_files {
let buffer = read_file(path, reporter)?;
let (buffer, content_type) = massage_data(buffer)?;
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())?;
Expand Down Expand Up @@ -281,8 +371,7 @@ impl Check for FoundFiles {
let msg = report::File::new(path);
reporter.report(msg.into())?;
} else {
let buffer = read_file(path, reporter)?;
let (_buffer, content_type) = massage_data(buffer)?;
let (_buffer, content_type) = read_file(path, reporter)?;
if !explicit && content_type.is_binary() {
let msg = report::BinaryFile { path };
reporter.report(msg.into())?;
Expand All @@ -296,10 +385,10 @@ impl Check for FoundFiles {
}
}

fn read_file(
pub fn read_file(
path: &std::path::Path,
reporter: &dyn report::Report,
) -> Result<Vec<u8>, std::io::Error> {
) -> Result<(Vec<u8>, content_inspector::ContentType), std::io::Error> {
let buffer = match std::fs::read(path) {
Ok(buffer) => buffer,
Err(err) => {
Expand All @@ -308,14 +397,8 @@ fn read_file(
Vec::new()
}
};
Ok(buffer)
}

fn massage_data(
buffer: Vec<u8>,
) -> Result<(Vec<u8>, content_inspector::ContentType), std::io::Error> {
let mut content_type = content_inspector::inspect(&buffer);

// HACK: We only support UTF-8 at the moment
if content_type != content_inspector::ContentType::UTF_8_BOM
&& content_type != content_inspector::ContentType::UTF_8
Expand All @@ -326,6 +409,27 @@ fn massage_data(
Ok((buffer, content_type))
}

pub fn write_file(
path: &std::path::Path,
content_type: content_inspector::ContentType,
buffer: &[u8],
reporter: &dyn report::Report,
) -> Result<(), std::io::Error> {
assert!(
content_type == content_inspector::ContentType::UTF_8_BOM
|| content_type == content_inspector::ContentType::UTF_8
|| content_type == content_inspector::ContentType::BINARY
);
match std::fs::write(path, buffer) {
Ok(()) => (),
Err(err) => {
let msg = report::Error::new(err.to_string());
reporter.report(msg.into())?;
}
};
Ok(())
}

struct AccumulateLineNum {
line_num: usize,
last_offset: usize,
Expand Down Expand Up @@ -365,6 +469,31 @@ fn extract_line(buffer: &[u8], byte_offset: usize) -> (&[u8], usize) {
(line, line_offset)
}

fn extract_fix<'t>(typo: &'t typos::Typo<'t>) -> Option<&'t str> {
match &typo.corrections {
typos::Status::Corrections(c) if c.len() == 1 => Some(c[0].as_ref()),
_ => None,
}
}

fn is_fixable<'t>(typo: &typos::Typo<'t>) -> bool {
extract_fix(typo).is_some()
}

fn fix_buffer(mut buffer: Vec<u8>, typos: impl Iterator<Item = typos::Typo<'static>>) -> Vec<u8> {
let mut offset = 0isize;
for typo in typos {
let fix = extract_fix(&typo).expect("Caller only provides fixable typos");
let start = ((typo.byte_offset as isize) + offset) as usize;
let end = start + typo.typo.len();

buffer.splice(start..end, fix.as_bytes().iter().copied());

offset += (fix.len() as isize) - (typo.typo.len() as isize);
}
buffer
}

pub fn check_path(
walk: ignore::Walk,
checks: &dyn Check,
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ pub mod checks;
pub mod config;
pub mod dict;
pub mod diff;
pub mod replace;
pub(crate) mod replace;
pub mod report;
13 changes: 4 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use typos_cli::checks;
use typos_cli::config;
use typos_cli::dict;
use typos_cli::diff;
use typos_cli::replace;
use typos_cli::report;

use proc_exit::WithCodeResultExt;
Expand Down Expand Up @@ -101,15 +100,12 @@ fn run() -> proc_exit::ExitResult {
};
let status_reporter = report::MessageStatus::new(output_reporter);
let mut reporter: &dyn report::Report = &status_reporter;
let replace_reporter = replace::Replace::new(reporter);
let diff_reporter = diff::Diff::new(reporter);
if args.diff {
reporter = &diff_reporter;
} else if args.write_changes {
reporter = &replace_reporter;
}

let (files, identifier_parser, word_parser, checks);
let (files, identifier_parser, word_parser, checks, fixer);
let selected_checks: &dyn checks::Check = if args.files {
files = settings.build_files();
&files
Expand All @@ -119,6 +115,9 @@ fn run() -> proc_exit::ExitResult {
} else if args.words {
word_parser = settings.build_word_parser();
&word_parser
} else if args.write_changes {
fixer = settings.build_fix_typos();
&fixer
} else {
checks = settings.build_typos();
&checks
Expand Down Expand Up @@ -156,10 +155,6 @@ fn run() -> proc_exit::ExitResult {

if args.diff {
diff_reporter.show().with_code(proc_exit::Code::FAILURE)?;
} else if args.write_changes {
replace_reporter
.write()
.with_code(proc_exit::Code::FAILURE)?;
}
}

Expand Down
Loading

0 comments on commit c900e48

Please sign in to comment.