diff --git a/CHANGELOG.md b/CHANGELOG.md index 868e78e..ddb3594 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.1.7 + +- `mktemp_d` creates an (insecure, world readable) temporary directory. +- `cp(foo, bar)` copies `foo` _into_ `bar`, if `bar` is an existing directory. + ## 0.1.6 - `.read()` chomps `\r\n` on Windows. diff --git a/Cargo.toml b/Cargo.toml index c3e9bd6..b2b1635 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "xshell" description = "Utilities for quick shell scripting in Rust" categories = ["development-tools::build-utils", "filesystem"] -version = "0.1.6" +version = "0.1.7" license = "MIT OR Apache-2.0" repository = "https://github.com/matklad/xshell" authors = ["Aleksey Kladov "] diff --git a/src/fs.rs b/src/fs.rs index 67b4a38..e47e5fe 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1,4 +1,7 @@ -use std::path::{Path, PathBuf}; +use std::{ + path::{Path, PathBuf}, + sync::atomic::{AtomicUsize, Ordering}, +}; use crate::{error::fs_err, gsl, Result}; @@ -66,6 +69,29 @@ pub fn cwd() -> Result { with_path(&Path::new("."), std::env::current_dir()) } +pub fn mktemp_d() -> Result { + let _guard = gsl::read(); + let base = std::env::temp_dir(); + let pid = std::process::id(); + mkdir_p(&base)?; + + static CNT: AtomicUsize = AtomicUsize::new(0); + + let mut n_try = 0u32; + loop { + n_try += 1; + let cnt = CNT.fetch_add(1, Ordering::Relaxed); + let path = base.join(format!("{}_{}", pid, cnt)); + if let Err(io_err) = std::fs::create_dir(&path) { + if n_try == 1024 { + return Err(fs_err(path, io_err)); + } + continue; + } + return Ok(TempDir { path }); + } +} + fn with_path(path: &Path, res: Result) -> Result { res.map_err(|io_err| fs_err(path.to_path_buf(), io_err)) } @@ -95,3 +121,19 @@ fn read_dir_aux(path: &Path) -> std::io::Result> { res.sort(); Ok(res) } + +pub struct TempDir { + path: PathBuf, +} + +impl TempDir { + pub fn path(&self) -> &Path { + &self.path + } +} + +impl Drop for TempDir { + fn drop(&mut self) { + rm_rf(&self.path).unwrap() + } +} diff --git a/src/lib.rs b/src/lib.rs index e2e1641..2c88122 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -257,7 +257,7 @@ pub use xshell_macros::__cmd; pub use crate::{ env::{pushd, pushenv, Pushd, Pushenv}, error::{Error, Result}, - fs::{cp, cwd, mkdir_p, read_dir, read_file, rm_rf, write_file}, + fs::{cp, cwd, mkdir_p, mktemp_d, read_dir, read_file, rm_rf, write_file, TempDir}, }; #[macro_export] diff --git a/tests/it.rs b/tests/it.rs index c21341c..0f6e668 100644 --- a/tests/it.rs +++ b/tests/it.rs @@ -1,6 +1,6 @@ use std::{ffi::OsStr, thread, time::Duration, time::Instant}; -use xshell::{cmd, cwd, mkdir_p, pushd, pushenv, read_file, rm_rf, write_file}; +use xshell::{cmd, cp, cwd, mkdir_p, mktemp_d, pushd, pushenv, read_file, rm_rf, write_file}; #[test] fn smoke() { @@ -208,6 +208,28 @@ fn test_pushenv_lock() { t2.join().unwrap(); } +#[test] +fn test_cp() { + let path; + { + let tempdir = mktemp_d().unwrap(); + path = tempdir.path().to_path_buf(); + let foo = tempdir.path().join("foo.txt"); + let bar = tempdir.path().join("bar.txt"); + let dir = tempdir.path().join("dir"); + write_file(&foo, "hello world").unwrap(); + mkdir_p(&dir).unwrap(); + + cp(&foo, &bar).unwrap(); + assert_eq!(read_file(&bar).unwrap(), "hello world"); + + cp(&foo, &dir).unwrap(); + assert_eq!(read_file(&dir.join("foo.txt")).unwrap(), "hello world"); + assert!(path.exists()); + } + assert!(!path.exists()); +} + fn check_failure(code: &str, err_msg: &str) { mkdir_p("./target/cf").unwrap(); let _p = pushd("./target/cf").unwrap();