Extract replacer into own file

This commit is contained in:
Gregory 2021-02-10 21:55:32 -05:00
parent 495fa3ea9c
commit 675fc614f6
No known key found for this signature in database
GPG key ID: 2E44FAEEDC94B1E2
3 changed files with 128 additions and 126 deletions

View file

@ -1,10 +1,5 @@
use crate::{utils, Error, Result};
use regex::bytes::Regex;
use std::{
fs::File,
io::prelude::*,
path::{Path, PathBuf},
};
use crate::{Replacer, Result};
use std::{fs::File, io::prelude::*, path::PathBuf};
pub(crate) enum Source {
Stdin,
@ -19,127 +14,15 @@ impl Source {
.filter_map(|entry| {
if let Ok(e) = entry {
Some(e.into_path())
} else { None }
}) .collect(),
} else {
None
}
})
.collect(),
))
}
}
pub struct Replacer {
regex: Regex,
replace_with: Vec<u8>,
is_literal: bool,
}
impl Replacer {
pub(crate) fn new(
look_for: String,
replace_with: String,
is_literal: bool,
flags: Option<String>,
) -> Result<Self> {
let (look_for, replace_with) = if is_literal {
(regex::escape(&look_for), replace_with.into_bytes())
} else {
(
look_for,
utils::unescape(&replace_with)
.unwrap_or_else(|| replace_with)
.into_bytes(),
)
};
let mut regex = regex::bytes::RegexBuilder::new(&look_for);
regex.multi_line(true);
if let Some(flags) = flags {
flags.chars().for_each(|c| {
#[rustfmt::skip]
match c {
'c' => { regex.case_insensitive(false); },
'i' => { regex.case_insensitive(true); },
'm' => {},
'e' => { regex.multi_line(false); },
's' => {
if !flags.contains("m") {
regex.multi_line(false);
}
regex.dot_matches_new_line(true);
},
'w' => {
regex = regex::bytes::RegexBuilder::new(&format!(
"\\b{}\\b",
look_for
));
},
_ => {},
};
});
};
Ok(Self {
regex: regex.build()?,
replace_with,
is_literal,
})
}
fn has_matches(&self, content: &[u8]) -> bool {
self.regex.is_match(content)
}
fn check_not_empty(mut file: File) -> Result<()> {
let mut buf: [u8; 1] = Default::default();
file.read_exact(&mut buf)?;
Ok(())
}
fn replace<'a>(&'a self, content: &'a [u8]) -> std::borrow::Cow<'a, [u8]> {
if self.is_literal {
self.regex.replace_all(
&content,
regex::bytes::NoExpand(&self.replace_with),
)
} else {
self.regex.replace_all(&content, &*self.replace_with)
}
}
fn replace_file(&self, path: &Path) -> Result<()> {
use memmap::{Mmap, MmapMut};
use std::ops::DerefMut;
if let Err(_) = Self::check_not_empty(File::open(path)?) {
return Ok(());
}
let source = File::open(path)?;
let meta = source.metadata()?;
let mmap_source = unsafe { Mmap::map(&source)? };
let replaced = self.replace(&mmap_source);
let target = tempfile::NamedTempFile::new_in(
path.parent()
.ok_or_else(|| Error::InvalidPath(path.to_path_buf()))?,
)?;
let file = target.as_file();
file.set_len(replaced.len() as u64)?;
file.set_permissions(meta.permissions())?;
if !replaced.is_empty() {
let mut mmap_target = unsafe { MmapMut::map_mut(&file)? };
mmap_target.deref_mut().write_all(&replaced)?;
mmap_target.flush_async()?;
}
drop(mmap_source);
drop(source);
target.persist(path)?;
Ok(())
}
}
pub(crate) struct App {
replacer: Replacer,
source: Source,
@ -212,7 +95,7 @@ mod tests {
src: &'static str,
target: &'static str,
) {
let replacer = App::new(
let replacer = Replacer::new(
look_for.into(),
replace_with.into(),
literal,

View file

@ -1,11 +1,12 @@
mod cli;
mod error;
mod input;
pub(crate) mod replacer;
pub(crate) mod utils;
pub(crate) use self::input::{App, Source};
pub(crate) use error::{Error, Result};
use input::Replacer;
use replacer::Replacer;
fn main() -> Result<()> {
use structopt::StructOpt;

118
src/replacer.rs Normal file
View file

@ -0,0 +1,118 @@
use crate::{utils, Error, Result};
use regex::bytes::Regex;
use std::{fs::File, io::prelude::*, path::Path};
pub(crate) struct Replacer {
regex: Regex,
replace_with: Vec<u8>,
is_literal: bool,
}
impl Replacer {
pub(crate) fn new(
look_for: String,
replace_with: String,
is_literal: bool,
flags: Option<String>,
) -> Result<Self> {
let (look_for, replace_with) = if is_literal {
(regex::escape(&look_for), replace_with.into_bytes())
} else {
(
look_for,
utils::unescape(&replace_with)
.unwrap_or_else(|| replace_with)
.into_bytes(),
)
};
let mut regex = regex::bytes::RegexBuilder::new(&look_for);
regex.multi_line(true);
if let Some(flags) = flags {
flags.chars().for_each(|c| {
#[rustfmt::skip]
match c {
'c' => { regex.case_insensitive(false); },
'i' => { regex.case_insensitive(true); },
'm' => {},
'e' => { regex.multi_line(false); },
's' => {
if !flags.contains("m") {
regex.multi_line(false);
}
regex.dot_matches_new_line(true);
},
'w' => {
regex = regex::bytes::RegexBuilder::new(&format!(
"\\b{}\\b",
look_for
));
},
_ => {},
};
});
};
Ok(Self {
regex: regex.build()?,
replace_with,
is_literal,
})
}
pub(crate) fn has_matches(&self, content: &[u8]) -> bool {
self.regex.is_match(content)
}
pub(crate) fn check_not_empty(mut file: File) -> Result<()> {
let mut buf: [u8; 1] = Default::default();
file.read_exact(&mut buf)?;
Ok(())
}
pub(crate) fn replace<'a>(&'a self, content: &'a [u8]) -> std::borrow::Cow<'a, [u8]> {
if self.is_literal {
self.regex.replace_all(
&content,
regex::bytes::NoExpand(&self.replace_with),
)
} else {
self.regex.replace_all(&content, &*self.replace_with)
}
}
pub(crate) fn replace_file(&self, path: &Path) -> Result<()> {
use memmap::{Mmap, MmapMut};
use std::ops::DerefMut;
if let Err(_) = Self::check_not_empty(File::open(path)?) {
return Ok(());
}
let source = File::open(path)?;
let meta = source.metadata()?;
let mmap_source = unsafe { Mmap::map(&source)? };
let replaced = self.replace(&mmap_source);
let target = tempfile::NamedTempFile::new_in(
path.parent()
.ok_or_else(|| Error::InvalidPath(path.to_path_buf()))?,
)?;
let file = target.as_file();
file.set_len(replaced.len() as u64)?;
file.set_permissions(meta.permissions())?;
if !replaced.is_empty() {
let mut mmap_target = unsafe { MmapMut::map_mut(&file)? };
mmap_target.deref_mut().write_all(&replaced)?;
mmap_target.flush_async()?;
}
drop(mmap_source);
drop(source);
target.persist(path)?;
Ok(())
}
}