Skip to content

Commit 5d34bdb

Browse files
committed
refactor!: overall reorganization
1 parent 4f77cfe commit 5d34bdb

6 files changed

Lines changed: 74 additions & 189 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ memmap2 = "0.9.0"
3434
tempfile = "3.8.0"
3535
thiserror = "1.0.50"
3636
ansi_term = "0.12.1"
37-
is-terminal = "0.4.9"
3837
clap.workspace = true
3938

4039
[dev-dependencies]

src/error.rs

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
use std::{
2-
fmt::{self, Write},
3-
path::PathBuf,
4-
};
1+
use std::path::PathBuf;
52

63
use crate::replacer::InvalidReplaceCapture;
74

@@ -15,30 +12,10 @@ pub enum Error {
1512
TempfilePersist(#[from] tempfile::PersistError),
1613
#[error("file doesn't have parent path: {0}")]
1714
InvalidPath(PathBuf),
18-
#[error("failed processing files:\n{0}")]
19-
FailedProcessing(FailedJobs),
2015
#[error("{0}")]
2116
InvalidReplaceCapture(#[from] InvalidReplaceCapture),
2217
}
2318

24-
pub struct FailedJobs(Vec<(PathBuf, Error)>);
25-
26-
impl From<Vec<(PathBuf, Error)>> for FailedJobs {
27-
fn from(vec: Vec<(PathBuf, Error)>) -> Self {
28-
Self(vec)
29-
}
30-
}
31-
32-
impl fmt::Display for FailedJobs {
33-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34-
f.write_str("\tFailedJobs(\n")?;
35-
for (path, err) in &self.0 {
36-
f.write_str(&format!("\t{:?}: {}\n", path, err))?;
37-
}
38-
f.write_char(')')
39-
}
40-
}
41-
4219
// pretty-print the error
4320
impl std::fmt::Debug for Error {
4421
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

src/input.rs

Lines changed: 70 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
use std::{fs::File, io::prelude::*, path::PathBuf};
1+
use std::{
2+
fs::{File, self},
3+
io::{Write, stdin, stdout, Read},
4+
path::PathBuf,
5+
ops::DerefMut,
6+
};
27

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

5-
use is_terminal::IsTerminal;
10+
use memmap2::{Mmap, MmapMut, MmapOptions};
611

7-
#[derive(Debug)]
12+
#[derive(Debug, PartialEq)]
813
pub(crate) enum Source {
914
Stdin,
1015
Files(Vec<PathBuf>),
@@ -16,83 +21,80 @@ pub(crate) struct App {
1621
}
1722

1823
impl App {
19-
fn stdin_replace(&self, is_tty: bool) -> Result<()> {
20-
let mut buffer = Vec::with_capacity(256);
21-
let stdin = std::io::stdin();
22-
let mut handle = stdin.lock();
23-
handle.read_to_end(&mut buffer)?;
24-
25-
let stdout = std::io::stdout();
26-
let mut handle = stdout.lock();
27-
28-
handle.write_all(&if is_tty {
29-
self.replacer.replace_preview(&buffer)
30-
} else {
31-
self.replacer.replace(&buffer)
32-
})?;
33-
34-
Ok(())
35-
}
36-
3724
pub(crate) fn new(source: Source, replacer: Replacer) -> Self {
3825
Self { source, replacer }
3926
}
27+
4028
pub(crate) fn run(&self, preview: bool) -> Result<()> {
41-
let is_tty = std::io::stdout().is_terminal();
29+
let sources: Vec<(PathBuf, Mmap)> = match &self.source {
30+
Source::Stdin => {
31+
let mut handle = stdin().lock();
32+
let mut buf = Vec::new();
33+
handle.read_to_end(&mut buf)?;
34+
let mut mmap = MmapOptions::new()
35+
.len(buf.len())
36+
.map_anon()?;
37+
mmap.copy_from_slice(&buf);
38+
let mmap = mmap.make_read_only()?;
39+
vec![(PathBuf::from("STDIN"), mmap)]
40+
},
41+
Source::Files(paths) => {
42+
let mut refs = Vec::new();
43+
for path in paths {
44+
if !path.exists() {
45+
return Err(Error::InvalidPath(path.clone()));
46+
}
47+
let mmap = unsafe { Mmap::map(&File::open(path)?)? };
48+
refs.push((path.clone(), mmap));
49+
}
50+
refs
51+
},
52+
};
53+
let needs_separator = sources.len() > 1;
4254

43-
match (&self.source, preview) {
44-
(Source::Stdin, true) => self.stdin_replace(is_tty),
45-
(Source::Stdin, false) => self.stdin_replace(is_tty),
46-
(Source::Files(paths), false) => {
47-
use rayon::prelude::*;
55+
let replaced: Vec<_> = {
56+
use rayon::prelude::*;
57+
sources.par_iter()
58+
.map(|(path, mmap)| {
59+
let replaced = self.replacer.replace(mmap);
60+
(path, mmap, replaced)
61+
})
62+
.collect()
63+
};
4864

49-
let failed_jobs: Vec<_> = paths
50-
.par_iter()
51-
.filter_map(|p| {
52-
if let Err(e) = self.replacer.replace_file(p) {
53-
Some((p.to_owned(), e))
54-
} else {
55-
None
56-
}
57-
})
58-
.collect();
65+
if preview || self.source == Source::Stdin {
66+
let mut handle = stdout().lock();
5967

60-
if failed_jobs.is_empty() {
61-
Ok(())
62-
} else {
63-
let failed_jobs =
64-
crate::error::FailedJobs::from(failed_jobs);
65-
Err(Error::FailedProcessing(failed_jobs))
68+
for (path, _, replaced) in replaced {
69+
if needs_separator {
70+
writeln!(handle, "----- FILE {} -----", path.display())?;
6671
}
72+
handle.write_all(replaced.as_ref())?;
6773
}
68-
(Source::Files(paths), true) => {
69-
let stdout = std::io::stdout();
70-
let mut handle = stdout.lock();
71-
let print_path = paths.len() > 1;
72-
73-
paths.iter().try_for_each(|path| {
74-
if Replacer::check_not_empty(File::open(path)?).is_err() {
75-
return Ok(());
76-
}
77-
let file =
78-
unsafe { memmap2::Mmap::map(&File::open(path)?)? };
79-
if self.replacer.has_matches(&file) {
80-
if print_path {
81-
writeln!(
82-
handle,
83-
"----- FILE {} -----",
84-
path.display()
85-
)?;
86-
}
74+
} else {
75+
for (path, _, replaced) in replaced {
76+
let source = File::open(path)?;
77+
let meta = fs::metadata(path)?;
78+
drop(source);
8779

88-
handle
89-
.write_all(&self.replacer.replace_preview(&file))?;
90-
writeln!(handle)?;
91-
}
80+
let target = tempfile::NamedTempFile::new_in(
81+
path.parent()
82+
.ok_or_else(|| Error::InvalidPath(path.to_path_buf()))?,
83+
)?;
84+
let file = target.as_file();
85+
file.set_len(replaced.len() as u64)?;
86+
file.set_permissions(meta.permissions())?;
9287

93-
Ok(())
94-
})
88+
if !replaced.is_empty() {
89+
let mut mmap_target = unsafe { MmapMut::map_mut(file)? };
90+
mmap_target.deref_mut().write_all(&replaced)?;
91+
mmap_target.flush_async()?;
92+
}
93+
94+
target.persist(fs::canonicalize(path)?)?;
9595
}
9696
}
97+
98+
Ok(())
9799
}
98100
}

src/replacer/mod.rs

Lines changed: 2 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use std::{borrow::Cow, fs, fs::File, io::prelude::*, path::Path};
1+
use std::borrow::Cow;
22

3-
use crate::{utils, Error, Result};
3+
use crate::{utils, Result};
44

55
use regex::bytes::Regex;
66

@@ -74,16 +74,6 @@ impl Replacer {
7474
})
7575
}
7676

77-
pub(crate) fn has_matches(&self, content: &[u8]) -> bool {
78-
self.regex.is_match(content)
79-
}
80-
81-
pub(crate) fn check_not_empty(mut file: File) -> Result<()> {
82-
let mut buf: [u8; 1] = Default::default();
83-
file.read_exact(&mut buf)?;
84-
Ok(())
85-
}
86-
8777
pub(crate) fn replace<'a>(
8878
&'a self,
8979
content: &'a [u8],
@@ -148,65 +138,4 @@ impl Replacer {
148138
new.extend_from_slice(&haystack[last_match..]);
149139
Cow::Owned(new)
150140
}
151-
152-
pub(crate) fn replace_preview<'a>(
153-
&self,
154-
content: &'a [u8],
155-
) -> std::borrow::Cow<'a, [u8]> {
156-
let regex = &self.regex;
157-
let limit = self.replacements;
158-
// TODO: refine this condition more
159-
let use_color = true;
160-
if self.is_literal {
161-
Self::replacen(
162-
regex,
163-
limit,
164-
content,
165-
use_color,
166-
regex::bytes::NoExpand(&self.replace_with),
167-
)
168-
} else {
169-
Self::replacen(
170-
regex,
171-
limit,
172-
content,
173-
use_color,
174-
&*self.replace_with,
175-
)
176-
}
177-
}
178-
179-
pub(crate) fn replace_file(&self, path: &Path) -> Result<()> {
180-
use memmap2::{Mmap, MmapMut};
181-
use std::ops::DerefMut;
182-
183-
if Self::check_not_empty(File::open(path)?).is_err() {
184-
return Ok(());
185-
}
186-
187-
let source = File::open(path)?;
188-
let meta = fs::metadata(path)?;
189-
let mmap_source = unsafe { Mmap::map(&source)? };
190-
let replaced = self.replace(&mmap_source);
191-
192-
let target = tempfile::NamedTempFile::new_in(
193-
path.parent()
194-
.ok_or_else(|| Error::InvalidPath(path.to_path_buf()))?,
195-
)?;
196-
let file = target.as_file();
197-
file.set_len(replaced.len() as u64)?;
198-
file.set_permissions(meta.permissions())?;
199-
200-
if !replaced.is_empty() {
201-
let mut mmap_target = unsafe { MmapMut::map_mut(file)? };
202-
mmap_target.deref_mut().write_all(&replaced)?;
203-
mmap_target.flush_async()?;
204-
}
205-
206-
drop(mmap_source);
207-
drop(source);
208-
209-
target.persist(fs::canonicalize(path)?)?;
210-
Ok(())
211-
}
212141
}

tests/cli.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,7 @@ mod cli {
8181
sd().args(["-p", "abc\\d+", "", file.path().to_str().unwrap()])
8282
.assert()
8383
.success()
84-
.stdout(format!(
85-
"{}{}def\n",
86-
ansi_term::Color::Green.prefix(),
87-
ansi_term::Color::Green.suffix()
88-
));
84+
.stdout("def");
8985

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

0 commit comments

Comments
 (0)