mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
ln: Improve to LnError and make public fns for nushell (#12009)
* ln: Improve to LnError and make public fns for nushell * Fix lints for Windows/wasm * Add comment explaining pub visibility * Rename Err Variants
This commit is contained in:
+75
-41
@@ -6,9 +6,10 @@
|
||||
// spell-checker:ignore (ToDO) srcpath targetpath EEXIST
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use std::io::{Write, stdout};
|
||||
use std::io::{self, Write, stdout};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UError, UResult};
|
||||
use uucore::error::{UError, UIoError, UResult};
|
||||
|
||||
use uucore::fs::{make_path_relative_to, paths_refer_to_same_file};
|
||||
use uucore::translate;
|
||||
use uucore::{format_usage, prompt_yes, show_error};
|
||||
@@ -27,17 +28,19 @@ use std::path::{Path, PathBuf};
|
||||
use uucore::backup_control::{self, BackupMode};
|
||||
use uucore::fs::{MissingHandling, ResolveMode, canonicalize};
|
||||
|
||||
/// Public visibility allows other apps to integrate with our
|
||||
/// `ln` utility by calling `exec` directly with their `Settings`.
|
||||
pub struct Settings {
|
||||
overwrite: OverwriteMode,
|
||||
backup: BackupMode,
|
||||
suffix: OsString,
|
||||
symbolic: bool,
|
||||
relative: bool,
|
||||
logical: bool,
|
||||
target_dir: Option<PathBuf>,
|
||||
no_target_dir: bool,
|
||||
no_dereference: bool,
|
||||
verbose: bool,
|
||||
pub overwrite: OverwriteMode,
|
||||
pub backup: BackupMode,
|
||||
pub suffix: OsString,
|
||||
pub symbolic: bool,
|
||||
pub relative: bool,
|
||||
pub logical: bool,
|
||||
pub target_dir: Option<PathBuf>,
|
||||
pub no_target_dir: bool,
|
||||
pub no_dereference: bool,
|
||||
pub verbose: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
@@ -48,10 +51,16 @@ pub enum OverwriteMode {
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum LnError {
|
||||
pub enum LnError {
|
||||
#[error("{}", translate!("ln-error-target-is-not-directory", "target" => _0.quote()))]
|
||||
TargetIsNotADirectory(PathBuf),
|
||||
|
||||
#[error("{0}")]
|
||||
Io(#[from] UIoError),
|
||||
|
||||
#[error("{1}: {0}")]
|
||||
IoContext(UIoError, String),
|
||||
|
||||
#[error("")]
|
||||
SomeLinksFailed,
|
||||
|
||||
@@ -73,6 +82,13 @@ impl UError for LnError {
|
||||
1
|
||||
}
|
||||
}
|
||||
pub type LnResult<T> = Result<T, LnError>;
|
||||
|
||||
impl From<io::Error> for LnError {
|
||||
fn from(err: io::Error) -> Self {
|
||||
Self::Io(UIoError::from(err))
|
||||
}
|
||||
}
|
||||
|
||||
mod options {
|
||||
pub const FORCE: &str = "force";
|
||||
@@ -133,7 +149,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
verbose: matches.get_flag(options::VERBOSE),
|
||||
};
|
||||
|
||||
exec(&paths[..], &settings)
|
||||
exec(&paths[..], &settings)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn uu_app() -> Command {
|
||||
@@ -249,7 +266,10 @@ pub fn uu_app() -> Command {
|
||||
)
|
||||
}
|
||||
|
||||
fn exec(files: &[PathBuf], settings: &Settings) -> UResult<()> {
|
||||
/// Executes the `ln` utility with the given paths and settings.
|
||||
///
|
||||
/// This is made public to allow other apps to use `ln` as a library.
|
||||
pub fn exec(files: &[PathBuf], settings: &Settings) -> LnResult<()> {
|
||||
// Handle cases where we create links in a directory first.
|
||||
if let Some(ref target_path) = settings.target_dir {
|
||||
// 4th form: a directory is specified by -t.
|
||||
@@ -270,14 +290,13 @@ fn exec(files: &[PathBuf], settings: &Settings) -> UResult<()> {
|
||||
// 1st form. Now there should be only two operands, but if -T is
|
||||
// specified we may have a wrong number of operands.
|
||||
if files.len() == 1 {
|
||||
return Err(LnError::MissingDestination(files[0].clone()).into());
|
||||
return Err(LnError::MissingDestination(files[0].clone()));
|
||||
}
|
||||
if files.len() > 2 {
|
||||
return Err(LnError::ExtraOperand(
|
||||
files[2].clone().into(),
|
||||
uucore::execution_phrase().to_string(),
|
||||
)
|
||||
.into());
|
||||
));
|
||||
}
|
||||
assert!(!files.is_empty());
|
||||
|
||||
@@ -285,9 +304,9 @@ fn exec(files: &[PathBuf], settings: &Settings) -> UResult<()> {
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) -> UResult<()> {
|
||||
fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) -> LnResult<()> {
|
||||
if !target_dir.is_dir() {
|
||||
return Err(LnError::TargetIsNotADirectory(target_dir.to_owned()).into());
|
||||
return Err(LnError::TargetIsNotADirectory(target_dir.to_owned()));
|
||||
}
|
||||
// remember the linked destinations for further usage
|
||||
let mut linked_destinations: HashSet<PathBuf> = HashSet::with_capacity(files.len());
|
||||
@@ -369,7 +388,7 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
|
||||
if all_successful {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(LnError::SomeLinksFailed.into())
|
||||
Err(LnError::SomeLinksFailed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,7 +406,7 @@ fn relative_path<'a>(src: &'a Path, dst: &Path) -> Cow<'a, Path> {
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn link(src: &Path, dst: &Path, settings: &Settings) -> UResult<()> {
|
||||
fn link(src: &Path, dst: &Path, settings: &Settings) -> LnResult<()> {
|
||||
let mut backup_path = None;
|
||||
let source: Cow<'_, Path> = if settings.relative {
|
||||
relative_path(src, dst)
|
||||
@@ -400,18 +419,22 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> UResult<()> {
|
||||
if settings.backup == BackupMode::Existing && !settings.symbolic {
|
||||
// when ln --backup f f, it should detect that it is the same file
|
||||
if paths_refer_to_same_file(src, dst, true) {
|
||||
return Err(LnError::SameFile(src.to_owned(), dst.to_owned()).into());
|
||||
return Err(LnError::SameFile(src.to_owned(), dst.to_owned()));
|
||||
}
|
||||
}
|
||||
if let Some(ref p) = backup_path {
|
||||
fs::rename(dst, p)
|
||||
.map_err_context(|| translate!("ln-cannot-backup", "file" => dst.quote()))?;
|
||||
fs::rename(dst, p).map_err(|e| {
|
||||
LnError::IoContext(
|
||||
UIoError::from(e),
|
||||
translate!("ln-cannot-backup", "file" => dst.quote()),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
match settings.overwrite {
|
||||
OverwriteMode::NoClobber => {}
|
||||
OverwriteMode::Interactive => {
|
||||
if !prompt_yes!("{}", translate!("ln-prompt-replace", "file" => dst.quote())) {
|
||||
return Err(LnError::SomeLinksFailed.into());
|
||||
return Err(LnError::SomeLinksFailed);
|
||||
}
|
||||
|
||||
let _ = fs::remove_file(dst);
|
||||
@@ -428,7 +451,7 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> UResult<()> {
|
||||
_ => true,
|
||||
};
|
||||
if same_entry {
|
||||
return Err(LnError::SameFile(src.to_owned(), dst.to_owned()).into());
|
||||
return Err(LnError::SameFile(src.to_owned(), dst.to_owned()));
|
||||
}
|
||||
}
|
||||
let _ = fs::remove_file(dst);
|
||||
@@ -441,26 +464,37 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> UResult<()> {
|
||||
symlink(&source, dst).map_err(Into::into)
|
||||
} else {
|
||||
let p = if settings.logical && source.is_symlink() {
|
||||
fs::canonicalize(&source)
|
||||
.map_err_context(|| translate!("ln-failed-to-access", "file" => source.quote()))?
|
||||
fs::canonicalize(&source).map_err(|e| {
|
||||
LnError::IoContext(
|
||||
UIoError::from(e),
|
||||
translate!("ln-failed-to-access", "file" => source.quote()),
|
||||
)
|
||||
})?
|
||||
} else {
|
||||
source.to_path_buf()
|
||||
};
|
||||
match fs::hard_link(&p, dst) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(_) if p.is_dir() => {
|
||||
Err(LnError::FailedToCreateHardLinkDir(source.to_path_buf()).into())
|
||||
}
|
||||
Err(e) => Err(e).map_err_context(|| {
|
||||
translate!("ln-failed-to-create-hard-link", "source" => source.quote(), "dest" => dst.quote())
|
||||
}),
|
||||
Err(_) if p.is_dir() => Err(LnError::FailedToCreateHardLinkDir(source.to_path_buf())),
|
||||
Err(e) => Err(LnError::IoContext(
|
||||
UIoError::from(e),
|
||||
translate!(
|
||||
"ln-failed-to-create-hard-link",
|
||||
"source" => source.quote(),
|
||||
"dest" => dst.quote()
|
||||
),
|
||||
)),
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = res {
|
||||
if let Some(ref p) = backup_path {
|
||||
fs::rename(p, dst)
|
||||
.map_err_context(|| translate!("ln-cannot-backup", "file" => dst.quote()))?;
|
||||
fs::rename(p, dst).map_err(|e| {
|
||||
LnError::IoContext(
|
||||
UIoError::from(e),
|
||||
translate!("ln-cannot-backup", "file" => dst.quote()),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
@@ -481,7 +515,7 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> UResult<()> {
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn symlink<P1: AsRef<Path>, P2: AsRef<Path>>(src: P1, dst: P2) -> std::io::Result<()> {
|
||||
pub fn symlink<P1: AsRef<Path>, P2: AsRef<Path>>(src: P1, dst: P2) -> io::Result<()> {
|
||||
if src.as_ref().is_dir() {
|
||||
symlink_dir(src, dst)
|
||||
} else {
|
||||
@@ -490,9 +524,9 @@ pub fn symlink<P1: AsRef<Path>, P2: AsRef<Path>>(src: P1, dst: P2) -> std::io::R
|
||||
}
|
||||
|
||||
#[cfg(target_os = "wasi")]
|
||||
fn symlink<P1: AsRef<Path>, P2: AsRef<Path>>(_src: P1, _dst: P2) -> std::io::Result<()> {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Unsupported,
|
||||
fn symlink<P1: AsRef<Path>, P2: AsRef<Path>>(_src: P1, _dst: P2) -> io::Result<()> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
"symlinks not supported on this platform",
|
||||
))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user