Skip to content

Commit c8a1e72

Browse files
committed
refactor!: overall reorganization
1 parent e4e42a2 commit c8a1e72

7 files changed

Lines changed: 67 additions & 206 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 30 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 & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,8 @@ unescape = "0.1.0"
3333
memmap2 = "0.9.0"
3434
tempfile = "3.8.0"
3535
thiserror = "1.0.50"
36-
globwalk = "0.8.1"
3736
ignore = "0.4.20"
3837
ansi_term = "0.12.1"
39-
is-terminal = "0.4.9"
4038
clap.workspace = true
4139

4240
[dev-dependencies]

src/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub struct Options {
3030
pub literal_mode: bool,
3131

3232
#[arg(short)]
33-
/// Recursively replace files
33+
/// Recursively replace files under current directory
3434
pub recursive: bool,
3535

3636
#[arg(short = 'n')]

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: 63 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
use std::{fs::File, io::prelude::*, path::PathBuf};
1+
use std::{
2+
fs::{File, self},
3+
io::{Write, stdin, stdout},
4+
path::PathBuf,
5+
borrow::Cow,
6+
ops::DerefMut
7+
};
28

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

5-
use is_terminal::IsTerminal;
11+
use memmap2::{Mmap, MmapMut};
612

713
#[derive(Debug)]
814
pub(crate) enum Source {
@@ -11,7 +17,7 @@ pub(crate) enum Source {
1117
}
1218

1319
impl Source {
14-
pub(crate) fn recursive() -> Result<Self> {
20+
pub(crate) fn cwd_recursive() -> Result<Self> {
1521
Ok(Self::Files(
1622
ignore::WalkBuilder::new(".")
1723
.hidden(false)
@@ -33,83 +39,69 @@ pub(crate) struct App {
3339
}
3440

3541
impl App {
36-
fn stdin_replace(&self, is_tty: bool) -> Result<()> {
37-
let mut buffer = Vec::with_capacity(256);
38-
let stdin = std::io::stdin();
39-
let mut handle = stdin.lock();
40-
handle.read_to_end(&mut buffer)?;
41-
42-
let stdout = std::io::stdout();
43-
let mut handle = stdout.lock();
44-
45-
handle.write_all(&if is_tty {
46-
self.replacer.replace_preview(&buffer)
47-
} else {
48-
self.replacer.replace(&buffer)
49-
})?;
50-
51-
Ok(())
52-
}
53-
5442
pub(crate) fn new(source: Source, replacer: Replacer) -> Self {
5543
Self { source, replacer }
5644
}
5745
pub(crate) fn run(&self, preview: bool) -> Result<()> {
58-
let is_tty = std::io::stdout().is_terminal();
59-
60-
match (&self.source, preview) {
61-
(Source::Stdin, true) => self.stdin_replace(is_tty),
62-
(Source::Stdin, false) => self.stdin_replace(is_tty),
63-
(Source::Files(paths), false) => {
64-
use rayon::prelude::*;
46+
let sources: Vec<_> = match &self.source {
47+
Source::Stdin => vec![(PathBuf::from("STDIN"), unsafe { Mmap::map(&stdin().lock())? })],
48+
Source::Files(paths) => {
49+
let mut mmaps = Vec::new();
50+
for path in paths {
51+
if !path.exists() {
52+
return Err(Error::InvalidPath(path.clone()));
53+
}
54+
mmaps.push((path.clone(), unsafe { Mmap::map(&File::open(path)?)? }));
55+
}
56+
mmaps
57+
},
58+
};
59+
let needs_separator = sources.len() > 1;
60+
61+
let replaced: Vec<(&PathBuf, &Mmap, Cow<'_, [u8]>)> = {
62+
use rayon::prelude::*;
63+
sources.par_iter()
64+
.map(|(path, mmap)| {
65+
let replaced = self.replacer.replace(mmap);
66+
(path, mmap, replaced)
67+
})
68+
.collect()
69+
};
6570

66-
let failed_jobs: Vec<_> = paths
67-
.par_iter()
68-
.filter_map(|p| {
69-
if let Err(e) = self.replacer.replace_file(p) {
70-
Some((p.to_owned(), e))
71-
} else {
72-
None
73-
}
74-
})
75-
.collect();
71+
if preview {
72+
let mut handle = stdout().lock();
7673

77-
if failed_jobs.is_empty() {
78-
Ok(())
79-
} else {
80-
let failed_jobs =
81-
crate::error::FailedJobs::from(failed_jobs);
82-
Err(Error::FailedProcessing(failed_jobs))
74+
for (path, _, piece) in replaced {
75+
if needs_separator {
76+
writeln!(handle, "----- FILE {} -----", path.display())?;
8377
}
78+
handle.write_all(&piece)?;
79+
writeln!(handle)?;
8480
}
85-
(Source::Files(paths), true) => {
86-
let stdout = std::io::stdout();
87-
let mut handle = stdout.lock();
88-
let print_path = paths.len() > 1;
89-
90-
paths.iter().try_for_each(|path| {
91-
if Replacer::check_not_empty(File::open(path)?).is_err() {
92-
return Ok(());
93-
}
94-
let file =
95-
unsafe { memmap2::Mmap::map(&File::open(path)?)? };
96-
if self.replacer.has_matches(&file) {
97-
if print_path {
98-
writeln!(
99-
handle,
100-
"----- FILE {} -----",
101-
path.display()
102-
)?;
103-
}
104-
105-
handle
106-
.write_all(&self.replacer.replace_preview(&file))?;
107-
writeln!(handle)?;
108-
}
109-
110-
Ok(())
111-
})
81+
} else {
82+
for (path, _, replaced) in replaced {
83+
let source = File::open(path)?;
84+
let meta = fs::metadata(path)?;
85+
drop(source);
86+
87+
let target = tempfile::NamedTempFile::new_in(
88+
path.parent()
89+
.ok_or_else(|| Error::InvalidPath(path.to_path_buf()))?,
90+
)?;
91+
let file = target.as_file();
92+
file.set_len(replaced.len() as u64)?;
93+
file.set_permissions(meta.permissions())?;
94+
95+
if !replaced.is_empty() {
96+
let mut mmap_target = unsafe { MmapMut::map_mut(file)? };
97+
mmap_target.deref_mut().write_all(&replaced)?;
98+
mmap_target.flush_async()?;
99+
}
100+
101+
target.persist(fs::canonicalize(path)?)?;
112102
}
113103
}
104+
105+
Ok(())
114106
}
115107
}

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ fn try_main() -> Result<()> {
2525
let options = cli::Options::parse();
2626

2727
let source = if options.recursive {
28-
Source::recursive()?
28+
Source::cwd_recursive()?
2929
} else if !options.files.is_empty() {
3030
Source::Files(options.files)
3131
} else {

src/replacer/mod.rs

Lines changed: 1 addition & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
use std::{fs, fs::File, io::prelude::*, path::Path};
2-
3-
use crate::{utils, Error, Result};
1+
use crate::{utils, Result};
42

53
use regex::bytes::Regex;
64

@@ -74,16 +72,6 @@ impl Replacer {
7472
})
7573
}
7674

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-
8775
pub(crate) fn replace<'a>(
8876
&'a self,
8977
content: &'a [u8],
@@ -99,68 +87,4 @@ impl Replacer {
9987
.replacen(content, self.replacements, &*self.replace_with)
10088
}
10189
}
102-
103-
pub(crate) fn replace_preview<'a>(
104-
&'a self,
105-
content: &[u8],
106-
) -> std::borrow::Cow<'a, [u8]> {
107-
let mut v = Vec::<u8>::new();
108-
let mut captures = self.regex.captures_iter(content);
109-
110-
self.regex.split(content).for_each(|sur_text| {
111-
use regex::bytes::Replacer;
112-
113-
v.extend(sur_text);
114-
if let Some(capture) = captures.next() {
115-
v.extend_from_slice(
116-
ansi_term::Color::Green.prefix().to_string().as_bytes(),
117-
);
118-
if self.is_literal {
119-
regex::bytes::NoExpand(&self.replace_with)
120-
.replace_append(&capture, &mut v);
121-
} else {
122-
(&*self.replace_with).replace_append(&capture, &mut v);
123-
}
124-
v.extend_from_slice(
125-
ansi_term::Color::Green.suffix().to_string().as_bytes(),
126-
);
127-
}
128-
});
129-
130-
return std::borrow::Cow::Owned(v);
131-
}
132-
133-
pub(crate) fn replace_file(&self, path: &Path) -> Result<()> {
134-
use memmap2::{Mmap, MmapMut};
135-
use std::ops::DerefMut;
136-
137-
if Self::check_not_empty(File::open(path)?).is_err() {
138-
return Ok(());
139-
}
140-
141-
let source = File::open(path)?;
142-
let meta = fs::metadata(path)?;
143-
let mmap_source = unsafe { Mmap::map(&source)? };
144-
let replaced = self.replace(&mmap_source);
145-
146-
let target = tempfile::NamedTempFile::new_in(
147-
path.parent()
148-
.ok_or_else(|| Error::InvalidPath(path.to_path_buf()))?,
149-
)?;
150-
let file = target.as_file();
151-
file.set_len(replaced.len() as u64)?;
152-
file.set_permissions(meta.permissions())?;
153-
154-
if !replaced.is_empty() {
155-
let mut mmap_target = unsafe { MmapMut::map_mut(file)? };
156-
mmap_target.deref_mut().write_all(&replaced)?;
157-
mmap_target.flush_async()?;
158-
}
159-
160-
drop(mmap_source);
161-
drop(source);
162-
163-
target.persist(fs::canonicalize(path)?)?;
164-
Ok(())
165-
}
16690
}

0 commit comments

Comments
 (0)