use memmap::{Mmap, MmapMut};
use std::ops::Range;
use std::{fs, fs::File, io::prelude::*, path::PathBuf};
use super::file;
use crate::error::{Error, Result};
use crate::replacers::ReplacementBuilder;
pub struct Replacer {
path: PathBuf,
new_data: Vec<u8>,
regex: regex::bytes::Regex,
verification_regex: regex::bytes::Regex,
}
impl Replacer {
pub fn new(
path: PathBuf,
old_content: &str,
verification_regex: &str,
new_content: &str,
) -> Result<Self> {
let search_text = regex::escape(old_content);
let expr = format!("((.*(\\n)){{2}}).*({})", &search_text);
let regex = regex::bytes::RegexBuilder::new(&expr).build()?;
let verification_regex = regex::bytes::Regex::new(verification_regex)?;
Ok(Self {
path,
regex,
verification_regex,
new_data: String::from(new_content).into_bytes(),
})
}
fn determine_replacement_locations(&self, source_buf: &Mmap) -> Result<Vec<Range<usize>>> {
self.regex
.captures_iter(source_buf)
.map(|capture| {
let first_capture = capture.get(1).unwrap();
let result = self.verification_regex.find(first_capture.as_bytes());
if result.is_none() {
return Ok(None);
}
let last_capture = capture.get(capture.len() - 1);
if let Some(mtch) = last_capture {
Ok(Some(Range {
start: mtch.start(),
end: mtch.end(),
}))
} else {
Ok(None)
}
})
.filter_map(|val| match val {
Ok(Some(val)) => Some(Ok(val)),
Ok(None) => None,
Err(err) => Some(Err(err)),
})
.collect::<Result<Vec<_>>>()
}
fn get_replacement(
self,
source_buf: &Mmap,
replacement_locations: &[Range<usize>],
file_permissions: std::fs::Permissions,
) -> Result<file::Replacer> {
let temp_file = tempfile::NamedTempFile::new_in(
(self.path)
.parent()
.ok_or_else(|| Error::InvalidPath(self.path.clone()))?,
)?;
let mut file = temp_file.as_file();
let replacers_removal_len = replacement_locations
.iter()
.fold(0, |acc, val| acc + val.end - val.start);
let new_value_len = self.new_data.len() * replacement_locations.len();
let file_len = source_buf.len() - replacers_removal_len + new_value_len;
file.set_len(file_len as u64)?;
file.set_permissions(file_permissions)?;
let mut target_map = unsafe { MmapMut::map_mut(file)? };
match replacement_locations.len() {
1 => {
let start = replacement_locations[0].start;
let end = replacement_locations[0].end;
let mut writer = &mut *target_map;
writer.write_all(&source_buf[0..start])?;
writer.write_all(&self.new_data)?;
writer.write_all(&source_buf[end..])?;
}
val if val > 1 => {
let mut writer = &mut *target_map;
let mut prev_end = 0;
for range in replacement_locations {
let start = range.start;
let end = range.end;
writer.write_all(&source_buf[prev_end..start])?;
writer.write_all(&self.new_data)?;
prev_end = end;
}
writer.write_all(&source_buf[prev_end..])?;
}
val => {
return Err(Error::InvalidReplacementCount(val));
}
}
file.flush()?;
Ok(file::Replacer {
path: self.path,
temp_file,
})
}
}
impl ReplacementBuilder for Replacer {
fn determine_replacements(self) -> Result<Option<Vec<file::Replacer>>> {
let mut replacers = Vec::new();
let source_file = File::open(&self.path)?;
let source_meta = fs::metadata(&self.path)?;
let source_buf = unsafe { Mmap::map(&source_file)? };
let offsets = self.determine_replacement_locations(&source_buf)?;
let replacer = self.get_replacement(&source_buf, &offsets, source_meta.permissions())?;
drop(source_buf);
drop(source_file);
replacers.push(replacer);
Ok(Some(replacers))
}
}