summaryrefslogtreecommitdiffstats
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs133
1 files changed, 110 insertions, 23 deletions
diff --git a/src/main.rs b/src/main.rs
index 07c48b8..c346ab6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,20 +3,25 @@ mod error;
mod input;
pub(crate) mod replacer;
-pub(crate) mod utils;
-
-use std::process;
-
-pub(crate) use self::input::{App, Source};
-use ansi_term::{Color, Style};
-pub(crate) use error::{Error, Result};
-use replacer::Replacer;
use clap::Parser;
+use memmap2::MmapMut;
+use std::{
+ fs,
+ io::{stdout, Write},
+ ops::DerefMut,
+ path::PathBuf,
+ process,
+};
+
+pub(crate) use self::error::{Error, FailedJobs, Result};
+pub(crate) use self::input::Source;
+use self::input::{make_mmap, make_mmap_stdin};
+use self::replacer::Replacer;
fn main() {
if let Err(e) = try_main() {
- eprintln!("{}: {}", Style::from(Color::Red).bold().paint("error"), e);
+ eprintln!("error: {e}");
process::exit(1);
}
}
@@ -24,22 +29,104 @@ fn main() {
fn try_main() -> Result<()> {
let options = cli::Options::parse();
- let source = if !options.files.is_empty() {
- Source::Files(options.files)
+ let replacer = Replacer::new(
+ options.find,
+ options.replace_with,
+ options.literal_mode,
+ options.flags,
+ options.replacements,
+ )?;
+
+ let sources = if !options.files.is_empty() {
+ Source::from_paths(options.files)
} else {
- Source::Stdin
+ Source::from_stdin()
};
- App::new(
- source,
- Replacer::new(
- options.find,
- options.replace_with,
- options.literal_mode,
- options.flags,
- options.replacements,
- )?,
- )
- .run(options.preview)?;
+ let mut mmaps = Vec::new();
+ for source in sources.iter() {
+ let mmap = match source {
+ Source::File(path) => {
+ if path.exists() {
+ unsafe { make_mmap(&path)? }
+ } else {
+ return Err(Error::InvalidPath(path.to_owned()));
+ }
+ }
+ Source::Stdin => make_mmap_stdin()?,
+ };
+
+ mmaps.push(mmap);
+ }
+
+ let needs_separator = sources.len() > 1;
+
+ let replaced: Vec<_> = {
+ use rayon::prelude::*;
+ mmaps
+ .par_iter()
+ .map(|mmap| replacer.replace(&mmap))
+ .collect()
+ };
+
+ if options.preview || sources.first() == Some(&Source::Stdin) {
+ let mut handle = stdout().lock();
+
+ for (source, replaced) in sources.iter().zip(replaced) {
+ if needs_separator {
+ writeln!(handle, "----- {} -----", source.display())?;
+ }
+ handle.write_all(&replaced)?;
+ }
+ } else {
+ // Windows requires closing mmap before writing:
+ // > The requested operation cannot be performed on a file with a user-mapped section open
+ #[cfg(target_family = "windows")]
+ let replaced: Vec<Vec<u8>> =
+ replaced.into_iter().map(|r| r.to_vec()).collect();
+ #[cfg(target_family = "windows")]
+ drop(mmaps);
+
+ let mut failed_jobs = Vec::new();
+ for (source, replaced) in sources.iter().zip(replaced) {
+ match source {
+ Source::File(path) => {
+ if let Err(e) = write_with_temp(path, &replaced) {
+ failed_jobs.push((path.to_owned(), e));
+ }
+ }
+ _ => unreachable!("stdin should go previous branch"),
+ }
+ }
+ if !failed_jobs.is_empty() {
+ return Err(Error::FailedJobs(FailedJobs(failed_jobs)));
+ }
+ }
+
+ Ok(())
+}
+
+fn write_with_temp(path: &PathBuf, data: &[u8]) -> Result<()> {
+ let path = fs::canonicalize(path)?;
+
+ let temp = tempfile::NamedTempFile::new_in(
+ path.parent()
+ .ok_or_else(|| Error::InvalidPath(path.to_path_buf()))?,
+ )?;
+
+ let file = temp.as_file();
+ file.set_len(data.len() as u64)?;
+ if let Ok(metadata) = fs::metadata(&path) {
+ file.set_permissions(metadata.permissions()).ok();
+ }
+
+ if !data.is_empty() {
+ let mut mmap_temp = unsafe { MmapMut::map_mut(file)? };
+ mmap_temp.deref_mut().write_all(data)?;
+ mmap_temp.flush_async()?;
+ }
+
+ temp.persist(&path)?;
+
Ok(())
}