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}