Skip to content

Commit

Permalink
chmln#191 Add --no-swap flag
Browse files Browse the repository at this point in the history
  • Loading branch information
tennox committed May 17, 2023
1 parent b5768a7 commit 0bdf62d
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 22 deletions.
4 changes: 4 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ w - match full words only
/// use captured values like $1, $2, etc.
pub replace_with: String,

#[arg(long)]
/// Overwrite file instead of creating tmp file and swaping atomically
pub no_swap: bool,

/// The path to file(s). This is optional - sd can also read from STDIN.
///{n}{n}Note: sd modifies files in-place by default. See documentation for
/// examples.
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ fn main() -> Result<()> {
options.literal_mode,
options.flags,
options.replacements,
options.no_swap,
)?,
)
.run(options.preview)?;
Expand Down
66 changes: 44 additions & 22 deletions src/replacer.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
use crate::{utils, Error, Result};
use regex::bytes::Regex;
use std::{fs, fs::File, io::prelude::*, path::Path};
use std::{
fs,
fs::{File, OpenOptions},
io::{prelude::*, SeekFrom},
path::Path,
};

pub(crate) struct Replacer {
regex: Regex,
replace_with: Vec<u8>,
is_literal: bool,
replacements: usize,
no_swap: bool,
}

impl Replacer {
Expand All @@ -16,6 +22,7 @@ impl Replacer {
is_literal: bool,
flags: Option<String>,
replacements: Option<usize>,
no_swap: bool,
) -> Result<Self> {
let (look_for, replace_with) = if is_literal {
(regex::escape(&look_for), replace_with.into_bytes())
Expand Down Expand Up @@ -61,6 +68,7 @@ impl Replacer {
replace_with,
is_literal,
replacements: replacements.unwrap_or(0),
no_swap,
})
}

Expand Down Expand Up @@ -128,29 +136,42 @@ impl Replacer {
return Ok(());
}

let source = File::open(path)?;
let meta = fs::metadata(path)?;
let mmap_source = unsafe { Mmap::map(&source)? };
let replaced = self.replace(&mmap_source);

let target = tempfile::NamedTempFile::new_in(
path.parent()
.ok_or_else(|| Error::InvalidPath(path.to_path_buf()))?,
)?;
let file = target.as_file();
file.set_len(replaced.len() as u64)?;
file.set_permissions(meta.permissions())?;

if !replaced.is_empty() {
let mut mmap_target = unsafe { MmapMut::map_mut(file)? };
mmap_target.deref_mut().write_all(&replaced)?;
mmap_target.flush_async()?;
}
if self.no_swap {
let mut source =
OpenOptions::new().read(true).write(true).open(path)?;
let mut buffer = Vec::new();
source.read_to_end(&mut buffer)?;

let replaced = self.replace(&buffer);

source.seek(SeekFrom::Start(0))?;
source.write_all(&replaced)?;
source.set_len(replaced.len() as u64)?;
} else {
let source = File::open(path)?;
let meta = fs::metadata(path)?;
let mmap_source = unsafe { Mmap::map(&source)? };
let replaced = self.replace(&mmap_source);

let target = tempfile::NamedTempFile::new_in(
path.parent()
.ok_or_else(|| Error::InvalidPath(path.to_path_buf()))?,
)?;
let file = target.as_file();
file.set_len(replaced.len() as u64)?;
file.set_permissions(meta.permissions())?;

if !replaced.is_empty() {
let mut mmap_target = unsafe { MmapMut::map_mut(file)? };
mmap_target.deref_mut().write_all(&replaced)?;
mmap_target.flush_async()?;
}

drop(mmap_source);
drop(source);
drop(mmap_source);
drop(source);
target.persist(fs::canonicalize(path)?)?;
}

target.persist(fs::canonicalize(path)?)?;
Ok(())
}
}
Expand All @@ -173,6 +194,7 @@ mod tests {
literal,
flags.map(ToOwned::to_owned),
None,
false,
)
.unwrap();
assert_eq!(
Expand Down

0 comments on commit 0bdf62d

Please sign in to comment.