Skip to content

Commit

Permalink
refactor!: overall reorganization
Browse files Browse the repository at this point in the history
  • Loading branch information
nc7s committed Nov 2, 2023
1 parent e4e42a2 commit 36624c6
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 209 deletions.
30 changes: 0 additions & 30 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,8 @@ unescape = "0.1.0"
memmap2 = "0.9.0"
tempfile = "3.8.0"
thiserror = "1.0.50"
globwalk = "0.8.1"
ignore = "0.4.20"
ansi_term = "0.12.1"
is-terminal = "0.4.9"
clap.workspace = true

[dev-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub struct Options {
pub literal_mode: bool,

#[arg(short)]
/// Recursively replace files
/// Recursively replace files under current directory
pub recursive: bool,

#[arg(short = 'n')]
Expand Down
25 changes: 1 addition & 24 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use std::{
fmt::{self, Write},
path::PathBuf,
};
use std::path::PathBuf;

use crate::replacer::InvalidReplaceCapture;

Expand All @@ -15,30 +12,10 @@ pub enum Error {
TempfilePersist(#[from] tempfile::PersistError),
#[error("file doesn't have parent path: {0}")]
InvalidPath(PathBuf),
#[error("failed processing files:\n{0}")]
FailedProcessing(FailedJobs),
#[error("{0}")]
InvalidReplaceCapture(#[from] InvalidReplaceCapture),
}

pub struct FailedJobs(Vec<(PathBuf, Error)>);

impl From<Vec<(PathBuf, Error)>> for FailedJobs {
fn from(vec: Vec<(PathBuf, Error)>) -> Self {
Self(vec)
}
}

impl fmt::Display for FailedJobs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("\tFailedJobs(\n")?;
for (path, err) in &self.0 {
f.write_str(&format!("\t{:?}: {}\n", path, err))?;
}
f.write_char(')')
}
}

// pretty-print the error
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Expand Down
150 changes: 76 additions & 74 deletions src/input.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
use std::{fs::File, io::prelude::*, path::PathBuf};
use std::{
fs::{File, self},
io::{Write, stdin, stdout, Read},
path::PathBuf,
ops::DerefMut,
};

use crate::{Error, Replacer, Result};

use is_terminal::IsTerminal;
use memmap2::{Mmap, MmapMut, MmapOptions};

#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub(crate) enum Source {
Stdin,
Files(Vec<PathBuf>),
}

impl Source {
pub(crate) fn recursive() -> Result<Self> {
pub(crate) fn cwd_recursive() -> Result<Self> {
Ok(Self::Files(
ignore::WalkBuilder::new(".")
.hidden(false)
Expand All @@ -33,83 +38,80 @@ pub(crate) struct App {
}

impl App {
fn stdin_replace(&self, is_tty: bool) -> Result<()> {
let mut buffer = Vec::with_capacity(256);
let stdin = std::io::stdin();
let mut handle = stdin.lock();
handle.read_to_end(&mut buffer)?;

let stdout = std::io::stdout();
let mut handle = stdout.lock();

handle.write_all(&if is_tty {
self.replacer.replace_preview(&buffer)
} else {
self.replacer.replace(&buffer)
})?;

Ok(())
}

pub(crate) fn new(source: Source, replacer: Replacer) -> Self {
Self { source, replacer }
}
pub(crate) fn run(&self, preview: bool) -> Result<()> {
let is_tty = std::io::stdout().is_terminal();

match (&self.source, preview) {
(Source::Stdin, true) => self.stdin_replace(is_tty),
(Source::Stdin, false) => self.stdin_replace(is_tty),
(Source::Files(paths), false) => {
use rayon::prelude::*;

let failed_jobs: Vec<_> = paths
.par_iter()
.filter_map(|p| {
if let Err(e) = self.replacer.replace_file(p) {
Some((p.to_owned(), e))
} else {
None
}
})
.collect();

if failed_jobs.is_empty() {
Ok(())
} else {
let failed_jobs =
crate::error::FailedJobs::from(failed_jobs);
Err(Error::FailedProcessing(failed_jobs))
pub(crate) fn run(&self, preview: bool) -> Result<()> {
let sources: Vec<(PathBuf, Mmap)> = match &self.source {
Source::Stdin => {
let mut handle = stdin().lock();
let mut buf = Vec::new();
handle.read_to_end(&mut buf)?;
let mut mmap = MmapOptions::new()
.len(buf.len())
.map_anon()?;
mmap.copy_from_slice(&buf);
let mmap = mmap.make_read_only()?;
vec![(PathBuf::from("STDIN"), mmap)]
},
Source::Files(paths) => {
let mut refs = Vec::new();
for path in paths {
if !path.exists() {
return Err(Error::InvalidPath(path.clone()));
}
let mmap = unsafe { Mmap::map(&File::open(path)?)? };
refs.push((path.clone(), mmap));
}
refs
},
};
let needs_separator = sources.len() > 1;

let replaced: Vec<_> = {
use rayon::prelude::*;
sources.par_iter()
.map(|(path, mmap)| {
let replaced = self.replacer.replace(mmap);
(path, mmap, replaced)
})
.collect()
};

if preview || self.source == Source::Stdin {
let mut handle = stdout().lock();

for (path, _, replaced) in replaced {
if needs_separator {
writeln!(handle, "----- FILE {} -----", path.display())?;
}
handle.write_all(replaced.as_ref())?;
}
(Source::Files(paths), true) => {
let stdout = std::io::stdout();
let mut handle = stdout.lock();
let print_path = paths.len() > 1;

paths.iter().try_for_each(|path| {
if Replacer::check_not_empty(File::open(path)?).is_err() {
return Ok(());
}
let file =
unsafe { memmap2::Mmap::map(&File::open(path)?)? };
if self.replacer.has_matches(&file) {
if print_path {
writeln!(
handle,
"----- FILE {} -----",
path.display()
)?;
}

handle
.write_all(&self.replacer.replace_preview(&file))?;
writeln!(handle)?;
}

Ok(())
})
} else {
for (path, _, replaced) in replaced {
let source = File::open(path)?;
let meta = fs::metadata(path)?;
drop(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()?;
}

target.persist(fs::canonicalize(path)?)?;
}
}

Ok(())
}
}
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fn try_main() -> Result<()> {
let options = cli::Options::parse();

let source = if options.recursive {
Source::recursive()?
Source::cwd_recursive()?
} else if !options.files.is_empty() {
Source::Files(options.files)
} else {
Expand Down
78 changes: 1 addition & 77 deletions src/replacer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use std::{fs, fs::File, io::prelude::*, path::Path};

use crate::{utils, Error, Result};
use crate::{utils, Result};

use regex::bytes::Regex;

Expand Down Expand Up @@ -74,16 +72,6 @@ impl Replacer {
})
}

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],
Expand All @@ -99,68 +87,4 @@ impl Replacer {
.replacen(content, self.replacements, &*self.replace_with)
}
}

pub(crate) fn replace_preview<'a>(
&'a self,
content: &[u8],
) -> std::borrow::Cow<'a, [u8]> {
let mut v = Vec::<u8>::new();
let mut captures = self.regex.captures_iter(content);

self.regex.split(content).for_each(|sur_text| {
use regex::bytes::Replacer;

v.extend(sur_text);
if let Some(capture) = captures.next() {
v.extend_from_slice(
ansi_term::Color::Green.prefix().to_string().as_bytes(),
);
if self.is_literal {
regex::bytes::NoExpand(&self.replace_with)
.replace_append(&capture, &mut v);
} else {
(&*self.replace_with).replace_append(&capture, &mut v);
}
v.extend_from_slice(
ansi_term::Color::Green.suffix().to_string().as_bytes(),
);
}
});

return std::borrow::Cow::Owned(v);
}

pub(crate) fn replace_file(&self, path: &Path) -> Result<()> {
use memmap2::{Mmap, MmapMut};
use std::ops::DerefMut;

if Self::check_not_empty(File::open(path)?).is_err() {
return Ok(());
}

let source = File::open(path)?;
let meta = fs::metadata(path)?;
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(fs::canonicalize(path)?)?;
Ok(())
}
}

0 comments on commit 36624c6

Please sign in to comment.