cross_path/lib.rs
1//! Advanced cross-platform path handling library
2//!
3//! Provides perfect compatibility handling for Windows and Linux paths, supporting:
4//! - Windows ↔ Linux bidirectional path conversion
5//! - Automatic encoding detection and conversion
6//! - Path security verification
7//! - Cross-platform file operations
8//!
9//! # Examples
10//!
11//! ```rust
12//! use cross_path::{CrossPath, PathStyle};
13//!
14//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
15//! // Convert Windows path to Unix path
16//! let path = CrossPath::new(r"C:\Users\name\file.txt")?;
17//! assert_eq!(path.to_unix()?, "/mnt/c/Users/name/file.txt");
18//!
19//! // Convert Unix path to Windows path
20//! let path = CrossPath::new("/home/name/file.txt")?;
21//! assert_eq!(path.to_windows()?, r"C:\home\name\file.txt");
22//! # Ok(())
23//! # }
24//! ```
25
26#![deny(missing_docs)]
27#![warn(clippy::pedantic)]
28#![allow(clippy::module_name_repetitions)]
29extern crate alloc;
30
31/// Path converter module
32pub mod converter;
33/// Error handling module
34pub mod error;
35/// Path formatter module
36pub mod formatter;
37/// Path parser module
38pub mod parser;
39/// Platform-specific operations module
40pub mod platform;
41#[cfg(feature = "security")]
42/// Security verification module
43pub mod security;
44#[cfg(feature = "unicode")]
45/// Unicode handling module
46pub mod unicode;
47
48pub use converter::PathConverter;
49pub use error::PathError;
50pub use formatter::PathFormatter;
51pub use parser::PathParser;
52
53use std::path::{Path, PathBuf};
54
55/// Cross-platform path result type
56pub type PathResult<T> = Result<T, PathError>;
57
58/// Path style enumeration
59#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
60pub enum PathStyle {
61 /// Windows path style (C:\Users\name)
62 Windows,
63 /// Unix/Linux path style (/home/name)
64 Unix,
65 /// Auto-detect based on current platform
66 Auto,
67}
68
69/// Path conversion configuration
70#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
71pub struct PathConfig {
72 /// Target path style
73 pub style: PathStyle,
74 /// Whether to preserve original encoding
75 pub preserve_encoding: bool,
76 /// Whether to perform security checks
77 pub security_check: bool,
78 /// Windows drive letter mappings (e.g., "C:" -> "/mnt/c")
79 pub drive_mappings: Vec<(String, String)>,
80 /// Whether to normalize paths (remove redundant components)
81 pub normalize: bool,
82}
83
84impl Default for PathConfig {
85 fn default() -> Self {
86 Self {
87 style: PathStyle::Auto,
88 preserve_encoding: true,
89 security_check: true,
90 drive_mappings: default_drive_mappings(),
91 normalize: true,
92 }
93 }
94}
95
96/// Default drive letter mappings
97fn default_drive_mappings() -> Vec<(String, String)> {
98 vec![
99 ("C:".to_string(), "/mnt/c".to_string()),
100 ("D:".to_string(), "/mnt/d".to_string()),
101 ("E:".to_string(), "/mnt/e".to_string()),
102 ]
103}
104
105/// Main cross-platform path structure
106#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
107pub struct CrossPath {
108 inner: PathBuf,
109 original_style: PathStyle,
110 config: PathConfig,
111}
112
113impl CrossPath {
114 /// Create a cross-platform path from a string
115 ///
116 /// # Arguments
117 ///
118 /// * `path` - The path string to parse
119 ///
120 /// # Errors
121 ///
122 /// Returns `PathError` if the path is invalid
123 pub fn new<P: AsRef<str>>(path: P) -> PathResult<Self> {
124 let path_str = path.as_ref();
125 let _ = PathParser::parse(path_str)?;
126 let style = PathParser::detect_style(path_str);
127
128 Ok(Self {
129 inner: PathBuf::from(path_str),
130 original_style: style,
131 config: PathConfig::default(),
132 })
133 }
134
135 /// Create path with custom configuration
136 ///
137 /// # Arguments
138 ///
139 /// * `path` - The path string to parse
140 /// * `config` - Custom configuration options
141 ///
142 /// # Errors
143 ///
144 /// Returns `PathError` if the path is invalid
145 pub fn with_config<P: AsRef<str>>(path: P, config: PathConfig) -> PathResult<Self> {
146 let mut cross_path = Self::new(path)?;
147 cross_path.config = config;
148 Ok(cross_path)
149 }
150
151 /// Convert to path string with specified style
152 ///
153 /// # Arguments
154 ///
155 /// * `style` - The target path style
156 ///
157 /// # Errors
158 ///
159 /// Returns `PathError` if conversion fails
160 pub fn to_style(&self, style: PathStyle) -> PathResult<String> {
161 let converter = PathConverter::new(&self.config);
162 converter.convert(self.inner.to_string_lossy().as_ref(), style)
163 }
164
165 /// Convert to platform-appropriate path
166 ///
167 /// Automatically detects the current operating system and converts the path
168 /// to the native format.
169 ///
170 /// # Errors
171 ///
172 /// Returns `PathError` if conversion fails
173 pub fn to_platform(&self) -> PathResult<String> {
174 let target_style = match self.config.style {
175 PathStyle::Auto => platform::current_style(),
176 style => style,
177 };
178 self.to_style(target_style)
179 }
180
181 /// Convert to Windows path
182 ///
183 /// Forces conversion to Windows style (e.g., `C:\path\to\file`)
184 ///
185 /// # Errors
186 ///
187 /// Returns `PathError` if conversion fails
188 pub fn to_windows(&self) -> PathResult<String> {
189 self.to_style(PathStyle::Windows)
190 }
191
192 /// Convert to Unix path
193 ///
194 /// Forces conversion to Unix style (e.g., `/mnt/c/path/to/file`)
195 ///
196 /// # Errors
197 ///
198 /// Returns `PathError` if conversion fails
199 pub fn to_unix(&self) -> PathResult<String> {
200 self.to_style(PathStyle::Unix)
201 }
202
203 /// Get original path
204 #[must_use]
205 pub fn as_original(&self) -> &Path {
206 &self.inner
207 }
208
209 /// Update configuration
210 pub fn set_config(&mut self, config: PathConfig) {
211 self.config = config;
212 }
213
214 /// Get configuration reference
215 #[must_use]
216 pub fn config(&self) -> &PathConfig {
217 &self.config
218 }
219
220 /// Check if path is safe
221 ///
222 /// Performs security checks including:
223 /// - Path traversal detection
224 /// - Dangerous pattern detection
225 /// - System directory access check
226 ///
227 /// # Errors
228 ///
229 /// Returns `PathError` if security check fails
230 pub fn is_safe(&self) -> PathResult<bool> {
231 security::PathSecurityChecker::check_path_security(&self.inner)
232 }
233
234 /// Normalize path
235 ///
236 /// Removes redundant components like `.` and `..`
237 ///
238 /// # Errors
239 ///
240 /// Returns `PathError` if normalization fails
241 pub fn normalize(&mut self) -> PathResult<()> {
242 let normalized = PathParser::normalize_path(&self.inner)?;
243 self.inner = normalized;
244 Ok(())
245 }
246}
247
248impl From<&Path> for CrossPath {
249 fn from(path: &Path) -> Self {
250 Self {
251 inner: path.to_path_buf(),
252 original_style: PathStyle::Auto,
253 config: PathConfig::default(),
254 }
255 }
256}
257
258impl From<PathBuf> for CrossPath {
259 fn from(path: PathBuf) -> Self {
260 Self {
261 inner: path,
262 original_style: PathStyle::Auto,
263 config: PathConfig::default(),
264 }
265 }
266}
267
268/// Path conversion trait
269///
270/// Extension trait to add conversion methods to string and path types
271pub trait PathConvert {
272 /// Convert to `CrossPath`
273 ///
274 /// # Errors
275 ///
276 /// Returns `PathError` if conversion fails
277 fn to_cross_path(&self) -> PathResult<CrossPath>;
278
279 /// Convert to Windows path
280 ///
281 /// # Errors
282 ///
283 /// Returns `PathError` if conversion fails
284 fn to_windows_path(&self) -> PathResult<String>;
285
286 /// Convert to Unix path
287 ///
288 /// # Errors
289 ///
290 /// Returns `PathError` if conversion fails
291 fn to_unix_path(&self) -> PathResult<String>;
292}
293
294impl PathConvert for str {
295 fn to_cross_path(&self) -> PathResult<CrossPath> {
296 CrossPath::new(self)
297 }
298
299 fn to_windows_path(&self) -> PathResult<String> {
300 let cross_path = CrossPath::new(self)?;
301 cross_path.to_windows()
302 }
303
304 fn to_unix_path(&self) -> PathResult<String> {
305 let cross_path = CrossPath::new(self)?;
306 cross_path.to_unix()
307 }
308}
309
310impl PathConvert for Path {
311 fn to_cross_path(&self) -> PathResult<CrossPath> {
312 Ok(CrossPath::from(self))
313 }
314
315 fn to_windows_path(&self) -> PathResult<String> {
316 let cross_path = CrossPath::from(self);
317 cross_path.to_windows()
318 }
319
320 fn to_unix_path(&self) -> PathResult<String> {
321 let cross_path = CrossPath::from(self);
322 cross_path.to_unix()
323 }
324}