1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use std::ffi::OsStr;
use std::path::{Path, PathBuf};

pub trait PathExt {
    fn to_slash(&self) -> Option<String>;
    fn to_slash_lossy(&self) -> String;
}

impl PathExt for Path {
    #[cfg(not(target_os = "windows"))]
    fn to_slash_lossy(&self) -> String {
        self.to_string_lossy().to_string()
    }

    #[cfg(target_os = "windows")]
    fn to_slash_lossy(&self) -> String {
        use std::path;

        let mut buf = String::new();
        for c in self.components() {
            match c {
                path::Component::RootDir => { /* empty */ }
                path::Component::CurDir => buf.push('.'),
                path::Component::ParentDir => buf.push_str(".."),
                path::Component::Prefix(ref prefix) => {
                    let s = prefix.as_os_str();
                    match s.to_str() {
                        Some(ref s) => buf.push_str(s),
                        None => buf.push_str(&s.to_string_lossy()),
                    }
                }
                path::Component::Normal(ref s) => match s.to_str() {
                    Some(ref s) => buf.push_str(s),
                    None => buf.push_str(&s.to_string_lossy()),
                },
            }
            buf.push('/');
        }

        if buf != "/" {
            buf.pop(); // Pop last '/'
        }

        buf
    }

    #[cfg(not(target_os = "windows"))]
    fn to_slash(&self) -> Option<String> {
        self.to_str().map(str::to_string)
    }

    #[cfg(target_os = "windows")]
    fn to_slash(&self) -> Option<String> {
        use std::path;
        let components = self
            .components()
            .map(|c| match c {
                path::Component::RootDir => Some(""),
                path::Component::CurDir => Some("."),
                path::Component::ParentDir => Some(".."),
                path::Component::Prefix(ref p) => p.as_os_str().to_str(),
                path::Component::Normal(ref s) => s.to_str(),
            })
            .collect::<Option<Vec<_>>>();

        components.map(|v| {
            if v.len() == 1 && v[0].is_empty() {
                // Special case for '/'
                "/".to_string()
            } else {
                v.join("/")
            }
        })
    }
}

pub trait PathBufExt {
    fn from_slash<S: AsRef<str>>(s: S) -> Self;
    fn from_slash_lossy<S: AsRef<OsStr>>(s: S) -> Self;
    fn to_slash(&self) -> Option<String>;
    fn to_slash_lossy(&self) -> String;
}

impl PathBufExt for PathBuf {
    #[cfg(not(target_os = "windows"))]
    fn from_slash<S: AsRef<str>>(s: S) -> Self {
        PathBuf::from(s.as_ref())
    }

    #[cfg(not(target_os = "windows"))]
    fn from_slash_lossy<S: AsRef<OsStr>>(s: S) -> Self {
        PathBuf::from(s.as_ref())
    }

    #[cfg(target_os = "windows")]
    fn from_slash<S: AsRef<str>>(s: S) -> Self {
        use std::path;

        let s = s
            .as_ref()
            .chars()
            .map(|c| match c {
                '/' => path::MAIN_SEPARATOR,
                c => c,
            })
            .collect::<String>();
        PathBuf::from(s)
    }

    #[cfg(target_os = "windows")]
    fn from_slash_lossy<S: AsRef<OsStr>>(s: S) -> Self {
        Self::from_slash(s.as_ref().to_string_lossy().chars().as_str())
    }

    fn to_slash_lossy(&self) -> String {
        self.as_path().to_slash_lossy()
    }

    fn to_slash(&self) -> Option<String> {
        self.as_path().to_slash()
    }
}

#[cfg(test)]
#[macro_use]
extern crate lazy_static;

#[cfg(test)]
mod test;