Skip to main content

simple_unc/
simple_unc.rs

1#![cfg_attr(not(target_os = "windows"), allow(unused))]
2#[cfg(windows)]
3use crate::{Drives, PathExt};
4use std::{
5    borrow::Cow,
6    fs,
7    path::{Path, PathBuf},
8};
9
10#[derive(Default)]
11pub struct SimpleUnc {
12    /// Map to the network share drive when possible.
13    /// ```
14    /// # use simple_unc::SimpleUnc;
15    /// let path = "file.txt";
16    /// let unc = SimpleUnc { map_to_drive: true, ..Default::default() };
17    /// let canonicalized = unc.canonicalize(path);
18    /// ```
19    /// If the `file.txt` is in a network drive,
20    /// the result should be `Z:\dir\file.txt`
21    /// instead of `\\server\share\dir\file.txt`.
22    pub map_to_drive: bool,
23
24    /// Skip the [`dunce`] crate simplification.
25    ///
26    /// [`dunce`]: https://crates.io/crates/dunce
27    pub skip_dunce: bool,
28}
29
30impl SimpleUnc {
31    pub fn canonicalize(&self, path: impl AsRef<Path>) -> anyhow::Result<PathBuf> {
32        let canonicalized = fs::canonicalize(path)?;
33        #[cfg(windows)]
34        if let Some(simplified) = self.simplify(&canonicalized)? {
35            return Ok(simplified.into_owned());
36        }
37        Ok(canonicalized)
38    }
39
40    pub fn simplify<'a>(&self, path: &'a Path) -> anyhow::Result<Option<Cow<'a, Path>>> {
41        #[cfg(windows)]
42        {
43            // Try mapped network share drives.
44            if let Some(drive_path) = Drives::drive_path(path)? {
45                if self.map_to_drive {
46                    return Ok(Some(Cow::Owned(drive_path.to_path_buf())));
47                }
48                if let Some(stripped) = path.strip_win32_file_namespace_unc() {
49                    return Ok(Some(Cow::Owned(stripped)));
50                }
51            }
52
53            if !self.skip_dunce {
54                // Try `dunce::simplified`.
55                let simplified = dunce::simplified(path);
56                if !std::ptr::eq(path, simplified) {
57                    return Ok(Some(Cow::Borrowed(simplified)));
58                }
59            }
60        }
61        Ok(None)
62    }
63
64    /// Refreshes the cached information.
65    pub fn refresh() -> anyhow::Result<()> {
66        #[cfg(windows)]
67        Drives::refresh()?;
68        Ok(())
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn simplify_none() {
78        let unc = SimpleUnc::default();
79        assert_eq!(unc.simplify(Path::new(r"C:\foo")).unwrap(), None);
80    }
81
82    #[cfg(windows)]
83    #[test]
84    fn simplify_dunce() {
85        let unc = SimpleUnc::default();
86        assert_eq!(
87            unc.simplify(Path::new(r"\\?\C:\foo")).unwrap(),
88            Some(Cow::Borrowed(Path::new(r"C:\foo")))
89        );
90    }
91
92    #[cfg(windows)]
93    #[test]
94    fn simplify_dunce_skip() {
95        let unc = SimpleUnc {
96            skip_dunce: true,
97            ..Default::default()
98        };
99        assert_eq!(unc.simplify(Path::new(r"\\?\C:\foo")).unwrap(), None);
100    }
101}