Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overall reorganization #265

Merged
merged 22 commits into from
Nov 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2c9ee8d
refactor!: overall reorganization
nc7s Nov 2, 2023
29d390d
fmt: tab to spaces
nc7s Nov 8, 2023
7b1db44
feat: split out funcs for better error collection
nc7s Nov 9, 2023
d66f8bd
feat: replace Vec-varianted Source with Vec<Source>
nc7s Nov 9, 2023
5e7d0e4
docs: more generic description for Error::InvalidPath
nc7s Nov 9, 2023
69094ae
fmt: reduce signature verbosity
nc7s Nov 9, 2023
4530e08
internal: decolorize to prepare for proper colorization
nc7s Nov 9, 2023
857c630
fmt: tab to spaces (again)
nc7s Nov 9, 2023
7fcfdde
refactor: break up App into main.rs, rm utils.rs
nc7s Nov 9, 2023
e78ce15
test: only run cli::in_place_following_symlink on Unix, symlinks are …
nc7s Nov 9, 2023
a8f4e1c
test: update insta snapshots due to Replacer::new()? in main()
nc7s Nov 9, 2023
100db82
fix: restructure logic, Windows requires closing mmap before write
nc7s Nov 9, 2023
9f0c476
test: properly mark no-Windows test
nc7s Nov 10, 2023
601db39
test: properly mark temp. ignored test
nc7s Nov 10, 2023
559dfcb
fix: retain unsafe property of Mmap::map in separate function
nc7s Nov 16, 2023
8fc2aa4
chore: `cargo fmt`
CosmicHorrorDev Nov 17, 2023
5168104
chore: Resolve lone warning
CosmicHorrorDev Nov 18, 2023
56f4d25
test: Test a variety of fs failure cases
CosmicHorrorDev Nov 18, 2023
fcd631d
refactor: Add back `try_main()`
CosmicHorrorDev Nov 18, 2023
8af2eff
refactor: Rework error handling
CosmicHorrorDev Nov 18, 2023
c1cb879
test: Update snapshots
CosmicHorrorDev Nov 18, 2023
f8613a9
test: fix path inconsistency
nc7s Nov 18, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
[*]
indent_style = space
indent_size = 4

[*.rs]
max_line_length = 80
50 changes: 0 additions & 50 deletions Cargo.lock

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

2 changes: 0 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ unescape = "0.1.0"
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
40 changes: 19 additions & 21 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::{fmt, path::PathBuf};

use crate::replacer::InvalidReplaceCapture;

Expand All @@ -13,37 +10,38 @@ pub enum Error {
File(#[from] std::io::Error),
#[error("failed to move file: {0}")]
TempfilePersist(#[from] tempfile::PersistError),
#[error("file doesn't have parent path: {0}")]
#[error("invalid path: {0}")]
InvalidPath(PathBuf),
#[error("failed processing files:\n{0}")]
FailedProcessing(FailedJobs),
#[error("{0}")]
InvalidReplaceCapture(#[from] InvalidReplaceCapture),
#[error("{0}")]
FailedJobs(FailedJobs),
}

pub struct FailedJobs(Vec<(PathBuf, Error)>);

impl From<Vec<(PathBuf, Error)>> for FailedJobs {
fn from(vec: Vec<(PathBuf, Error)>) -> Self {
Self(vec)
// pretty-print the error
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self)
}
}

pub type Result<T, E = Error> = std::result::Result<T, E>;

pub struct FailedJobs(pub Vec<(PathBuf, Error)>);

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_str("Failed processing some inputs\n")?;
for (source, error) in &self.0 {
writeln!(f, " {}: {}", source.display(), error)?;
}
f.write_char(')')

Ok(())
}
}

// pretty-print the error
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Debug for FailedJobs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self)
}
}

pub type Result<T, E = Error> = std::result::Result<T, E>;
120 changes: 35 additions & 85 deletions src/input.rs
Original file line number Diff line number Diff line change
@@ -1,98 +1,48 @@
use std::{fs::File, io::prelude::*, path::PathBuf};
use memmap2::{Mmap, MmapOptions};
use std::{
fs::File,
io::{stdin, Read},
path::PathBuf,
};

use crate::{Error, Replacer, Result};
use crate::error::Result;

use is_terminal::IsTerminal;

#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub(crate) enum Source {
Stdin,
Files(Vec<PathBuf>),
}

pub(crate) struct App {
replacer: Replacer,
source: Source,
File(PathBuf),
}

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

pub(crate) fn new(source: Source, replacer: Replacer) -> Self {
Self { source, replacer }
pub(crate) fn from_stdin() -> Vec<Self> {
vec![Self::Stdin]
}
pub(crate) fn run(&self, preview: bool) -> Result<()> {
let is_tty = std::io::stdout().is_terminal();

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 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 failed_jobs.is_empty() {
Ok(())
} else {
let failed_jobs =
crate::error::FailedJobs::from(failed_jobs);
Err(Error::FailedProcessing(failed_jobs))
}
}
(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()
)?;
}

handle
.write_all(&self.replacer.replace_preview(&file))?;
writeln!(handle)?;
}

Ok(())
})
}
pub(crate) fn display(&self) -> String {
match self {
Self::Stdin => "STDIN".to_string(),
Self::File(path) => format!("FILE {}", path.display()),
}
}
}

// TODO: memmap2 docs state that users should implement proper
// procedures to avoid problems the `unsafe` keyword indicate.
// This would be in a later PR.
pub(crate) unsafe fn make_mmap(path: &PathBuf) -> Result<Mmap> {
Ok(Mmap::map(&File::open(path)?)?)
}

pub(crate) fn make_mmap_stdin() -> Result<Mmap> {
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()?;
Ok(mmap)
}
Loading