mirror of
https://github.com/chmln/sd
synced 2024-10-02 21:53:33 +00:00
Extract replacer into own file
This commit is contained in:
parent
495fa3ea9c
commit
675fc614f6
133
src/input.rs
133
src/input.rs
|
@ -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,
|
||||
|
|
|
@ -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
118
src/replacer.rs
Normal 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(())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue