use super::Cmd; use clap::{Arg, ArgAction, Command}; use std::{error::Error, fs, io, os, path::PathBuf}; #[derive(Debug, Default)] pub struct Ln; impl Cmd for Ln { fn cli(&self) -> clap::Command { Command::new("ln") .about("make links between files") .author("Nathan Fisher") .version(env!("CARGO_PKG_VERSION")) .args([ Arg::new("force") .help("remove existing destination files") .short('f') .long("force") .action(ArgAction::SetTrue), Arg::new("symbolic") .help("make symbolic links instead of hard links") .short('s') .long("symbolic") .action(ArgAction::SetTrue), Arg::new("verbose") .help("print name of each linked file") .short('v') .long("verbose") .action(ArgAction::SetTrue), Arg::new("to-target") .help( "For each source_file operand that names a file of type \ symbolic link, create a (hard) link to the file referenced \ by the symbolic link.", ) .short('L') .overrides_with("to-link") .action(ArgAction::SetTrue), Arg::new("to-link") .help( "For each source_file operand that names a file of type \ symbolic link, create a (hard) link to the symbolic link itself.", ) .short('P') .conflicts_with("to-target") .action(ArgAction::SetTrue), Arg::new("file") .value_name("source") .num_args(1..) .required(true), Arg::new("dest").num_args(1).required(true), ]) } fn run(&self, matches: &clap::ArgMatches) -> Result<(), Box> { let files = matches .get_many::("file") .unwrap() .collect::>(); let dest = matches.get_one::("dest").unwrap(); let dest = PathBuf::from(dest); let sym_target = if matches.get_flag("to-target") { SymTarget::ToTarget } else { SymTarget::ToLink }; if files.len() > 1 && !dest.is_dir() { return Err( io::Error::new(io::ErrorKind::Other, "destination must be a directory").into(), ); } for f in files { let source = PathBuf::from(f); do_link( source, dest.clone(), sym_target, matches.get_flag("force"), matches.get_flag("symbolic"), matches.get_flag("verbose"), )?; } Ok(()) } fn path(&self) -> Option { Some(shitbox::Path::Bin) } } #[derive(Clone, Copy, PartialEq)] enum SymTarget { ToTarget, ToLink, } fn do_link( source: PathBuf, mut dest: PathBuf, sym_target: SymTarget, force: bool, symbolic: bool, verbose: bool, ) -> Result<(), Box> { let source = if source.is_symlink() && sym_target == SymTarget::ToTarget { fs::canonicalize(&source)? } else { source }; if dest.is_dir() { let name = source.file_name().unwrap(); dest.push(name); } if dest.exists() && force { unistd::unlink( dest.to_str() .ok_or(io::Error::new(io::ErrorKind::Other, "invalid utf8"))?, )?; } if symbolic { os::unix::fs::symlink(&source, &dest)?; } else { fs::hard_link(&source, &dest)?; } if verbose { println!("{} -> {}", dest.display(), source.display()); } Ok(()) }