From 5d34bdbd869ece3fd8f1b08de3b0cbba1b91d5ed Mon Sep 17 00:00:00 2001 From: Blair Noctis Date: Fri, 3 Nov 2023 02:27:11 +0800 Subject: [PATCH] refactor!: overall reorganization --- Cargo.lock | 18 ------ Cargo.toml | 1 - src/error.rs | 25 +------- src/input.rs | 138 ++++++++++++++++++++++---------------------- src/replacer/mod.rs | 75 +----------------------- tests/cli.rs | 6 +- 6 files changed, 74 insertions(+), 189 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e28893..1bceacb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -324,12 +324,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "hermit-abi" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" - [[package]] name = "insta" version = "1.34.0" @@ -343,17 +337,6 @@ dependencies = [ "yaml-rust", ] -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "itertools" version = "0.11.0" @@ -657,7 +640,6 @@ dependencies = [ "clap_mangen", "console", "insta", - "is-terminal", "memmap2", "proptest", "rayon", diff --git a/Cargo.toml b/Cargo.toml index 25032bc..12a2cb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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] diff --git a/src/error.rs b/src/error.rs index 517defd..ad5fe31 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,4 @@ -use std::{ - fmt::{self, Write}, - path::PathBuf, -}; +use std::path::PathBuf; use crate::replacer::InvalidReplaceCapture; @@ -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> 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 { diff --git a/src/input.rs b/src/input.rs index 79a174d..b099bcc 100644 --- a/src/input.rs +++ b/src/input.rs @@ -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), @@ -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(()) } } diff --git a/src/replacer/mod.rs b/src/replacer/mod.rs index d0a90a2..c7ec32b 100644 --- a/src/replacer/mod.rs +++ b/src/replacer/mod.rs @@ -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; @@ -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], @@ -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(()) - } } diff --git a/tests/cli.rs b/tests/cli.rs index a727ed5..88159b0 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -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");