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;