diff options
| author | [object Object] <max@bossi.ng> | 2025-07-16 11:51:18 +0200 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-16 11:51:18 +0200 | 
| commit | b284510c1b66c9b830883d04097239756694221c (patch) | |
| tree | d96c3975d78c4cf2ba0203e89d8d3bf8d56155dc /src | |
| parent | c40e7f89ecacce4fccf3403124f187f64ce7131b (diff) | |
feat!: dots-unlinking and ux changes
* start of unlink implementation
* refactor: move dot logic out of main mod
* feat: finish implementing unlinking of dots
* chore: add test files to gitignore
* chore: "use" cleanup
* chore: add explanation and additional info to cli
* chore: add crate description
* chore: bump version to 2.0.0
Diffstat (limited to 'src')
| -rw-r--r-- | src/cli.rs | 20 | ||||
| -rw-r--r-- | src/dots.rs | 48 | ||||
| -rw-r--r-- | src/main.rs | 52 | 
3 files changed, 81 insertions, 39 deletions
| diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..f43bda4 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,20 @@ +use std::path::PathBuf; +use clap::{Parser, Subcommand}; + +#[derive(Parser)] +#[clap(author = "Max Bossing", version, about = "System-agnostic dotfile deployer", long_about = None)] +pub struct Cli { +    #[arg(short, long, default_value = "dots.toml")] +    pub config: Option<PathBuf>, +     +    #[command(subcommand)] +    pub command: CliCommand  +} + +#[derive(Subcommand)] +pub enum CliCommand { +    #[clap(about = "Deploys a dots set")] +    Deploy, +    #[clap(about = "Unlinks (tries to remove) a dots deployment")] +    Unlink +}
\ No newline at end of file diff --git a/src/dots.rs b/src/dots.rs new file mode 100644 index 0000000..5b75254 --- /dev/null +++ b/src/dots.rs @@ -0,0 +1,48 @@ +use std::fs::{remove_file, symlink_metadata}; +use std::os::unix::fs::symlink; +use std::path::PathBuf; +use dirs::home_dir; +use crate::config::Dot; + +pub fn deploy_dots(dots: Vec<Dot>, 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!("linking from {} to {}", dot.source.display(), dot.destination.display()); +        let _ = symlink(&dot.source, &dot.destination).map_err(|err| +            eprintln!("failed to symlink: {}", err.to_string()) +        ); +    } +} + +pub fn unlink_dots(dots: Vec<Dot>, 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()); +        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| +                eprintln!("failed to remove symlink: {}", err) +            ); +        } +    } +} + +fn prepend_user_dir(path: &PathBuf) -> PathBuf { +    home_dir().unwrap().join(path) +}
\ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 8fc22c0..c2c103d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,30 +1,17 @@ -use std::env; -use std::os::unix::fs::symlink; -use std::path::PathBuf;  use std::process::exit; -use dirs::{home_dir}; -use crate::config::{Config, ConfigLoadError, Dot}; +use clap::Parser; +use crate::cli::{Cli, CliCommand}; +use crate::config::{Config, ConfigLoadError}; +use crate::dots::{deploy_dots, unlink_dots};  mod config; +mod cli; +mod dots;  fn main() { -    let args = env::args().skip(1).collect::<Vec<String>>(); - -    if args.len() > 1 { -        eprintln!("usage: {} [config]", args[0]); -        exit(1); -    } - -    if args.len() == 1 && (args[0] == "-h" || args[0] == "--help") { -        println!("Usage: {} [config]", args[0]); -        println!("options:"); -        println!("  -h, --help        Print help information"); -        println!("By default, dots will look for a bummsdots.toml file in the current directory"); -        println!("This can be changed by passing the filename"); -        exit(0); -    } - -    let config = Config::load(args.get(0).map(|o| PathBuf::from(o))).map_err(|err| +    let cli = Cli::parse(); +     +    let config = Config::load(cli.config).map_err(|err|          match err {              ConfigLoadError::IOError(err) => {                  eprintln!("failed to load config file: {}", err); @@ -36,22 +23,9 @@ fn main() {              }          }      ).unwrap(); - -    let prepended_mappings = config.dots.iter().map(|m| -        Dot { -            source: config.dots_dir.join(&m.source), -            destination: prepend_user_dir(&m.destination) -        } -    ); - -    for dot in prepended_mappings { -        println!("linking from {} to {}", dot.source.display(), dot.destination.display()); -        let _ = symlink(&dot.source, &dot.destination).map_err(|err| -            eprintln!("failed to symlink: {}", err.to_string()) -        ); +     +    match cli.command { +        CliCommand::Deploy => deploy_dots(config.dots, config.dots_dir), +        CliCommand::Unlink => unlink_dots(config.dots, config.dots_dir),      } -} - -fn prepend_user_dir(path: &PathBuf) -> PathBuf { -    home_dir().unwrap().join(path)  }
\ No newline at end of file | 
