1use anyhow::Result;
2use std::path::Path;
3use std::process::Command;
4
5pub enum BackupControl {
6 None,
7 Off,
8 Numbered,
9 T,
10 Existing,
11 Nil,
12 Simple,
13 Never,
14}
15
16#[derive(Default)]
17pub struct LnOptions<'a> {
18 backup: Option<BackupControl>,
19 b: bool,
20 directory: bool,
21 force: bool,
22 logical: bool,
23 no_dereference: bool,
24 physical: bool,
25 relative: bool,
26 symbolic: bool,
27 suffix: Option<&'a str>,
28 target_directory: Option<&'a str>,
29 no_target_directory: bool,
30}
31
32pub fn ln<'a>(
34 targets: &[impl AsRef<Path>],
35 destination: Option<impl AsRef<Path>>,
36 opts: Option<&LnOptions<'a>>,
37 workdir: Option<&str>,
38) -> Result<i32> {
39 let mut cmd = Command::new("/usr/bin/env");
40 cmd.arg("ln");
41
42 if let Some(o) = opts {
43 match o.backup {
44 Some(BackupControl::None) => {
45 cmd.arg("--backup=none");
46 }
47 Some(BackupControl::Off) => {
48 cmd.arg("--backup=off");
49 }
50 Some(BackupControl::Numbered) => {
51 cmd.arg("--backup=numbered");
52 }
53 Some(BackupControl::T) => {
54 cmd.arg("--backup=t");
55 }
56 Some(BackupControl::Existing) => {
57 cmd.arg("--backup=existing");
58 }
59 Some(BackupControl::Nil) => {
60 cmd.arg("--backup=nil");
61 }
62 Some(BackupControl::Simple) => {
63 cmd.arg("--backup=simple");
64 }
65 Some(BackupControl::Never) => {
66 cmd.arg("--backup=never");
67 }
68 None => {
69 if o.b {
70 cmd.arg("-b");
71 }
72 }
73 }
74 if o.directory {
75 cmd.arg("-d");
76 }
77 if o.force {
78 cmd.arg("-f");
79 }
80 if o.logical {
81 cmd.arg("-L");
82 }
83 if o.no_dereference {
84 cmd.arg("-n");
85 }
86 if o.physical {
87 cmd.arg("-P");
88 }
89 if o.relative {
90 cmd.arg("-r");
91 }
92 if o.symbolic {
93 cmd.arg("-s");
94 }
95 if let Some(s) = o.suffix {
96 cmd.args(&["-S", s]);
97 }
98 if let Some(d) = o.target_directory {
99 cmd.args(&["-t", d]);
100 }
101 if o.no_target_directory {
102 cmd.arg("-T");
103 }
104 }
105
106 cmd.args(targets.iter().map(|t| t.as_ref()));
107
108 if let Some(dest) = destination {
109 cmd.arg(dest.as_ref());
110 }
111
112 if let Some(dir) = workdir {
113 cmd.current_dir(&dir);
114 }
115
116 match cmd.status() {
117 Ok(status) => Ok(status.code().unwrap_or(-1)),
118 Err(err) => Err(err.into()),
119 }
120}
121
122pub fn force_symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<i32> {
124 let opts = LnOptions {
125 force: true,
126 symbolic: true,
127 ..LnOptions::default()
128 };
129 ln(&[src], Some(&dst), Some(&opts), None)
130}
131
132pub fn unlink(file: impl AsRef<Path>) -> Result<i32> {
134 let status = Command::new("/usr/bin/env")
135 .arg("unlink")
136 .arg(&file.as_ref())
137 .status();
138 match status {
139 Ok(status) => Ok(status.code().unwrap_or(-1)),
140 Err(err) => Err(err.into()),
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_link_and_unlink() {
150 assert_eq!(2 + 2, 4);
151 force_symlink("1.txt", "some.txt").unwrap();
152 force_symlink("2.txt", "some.txt").unwrap();
153 unlink("some.txt").unwrap();
154 }
155}