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 3, 2023
1 parent 4f77cfe commit 5d34bdb
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 189 deletions.
18 changes: 0 additions & 18 deletions Cargo.lock

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

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ memmap2 = "0.9.0"
tempfile = "3.8.0"
thiserror = "1.0.50"
ansi_term = "0.12.1"
is-terminal = "0.4.9"
clap.workspace = true

[dev-dependencies]
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
138 changes: 70 additions & 68 deletions src/input.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
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>),
Expand All @@ -16,83 +21,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();
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;

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

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 preview || self.source == Source::Stdin {
let mut handle = stdout().lock();

if failed_jobs.is_empty() {
Ok(())
} else {
let failed_jobs =
crate::error::FailedJobs::from(failed_jobs);
Err(Error::FailedProcessing(failed_jobs))
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()
)?;
}
} else {
for (path, _, replaced) in replaced {
let source = File::open(path)?;
let meta = fs::metadata(path)?;
drop(source);

handle
.write_all(&self.replacer.replace_preview(&file))?;
writeln!(handle)?;
}
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())?;

Ok(())
})
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(())
}
}
75 changes: 2 additions & 73 deletions src/replacer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{borrow::Cow, fs, fs::File, io::prelude::*, path::Path};
use std::borrow::Cow;

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

use regex::bytes::Regex;

Expand Down Expand Up @@ -74,16 +74,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 Down Expand Up @@ -148,65 +138,4 @@ impl Replacer {
new.extend_from_slice(&haystack[last_match..]);
Cow::Owned(new)
}

pub(crate) fn replace_preview<'a>(
&self,
content: &'a [u8],
) -> std::borrow::Cow<'a, [u8]> {
let regex = &self.regex;
let limit = self.replacements;
// TODO: refine this condition more
let use_color = true;
if self.is_literal {
Self::replacen(
regex,
limit,
content,
use_color,
regex::bytes::NoExpand(&self.replace_with),
)
} else {
Self::replacen(
regex,
limit,
content,
use_color,
&*self.replace_with,
)
}
}

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(())
}
}
6 changes: 1 addition & 5 deletions tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,7 @@ mod cli {
sd().args(["-p", "abc\\d+", "", file.path().to_str().unwrap()])
.assert()
.success()
.stdout(format!(
"{}{}def\n",
ansi_term::Color::Green.prefix(),
ansi_term::Color::Green.suffix()
));
.stdout("def");

assert_file(file.path(), "abc123def");

Expand Down

0 comments on commit 5d34bdb

Please sign in to comment.