From 5b2e7756e808151b5487f7e67b53accc745bee07 Mon Sep 17 00:00:00 2001 From: "[object Object]" Date: Tue, 29 Jul 2025 16:52:39 +0200 Subject: merge develop into master * refactor: move platform-specifics into separate module * feat: Windows Support (wip) This is untested because I do not own Windows machines, and I think there are some problems concerning unlinking. * wip: inplementation of platform-agnostic deploying and removing etc... * feat: host-based deployment condition * feat: command flag to skip hostname checks * simplyfing the whole preparation step quite a bit --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + README.md | 9 ++++++++- src/cli.rs | 5 +++-- src/config.rs | 5 +++++ src/dots.rs | 46 ++++++++++++++++++++++++++++------------------ src/main.rs | 6 +++--- 7 files changed, 55 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 70f66c6..7e7b949 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -138,6 +138,7 @@ dependencies = [ "clap", "dirs", "serde", + "symlink", "toml", ] @@ -279,6 +280,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + +[[package]] name = "syn" version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml index bab7836..848d526 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,5 @@ edition = "2024" clap = { version = "4.5.41", features = ["derive"] } dirs = "6.0.0" serde = { version = "1.0.219", features = ["derive"] } +symlink = "0.1.0" toml = "0.9.2" diff --git a/README.md b/README.md index 3409e07..1367dd8 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ src = "nvim" # Resolved against dots_dir dest = ".config/nvim" # Relative paths are resolved against $HOME [[dot]] +host = "hermes" # You can specify on which hosts this dot should be deployed src = "Xmodmap" dest = "/etc/X11" # Absolute paths are respected @@ -51,10 +52,16 @@ If you want to use a different file (for example a different machine but based o dots deploy --config some_file.dots.toml ``` +If you want to skip the hostname check: +```bash +dots --no-hostname-check --deploy +``` + ## Todo - [x] Allow to "undeploy", eg. to automatically remove symlinks created by `dots` - [x] Absolute Paths in dest field -- [ ] Windows Support +- [x] Hostname condition for deployment +- [ ] Windows Support (should work in theory but I haven't tested it yet) ## License This is licensed under the [MIT](LICENSE) license. diff --git a/src/cli.rs b/src/cli.rs index f43bda4..bb0c621 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -6,7 +6,8 @@ use clap::{Parser, Subcommand}; pub struct Cli { #[arg(short, long, default_value = "dots.toml")] pub config: Option, - + #[arg(short, long)] + pub no_hostname_check: bool, #[command(subcommand)] pub command: CliCommand } @@ -17,4 +18,4 @@ pub enum CliCommand { Deploy, #[clap(about = "Unlinks (tries to remove) a dots deployment")] Unlink -} \ No newline at end of file +} diff --git a/src/config.rs b/src/config.rs index 6fed687..4be977b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,6 +16,8 @@ pub struct Dot { pub source: PathBuf, #[serde(rename = "dest")] pub destination: PathBuf, + #[serde(default = "default_host")] + pub host: String, } pub enum ConfigLoadError { @@ -42,3 +44,6 @@ fn default_dots_dir() -> PathBuf { env::current_dir().expect("failed to get current dir") } +fn default_host() -> String { + "*".to_string() +} diff --git a/src/dots.rs b/src/dots.rs index 0259c0c..29059a1 100644 --- a/src/dots.rs +++ b/src/dots.rs @@ -1,42 +1,52 @@ -use std::fs::{remove_file, symlink_metadata}; -use std::os::unix::fs::symlink; +use std::process::Command; +use std::fs::symlink_metadata; use std::path::PathBuf; use dirs::home_dir; use crate::config::Dot; +use symlink::{symlink_auto, remove_symlink_auto}; -pub fn deploy_dots(dots: Vec, dots_dir: PathBuf) { - let prepended_dots = dots.iter().map(|m| + +fn prepare_dots(dots: Vec, dots_dir: PathBuf, no_hostname_check: bool) -> Vec { + let mut hostname = "*".to_string(); + if !no_hostname_check { + // Deeply saddened there is no better way to do this :( + let hostname_cmd = Command::new("hostname").output().expect("failed to get hostname: command \"hostname\" failed"); + let hostname_stdout = hostname_cmd.stdout; + let mut new_hostname = String::from_utf8(hostname_stdout.clone()).expect("failed to get hostname: unknown hostname encoding"); + new_hostname.pop(); // stdout contains a newline + hostname = new_hostname; + } + + let prepared_dots = dots.iter().filter(|dot| if no_hostname_check { true } else { dot.host == "*" || dot.host == hostname }).map(|m| Dot { source: dots_dir.join(&m.source), - destination: if m.destination.is_absolute() { m.destination.clone() } else { prepend_user_dir(&m.destination) } + destination: if m.destination.is_absolute() { m.destination.clone() } else { prepend_user_dir(&m.destination) }, + host: m.host.clone() } ); - for dot in prepended_dots { + prepared_dots.collect() +} + +pub fn deploy_dots(dots: Vec, dots_dir: PathBuf, no_hostname_check: bool) { + for dot in prepare_dots(dots, dots_dir, no_hostname_check) { println!("linking from {} to {}", dot.source.display(), dot.destination.display()); - let _ = symlink(&dot.source, &dot.destination).map_err(|err| + let _ = symlink_auto(&dot.source, &dot.destination).map_err(|err| eprintln!("failed to symlink: {}", err.to_string()) ); } } -pub fn unlink_dots(dots: Vec, dots_dir: PathBuf) { - let prepended_dots = dots.iter().map(|m| - Dot { - source: dots_dir.join(&m.source), - destination: prepend_user_dir(&m.destination) - } - ); - - for dot in prepended_dots { - println!("unlinking {}", dot.destination.display()); +pub fn unlink_dots(dots: Vec, dots_dir: PathBuf, no_hostname_check: bool) { + for dot in prepare_dots(dots, dots_dir, no_hostname_check) { + println!("unlinking {}", dot.destination.display()); let metadata = symlink_metadata(dot.destination.clone()); if metadata.is_err() { eprintln!("failed to query metadata for {}: {}", dot.destination.display(), metadata.err().unwrap()); continue } if metadata.ok().unwrap().is_symlink() { - let _ = remove_file(dot.destination).map_err(|err| + let _ = remove_symlink_auto(dot.destination).map_err(|err| eprintln!("failed to remove symlink: {}", err) ); } diff --git a/src/main.rs b/src/main.rs index c2c103d..e07ff28 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,7 @@ fn main() { ).unwrap(); match cli.command { - CliCommand::Deploy => deploy_dots(config.dots, config.dots_dir), - CliCommand::Unlink => unlink_dots(config.dots, config.dots_dir), + CliCommand::Deploy => deploy_dots(config.dots, config.dots_dir, cli.no_hostname_check), + CliCommand::Unlink => unlink_dots(config.dots, config.dots_dir, cli.no_hostname_check), } -} \ No newline at end of file +} -- cgit v1.0