Skip to main content

windjammer_runtime/
path.rs

1//! Path manipulation utilities
2//!
3//! Windjammer's path module provides a clean, ergonomic API for working with
4//! file system paths. It wraps Rust's std::path but with simplified error handling.
5
6use std::ffi::OsStr;
7use std::path::{Path as StdPath, PathBuf as StdPathBuf};
8
9/// Re-export std::path::Path for direct use
10pub use std::path::Path;
11
12/// Re-export std::path::PathBuf for direct use  
13pub use std::path::PathBuf;
14
15/// Create a new Path from a string
16pub fn new(s: &str) -> &StdPath {
17    StdPath::new(s)
18}
19
20/// Create a new PathBuf from a string
21pub fn from_str(s: &str) -> StdPathBuf {
22    StdPathBuf::from(s)
23}
24
25/// Get the file name from a path
26pub fn file_name(path: &StdPath) -> Option<&str> {
27    path.file_name().and_then(|s| s.to_str())
28}
29
30/// Get the file stem (name without extension)
31pub fn file_stem(path: &StdPath) -> Option<&str> {
32    path.file_stem().and_then(|s| s.to_str())
33}
34
35/// Get the file extension (works with &str, &String, &Path, &PathBuf)
36pub fn extension<P: AsRef<StdPath>>(path: P) -> Option<String> {
37    path.as_ref()
38        .extension()
39        .and_then(|s| s.to_str())
40        .map(|s| s.to_string())
41}
42
43/// Get the parent directory
44pub fn parent(path: &StdPath) -> Option<&StdPath> {
45    path.parent()
46}
47
48/// Check if path is absolute
49pub fn is_absolute(path: &StdPath) -> bool {
50    path.is_absolute()
51}
52
53/// Check if path is relative
54pub fn is_relative(path: &StdPath) -> bool {
55    path.is_relative()
56}
57
58/// Check if path exists
59pub fn exists(path: &StdPath) -> bool {
60    path.exists()
61}
62
63/// Check if path is a file
64pub fn is_file(path: &StdPath) -> bool {
65    path.is_file()
66}
67
68/// Check if path is a directory
69pub fn is_dir(path: &StdPath) -> bool {
70    path.is_dir()
71}
72
73/// Join path segments
74pub fn join(path: &StdPath, other: &str) -> StdPathBuf {
75    path.join(other)
76}
77
78/// Convert path to string
79pub fn to_string(path: &StdPath) -> Option<String> {
80    path.to_str().map(String::from)
81}
82
83/// Convert path to string, lossy
84pub fn to_string_lossy(path: &StdPath) -> String {
85    path.to_string_lossy().into_owned()
86}
87
88/// Get components of the path as strings
89pub fn components(path: &StdPath) -> Vec<String> {
90    path.components()
91        .filter_map(|c| c.as_os_str().to_str())
92        .map(String::from)
93        .collect()
94}
95
96/// Normalize a path (resolve . and ..)
97pub fn canonicalize(path: &StdPath) -> Result<StdPathBuf, String> {
98    path.canonicalize()
99        .map_err(|e| format!("Failed to canonicalize path: {}", e))
100}
101
102/// Get the current working directory
103pub fn current_dir() -> Result<StdPathBuf, String> {
104    std::env::current_dir().map_err(|e| format!("Failed to get current directory: {}", e))
105}
106
107/// Check if a path has a specific extension
108pub fn has_extension(path: &StdPath, ext: &str) -> bool {
109    path.extension()
110        .and_then(|e| e.to_str())
111        .map(|e| e == ext)
112        .unwrap_or(false)
113}
114
115/// Strip prefix from path
116pub fn strip_prefix<'a>(path: &'a StdPath, prefix: &StdPath) -> Option<&'a StdPath> {
117    path.strip_prefix(prefix).ok()
118}
119
120/// Get the path as an OsStr
121pub fn as_os_str(path: &StdPath) -> &OsStr {
122    path.as_os_str()
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn test_path_operations() {
131        let path = new("/foo/bar/baz.txt");
132
133        assert_eq!(file_name(path), Some("baz.txt"));
134        assert_eq!(file_stem(path), Some("baz"));
135        assert_eq!(extension(path), Some("txt".to_string()));
136        assert!(is_absolute(path));
137
138        let relative = new("foo/bar.txt");
139        assert!(is_relative(relative));
140    }
141
142    #[test]
143    fn test_path_join() {
144        let base = new("/foo");
145        let joined = join(base, "bar");
146        assert_eq!(to_string(&joined), Some("/foo/bar".to_string()));
147    }
148
149    #[test]
150    fn test_extension_check() {
151        let path = new("file.wj");
152        assert!(has_extension(path, "wj"));
153        assert!(!has_extension(path, "rs"));
154    }
155}