use std::path::{Path, PathBuf};
use indexmap::IndexMap;
use crate::{
dry_run_or_failure, dry_run_or_success,
model::{Link, Outcome},
};
pub(crate) struct LinkProcessor;
impl LinkProcessor {
pub(crate) fn new() -> LinkProcessor {
LinkProcessor {}
}
pub(crate) fn process(
&self,
approve: bool,
recipe_name: &String,
recipe_root: &Path,
link: &IndexMap<PathBuf, Link>,
) -> Vec<Outcome> {
let mut outcomes = vec![];
for (source, link) in link {
outcomes.extend(self.link(approve, recipe_name, recipe_root, source, &link.to));
}
outcomes
}
fn link(
&self,
approve: bool,
recipe_name: &String,
recipe_root: &Path,
source: &PathBuf,
to: &Path,
) -> Vec<Outcome> {
let mut outcomes = vec![];
let source_path = recipe_root.join(source);
if !source_path.exists() {
outcomes.push(dry_run_or_failure!(
approve,
format!("{recipe_name}/link/{}", source.display()),
format!("link {} to {}", source.display(), to.display())
));
}
let to_path = PathBuf::from(shellexpand::tilde(&to.display().to_string()).to_string());
if to_path.exists() {
outcomes.push(dry_run_or_failure!(
approve,
format!("{recipe_name}/link/{}", source.display()),
format!("source '{}' already exists", source.display())
));
}
if outcomes.is_empty() {
if approve {
match self.symlink(&source_path, &to_path) {
Ok(_) => {
outcomes.push(dry_run_or_success!(
approve,
format!("{recipe_name}/link/{}", source.display()),
format!(
"successfully linked '{}' to '{}'",
source.display(),
to_path.display()
)
));
}
Err(err) => {
outcomes.push(dry_run_or_failure!(
approve,
format!("{recipe_name}/link/{}", source.display()),
format!(
"link from '{}' to '{}' failed: {err}",
source.display(),
to_path.display()
)
));
}
}
} else {
println!(
"dry-run: link '{}' to '{}'",
source_path.display(),
to_path.display()
);
}
}
outcomes
}
#[cfg(windows)]
fn symlink(&self, source_path: &PathBuf, to: &PathBuf) -> anyhow::Result<()> {
std::os::windows::fs::symlink_file(source_path, to).map_err(anyhow::Error::from)
}
#[cfg(unix)]
fn symlink(&self, source_path: &PathBuf, to: &PathBuf) -> anyhow::Result<()> {
std::os::unix::fs::symlink(source_path, to).map_err(anyhow::Error::from)
}
}
#[cfg(test)]
mod tests {
use std::io::Write;
use tempfile::NamedTempFile;
use crate::model::Outcome;
use super::LinkProcessor;
use pretty_assertions::assert_eq;
#[test]
fn test_symlink() {
let link_processor = LinkProcessor::new();
let recipe_root = tempfile::tempdir().unwrap();
let mut npmrc = NamedTempFile::new_in(&recipe_root).unwrap();
write!(npmrc, "fkbr").unwrap();
let source = npmrc.path().to_path_buf();
let target_directory = tempfile::tempdir().unwrap();
let to = target_directory.path().join(".npmrc").to_path_buf();
let outcomes = link_processor.link(
true,
&"fkbr".to_string(),
&recipe_root.into_path(),
&source,
&to,
);
assert_eq!(to.exists(), true);
assert_eq!(outcomes.len(), 1);
if let Outcome::Failure { .. } = outcomes.first().unwrap() {
panic!("the outcome should be successful!");
}
}
}