Skip to main content

lechange_core/platform/
mod.rs

1//! Platform-specific utilities
2
3use std::path::MAIN_SEPARATOR;
4
5/// Platform-aware path utilities with zero allocation where possible
6pub struct PathUtil;
7
8impl PathUtil {
9    /// Normalize path separator for current platform
10    #[inline]
11    pub fn normalize_separator(path: &str) -> String {
12        if cfg!(windows) {
13            path.replace('/', "\\")
14        } else {
15            path.replace('\\', "/")
16        }
17    }
18
19    /// Get platform-specific separator
20    #[inline]
21    pub const fn separator() -> char {
22        MAIN_SEPARATOR
23    }
24
25    /// Check if path contains any separator
26    #[inline]
27    pub fn has_separator(path: &str) -> bool {
28        path.contains('/') || path.contains('\\')
29    }
30
31    /// Split path by any separator (zero-copy iterator)
32    #[inline]
33    pub fn components(path: &str) -> impl Iterator<Item = &str> {
34        path.split(['/', '\\']).filter(|s| !s.is_empty())
35    }
36
37    /// Convert path to POSIX format (forward slashes)
38    ///
39    /// No-op on Unix (returns borrowed), replaces `\` on Windows.
40    #[inline]
41    pub fn to_posix(path: &str) -> std::borrow::Cow<'_, str> {
42        if cfg!(windows) || path.contains('\\') {
43            std::borrow::Cow::Owned(path.replace('\\', "/"))
44        } else {
45            std::borrow::Cow::Borrowed(path)
46        }
47    }
48
49    /// Apply separator to path based on config
50    #[inline]
51    pub fn with_separator(path: &str, use_posix: bool) -> std::borrow::Cow<'_, str> {
52        if use_posix {
53            Self::to_posix(path)
54        } else {
55            std::borrow::Cow::Borrowed(path)
56        }
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn test_has_separator() {
66        assert!(PathUtil::has_separator("foo/bar"));
67        assert!(PathUtil::has_separator("foo\\bar"));
68        assert!(!PathUtil::has_separator("foo"));
69    }
70
71    #[test]
72    fn test_components() {
73        let parts: Vec<&str> = PathUtil::components("foo/bar/baz").collect();
74        assert_eq!(parts, vec!["foo", "bar", "baz"]);
75
76        let parts: Vec<&str> = PathUtil::components("foo\\bar\\baz").collect();
77        assert_eq!(parts, vec!["foo", "bar", "baz"]);
78    }
79
80    #[test]
81    fn test_normalize_separator() {
82        // On non-windows, backslashes become forward slashes
83        if !cfg!(windows) {
84            assert_eq!(PathUtil::normalize_separator("foo\\bar"), "foo/bar");
85            assert_eq!(PathUtil::normalize_separator("foo/bar"), "foo/bar");
86        }
87    }
88
89    #[test]
90    fn test_to_posix() {
91        let result = PathUtil::to_posix("foo\\bar\\baz");
92        assert_eq!(result.as_ref(), "foo/bar/baz");
93
94        // Already POSIX on non-windows: should be Cow::Borrowed (no allocation)
95        if !cfg!(windows) {
96            let result = PathUtil::to_posix("foo/bar/baz");
97            assert!(matches!(result, std::borrow::Cow::Borrowed(_)));
98            assert_eq!(result.as_ref(), "foo/bar/baz");
99        }
100    }
101
102    #[test]
103    fn test_to_posix_no_backslash() {
104        let input = "already/posix/path";
105        let result = PathUtil::to_posix(input);
106        if !cfg!(windows) {
107            assert!(matches!(result, std::borrow::Cow::Borrowed(_)));
108        }
109        assert_eq!(result.as_ref(), "already/posix/path");
110    }
111
112    #[test]
113    fn test_with_separator_posix_true() {
114        let result = PathUtil::with_separator("foo\\bar", true);
115        assert_eq!(result.as_ref(), "foo/bar");
116    }
117
118    #[test]
119    fn test_with_separator_posix_false() {
120        let result = PathUtil::with_separator("foo\\bar", false);
121        // When use_posix is false, returns Cow::Borrowed (original string)
122        assert!(matches!(result, std::borrow::Cow::Borrowed(_)));
123        assert_eq!(result.as_ref(), "foo\\bar");
124    }
125}