commit 0058b790ce465c8e3c3be7a5b7b03317db310024
parent f49508720c82a8504f9405947e933311a086db0c
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date: Thu, 20 Jun 2024 16:05:00 -0700
Implement link command
Diffstat:
4 files changed, 74 insertions(+), 18 deletions(-)
diff --git a/examples/2.yml b/examples/2.yml
@@ -1,7 +1,7 @@
# Valid manifest
steps:
- - copy:
+ - link:
- src: foo
dst: ~/foo
- src: bar
@@ -11,4 +11,4 @@ steps:
- copy:
- src: baz
dst: /baz
- tags: [ b, c ]
+ # tags default to []
diff --git a/src/core.rs b/src/core.rs
@@ -1,8 +1,8 @@
use std::env::set_current_dir;
use std::path::Path;
-use super::manifest::{CopyOptions, Manifest, parse_manifest_file};
+use super::manifest::{CopyLinkOptions, Manifest, parse_manifest_file};
use super::tags::tags_match;
-use super::utils::copy_file;
+use super::utils::{copy_file, link_file};
/// Execute the steps in a coliru manifest file according to a set of tag rules
pub fn execute_manifest_file(path: &Path, tag_rules: Vec<String>, dry_run: bool)
@@ -29,11 +29,12 @@ fn execute_manifest(manifest: Manifest, tag_rules: Vec<String>, dry_run: bool) {
println!("");
execute_copies(&step.copy, dry_run);
+ execute_links(&step.link, dry_run);
}
}
/// Execute the copy commands specified in a coliru manifest step
-fn execute_copies(copies: &[CopyOptions], dry_run: bool) {
+fn execute_copies(copies: &[CopyLinkOptions], dry_run: bool) {
for copy in copies {
print!(" Copy {} to {}", copy.src, copy.dst);
if dry_run {
@@ -47,3 +48,19 @@ fn execute_copies(copies: &[CopyOptions], dry_run: bool) {
}
}
}
+
+/// Execute the link commands specified in a coliru manifest step
+fn execute_links(copies: &[CopyLinkOptions], dry_run: bool) {
+ for copy in copies {
+ print!(" Link {} to {}", copy.src, copy.dst);
+ if dry_run {
+ println!(" (skipped due to --dry-run)");
+ return;
+ }
+ println!("");
+
+ if let Err(why) = link_file(©.src, ©.dst) {
+ eprintln!(" Error: {}", why);
+ }
+ }
+}
diff --git a/src/manifest.rs b/src/manifest.rs
@@ -4,14 +4,20 @@ use std::fs::read_to_string;
use std::path::{Path, PathBuf};
#[derive(Debug, PartialEq, Deserialize)]
-pub struct CopyOptions {
+pub struct CopyLinkOptions {
pub src: String,
pub dst: String,
}
#[derive(Debug, PartialEq, Deserialize)]
pub struct Step {
- pub copy: Vec<CopyOptions>,
+ #[serde(default)]
+ pub copy: Vec<CopyLinkOptions>,
+
+ #[serde(default)]
+ pub link: Vec<CopyLinkOptions>,
+
+ #[serde(default)]
pub tags: Vec<String>,
}
@@ -53,7 +59,7 @@ mod tests {
#[test]
fn parse_manifest_file_invalid() {
- let expected = "steps[0]: missing field `copy` at line 4 column 5";
+ let expected = "steps[1].copy[0]: missing field `src` at line 12 column 7";
let actual = parse_manifest_file(Path::new("examples/1.yml"));
assert_eq!(actual, Err(String::from(expected)));
}
@@ -63,12 +69,13 @@ mod tests {
let expected = Manifest {
steps: vec![
Step {
- copy: vec![
- CopyOptions{
+ copy: vec![],
+ link: vec![
+ CopyLinkOptions{
src: String::from("foo"),
dst: String::from("~/foo"),
},
- CopyOptions{
+ CopyLinkOptions{
src: String::from("bar"),
dst: String::from("~/test/bar"),
},
@@ -77,12 +84,13 @@ mod tests {
},
Step {
copy: vec![
- CopyOptions{
+ CopyLinkOptions{
src: String::from("baz"),
dst: String::from("/baz"),
},
],
- tags: vec![String::from("b"), String::from("c")],
+ link: vec![],
+ tags: vec![],
}
],
base_dir: PathBuf::from("examples"),
diff --git a/src/utils.rs b/src/utils.rs
@@ -2,17 +2,48 @@ extern crate expanduser;
use expanduser::expanduser;
use std::io::Result;
-use std::fs::{copy, create_dir_all};
+use std::fs;
+#[cfg(target_family = "unix")]
+use std::os::unix::fs::symlink;
+use std::path::PathBuf;
+
/// Copies the contents of a local file to another local file.
///
/// Tildes are expanded if present and the destination file is overwritten if
/// necessary.
pub fn copy_file(src: &str, dst: &str) -> Result<()> {
- let _dst = expanduser(dst)?;
- if let Some(path) = _dst.parent() {
- create_dir_all(path)?;
+ let _dst = prepare_path(dst)?;
+ fs::copy(src, _dst)?;
+ Ok(())
+}
+
+/// Creates a symbolic link to a local file.
+///
+/// Tildes are expanded if present and the destination file is overwritten if
+/// necessary. On non-Unix platforms, a hard link will be created instead.
+pub fn link_file(src: &str, dst: &str) -> Result<()> {
+ let _dst = prepare_path(dst)?;
+
+ if cfg!(target_family = "unix") {
+ symlink(fs::canonicalize(src)?, _dst)?;
+ } else {
+ fs::hard_link(src, _dst)?;
}
- copy(src, _dst)?;
+
Ok(())
}
+
+/// Create the parent directories of a path and return the path with tildes
+/// expanded.
+fn prepare_path(path: &str) -> Result<PathBuf> {
+ let _dst = expanduser(path)?;
+ if let Some(_path) = _dst.parent() {
+ fs::create_dir_all(_path)?;
+ }
+ if fs::symlink_metadata(&_dst).is_ok() {
+ // Check for existing files, including broken symlinks
+ fs::remove_file(&_dst)?;
+ }
+ Ok(_dst)
+}