d248f7d17b
derive(Default) from applets
130 lines
4.0 KiB
Rust
130 lines
4.0 KiB
Rust
use super::Cmd;
|
|
use clap::{Arg, ArgAction, Command};
|
|
use std::{error::Error, fs, io, os, path::PathBuf};
|
|
|
|
#[derive(Debug)]
|
|
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<dyn std::error::Error>> {
|
|
let files = matches
|
|
.get_many::<String>("file")
|
|
.unwrap()
|
|
.collect::<Vec<_>>();
|
|
let dest = matches.get_one::<String>("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<shitbox::Path> {
|
|
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<dyn Error>> {
|
|
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(())
|
|
}
|