mirror of
https://github.com/crate-ci/typos.git
synced 2026-10-17 01:25:12 +02:00
@@ -54,6 +54,10 @@ pub(crate) struct Args {
|
||||
/// Ignore implicit configuration files.
|
||||
pub(crate) isolated: bool,
|
||||
|
||||
#[structopt(long, short = "w")]
|
||||
/// Write corrections out
|
||||
pub(crate) write_changes: bool,
|
||||
|
||||
#[structopt(long)]
|
||||
/// Print each file that would be spellchecked.
|
||||
pub(crate) files: bool,
|
||||
|
||||
+10
-1
@@ -11,6 +11,7 @@ mod args;
|
||||
mod checks;
|
||||
mod config;
|
||||
mod dict;
|
||||
mod replace;
|
||||
|
||||
fn main() {
|
||||
let code = run().unwrap();
|
||||
@@ -75,7 +76,11 @@ fn run() -> Result<i32, anyhow::Error> {
|
||||
.git_exclude(config.files.ignore_vcs())
|
||||
.parents(config.files.ignore_parent());
|
||||
|
||||
let reporter = args.format.reporter();
|
||||
let mut reporter = args.format.reporter();
|
||||
let replace_reporter = replace::Replace::new(reporter);
|
||||
if args.write_changes {
|
||||
reporter = &replace_reporter;
|
||||
}
|
||||
|
||||
if args.files {
|
||||
if single_threaded {
|
||||
@@ -149,6 +154,10 @@ fn run() -> Result<i32, anyhow::Error> {
|
||||
errors_found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if args.write_changes {
|
||||
replace_reporter.write()?;
|
||||
}
|
||||
}
|
||||
|
||||
if errors_found {
|
||||
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::Write;
|
||||
use std::path;
|
||||
use std::sync;
|
||||
|
||||
use bstr::ByteSlice;
|
||||
|
||||
pub struct Replace<'r> {
|
||||
reporter: &'r dyn typos::report::Report,
|
||||
deferred: sync::Mutex<Deferred>,
|
||||
}
|
||||
|
||||
impl<'r> Replace<'r> {
|
||||
pub(crate) fn new(reporter: &'r dyn typos::report::Report) -> Self {
|
||||
Self {
|
||||
reporter,
|
||||
deferred: sync::Mutex::new(Deferred::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&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 file = std::fs::File::create(path)?;
|
||||
for (line_idx, line) in buffer.lines_with_terminator().enumerate() {
|
||||
let line_num = line_idx + 1;
|
||||
if let Some(corrections) = corrections.get(&line_num) {
|
||||
let line = line.to_vec();
|
||||
let line = correct(line, &corrections);
|
||||
file.write_all(&line)?;
|
||||
} else {
|
||||
file.write_all(&line)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (path, corrections) in deferred.paths.iter() {
|
||||
let orig_name = path
|
||||
.file_name()
|
||||
.and_then(|s| s.to_str())
|
||||
.expect("generating a correction requires the filename to be valid.")
|
||||
.to_owned()
|
||||
.into_bytes();
|
||||
let new_name = correct(orig_name, &corrections);
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> typos::report::Report for Replace<'r> {
|
||||
fn report(&self, msg: typos::report::Message<'_>) {
|
||||
match msg {
|
||||
typos::report::Message::Correction(msg) => {
|
||||
let path = msg.path.to_owned();
|
||||
let line_num = msg.line_num;
|
||||
let correction = Correction::from_content(msg);
|
||||
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);
|
||||
}
|
||||
typos::report::Message::PathCorrection(msg) => {
|
||||
let path = msg.path.to_owned();
|
||||
let correction = Correction::from_path(msg);
|
||||
let mut deferred = self.deferred.lock().unwrap();
|
||||
let content = deferred.paths.entry(path).or_insert_with(Vec::new);
|
||||
content.push(correction);
|
||||
}
|
||||
_ => self.reporter.report(msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct Deferred {
|
||||
content: BTreeMap<path::PathBuf, BTreeMap<usize, Vec<Correction>>>,
|
||||
paths: BTreeMap<path::PathBuf, Vec<Correction>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
|
||||
struct Correction {
|
||||
pub byte_offset: usize,
|
||||
pub typo: Vec<u8>,
|
||||
pub correction: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Correction {
|
||||
fn from_content(other: typos::report::Correction<'_>) -> Self {
|
||||
Self {
|
||||
byte_offset: other.byte_offset,
|
||||
typo: other.typo.as_bytes().to_vec(),
|
||||
correction: other.correction.as_bytes().to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_path(other: typos::report::PathCorrection<'_>) -> Self {
|
||||
Self {
|
||||
byte_offset: other.byte_offset,
|
||||
typo: other.typo.as_bytes().to_vec(),
|
||||
correction: other.correction.as_bytes().to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn correct(mut line: Vec<u8>, corrections: &[Correction]) -> Vec<u8> {
|
||||
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::*;
|
||||
|
||||
use assert_fs::prelude::*;
|
||||
use typos::report::Report;
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_content() {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let input_file = temp.child("foo.txt");
|
||||
input_file.write_str("1 foo 2\n3 4 5").unwrap();
|
||||
|
||||
let primary = typos::report::PrintSilent;
|
||||
let replace = Replace::new(&primary);
|
||||
replace.report(
|
||||
typos::report::Correction::default()
|
||||
.path(input_file.path())
|
||||
.line(b"1 foo 2\n3 4 5")
|
||||
.line_num(1)
|
||||
.byte_offset(2)
|
||||
.typo("foo")
|
||||
.correction(std::borrow::Cow::Borrowed("bar"))
|
||||
.into(),
|
||||
);
|
||||
replace.write().unwrap();
|
||||
|
||||
input_file.assert("1 bar 2\n3 4 5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_path() {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let input_file = temp.child("foo.txt");
|
||||
input_file.write_str("foo foo foo").unwrap();
|
||||
|
||||
let primary = typos::report::PrintSilent;
|
||||
let replace = Replace::new(&primary);
|
||||
replace.report(
|
||||
typos::report::PathCorrection::default()
|
||||
.path(input_file.path())
|
||||
.byte_offset(0)
|
||||
.typo("foo")
|
||||
.correction(std::borrow::Cow::Borrowed("bar"))
|
||||
.into(),
|
||||
);
|
||||
replace.write().unwrap();
|
||||
|
||||
input_file.assert(predicates::path::missing());
|
||||
temp.child("bar.txt").assert("foo foo foo");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user