gnu_ln/
lib.rs

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
32/// Make links between files
33pub 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
122/// Simple make symlink for file
123pub 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
132/// Call the unlink function to remove the specified file
133pub 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}