#![allow(clippy::redundant_field_names)]
extern crate ropey;
use std::fs::File;
use std::io;
use ropey::{iter::Chars, Rope, RopeSlice};
fn main() {
let (search_pattern, replacement_text, filepath) = if std::env::args().count() > 3 {
(
std::env::args().nth(1).unwrap(),
std::env::args().nth(2).unwrap(),
std::env::args().nth(3).unwrap(),
)
} else {
eprintln!(
"Usage:\n search_and_replace <search_pattern> <replacement_text> <input_filepath>"
);
return;
};
let mut text = Rope::from_reader(io::BufReader::new(File::open(&filepath).unwrap())).expect("Cannot read file: either it doesn't exist, file permissions don't allow reading, or is not utf8 text.");
search_and_replace(&mut text, &search_pattern, &replacement_text);
println!("{}", text);
}
fn search_and_replace(rope: &mut Rope, search_pattern: &str, replacement_text: &str) {
const BATCH_SIZE: usize = 256;
let replacement_text_len = replacement_text.chars().count();
let mut head = 0; let mut matches = Vec::with_capacity(BATCH_SIZE);
loop {
matches.clear();
for m in SearchIter::from_rope_slice(&rope.slice(head..), search_pattern).take(BATCH_SIZE) {
matches.push(m);
}
if matches.is_empty() {
break;
}
let mut index_diff: isize = 0;
for &(start, end) in matches.iter() {
let start_d = (head as isize + start as isize + index_diff) as usize;
let end_d = (head as isize + end as isize + index_diff) as usize;
rope.remove(start_d..end_d);
rope.insert(start_d, replacement_text);
let match_len = (end - start) as isize;
index_diff = index_diff - match_len + replacement_text_len as isize;
}
head = (head as isize + index_diff + matches.last().unwrap().1 as isize) as usize;
}
}
struct SearchIter<'a> {
char_iter: Chars<'a>,
search_pattern: &'a str,
search_pattern_char_len: usize,
cur_index: usize, possible_matches: Vec<std::str::Chars<'a>>, }
impl<'a> SearchIter<'a> {
fn from_rope_slice<'b>(slice: &'b RopeSlice, search_pattern: &'b str) -> SearchIter<'b> {
assert!(
!search_pattern.is_empty(),
"Can't search using an empty search pattern."
);
SearchIter {
char_iter: slice.chars(),
search_pattern: search_pattern,
search_pattern_char_len: search_pattern.chars().count(),
cur_index: 0,
possible_matches: Vec::new(),
}
}
}
impl<'a> Iterator for SearchIter<'a> {
type Item = (usize, usize);
fn next(&mut self) -> Option<(usize, usize)> {
#[allow(clippy::while_let_on_iterator)]
while let Some(next_char) = self.char_iter.next() {
self.cur_index += 1;
self.possible_matches.push(self.search_pattern.chars());
let mut i = 0;
while i < self.possible_matches.len() {
let pattern_char = self.possible_matches[i].next().unwrap();
if next_char == pattern_char {
if self.possible_matches[i].clone().next() == None {
let char_match_range = (
self.cur_index - self.search_pattern_char_len,
self.cur_index,
);
self.possible_matches.clear();
return Some(char_match_range);
} else {
i += 1;
}
} else {
let _ = self.possible_matches.swap_remove(i);
}
}
}
None
}
}