app_path/
lib.rs

1//! # app-path
2//!
3//! Create file paths relative to your executable for truly portable applications.
4//!
5//! This crate provides a simple, robust solution for applications that need to access files
6//! and directories relative to their executable location. The design prioritizes **simplicity**,
7//! **performance**, and **reliability** for the common use case of portable applications.
8//!
9//! ## Design Philosophy
10//!
11//! **AppPath** is designed around these core principles:
12//!
13//! 1. **Portable-first**: Everything stays together with your executable
14//! 2. **Simple API**: Infallible constructors with clear panic conditions
15//! 3. **High performance**: Static caching and zero-allocation design
16//! 4. **Ergonomic**: Works seamlessly with all Rust path types
17//! 5. **Robust**: Handles edge cases in containerized/embedded environments
18//!
19//! The crate makes a deliberate trade-off: **simplicity and performance over fallible APIs**.
20//! Since executable location determination rarely fails in practice, and when it does fail
21//! it indicates fundamental system issues, we choose to panic with clear documentation
22//! rather than burden every usage site with error handling.
23//!
24//! ## Primary Use Cases
25//!
26//! This crate is specifically designed for applications that benefit from **portable layouts**:
27//!
28//! - **Portable applications** that run from USB drives or network shares
29//! - **Development tools** that should work without installation
30//! - **Corporate software** deployed without admin rights
31//! - **Containerized applications** with predictable file layouts
32//! - **Embedded systems** with simple, fixed directory structures
33//! - **CLI tools** that need configuration and data files nearby
34//!
35//! ## Quick Start
36//!
37//! ```rust
38//! use app_path::AppPath;
39//! use std::path::{Path, PathBuf};
40//!
41//! // Create paths relative to your executable - simple and clean
42//! let config = AppPath::new("config.toml");
43//! let data = AppPath::new("data/users.db");
44//!
45//! // Accepts any AsRef<Path> type - no unnecessary allocations
46//! let log_file = "logs/app.log".to_string();
47//! let logs = AppPath::new(&log_file);
48//!
49//! let path_buf = PathBuf::from("cache/data.bin");
50//! let cache = AppPath::new(&path_buf);
51//!
52//! // Works with any path-like type
53//! let from_path = AppPath::new(Path::new("temp.txt"));
54//!
55//! // Alternative: Use From for any path type
56//! let settings: AppPath = "settings.json".into();
57//! let data_file: AppPath = PathBuf::from("data.db").into();
58//! let temp_file: AppPath = Path::new("temp.log").into();
59//!
60//! // Get the paths for use with standard library functions
61//! println!("Config: {}", config.path().display());
62//! println!("Data: {}", data.path().display());
63//!
64//! // Check existence and create directories
65//! if !logs.exists() {
66//!     logs.create_dir_all()?;
67//! }
68//!
69//! # Ok::<(), Box<dyn std::error::Error>>(())
70//! ```
71//!
72//! ## Path Resolution Strategy
73//!
74//! **AppPath** uses intelligent path resolution to support both portable and system-integrated applications:
75//!
76//! ```rust
77//! use app_path::AppPath;
78//!
79//! // Relative paths are resolved relative to the executable directory
80//! // This is the primary use case for portable applications
81//! let config = AppPath::new("config.toml");           // → exe_dir/config.toml
82//! let data = AppPath::new("data/users.db");           // → exe_dir/data/users.db
83//! let nested = AppPath::new("plugins/my_plugin.dll"); // → exe_dir/plugins/my_plugin.dll
84//!
85//! // Absolute paths are used as-is for system integration
86//! // This allows hybrid applications that also integrate with the system
87//! let system_config = AppPath::new("/etc/myapp/config.toml");  // → /etc/myapp/config.toml
88//! let windows_temp = AppPath::new(r"C:\temp\cache.dat");       // → C:\temp\cache.dat
89//! ```
90//!
91//! This dual behavior enables applications to be **primarily portable** while still
92//! allowing **system integration** when needed.
93//!
94//! ## Performance and Memory Design
95//!
96//! **AppPath** is optimized for high-performance applications:
97//!
98//! - **Static caching**: Executable directory determined once, cached forever
99//! - **Minimal memory**: Only stores the final resolved path (no input path retained)
100//! - **Zero allocations**: Uses `AsRef<Path>` to avoid unnecessary conversions
101//! - **Efficient conversions**: `From` trait implementations for all common types
102//! - **Thread-safe**: Safe concurrent access to cached executable directory
103//!
104//! ```rust
105//! use app_path::AppPath;
106//! use std::path::{Path, PathBuf};
107//!
108//! // All of these are efficient - no unnecessary allocations
109//! let from_str = AppPath::new("config.toml");          // &str → direct usage
110//! let from_string = AppPath::new(&"data.db".to_string()); // &String → no move needed
111//! let from_path = AppPath::new(Path::new("logs.txt")); // &Path → direct usage
112//! let from_pathbuf = AppPath::new(&PathBuf::from("cache.bin")); // &PathBuf → no move needed
113//!
114//! // When you want ownership transfer, use From trait
115//! let owned: AppPath = PathBuf::from("important.db").into(); // PathBuf moved efficiently
116//! ```
117//!
118//! ## Reliability and Edge Cases
119//!
120//! **AppPath** is designed to be robust in various deployment environments:
121//!
122//! - **Handles root-level executables**: Works when executable is at filesystem root
123//! - **Container-friendly**: Designed for containerized and jailed environments
124//! - **Cross-platform**: Consistent behavior across Windows, Linux, and macOS
125//! - **Clear failure modes**: Panics with descriptive messages on rare system failures
126//!
127//! ### Panic Conditions
128//!
129//! The crate uses an **infallible API** that panics on rare system failures during static initialization.
130//! This design choice prioritizes **simplicity and performance** for the common case where
131//! executable location determination succeeds (which is the vast majority of real-world usage).
132//!
133//! **AppPath will panic if:**
134//!
135//! - **Cannot determine executable location** - When [`std::env::current_exe()`] fails
136//!   (rare, but possible in some embedded or heavily sandboxed environments)
137//! - **Executable path is empty** - When the system returns an empty executable path
138//!   (extremely rare, indicates a broken/corrupted system)
139//!
140//! **These panics occur:**
141//! - Once during the first use of any AppPath function
142//! - Indicate fundamental system issues that are typically unrecoverable
143//! - Are documented with clear error messages explaining the failure
144//!
145//! **Edge case handling:**
146//! - **Root-level executables**: When executable runs at filesystem root (e.g., `/init`, `C:\`),
147//!   the crate uses the root directory itself as the base
148//! - **Containerized environments**: Properly handles Docker, chroot, and other containerized environments
149//! - **Jailed environments**: Handles various forms of process isolation and sandboxing
150//!
151//! For applications that need fallible behavior, consider these practical alternatives:
152//!
153//! ```rust
154//! use app_path::AppPath;
155//! use std::env;
156//!
157//! // Pattern 1: Environment variable fallback (recommended)
158//! fn get_config_path() -> AppPath {
159//!     if let Ok(custom_dir) = env::var("MYAPP_CONFIG_DIR") {
160//!         let config_path = std::path::Path::new(&custom_dir).join("config.toml");
161//!         AppPath::new(config_path)
162//!     } else {
163//!         AppPath::new("config.toml")
164//!     }
165//! }
166//!
167//! // Pattern 2: Conditional development/production paths
168//! fn get_data_path() -> AppPath {
169//!     if env::var("DEVELOPMENT").is_ok() {
170//!         let dev_path = env::current_dir().unwrap().join("dev_data").join("app.db");
171//!         AppPath::new(dev_path)
172//!     } else {
173//!         AppPath::new("data/app.db")
174//!     }
175//! }
176//! ```
177//!
178//! For applications that need to handle executable location failures:
179//!
180//! ```rust
181//! use app_path::AppPath;
182//! use std::env;
183//!
184//! fn get_config_path_safe() -> AppPath {
185//!     match env::current_exe() {
186//!         Ok(exe_path) => {
187//!             if let Some(exe_dir) = exe_path.parent() {
188//!                 let config_path = exe_dir.join("config.toml");
189//!                 AppPath::new(config_path)
190//!             } else {
191//!                 // Fallback for edge case where exe has no parent
192//!                 let temp_dir = env::temp_dir().join("myapp");
193//!                 let _ = std::fs::create_dir_all(&temp_dir);
194//!                 let config_path = temp_dir.join("config.toml");
195//!                 AppPath::new(config_path)
196//!             }
197//!         }
198//!         Err(_) => {
199//!             // Fallback when executable location cannot be determined
200//!             let temp_dir = env::temp_dir().join("myapp");
201//!             let _ = std::fs::create_dir_all(&temp_dir);
202//!             let config_path = temp_dir.join("config.toml");
203//!             AppPath::new(config_path)
204//!         }
205//!     }
206//! }
207//! ```
208//!
209//! **Note:** Using `std::env::current_exe()` directly is simpler and more idiomatic
210//! than `panic::catch_unwind` patterns. Most applications should use environment
211//! variable and conditional patterns instead.
212//!
213//! ## Flexible Creation Methods
214//!
215//! ```rust
216//! use app_path::AppPath;
217//! use std::path::{Path, PathBuf};
218//!
219//! // Method 1: Direct construction (recommended)
220//! let config = AppPath::new("config.toml");
221//! let logs = AppPath::new(PathBuf::from("logs/app.log"));
222//!
223//! // Method 2: From trait for various path types
224//! let data1: AppPath = "data/users.db".into();               // &str
225//! let data2: AppPath = "settings.json".to_string().into();   // String
226//! let data3: AppPath = Path::new("cache/data.bin").into();   // &Path
227//! let data4: AppPath = PathBuf::from("temp/file.txt").into(); // PathBuf
228//!
229//! // All methods support efficient zero-copy when possible
230//! let owned_path = PathBuf::from("important/data.db");
231//! let app_path: AppPath = owned_path.into(); // PathBuf is moved efficiently
232//! ```
233
234use std::borrow::Borrow;
235use std::cmp::Ordering;
236use std::env::current_exe;
237use std::hash::{Hash, Hasher};
238use std::ops::Deref;
239use std::path::{Path, PathBuf};
240use std::sync::OnceLock;
241
242// Global executable directory - computed once, cached forever
243static EXE_DIR: OnceLock<PathBuf> = OnceLock::new();
244
245/// Internal function to initialize and get the executable directory
246fn exe_dir_inner() -> &'static PathBuf {
247    EXE_DIR.get_or_init(|| {
248        let exe = current_exe().expect("Failed to determine executable location");
249
250        // Handle edge case: executable at filesystem root (jailed environments, etc.)
251        match exe.parent() {
252            Some(parent) => parent.to_path_buf(),
253            None => {
254                // If exe has no parent (e.g., running as "/init" or "C:\"),
255                // use the root directory itself
256                if exe.as_os_str().is_empty() {
257                    panic!("Executable path is empty - unsupported environment");
258                }
259
260                // For root-level executables, use the root directory
261                exe.ancestors()
262                    .last()
263                    .expect("Failed to determine filesystem root")
264                    .to_path_buf()
265            }
266        }
267    })
268}
269
270/// Creates paths relative to the executable location for applications.
271///
272/// **AppPath** is the core type for building portable applications where all files and directories
273/// stay together with the executable. This design choice makes applications truly portable -
274/// they can run from USB drives, network shares, or any directory without installation.
275///
276/// ## Available Trait Implementations
277///
278/// `AppPath` implements a comprehensive set of traits for seamless integration with Rust's
279/// standard library and idiomatic code patterns:
280///
281/// **Core Traits:**
282/// - `Clone` - Efficient cloning (only copies the resolved path)
283/// - `Debug` - Useful debug output showing the resolved path
284/// - `Default` - Creates a path pointing to the executable directory
285/// - `Display` - Human-readable path display
286///
287/// **Comparison Traits:**
288/// - `PartialEq`, `Eq` - Compare paths for equality
289/// - `PartialOrd`, `Ord` - Lexicographic ordering for sorting
290/// - `Hash` - Use as keys in `HashMap`, `HashSet`, etc.
291///
292/// **Conversion Traits:**
293/// - `AsRef<Path>` - Use with any API expecting `&Path`
294/// - `Deref<Target=Path>` - Direct access to `Path` methods (e.g., `.extension()`)
295/// - `Borrow<Path>` - Enable borrowing as `&Path` for collection compatibility
296/// - `From<T>` for `&str`, `String`, `&Path`, `PathBuf`, etc. - Flexible construction
297/// - `Into<PathBuf>` - Convert to owned `PathBuf`
298///
299/// These implementations make `AppPath` a **zero-cost abstraction** that works seamlessly
300/// with existing Rust code while providing portable path resolution.
301///
302/// ## Design Rationale
303///
304/// **Why relative to executable instead of current directory?**
305/// - Current directory depends on where the user runs the program from
306/// - Executable location is reliable and predictable
307/// - Enables true portability - the entire application can be moved as one unit
308///
309/// **Why infallible API instead of Result-based?**
310/// - Executable location determination rarely fails in practice
311/// - When it does fail, it indicates fundamental system issues
312/// - Infallible API eliminates error handling boilerplate from every usage site
313/// - Results in cleaner, more maintainable application code
314///
315/// **Why static caching?**
316/// - Executable location never changes during program execution
317/// - Avoids repeated system calls for performance
318/// - Thread-safe and efficient for concurrent applications
319///
320/// ## Perfect For
321///
322/// - **Portable applications** that run from USB drives or network shares
323/// - **Development tools** that should work anywhere without installation
324/// - **Corporate environments** where you can't install software system-wide
325/// - **Containerized applications** with predictable, self-contained layouts
326/// - **Embedded systems** with simple, fixed directory structures
327/// - **CLI tools** that need configuration and data files nearby
328///
329/// ## Memory Layout
330///
331/// Each `AppPath` instance stores only the final resolved path (`PathBuf`), making it
332/// memory-efficient. The original input path is not retained, as the resolved path
333/// contains all necessary information for file operations.
334///
335/// ```text
336/// AppPath {
337///     full_path: PathBuf  // Only field - minimal memory usage
338/// }
339/// ```
340///
341/// ## Thread Safety
342///
343/// `AppPath` is `Send + Sync` and can be safely shared between threads. The static
344/// executable directory cache is initialized once and safely shared across all threads.
345///
346/// # Panics
347///
348/// Panics on first use if the executable location cannot be determined.
349/// See [crate-level documentation](crate) for comprehensive details on panic conditions
350/// and edge case handling.
351///
352/// # Examples
353///
354/// ## Basic Usage
355///
356/// ```rust
357/// use app_path::AppPath;
358///
359/// // Simple relative paths - the common case
360/// let config = AppPath::new("config.toml");
361/// let data_dir = AppPath::new("data");
362/// let logs = AppPath::new("logs/app.log");
363///
364/// // Use like normal paths
365/// if config.exists() {
366///     let settings = std::fs::read_to_string(config.path())?;
367/// }
368///
369/// // Create directories as needed
370/// data_dir.create_dir_all()?;
371///
372/// # Ok::<(), Box<dyn std::error::Error>>(())
373/// ```
374///
375/// ## Mixed Portable and System Paths
376///
377/// ```rust
378/// use app_path::AppPath;
379///
380/// // Portable app files (relative paths)
381/// let app_config = AppPath::new("config.toml");       // → exe_dir/config.toml
382/// let app_data = AppPath::new("data/users.db");       // → exe_dir/data/users.db
383///
384/// // System integration (absolute paths)
385/// let system_log = AppPath::new("/var/log/myapp.log"); // → /var/log/myapp.log
386/// let temp_cache = AppPath::new(std::env::temp_dir().join("cache.db"));
387///
388/// println!("App config: {}", app_config);    // Portable
389/// println!("System log: {}", system_log);    // System integration
390/// ```
391///
392/// ## Performance-Conscious Usage
393///
394/// ```rust
395/// use app_path::AppPath;
396/// use std::path::{Path, PathBuf};
397///
398/// // Efficient - no unnecessary allocations
399/// let config = AppPath::new("config.toml");          // &str
400/// let data = AppPath::new(Path::new("data.db"));     // &Path
401///
402/// // When you have owned values, borrow them to avoid moves
403/// let filename = "important.log".to_string();
404/// let logs = AppPath::new(&filename);                 // &String - no move
405///
406/// // Use From trait when you want ownership transfer
407/// let owned_path = PathBuf::from("cache.bin");
408/// let cache: AppPath = owned_path.into();             // PathBuf moved
409/// ```
410///
411/// ## Portable vs System Integration
412///
413/// ```rust
414/// use app_path::AppPath;
415///
416/// // Portable application files (relative paths)
417/// let app_config = AppPath::new("config.toml");           // → exe_dir/config.toml
418/// let app_data = AppPath::new("data/users.db");           // → exe_dir/data/users.db
419/// let plugins = AppPath::new("plugins/my_plugin.dll");    // → exe_dir/plugins/my_plugin.dll
420///
421/// // System integration (absolute paths)
422/// let system_config = AppPath::new("/etc/myapp/global.toml");  // → /etc/myapp/global.toml
423/// let temp_file = AppPath::new(r"C:\temp\cache.dat");          // → C:\temp\cache.dat
424/// let user_data = AppPath::new("/home/user/.myapp/prefs");     // → /home/user/.myapp/prefs
425/// ```
426///
427/// ## Common Patterns
428///
429/// ```rust
430/// use app_path::AppPath;
431/// use std::fs;
432///
433/// // Configuration file pattern
434/// let config = AppPath::new("config.toml");
435/// if config.exists() {
436///     let content = fs::read_to_string(config.path())?;
437///     // Parse configuration...
438/// }
439///
440/// // Data directory pattern
441/// let data_dir = AppPath::new("data");
442/// data_dir.create_dir_all()?;  // Ensure directory exists
443/// let user_db = AppPath::new("data/users.db");
444///
445/// // Logging pattern
446/// let log_file = AppPath::new("logs/app.log");
447/// log_file.create_dir_all()?;  // Create logs directory if needed
448/// fs::write(log_file.path(), "Application started\n")?;
449///
450/// # Ok::<(), Box<dyn std::error::Error>>(())
451/// ```
452///
453/// ## Trait Implementation Examples
454///
455/// `AppPath` implements many useful traits that enable ergonomic usage patterns:
456///
457/// ```rust
458/// use app_path::AppPath;
459/// use std::collections::{HashMap, BTreeSet};
460///
461/// // Default trait - creates path to executable directory
462/// let exe_dir = AppPath::default();
463/// assert_eq!(exe_dir, AppPath::new(""));
464///
465/// // Comparison traits - enable sorting and equality checks
466/// let mut paths = vec![
467///     AppPath::new("z.txt"),
468///     AppPath::new("a.txt"),
469///     AppPath::new("m.txt"),
470/// ];
471/// paths.sort(); // Uses Ord trait
472/// assert!(paths[0] < paths[1]); // Uses PartialOrd trait
473///
474/// // Hash trait - use as keys in collections
475/// let mut file_types = HashMap::new();
476/// file_types.insert(AppPath::new("config.toml"), "Configuration");
477/// file_types.insert(AppPath::new("data.db"), "Database");
478///
479/// // Ordered collections work automatically
480/// let mut sorted_paths = BTreeSet::new();
481/// sorted_paths.insert(AppPath::new("config.toml"));
482/// sorted_paths.insert(AppPath::new("data.db"));
483///
484/// // Deref trait - direct access to Path methods
485/// let config = AppPath::new("config.toml");
486/// assert_eq!(config.extension(), Some("toml".as_ref())); // Direct Path method
487/// assert_eq!(config.file_name(), Some("config.toml".as_ref()));
488///
489/// // Works with functions expecting &Path (deref coercion)
490/// fn analyze_path(path: &std::path::Path) -> Option<&str> {
491///     path.extension()?.to_str()
492/// }
493/// assert_eq!(analyze_path(&config), Some("toml"));
494///
495/// // From trait - flexible construction from many types
496/// let from_str: AppPath = "data.txt".into();
497/// let from_pathbuf: AppPath = std::path::PathBuf::from("logs.txt").into();
498///
499/// // Display trait - human-readable output
500/// println!("Config path: {}", config); // Clean path display
501/// ```
502#[derive(Clone, Debug)]
503pub struct AppPath {
504    full_path: PathBuf,
505}
506
507impl AppPath {
508    /// Creates file paths relative to the executable location.
509    ///
510    /// This is the primary constructor for `AppPath`. The method accepts any type that implements
511    /// [`AsRef<Path>`], providing maximum flexibility while maintaining zero-allocation performance
512    /// for most use cases.
513    ///
514    /// ## Design Choices
515    ///
516    /// **Why `impl AsRef<Path>` instead of specific types?**
517    /// - Accepts all path-like types: `&str`, `String`, `&Path`, `PathBuf`, etc.
518    /// - Avoids unnecessary allocations through efficient borrowing
519    /// - Provides a single, consistent API instead of multiple overloads
520    /// - Enables efficient usage patterns for both owned and borrowed values
521    ///
522    /// **Why infallible constructor?**
523    /// - Executable location determination succeeds in >99.9% of real-world usage
524    /// - Eliminates error handling boilerplate from every call site
525    /// - Makes the API more ergonomic for the common case
526    /// - Follows Rust conventions where `new()` implies infallible construction
527    ///
528    /// **Path Resolution Strategy:**
529    /// - **Relative paths** (e.g., `"config.toml"`, `"data/file.txt"`) are resolved
530    ///   relative to the executable's directory - this is the primary use case
531    /// - **Absolute paths** (e.g., `"/etc/config"`, `"C:\\temp\\file.txt"`) are used
532    ///   as-is, ignoring the executable's directory - enables system integration
533    ///
534    /// This dual behavior supports both **portable applications** (relative paths) and
535    /// **system integration** (absolute paths) within the same API.
536    ///
537    /// # Arguments
538    ///
539    /// * `path` - A path that will be resolved according to AppPath's resolution strategy.
540    ///   Accepts any type implementing [`AsRef<Path>`]:
541    ///   - `&str` - String literals and string slices
542    ///   - `String` - Owned strings
543    ///   - `&Path` - Path references
544    ///   - `PathBuf` - Path buffers
545    ///   - And many others that implement `AsRef<Path>`
546    ///
547    ///   **Path Resolution:**
548    ///   - **Relative paths** are resolved relative to the executable directory
549    ///   - **Absolute paths** are used as-is (not modified)
550    ///
551    /// # Panics
552    ///
553    /// Panics on first use if the executable location cannot be determined.
554    /// This is a **one-time initialization panic** that occurs during static initialization
555    /// of the executable directory cache.
556    ///
557    /// See [crate-level documentation](crate) for comprehensive details on:
558    /// - When panics can occur (rare system failure conditions)
559    /// - How edge cases are handled (root-level executables, containers)
560    /// - Strategies for applications that need fallible behavior
561    ///
562    /// # Performance Notes
563    ///
564    /// This method is highly optimized:
565    /// - **Static caching**: Executable location determined once, reused forever
566    /// - **Zero allocations**: Uses `AsRef<Path>` to avoid unnecessary conversions
567    /// - **Minimal memory**: Only stores the final resolved path
568    /// - **Thread-safe**: Safe to call from multiple threads concurrently
569    ///
570    /// # Examples
571    ///
572    /// ## Basic Usage (Recommended)
573    ///
574    /// ```rust
575    /// use app_path::AppPath;
576    ///
577    /// // These create portable paths relative to your executable
578    /// let config = AppPath::new("config.toml");           // &str
579    /// let data = AppPath::new("data/users.db");           // &str with subdirectory
580    /// let logs = AppPath::new("logs/app.log");            // Nested directories
581    /// ```
582    ///
583    /// ## Efficient Usage with Different Types
584    ///
585    /// ```rust
586    /// use app_path::AppPath;
587    /// use std::path::{Path, PathBuf};
588    ///
589    /// // All of these are efficient - no unnecessary allocations
590    /// let from_str = AppPath::new("config.toml");         // &str → direct usage
591    /// let from_path = AppPath::new(Path::new("data.db")); // &Path → direct usage
592    ///
593    /// // Borrow owned values to avoid moves when you need them later
594    /// let filename = "important.log".to_string();
595    /// let logs = AppPath::new(&filename);                 // &String → efficient borrowing
596    /// println!("Original filename: {}", filename);       // filename still available
597    ///
598    /// let path_buf = PathBuf::from("cache.bin");
599    /// let cache = AppPath::new(&path_buf);                // &PathBuf → efficient borrowing
600    /// println!("Original path: {}", path_buf.display()); // path_buf still available
601    /// ```
602    ///
603    /// ## Portable vs System Integration
604    ///
605    /// ```rust
606    /// use app_path::AppPath;
607    ///
608    /// // Portable application files (relative paths)
609    /// let app_config = AppPath::new("config.toml");           // → exe_dir/config.toml
610    /// let app_data = AppPath::new("data/users.db");           // → exe_dir/data/users.db
611    /// let plugins = AppPath::new("plugins/my_plugin.dll");    // → exe_dir/plugins/my_plugin.dll
612    ///
613    /// // System integration (absolute paths)
614    /// let system_config = AppPath::new("/etc/myapp/global.toml");  // → /etc/myapp/global.toml
615    /// let temp_file = AppPath::new(r"C:\temp\cache.dat");          // → C:\temp\cache.dat
616    /// let user_data = AppPath::new("/home/user/.myapp/prefs");     // → /home/user/.myapp/prefs
617    /// ```
618    ///
619    /// ## Common Patterns
620    ///
621    /// ```rust
622    /// use app_path::AppPath;
623    /// use std::fs;
624    ///
625    /// // Configuration file pattern
626    /// let config = AppPath::new("config.toml");
627    /// if config.exists() {
628    ///     let content = fs::read_to_string(config.path())?;
629    ///     // Parse configuration...
630    /// }
631    ///
632    /// // Data directory pattern
633    /// let data_dir = AppPath::new("data");
634    /// data_dir.create_dir_all()?;  // Ensure directory exists
635    /// let user_db = AppPath::new("data/users.db");
636    ///
637    /// // Logging pattern
638    /// let log_file = AppPath::new("logs/app.log");
639    /// log_file.create_dir_all()?;  // Create logs directory if needed
640    /// fs::write(log_file.path(), "Application started\n")?;
641    ///
642    /// # Ok::<(), Box<dyn std::error::Error>>(())
643    /// ```
644    pub fn new(path: impl AsRef<Path>) -> Self {
645        let full_path = exe_dir_inner().join(path.as_ref());
646        Self { full_path }
647    }
648
649    /// Get the full resolved path.
650    ///
651    /// This is the primary method for getting the actual filesystem path
652    /// where your file or directory is located.
653    ///
654    /// # Examples
655    ///
656    /// ```rust
657    /// use app_path::AppPath;
658    ///
659    /// let config = AppPath::new("config.toml");
660    ///
661    /// // Get the path for use with standard library functions
662    /// println!("Config path: {}", config.path().display());
663    ///
664    /// // The path is always absolute
665    /// assert!(config.path().is_absolute());
666    /// ```
667    #[inline]
668    pub fn path(&self) -> &Path {
669        &self.full_path
670    }
671
672    /// Check if the path exists.
673    ///
674    /// # Examples
675    ///
676    /// ```rust
677    /// use app_path::AppPath;
678    ///
679    /// let config = AppPath::new("config.toml");
680    ///
681    /// if config.exists() {
682    ///     println!("Config file found!");
683    /// } else {
684    ///     println!("Config file not found, using defaults.");
685    /// }
686    /// ```
687    #[inline]
688    pub fn exists(&self) -> bool {
689        self.full_path.exists()
690    }
691
692    /// Create parent directories if they don't exist.
693    ///
694    /// This is equivalent to calling [`std::fs::create_dir_all`] on the
695    /// parent directory of this path.
696    ///
697    /// # Examples
698    ///
699    /// ```rust
700    /// use app_path::AppPath;
701    /// use std::env;
702    ///
703    /// // Use a temporary directory for the example
704    /// let temp_dir = env::temp_dir().join("app_path_example");
705    /// let data_file_path = temp_dir.join("data/users/profile.json");
706    /// let data_file = AppPath::new(data_file_path);
707    ///
708    /// // Ensure the "data/users" directory exists
709    /// data_file.create_dir_all()?;
710    ///
711    /// // Verify the directory was created
712    /// assert!(data_file.path().parent().unwrap().exists());
713    ///
714    /// # std::fs::remove_dir_all(&temp_dir).ok();
715    /// # Ok::<(), Box<dyn std::error::Error>>(())
716    /// ```
717    pub fn create_dir_all(&self) -> std::io::Result<()> {
718        if let Some(parent) = self.full_path.parent() {
719            std::fs::create_dir_all(parent)?;
720        }
721        Ok(())
722    }
723}
724
725/// Get the executable's directory.
726///
727/// This function provides access to the cached executable directory that AppPath uses.
728/// Useful for advanced use cases where you need the directory path directly.
729///
730/// # Panics
731///
732/// Panics if the executable location cannot be determined (same conditions as AppPath).
733///
734/// # Examples
735///
736/// ```rust
737/// use app_path::exe_dir;
738///
739/// println!("Executable directory: {}", exe_dir().display());
740/// ```
741pub fn exe_dir() -> &'static Path {
742    exe_dir_inner().as_path()
743}
744
745// Standard trait implementations
746impl std::fmt::Display for AppPath {
747    #[inline]
748    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
749        write!(f, "{}", self.full_path.display())
750    }
751}
752
753impl From<AppPath> for PathBuf {
754    #[inline]
755    fn from(app_path: AppPath) -> Self {
756        app_path.full_path
757    }
758}
759
760impl AsRef<Path> for AppPath {
761    #[inline]
762    fn as_ref(&self) -> &Path {
763        self.full_path.as_ref()
764    }
765}
766
767// Infallible From implementations for common types
768impl From<&str> for AppPath {
769    #[inline]
770    fn from(path: &str) -> Self {
771        Self::new(path)
772    }
773}
774
775impl From<String> for AppPath {
776    #[inline]
777    fn from(path: String) -> Self {
778        Self::new(path)
779    }
780}
781
782impl From<&String> for AppPath {
783    #[inline]
784    fn from(path: &String) -> Self {
785        Self::new(path)
786    }
787}
788
789impl From<&Path> for AppPath {
790    #[inline]
791    fn from(path: &Path) -> Self {
792        Self::new(path)
793    }
794}
795
796impl From<PathBuf> for AppPath {
797    #[inline]
798    fn from(path: PathBuf) -> Self {
799        Self::new(path)
800    }
801}
802
803// === Additional Trait Implementations ===
804
805impl Default for AppPath {
806    /// Creates an `AppPath` pointing to the executable's directory.
807    ///
808    /// This is equivalent to calling `AppPath::new("")`. The default instance
809    /// represents the directory containing the executable, which is useful as
810    /// a starting point for portable applications.
811    ///
812    /// # Examples
813    ///
814    /// ```rust
815    /// use app_path::AppPath;
816    ///
817    /// let exe_dir = AppPath::default();
818    /// let empty_path = AppPath::new("");
819    ///
820    /// // Default should be equivalent to new("")
821    /// assert_eq!(exe_dir, empty_path);
822    ///
823    /// // Both should point to the executable directory
824    /// assert_eq!(exe_dir.path(), app_path::exe_dir());
825    /// ```
826    #[inline]
827    fn default() -> Self {
828        Self::new("")
829    }
830}
831
832impl PartialEq for AppPath {
833    /// Compares two `AppPath` instances for equality based on their resolved paths.
834    ///
835    /// Two `AppPath` instances are considered equal if their full resolved paths
836    /// are identical, regardless of how they were constructed.
837    ///
838    /// # Examples
839    ///
840    /// ```rust
841    /// use app_path::AppPath;
842    ///
843    /// let path1 = AppPath::new("config.toml");
844    /// let path2 = AppPath::new("config.toml");
845    /// let path3 = AppPath::new("other.toml");
846    ///
847    /// assert_eq!(path1, path2);
848    /// assert_ne!(path1, path3);
849    /// ```
850    #[inline]
851    fn eq(&self, other: &Self) -> bool {
852        self.full_path == other.full_path
853    }
854}
855
856impl Eq for AppPath {}
857
858impl PartialOrd for AppPath {
859    /// Compares two `AppPath` instances lexicographically based on their resolved paths.
860    ///
861    /// The comparison is performed on the full resolved paths, providing consistent
862    /// ordering for sorting and collection operations.
863    ///
864    /// # Examples
865    ///
866    /// ```rust
867    /// use app_path::AppPath;
868    ///
869    /// let path1 = AppPath::new("a.txt");
870    /// let path2 = AppPath::new("b.txt");
871    ///
872    /// assert!(path1 < path2);
873    /// ```
874    #[inline]
875    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
876        Some(self.cmp(other))
877    }
878}
879
880impl Ord for AppPath {
881    /// Compares two `AppPath` instances lexicographically based on their resolved paths.
882    ///
883    /// This provides a total ordering that enables `AppPath` to be used in sorted
884    /// collections like `BTreeMap` and `BTreeSet`.
885    ///
886    /// # Examples
887    ///
888    /// ```rust
889    /// use app_path::AppPath;
890    /// use std::collections::BTreeSet;
891    ///
892    /// let mut paths = BTreeSet::new();
893    /// paths.insert(AppPath::new("config.toml"));
894    /// paths.insert(AppPath::new("data.db"));
895    /// paths.insert(AppPath::new("app.log"));
896    ///
897    /// // Paths are automatically sorted lexicographically
898    /// let sorted: Vec<_> = paths.into_iter().collect();
899    /// ```
900    #[inline]
901    fn cmp(&self, other: &Self) -> Ordering {
902        self.full_path.cmp(&other.full_path)
903    }
904}
905
906impl Hash for AppPath {
907    /// Computes a hash for the `AppPath` based on its resolved path.
908    ///
909    /// This enables `AppPath` to be used as keys in hash-based collections
910    /// like `HashMap` and `HashSet`. The hash is computed from the full
911    /// resolved path, ensuring consistent behavior.
912    ///
913    /// # Examples
914    ///
915    /// ```rust
916    /// use app_path::AppPath;
917    /// use std::collections::HashMap;
918    ///
919    /// let mut config_map = HashMap::new();
920    /// let config_path = AppPath::new("config.toml");
921    /// config_map.insert(config_path, "Configuration file");
922    /// ```
923    #[inline]
924    fn hash<H: Hasher>(&self, state: &mut H) {
925        self.full_path.hash(state);
926    }
927}
928
929impl Deref for AppPath {
930    type Target = Path;
931
932    /// Provides direct access to the underlying `Path` through deref coercion.
933    ///
934    /// This allows `AppPath` to be used directly with any API that expects a `&Path`,
935    /// making it a zero-cost abstraction in many contexts. All `Path` methods become
936    /// directly available on `AppPath` instances.
937    ///
938    /// # Examples
939    ///
940    /// ```rust
941    /// use app_path::AppPath;
942    ///
943    /// let app_path = AppPath::new("config.toml");
944    ///
945    /// // Direct access to Path methods through deref
946    /// assert_eq!(app_path.extension(), Some("toml".as_ref()));
947    /// assert_eq!(app_path.file_name(), Some("config.toml".as_ref()));
948    ///
949    /// // Works with functions expecting &Path
950    /// fn process_path(path: &std::path::Path) {
951    ///     println!("Processing: {}", path.display());
952    /// }
953    ///
954    /// process_path(&app_path); // Automatic deref coercion
955    /// ```
956    #[inline]
957    fn deref(&self) -> &Self::Target {
958        &self.full_path
959    }
960}
961
962impl Borrow<Path> for AppPath {
963    /// Allows `AppPath` to be borrowed as a `Path`.
964    ///
965    /// This enables `AppPath` to be used seamlessly in collections that are
966    /// keyed by `Path`, and allows for efficient lookups using `&Path` values.
967    ///
968    /// # Examples
969    ///
970    /// ```rust
971    /// use app_path::AppPath;
972    /// use std::collections::HashMap;
973    /// use std::path::Path;
974    ///
975    /// let mut path_map = HashMap::new();
976    /// let app_path = AppPath::new("config.toml");
977    /// path_map.insert(app_path, "config data");
978    ///
979    /// // Can look up using a &Path
980    /// let lookup_path = Path::new("relative/to/exe/config.toml");
981    /// // Note: This would only work if the paths actually match
982    /// ```
983    #[inline]
984    fn borrow(&self) -> &Path {
985        &self.full_path
986    }
987}
988
989impl From<&PathBuf> for AppPath {
990    #[inline]
991    fn from(path: &PathBuf) -> Self {
992        Self::new(path)
993    }
994}
995
996#[cfg(test)]
997mod tests;