use clap::{Arg, Command};
use color_eyre::Result;
use color_eyre::eyre::eyre;
use std::path::Path;
use pathdiff;
use crate::lfs;
pub struct LnOptions {
pub target: String,
pub link_name: String,
pub symbolic: bool,
pub force: bool,
pub relative: bool,
pub no_dereference: bool,
pub verbose: bool,
}
pub fn parse_options(matches: &clap::ArgMatches) -> Result<LnOptions> {
let symbolic = matches.get_flag("symbolic");
let force = matches.get_flag("force");
let relative = matches.get_flag("relative");
let no_dereference = matches.get_flag("no-dereference");
let verbose = matches.get_flag("verbose");
let args: Vec<String> = matches.get_many::<String>("args")
.map(|vals| vals.cloned().collect())
.unwrap_or_default();
if args.len() != 2 {
return Err(eyre!("ln: incorrect number of arguments"));
}
let target = args[0].clone();
let link_name = args[1].clone();
Ok(LnOptions {
target,
link_name,
symbolic,
force,
relative,
no_dereference,
verbose,
})
}
pub fn command() -> Command {
Command::new("ln")
.about("Create links")
.arg(Arg::new("symbolic")
.short('s')
.long("symbolic")
.help("Create symbolic links instead of hard links")
.action(clap::ArgAction::SetTrue))
.arg(Arg::new("force")
.short('f')
.long("force")
.help("Remove existing destination files")
.action(clap::ArgAction::SetTrue))
.arg(Arg::new("relative")
.short('r')
.long("relative")
.help("Create symbolic links relative to link location")
.action(clap::ArgAction::SetTrue))
.arg(Arg::new("no-dereference")
.short('n')
.long("no-dereference")
.help("Treat LINK_NAME as a normal file if it is a symbolic link to a directory")
.action(clap::ArgAction::SetTrue))
.arg(Arg::new("verbose")
.short('v')
.long("verbose")
.help("Print name of each linked file")
.action(clap::ArgAction::SetTrue))
.arg(Arg::new("args")
.num_args(2)
.help("Target and link name")
.required(true))
}
pub fn run(options: LnOptions) -> Result<()> {
let target_path = Path::new(&options.target);
let mut link_path = Path::new(&options.link_name).to_path_buf();
let is_existing_dir = link_path.is_dir() &&
(!options.no_dereference || !link_path.is_symlink());
if is_existing_dir {
let basename = target_path.file_name()
.ok_or_else(|| eyre!("ln: cannot derive link name from target '{}'", options.target))?;
link_path = link_path.join(basename);
}
if options.force {
if let Ok(metadata) = link_path.symlink_metadata() {
if metadata.file_type().is_symlink() || !metadata.file_type().is_dir() {
lfs::remove_file(&link_path)?;
} else {
lfs::remove_dir_all(&link_path)?;
}
}
}
if options.no_dereference && link_path.is_symlink() {
if link_path.symlink_metadata().is_ok() {
lfs::remove_file(&link_path)?;
}
}
let actual_target = if options.symbolic && options.relative {
let link_dir = link_path.parent().unwrap_or(Path::new("."));
pathdiff::diff_paths(target_path, link_dir)
.unwrap_or_else(|| target_path.to_path_buf())
} else {
target_path.to_path_buf()
};
if options.symbolic {
lfs::symlink_for_native(&actual_target, &link_path)?;
} else {
lfs::hard_link(&actual_target, &link_path)?;
}
if options.verbose {
println!("{} -> {}", link_path.display(), actual_target.display());
}
Ok(())
}