coliru

A minimal, flexible, dotfile installer
git clone https://git.ashermorgan.net/coliru/
Log | Files | Refs | README

commit 952f62ae824a50845a0b527f449f391a67b5da0c
parent 89cf19042172c9c8475c6334079edeb01d329238
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date:   Mon,  1 Jul 2024 17:07:44 -0700

Implement ssh::stage_file function

Diffstat:
Msrc/ssh.rs | 105++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 102 insertions(+), 3 deletions(-)

diff --git a/src/ssh.rs b/src/ssh.rs @@ -1,4 +1,46 @@ +use shellexpand::tilde_with_context; +use std::path::{MAIN_SEPARATOR_STR, PathBuf}; use std::process::Command; +use super::local::copy_file; + +/// Copy a file to an SCP staging directory +/// +/// The destination directory structure will be recreated in the staging +/// directory under either the home or root subdirectories. This staging system +/// allows for many files to transferred at once and for missing directories to +/// be created automatically on the remote machine. +#[allow(dead_code)] +fn stage_file(src: &str, dst: &str, staging_dir: &str) -> Result<(), String> { + let _staging_dir = PathBuf::from(staging_dir); + let home_dir = _staging_dir.join("home"); + let root_dir = _staging_dir.join("root"); + let get_home_dir = || { + Some::<String>(home_dir.to_string_lossy().into()) + }; + + // Resolve ~/... paths to home staging directory: + let mut _dst: PathBuf = (&tilde_with_context(dst, get_home_dir).to_mut()) + .into(); + + // Resolve relative paths to home staging directory: + _dst = home_dir.join(_dst); + + // Resolve other absolute paths to root staging directory: + if !_dst.starts_with(home_dir) { + // Root should be / and C:\ on Unix and Windows respectively, but + // iter().next() will return / and C:, so we must manually add another + // path separator. (Duplicate slashes are ignored on Unix). + let root = PathBuf::from(match _dst.iter().next() { + Some(x) => Ok(x), + None => Err(String::from("Destination path does not have root")), + }?).join(MAIN_SEPARATOR_STR); + _dst = root_dir.join(_dst.strip_prefix(root) + .map_err(|why| why.to_string())?); + } + + copy_file(src, _dst.to_string_lossy().to_mut()) + .map_err(|why| why.to_string()) +} /// Recursively copy a directory to another machine via SCP #[allow(dead_code)] @@ -19,14 +61,16 @@ fn send_dir(src: &str, dst: &str, host: &str) -> Result<(), String> { } #[cfg(test)] -#[cfg(target_family = "unix")] mod tests { + #![allow(unused_imports)] + use super::*; use crate::common::{SSH_HOST, read_file, setup_integration, write_file}; use std::fs; #[test] + #[cfg(target_family = "unix")] fn test_send_dir_basic() { let tmp = setup_integration("test_send_dir_basic"); @@ -35,7 +79,7 @@ mod tests { let result = send_dir(&tmp.local.to_str().unwrap(), "~/", SSH_HOST); - assert_eq!(result.is_ok(), true); + assert_eq!(result, Ok(())); let contents1 = read_file(&tmp.ssh.join("foo")); assert_eq!(contents1, "contents of foo"); let contents2 = read_file(&tmp.ssh.join("bar")); @@ -43,6 +87,7 @@ mod tests { } #[test] + #[cfg(target_family = "unix")] fn test_send_dir_nested() { let tmp = setup_integration("test_send_dir_nested"); @@ -52,10 +97,64 @@ mod tests { let result = send_dir(tmp.local.to_str().unwrap(), "~/", SSH_HOST); - assert_eq!(result.is_ok(), true); + assert_eq!(result, Ok(())); let contents1 = read_file(&tmp.ssh.join("foo")); assert_eq!(contents1, "contents of foo"); let contents2 = read_file(&tmp.ssh.join("dir").join("bar")); assert_eq!(contents2, "contents of bar"); } + + #[test] + fn test_stage_file_tilde() { + let tmp = setup_integration("test_stage_file_tilde"); + + let src = tmp.local.join("foo"); + let dst = "~/dir/bar"; + let dst_real = tmp.local.join("home").join("dir").join("bar"); + let staging = &tmp.local; + write_file(&src, "contents of foo"); + + let result = stage_file(src.to_str().unwrap(), dst, + staging.to_str().unwrap()); + + assert_eq!(result, Ok(())); + assert_eq!(dst_real.exists(), true); + assert_eq!(read_file(&dst_real), "contents of foo"); + } + + #[test] + fn test_stage_file_relative() { + let tmp = setup_integration("test_stage_file_relative"); + + let src = tmp.local.join("foo"); + let dst = "dir/bar"; + let dst_real = tmp.local.join("home").join("dir").join("bar"); + let staging = &tmp.local; + write_file(&src, "contents of foo"); + + let result = stage_file(src.to_str().unwrap(), dst, + staging.to_str().unwrap()); + + assert_eq!(result, Ok(())); + assert_eq!(dst_real.exists(), true); + assert_eq!(read_file(&dst_real), "contents of foo"); + } + + #[test] + fn test_stage_file_absolute() { + let tmp = setup_integration("test_stage_file_absolute"); + + let src = tmp.local.join("foo"); + let dst = "/dir/bar"; + let dst_real = tmp.local.join("root").join("dir").join("bar"); + let staging = &tmp.local; + write_file(&src, "contents of foo"); + + let result = stage_file(src.to_str().unwrap(), dst, + staging.to_str().unwrap()); + + assert_eq!(result, Ok(())); + assert_eq!(dst_real.exists(), true); + assert_eq!(read_file(&dst_real), "contents of foo"); + } }