Skip to content

Commit 2d287b9

Browse files
Overall reorganization (#265)
* refactor!: overall reorganization * fmt: tab to spaces * feat: split out funcs for better error collection * feat: replace Vec-varianted Source with Vec<Source> * docs: more generic description for Error::InvalidPath * fmt: reduce signature verbosity * internal: decolorize to prepare for proper colorization * fmt: tab to spaces (again) * refactor: break up App into main.rs, rm utils.rs * test: only run cli::in_place_following_symlink on Unix, symlinks are privileged on Windows * test: update insta snapshots due to Replacer::new()? in main() * fix: restructure logic, Windows requires closing mmap before write * test: properly mark no-Windows test * test: properly mark temp. ignored test * fix: retain unsafe property of Mmap::map in separate function * chore: `cargo fmt` * chore: Resolve lone warning * test: Test a variety of fs failure cases * refactor: Add back `try_main()` * refactor: Rework error handling * test: Update snapshots * test: fix path inconsistency --------- Co-authored-by: Cosmic Horror <CosmicHorrorDev@pm.me>
1 parent 4241bab commit 2d287b9

13 files changed

Lines changed: 373 additions & 306 deletions

.editorconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
1+
[*]
2+
indent_style = space
3+
indent_size = 4
4+
15
[*.rs]
26
max_line_length = 80

Cargo.lock

Lines changed: 0 additions & 50 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,8 +33,6 @@ unescape = "0.1.0"
3333
memmap2 = "0.9.0"
3434
tempfile = "3.8.0"
3535
thiserror = "1.0.50"
36-
ansi_term = "0.12.1"
37-
is-terminal = "0.4.9"
3836
clap.workspace = true
3937

4038
[dev-dependencies]

src/error.rs

Lines changed: 19 additions & 21 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::{fmt, path::PathBuf};
52

63
use crate::replacer::InvalidReplaceCapture;
74

@@ -13,37 +10,38 @@ pub enum Error {
1310
File(#[from] std::io::Error),
1411
#[error("failed to move file: {0}")]
1512
TempfilePersist(#[from] tempfile::PersistError),
16-
#[error("file doesn't have parent path: {0}")]
13+
#[error("invalid path: {0}")]
1714
InvalidPath(PathBuf),
18-
#[error("failed processing files:\n{0}")]
19-
FailedProcessing(FailedJobs),
2015
#[error("{0}")]
2116
InvalidReplaceCapture(#[from] InvalidReplaceCapture),
17+
#[error("{0}")]
18+
FailedJobs(FailedJobs),
2219
}
2320

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)
21+
// pretty-print the error
22+
impl fmt::Debug for Error {
23+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24+
write!(f, "{}", self)
2925
}
3026
}
3127

28+
pub type Result<T, E = Error> = std::result::Result<T, E>;
29+
30+
pub struct FailedJobs(pub Vec<(PathBuf, Error)>);
31+
3232
impl fmt::Display for FailedJobs {
3333
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))?;
34+
f.write_str("Failed processing some inputs\n")?;
35+
for (source, error) in &self.0 {
36+
writeln!(f, " {}: {}", source.display(), error)?;
3737
}
38-
f.write_char(')')
38+
39+
Ok(())
3940
}
4041
}
4142

42-
// pretty-print the error
43-
impl std::fmt::Debug for Error {
44-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43+
impl fmt::Debug for FailedJobs {
44+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4545
write!(f, "{}", self)
4646
}
4747
}
48-
49-
pub type Result<T, E = Error> = std::result::Result<T, E>;

src/input.rs

Lines changed: 35 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,48 @@
1-
use std::{fs::File, io::prelude::*, path::PathBuf};
1+
use memmap2::{Mmap, MmapOptions};
2+
use std::{
3+
fs::File,
4+
io::{stdin, Read},
5+
path::PathBuf,
6+
};
27

3-
use crate::{Error, Replacer, Result};
8+
use crate::error::Result;
49

5-
use is_terminal::IsTerminal;
6-
7-
#[derive(Debug)]
10+
#[derive(Debug, PartialEq)]
811
pub(crate) enum Source {
912
Stdin,
10-
Files(Vec<PathBuf>),
11-
}
12-
13-
pub(crate) struct App {
14-
replacer: Replacer,
15-
source: Source,
13+
File(PathBuf),
1614
}
1715

18-
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(())
16+
impl Source {
17+
pub(crate) fn from_paths(paths: Vec<PathBuf>) -> Vec<Self> {
18+
paths.into_iter().map(Self::File).collect()
3519
}
3620

37-
pub(crate) fn new(source: Source, replacer: Replacer) -> Self {
38-
Self { source, replacer }
21+
pub(crate) fn from_stdin() -> Vec<Self> {
22+
vec![Self::Stdin]
3923
}
40-
pub(crate) fn run(&self, preview: bool) -> Result<()> {
41-
let is_tty = std::io::stdout().is_terminal();
42-
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::*;
48-
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();
5924

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))
66-
}
67-
}
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-
}
87-
88-
handle
89-
.write_all(&self.replacer.replace_preview(&file))?;
90-
writeln!(handle)?;
91-
}
92-
93-
Ok(())
94-
})
95-
}
25+
pub(crate) fn display(&self) -> String {
26+
match self {
27+
Self::Stdin => "STDIN".to_string(),
28+
Self::File(path) => format!("FILE {}", path.display()),
9629
}
9730
}
9831
}
32+
33+
// TODO: memmap2 docs state that users should implement proper
34+
// procedures to avoid problems the `unsafe` keyword indicate.
35+
// This would be in a later PR.
36+
pub(crate) unsafe fn make_mmap(path: &PathBuf) -> Result<Mmap> {
37+
Ok(Mmap::map(&File::open(path)?)?)
38+
}
39+
40+
pub(crate) fn make_mmap_stdin() -> Result<Mmap> {
41+
let mut handle = stdin().lock();
42+
let mut buf = Vec::new();
43+
handle.read_to_end(&mut buf)?;
44+
let mut mmap = MmapOptions::new().len(buf.len()).map_anon()?;
45+
mmap.copy_from_slice(&buf);
46+
let mmap = mmap.make_read_only()?;
47+
Ok(mmap)
48+
}

0 commit comments

Comments
 (0)