Skip to content

Commit

Permalink
Implement new algorithm for replacements
Browse files Browse the repository at this point in the history
Instead of computing the new line and then trying to guess the diff,
we compute the positions where the string needs to change and use that
to both print the diffs and compute the new string

Fix #15
  • Loading branch information
dmerejkowsky committed Jul 28, 2020
1 parent 938b39b commit 8342cf3
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 156 deletions.
2 changes: 0 additions & 2 deletions TODO

This file was deleted.

13 changes: 4 additions & 9 deletions src/directory_patcher.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use ignore;

use crate::errors::Error;
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -44,13 +42,13 @@ impl DirectoryPatcher {
}
}
let file_patcher = file_patcher.unwrap();
let replacements = file_patcher.replacements();
if replacements.is_empty() {
let num_lines_patched = file_patcher.num_lines_patched();
if num_lines_patched == 0 {
return Ok(());
}
self.stats.update(replacements.len());
file_patcher.print_patch();
self.stats.update(file_patcher.num_lines_patched());
if self.settings.dry_run {
file_patcher.dry_run();
return Ok(());
}
if let Err(err) = file_patcher.run() {
Expand Down Expand Up @@ -91,6 +89,3 @@ impl DirectoryPatcher {
Ok(())
}
}

#[cfg(test)]
mod tests {}
3 changes: 0 additions & 3 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
use ignore;
use std;

#[derive(Debug)]
pub struct Error {
description: String,
Expand Down
145 changes: 50 additions & 95 deletions src/file_patcher.rs
Original file line number Diff line number Diff line change
@@ -1,104 +1,89 @@
use colored::*;
use difference::{Changeset, Difference};
use std;
use std::collections::BTreeMap;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;

use crate::line_patcher::LinePatcher;
use colored::*;

use crate::line_patcher::{self, LineReplacements};
use crate::query::Query;

pub struct FilePatcher {
replacements: Vec<Replacement>,
path: PathBuf,
new_contents: String,
lines: Vec<String>,
// Note: BTreeMap because order matters here
replacements: BTreeMap<usize, LineReplacements>,
}

impl FilePatcher {
pub fn new(path: PathBuf, query: &Query) -> Result<FilePatcher, std::io::Error> {
let mut replacements = vec![];
let file = File::open(&path)?;
let reader = BufReader::new(file);
let mut new_contents = String::new();
for (num, chunk) in reader.split(b'\n').enumerate() {
let mut replacements = BTreeMap::new();
let mut lines = vec![];
for (i, chunk) in reader.split(b'\n').enumerate() {
let chunk = chunk?; // consume the io::error
let line = String::from_utf8(chunk);
if line.is_err() {
let io_error: std::io::Error = std::io::ErrorKind::InvalidData.into();
return Err(io_error);
}
let line = line.unwrap();
let line_patcher = LinePatcher::new(&line);
let new_line = line_patcher.replace(&query);
if new_line != line {
let replacement = Replacement {
line_no: num + 1,
old: line,
new: new_line.clone(),
};
replacements.push(replacement);
new_contents.push_str(&new_line);
} else {
new_contents.push_str(&line);
lines.push(line.clone());
let replacements_for_line = line_patcher::get_replacements(&line, &query);
if !replacements_for_line.is_empty() {
replacements.insert(i, replacements_for_line);
}
new_contents.push_str("\n");
}
Ok(FilePatcher {
replacements,
path,
new_contents,
replacements,
lines,
})
}

pub fn replacements(&self) -> &Vec<Replacement> {
&self.replacements
pub fn num_lines_patched(&self) -> usize {
self.replacements.len()
}

pub fn run(&self) -> Result<(), std::io::Error> {
std::fs::write(&self.path, &self.new_contents)?;
Ok(())
pub fn dry_run(&self) {
if self.replacements.is_empty() {
return;
}
println!(
"{} {}",
"Would patch".blue(),
self.path.to_string_lossy().bold()
);
self.print_replacements();
}

pub fn print_patch(&self) {
pub fn run(&self) -> Result<(), std::io::Error> {
if self.replacements.is_empty() {
return Ok(());
}
println!(
"{} {}",
"Patching".blue(),
self.path.to_string_lossy().bold()
);
for replacement in &self.replacements {
replacement.print_self();
println!();
self.print_replacements();
let mut new_contents = String::new();
for (i, line) in self.lines.iter().enumerate() {
if let Some(replacement) = self.replacements.get(&i) {
let new_line = replacement.apply();
new_contents.push_str(&new_line);
} else {
new_contents.push_str(&line);
}
new_contents.push('\n');
}
println!();
std::fs::write(&self.path, &new_contents)
}
}

#[derive(PartialEq, Eq, Debug)]
pub struct Replacement {
line_no: usize,
old: String,
new: String,
}

impl Replacement {
fn print_self(&self) {
let changeset = Changeset::new(&self.old, &self.new, "");
print!("{} ", "--".red());
for diff in &changeset.diffs {
match diff {
Difference::Same(s) => print!("{}", s),
Difference::Rem(s) => print!("{}", s.red().underline()),
_ => (),
}
}
println!();
print!("{} ", "++".green());
for diff in &changeset.diffs {
match diff {
Difference::Same(s) => print!("{}", s),
Difference::Add(s) => print!("{}", s.green().underline()),
_ => (),
}
fn print_replacements(&self) {
for replacement in self.replacements.values() {
replacement.print();
}
}
}
Expand All @@ -109,47 +94,17 @@ mod tests {
use crate::query;
use std::fs;

#[test]
fn test_compute_replacements() {
let top_path = std::path::Path::new("tests/data/top.txt");
let file_patcher =
FilePatcher::new(top_path.to_path_buf(), &query::substring("old", "new")).unwrap();
let replacements = file_patcher.replacements();
assert_eq!(replacements.len(), 1);
let actual_replacement = &replacements[0];
assert_eq!(actual_replacement.line_no, 2);
// ruplacer preserves line endings: on Windows, there is a
// possibility the actual lines contain \r, depending
// of the git configuration.
// So strip the \r before comparing them to the expected result.
let actual_new = actual_replacement.new.replace("\r", "");
let actual_old = actual_replacement.old.replace("\r", "");
assert_eq!(actual_new, "Top: new is nice");
assert_eq!(actual_old, "Top: old is nice");
}

#[test]
fn test_patch_file() {
let temp_dir = tempdir::TempDir::new("test-ruplacer").unwrap();
let file_path = temp_dir.path().join("foo.txt");
fs::write(&file_path, "first line\nI say: old is nice\nlast line\n").unwrap();
let file_patcher =
FilePatcher::new(file_path.to_path_buf(), &query::substring("old", "new")).unwrap();
let file_patcher = FilePatcher::new(file_path, &query::substring("old", "new")).unwrap();
file_patcher.run().unwrap();
let actual = fs::read_to_string(&file_path).unwrap();

let file_path = temp_dir.path().join("foo.txt");
let actual = fs::read_to_string(file_path).unwrap();
let expected = "first line\nI say: new is nice\nlast line\n";
assert_eq!(actual, expected);
}

#[test]
fn test_replacement_display() {
// This test cannot fail. It's just here so you can tweak the look and feel
// of ruplacer easily.
let replacement = Replacement {
line_no: 1,
old: "trustchain_creation: 0".to_owned(),
new: "blockchain_creation: 0".to_owned(),
};
replacement.print_self();
}
}

0 comments on commit 8342cf3

Please sign in to comment.