1use crate::{
2 common::{util, AbsolutePath, FormattedItem, FormattedItems},
3 verbose_println,
4};
5use derive_more::From;
6use failure::Fail;
7use std::{
8 fs,
9 io::{self, Write},
10 path::Path,
11};
12
13enum YN {
14 Yes,
15 No,
16}
17use YN::*;
18
19fn read_yes_or_no(prompt: &str) -> io::Result<YN> {
22 let mut buf = String::new();
23 loop {
24 print!("{} (y/n) ", prompt);
25 io::stdout().flush()?;
26
27 io::stdin().read_line(&mut buf)?;
28 buf = buf.trim().to_lowercase();
29
30 if buf.is_empty() {
31 continue;
32 }
33
34 if buf.starts_with("yes") || "yes".starts_with(&buf) {
35 return Ok(Yes);
36 } else if buf.starts_with("no") || "no".starts_with(&buf) {
37 return Ok(No);
38 } else {
39 buf.clear();
40 continue;
41 }
42 }
43}
44
45#[cfg(unix)]
46fn symlink(source: impl AsRef<Path>, dest: impl AsRef<Path>) -> io::Result<()> {
47 std::os::unix::fs::symlink(source, dest)
48}
49
50fn link_item(item: &FormattedItem, dry_run: bool) -> Result<(), Error> {
51 let (source, dest) = (item.source(), item.dest());
52
53 let link = |item: &FormattedItem| -> Result<(), Error> {
56 verbose_println!("Linking {}", item);
57
58 if !dry_run {
59 fs::create_dir_all(dest.parent().unwrap_or(dest))?;
60 symlink(source, dest)?;
61 }
62
63 Ok(())
64 };
65
66 if !dest.exists() {
67 link(item)?
68 } else {
69 match fs::read_link(dest) {
70 Ok(ref target) if target.as_path() == source.as_path() => {
72 verbose_println!("Skipping identical {}", dest)
73 },
74 _ => {
76 let prompt = format!("Overwrite {}?", dest);
77 match read_yes_or_no(&prompt)? {
78 No => println!("Skipping {}", dest),
79 Yes => {
80 match util::file_type(dest)? {
81 util::FileType::File | util::FileType::Symlink => {
82 fs::remove_file(dest)?
83 },
84 util::FileType::Directory => {
90 return Err(DirectoryOverwrite(dest.clone()))
91 },
92 };
93 link(item)?;
94 },
95 }
96 },
97 }
98 }
99
100 Ok(())
101}
102
103pub fn link_items(items: FormattedItems, dry_run: bool) -> Result<(), Error> {
104 for item in &items {
105 link_item(item, dry_run)?;
106 }
107
108 Ok(())
109}
110
111#[derive(Debug, From, Fail)]
112pub enum Error {
113 #[fail(display = "error creating symlinks ({})", _0)]
114 IoError(#[fail(cause)] io::Error),
115
116 #[fail(
117 display = "won't delete directory {}. Please remove it manually if you want.",
118 _0
119 )]
120 DirectoryOverwrite(AbsolutePath),
121}
122use Error::*;