app_path/
app_path.rs

1use std::path::{Path, PathBuf};
2
3use crate::error::AppPathError;
4use crate::functions::try_exe_dir;
5
6/// Creates paths relative to the executable location for portable applications.
7///
8/// **AppPath** enables building truly portable applications where configuration, data,
9/// and executable stay together as a deployable unit. Perfect for USB drives, network
10/// shares, or any directory without installation.
11///
12/// ## Key Features
13///
14/// - **Portable**: Relative paths resolve to executable directory
15/// - **System integration**: Absolute paths work as-is  
16/// - **Zero-cost**: Implements `Deref<Target=Path>` and all path traits
17/// - **Thread-safe**: Static caching with proper synchronization
18/// - **Memory efficient**: Only stores the final resolved path
19///
20/// ## API Overview
21///
22/// - [`Self::new()`] - **Primary API**: Simple, infallible construction
23/// - [`Self::try_new()`] - **Libraries**: Fallible version for error handling  
24/// - [`Self::with_override()`] - **Deployment**: Environment-configurable paths
25/// - [`Self::path()`] - **Access**: Get the resolved `&Path`
26///
27/// # Panics
28///
29/// Methods panic if executable location cannot be determined (extremely rare).
30/// After first successful call, methods never panic (uses cached result).
31///
32/// # Examples
33///
34/// ```rust
35/// use app_path::AppPath;
36///
37/// // Basic usage - most common pattern
38/// let config = AppPath::new("config.toml");
39/// let data = AppPath::new("data/users.db");
40///
41/// // Works like standard paths
42/// if config.exists() {
43///     let content = std::fs::read_to_string(&config);
44/// }
45/// data.ensure_parent_dirs(); // Creates data/ directory for the file
46///
47/// // Mixed portable and system paths
48/// let portable = AppPath::new("app.conf");           // → exe_dir/app.conf
49/// let system = AppPath::new("/var/log/app.log");     // → /var/log/app.log
50///
51/// // Override for deployment flexibility
52/// let config = AppPath::with_override(
53///     "config.toml",
54///     std::env::var("CONFIG_PATH").ok()
55/// );
56/// ```
57#[derive(Clone, Debug)]
58pub struct AppPath {
59    full_path: PathBuf,
60}
61
62impl AppPath {
63    /// Creates file paths relative to the executable location.
64    ///
65    /// **Recommended for most applications.** This is the simple, infallible API that handles
66    /// the common case cleanly without error handling boilerplate.
67    ///
68    /// ## Path Resolution
69    ///
70    /// - **Relative paths**: `"config.toml"` → `exe_dir/config.toml` (portable)
71    /// - **Absolute paths**: `"/etc/config"` → `/etc/config` (system integration)
72    ///
73    /// ## Performance
74    ///
75    /// - **Static caching**: Executable location determined once, reused forever
76    /// - **Zero allocations**: Efficient path resolution
77    /// - **Thread-safe**: Safe to call from multiple threads
78    ///
79    /// # Panics
80    ///
81    /// Panics if executable location cannot be determined (extremely rare):
82    /// - `std::env::current_exe()` fails
83    /// - Executable path is empty (system corruption)
84    ///
85    /// After first successful call, this method never panics (uses cached result).
86    ///
87    /// # Examples
88    ///
89    /// ```rust
90    /// use app_path::AppPath;
91    ///
92    /// // Most common usage
93    /// let config = AppPath::new("config.toml");
94    /// let data = AppPath::new("data/users.db");
95    /// let logs = AppPath::new("logs/app.log");
96    ///
97    /// // Mixed portable and system paths
98    /// let app_config = AppPath::new("config.toml");           // → exe_dir/config.toml
99    /// let system_log = AppPath::new("/var/log/myapp.log");    // → /var/log/myapp.log
100    ///
101    /// // Use like normal paths
102    /// if config.exists() {
103    ///     let content = std::fs::read_to_string(&config);
104    /// }
105    /// data.ensure_parent_dirs(); // Creates data/ directory for the file
106    /// ```
107    ///
108    /// # Panics
109    ///
110    /// Panics on first use if the executable location cannot be determined.
111    /// This is extremely rare and indicates fundamental system issues.
112    /// See [`AppPathError`] for details on the possible failure conditions.
113    ///
114    /// After the first successful call, this method will never panic as it uses the cached result.
115    ///
116    /// # Performance
117    ///
118    /// This method is highly optimized:
119    /// - **Static caching**: Executable location determined once, reused forever
120    /// - **Zero allocations**: Uses `AsRef<Path>` to avoid unnecessary conversions
121    /// - **Minimal memory**: Only stores the final resolved path
122    /// - **Thread-safe**: Safe to call from multiple threads concurrently
123    ///
124    /// # Examples
125    ///
126    /// ```rust
127    /// use app_path::AppPath;
128    ///
129    /// // Most common usage
130    /// let config = AppPath::new("config.toml");
131    /// let data = AppPath::new("data/users.db");
132    /// let logs = AppPath::new("logs/app.log");
133    ///
134    /// // Mixed portable and system paths
135    /// let app_config = AppPath::new("config.toml");           // → exe_dir/config.toml
136    /// let system_log = AppPath::new("/var/log/myapp.log");    // → /var/log/myapp.log
137    ///
138    /// // Use like normal paths
139    /// if config.exists() {
140    ///     let content = std::fs::read_to_string(&config);
141    /// }
142    /// data.ensure_parent_dirs(); // Creates data/ directory for the file
143    /// ```
144    #[inline]
145    pub fn new(path: impl AsRef<Path>) -> Self {
146        match Self::try_new(path) {
147            Ok(app_path) => app_path,
148            Err(e) => panic!("Failed to create AppPath: {e}"),
149        }
150    }
151
152    /// Creates file paths relative to the executable location (fallible).
153    ///
154    /// **Use this only for libraries or specialized applications requiring explicit error handling.**
155    /// Most applications should use [`Self::new()`] instead for simpler, cleaner code.
156    ///
157    /// ## When to Use
158    ///
159    /// **Use `try_new()` for:**
160    /// - Reusable libraries that shouldn't panic
161    /// - System tools with fallback strategies
162    /// - Applications running in unusual environments
163    ///
164    /// **Use [`Self::new()`] for:**
165    /// - Desktop, web, server, CLI applications
166    /// - When you want simple, clean code (recommended)
167    ///
168    /// # Examples
169    ///
170    /// ```rust
171    /// use app_path::{AppPath, AppPathError};
172    ///
173    /// // Library with graceful error handling
174    /// fn load_config() -> Result<String, AppPathError> {
175    ///     let config_path = AppPath::try_new("config.toml")?;
176    ///     // Load configuration...
177    ///     Ok("config loaded".to_string())
178    /// }
179    ///
180    /// // Better: Use override API for environment variables
181    /// fn load_config_with_override() -> Result<String, AppPathError> {
182    ///     let config_path = AppPath::try_with_override(
183    ///         "config.toml",
184    ///         std::env::var("APP_CONFIG").ok()
185    ///     )?;
186    ///     // Load configuration...
187    ///     Ok("config loaded".to_string())
188    /// }
189    ///
190    /// // Multiple environment variable fallback (better approach)
191    /// fn get_config_with_fallback() -> Result<AppPath, AppPathError> {
192    ///     AppPath::try_with_override_fn("config.toml", || {
193    ///         std::env::var("APP_CONFIG").ok()
194    ///             .or_else(|| std::env::var("CONFIG_FILE").ok())
195    ///             .or_else(|| std::env::var("XDG_CONFIG_HOME").ok().map(|dir| format!("{}/myapp/config.toml", dir)))
196    ///     })
197    /// }
198    /// ```
199    ///
200    /// **Reality check:** Executable location determination failing is extremely rare:
201    /// - It requires fundamental system issues or unusual deployment scenarios
202    /// - When it happens, it usually indicates unrecoverable system problems
203    /// - Most applications can't meaningfully continue without knowing their location
204    /// - The error handling overhead isn't worth it for typical applications
205    ///
206    /// **Better approaches for most applications:**
207    /// ```rust
208    /// use app_path::AppPath;
209    /// use std::env;
210    ///
211    /// // Use our override API for environment variables (recommended)
212    /// fn get_config_path() -> AppPath {
213    ///     AppPath::with_override("config.toml", env::var("MYAPP_CONFIG_DIR").ok())
214    /// }
215    ///
216    /// // Or fallible version for libraries
217    /// fn try_get_config_path() -> Result<AppPath, app_path::AppPathError> {
218    ///     AppPath::try_with_override("config.toml", env::var("MYAPP_CONFIG_DIR").ok())
219    /// }
220    /// ```
221    ///
222    /// ## Global Caching Behavior
223    ///
224    /// Once the executable directory is successfully determined by either this method or [`AppPath::new()`],
225    /// the result is cached globally and all subsequent calls to both methods will use the cached value.
226    /// This means that after the first successful call, `try_new()` will never return an error.
227    ///
228    /// # Arguments
229    ///
230    /// * `path` - A path that will be resolved according to AppPath's resolution strategy.
231    ///   Accepts any type implementing [`AsRef<Path>`].
232    ///
233    /// # Returns
234    ///
235    /// * `Ok(AppPath)` - Successfully created AppPath with resolved path
236    /// * `Err(AppPathError)` - Failed to determine executable location (extremely rare)
237    ///
238    /// # Examples
239    ///
240    /// ## Library Error Handling
241    ///
242    /// ```rust
243    /// use app_path::{AppPath, AppPathError};
244    ///
245    /// // Library function that returns Result instead of panicking
246    /// pub fn create_config_manager() -> Result<ConfigManager, AppPathError> {
247    ///     let config_path = AppPath::try_new("config.toml")?;
248    ///     Ok(ConfigManager::new(config_path))
249    /// }
250    ///
251    /// pub struct ConfigManager {
252    ///     config_path: AppPath,
253    /// }
254    ///
255    /// impl ConfigManager {
256    ///     fn new(config_path: AppPath) -> Self {
257    ///         Self { config_path }
258    ///     }
259    /// }
260    /// ```
261    ///
262    /// ## Error Propagation Pattern
263    ///
264    /// ```rust
265    /// use app_path::{AppPath, AppPathError};
266    ///
267    /// fn initialize_app() -> Result<(), Box<dyn std::error::Error>> {
268    ///     let config = AppPath::try_new("config.toml")?;
269    ///     let data = AppPath::try_new("data/app.db")?;
270    ///     
271    ///     // Initialize application with these paths
272    ///     println!("Config: {}", config.path().display());
273    ///     println!("Data: {}", data.path().display());
274    ///     
275    ///     Ok(())
276    /// }
277    /// ```
278    #[inline]
279    pub fn try_new(path: impl AsRef<Path>) -> Result<Self, AppPathError> {
280        let exe_dir = try_exe_dir()?;
281        let full_path = exe_dir.join(path);
282        Ok(Self { full_path })
283    }
284
285    /// Get the full resolved path.
286    ///
287    /// This is the primary method for getting the actual filesystem path
288    /// where your file or directory is located.
289    ///
290    /// # Examples
291    ///
292    /// ```rust
293    /// use app_path::AppPath;
294    ///
295    /// let config = AppPath::new("config.toml");
296    ///
297    /// // Get the path for use with standard library functions
298    /// println!("Config path: {}", config.path().display());
299    ///
300    /// // The path is always absolute
301    /// assert!(config.path().is_absolute());
302    /// ```
303    #[inline]
304    pub fn path(&self) -> &Path {
305        &self.full_path
306    }
307
308    /// Check if the path exists.
309    ///
310    /// # Examples
311    ///
312    /// ```rust
313    /// use app_path::AppPath;
314    ///
315    /// let config = AppPath::new("config.toml");
316    ///
317    /// if config.exists() {
318    ///     println!("Config file found!");
319    /// } else {
320    ///     println!("Config file not found, using defaults.");
321    /// }
322    /// ```
323    #[inline]
324    pub fn exists(&self) -> bool {
325        self.full_path.exists()
326    }
327
328    /// Creates parent directories needed for this file path.
329    ///
330    /// This method creates all parent directories for a file path, making it ready
331    /// for file creation. It does not create the file itself.
332    ///
333    /// **Use this when you know the path represents a file and you want to prepare
334    /// the directory structure for writing the file.**
335    ///
336    /// # Examples
337    ///
338    /// ```rust
339    /// use app_path::AppPath;
340    /// use std::env;
341    ///
342    /// let temp_dir = env::temp_dir().join("app_path_example");
343    ///
344    /// // Prepare directories for a config file
345    /// let config_file = AppPath::new(temp_dir.join("config/app.toml"));
346    /// config_file.ensure_parent_dirs()?; // Creates config/ directory
347    ///
348    /// // Now you can write the file
349    /// std::fs::write(config_file.path(), "key = value")?;
350    /// assert!(config_file.exists());
351    ///
352    /// // Prepare directories for a log file
353    /// let log_file = AppPath::new(temp_dir.join("logs/2024/app.log"));
354    /// log_file.ensure_parent_dirs()?; // Creates logs/2024/ directories
355    ///
356    /// # std::fs::remove_dir_all(&temp_dir).ok();
357    /// # Ok::<(), Box<dyn std::error::Error>>(())
358    /// ```
359    #[inline]
360    pub fn ensure_parent_dirs(&self) -> std::io::Result<()> {
361        if let Some(parent) = self.parent() {
362            std::fs::create_dir_all(parent.path())
363        } else {
364            Ok(())
365        }
366    }
367
368    /// Creates this path as a directory, including all parent directories.
369    ///
370    /// This method treats the path as a directory and creates it along with
371    /// all necessary parent directories. The created directory will exist
372    /// after this call succeeds.
373    ///
374    /// **Use this when you know the path represents a directory that should be created.**
375    ///
376    /// # Examples
377    ///
378    /// ```rust
379    /// use app_path::AppPath;
380    /// use std::env;
381    ///
382    /// let temp_dir = env::temp_dir().join("app_path_dir_example");
383    ///
384    /// // Create a cache directory
385    /// let cache_dir = AppPath::new(temp_dir.join("cache"));
386    /// cache_dir.ensure_dir_exists()?; // Creates cache/ directory
387    /// assert!(cache_dir.exists());
388    /// assert!(cache_dir.is_dir());
389    ///
390    /// // Create nested directories
391    /// let deep_dir = AppPath::new(temp_dir.join("data/backups/daily"));
392    /// deep_dir.ensure_dir_exists()?; // Creates data/backups/daily/ directories
393    /// assert!(deep_dir.exists());
394    /// assert!(deep_dir.is_dir());
395    ///
396    /// # std::fs::remove_dir_all(&temp_dir).ok();
397    /// # Ok::<(), Box<dyn std::error::Error>>(())
398    /// ```
399    #[inline]
400    pub fn ensure_dir_exists(&self) -> std::io::Result<()> {
401        std::fs::create_dir_all(self.path())
402    }
403
404    /// Creates all directories needed for this path.
405    ///
406    /// **DEPRECATED**: Use [`ensure_parent_dirs()`](Self::ensure_parent_dirs) for file paths
407    /// or [`ensure_dir_exists()`](Self::ensure_dir_exists) for directory paths instead.
408    /// This method name was confusing as it didn't always create directories for the path itself.
409    ///
410    /// This method intelligently determines whether the path represents a file
411    /// or directory and creates the appropriate directories:
412    /// - **For existing directories**: does nothing (already exists)
413    /// - **For existing files**: creates parent directories if needed
414    /// - **For non-existing paths**: treats as file path and creates parent directories
415    ///
416    /// # Migration Guide
417    ///
418    /// ```rust
419    /// use app_path::AppPath;
420    ///
421    /// let file_path = AppPath::new("logs/app.log");
422    /// let dir_path = AppPath::new("cache");
423    ///
424    /// // Old (deprecated):
425    /// // file_path.create_dir_all()?;
426    /// // dir_path.create_dir_all()?; // This was confusing!
427    ///
428    /// // New (clear):
429    /// file_path.ensure_parent_dirs()?; // Creates logs/ for the file
430    /// dir_path.ensure_dir_exists()?;   // Creates cache/ directory
431    /// # Ok::<(), Box<dyn std::error::Error>>(())
432    /// ```
433    #[deprecated(
434        since = "0.2.2",
435        note = "Use `ensure_parent_dirs()` for file paths or `ensure_dir_exists()` for directory paths instead"
436    )]
437    #[inline]
438    pub fn create_dir_all(&self) -> std::io::Result<()> {
439        if let Some(parent) = self.full_path.parent() {
440            std::fs::create_dir_all(parent)?;
441        }
442        Ok(())
443    }
444
445    /// Creates a path with override support (infallible).
446    ///
447    /// This method provides a one-line solution for creating paths that can be overridden
448    /// by external configuration. If an override is provided, it takes precedence over
449    /// the default path. Otherwise, the default path is used with normal AppPath resolution.
450    ///
451    /// **This is the primary method for implementing configurable paths in applications.**
452    /// It combines the simplicity of [`AppPath::new()`] with the flexibility of external
453    /// configuration overrides.
454    ///
455    /// ## Common Use Cases
456    ///
457    /// - **Environment variable overrides**: Allow users to customize file locations
458    /// - **Command-line argument overrides**: CLI tools with configurable paths
459    ///
460    /// ## How It Works
461    ///
462    /// **If override is provided**: Use the override path directly (can be relative or absolute)
463    /// **If override is `None`**: Use the default path with normal AppPath resolution
464    ///
465    /// # Examples
466    ///
467    /// ```rust
468    /// use app_path::AppPath;
469    /// use std::env;
470    ///
471    /// // Environment variable override
472    /// let config = AppPath::with_override(
473    ///     "config.toml",
474    ///     env::var("APP_CONFIG").ok()
475    /// );
476    ///
477    /// // CLI argument override
478    /// fn get_config(cli_override: Option<&str>) -> AppPath {
479    ///     AppPath::with_override("config.toml", cli_override)
480    /// }
481    ///
482    /// // Configuration file override
483    /// struct Config {
484    ///     data_dir: Option<String>,
485    /// }
486    ///
487    /// let config = load_config();
488    /// let data_dir = AppPath::with_override("data", config.data_dir.as_deref());
489    /// # fn load_config() -> Config { Config { data_dir: None } }
490    /// ```
491    #[inline]
492    pub fn with_override(
493        default: impl AsRef<Path>,
494        override_option: Option<impl AsRef<Path>>,
495    ) -> Self {
496        match override_option {
497            Some(override_path) => Self::new(override_path),
498            None => Self::new(default),
499        }
500    }
501
502    /// Creates a path with dynamic override support.
503    ///
504    /// **Use this for complex override logic or lazy evaluation.** The closure is called once
505    /// to determine if an override should be applied.
506    ///
507    /// # Examples
508    ///
509    /// ```rust
510    /// use app_path::AppPath;
511    /// use std::env;
512    ///
513    /// // Multiple fallback sources
514    /// let config = AppPath::with_override_fn("config.toml", || {
515    ///     env::var("APP_CONFIG").ok()
516    ///         .or_else(|| env::var("CONFIG_FILE").ok())
517    ///         .or_else(|| {
518    ///             // Only check expensive operations if needed
519    ///             if env::var("USE_SYSTEM_CONFIG").is_ok() {
520    ///                 Some("/etc/myapp/config.toml".to_string())
521    ///             } else {
522    ///                 None
523    ///             }
524    ///         })
525    /// });
526    ///
527    /// // Development mode override
528    /// let data_dir = AppPath::with_override_fn("data", || {
529    ///     if env::var("DEVELOPMENT").is_ok() {
530    ///         Some("dev_data".to_string())
531    ///     } else {
532    ///         None
533    ///     }
534    /// });
535    /// ```
536    #[inline]
537    pub fn with_override_fn<F, P>(default: impl AsRef<Path>, override_fn: F) -> Self
538    where
539        F: FnOnce() -> Option<P>,
540        P: AsRef<Path>,
541    {
542        match override_fn() {
543            Some(override_path) => Self::new(override_path),
544            None => Self::new(default),
545        }
546    }
547
548    /// Creates a path with override support (fallible).
549    ///
550    /// **Fallible version of [`Self::with_override()`].** Most applications should use the
551    /// infallible version instead for cleaner code.
552    ///
553    /// # Examples
554    ///
555    /// ```rust
556    /// use app_path::{AppPath, AppPathError};
557    /// use std::env;
558    ///
559    /// fn get_config() -> Result<AppPath, AppPathError> {
560    ///     AppPath::try_with_override("config.toml", env::var("CONFIG").ok())
561    /// }
562    /// ```
563    /// cleaner, more idiomatic code.
564    ///
565    /// ## When to Use This Method
566    ///
567    /// - **Reusable libraries** that should handle errors gracefully
568    /// - **System-level tools** that need to handle broken environments
569    /// - **Applications with custom fallback strategies** for rare edge cases
570    ///
571    /// See [`AppPath::try_new()`] for detailed guidance on when to use fallible APIs.
572    ///
573    /// # Arguments
574    ///
575    /// * `default` - The default path to use if no override is provided
576    /// * `override_option` - Optional override path that takes precedence if provided
577    ///
578    /// # Returns
579    ///
580    /// * `Ok(AppPath)` - Successfully created AppPath with resolved path
581    /// * `Err(AppPathError)` - Failed to determine executable location
582    ///
583    /// # Examples
584    ///
585    /// ## Library with Error Handling
586    ///
587    /// ```rust
588    /// use app_path::{AppPath, AppPathError};
589    /// use std::env;
590    ///
591    /// fn create_config_path() -> Result<AppPath, AppPathError> {
592    ///     let config_override = env::var("MYAPP_CONFIG").ok();
593    ///     AppPath::try_with_override("config.toml", config_override.as_deref())
594    /// }
595    /// ```
596    ///
597    /// ## Error Propagation
598    ///
599    /// ```rust
600    /// use app_path::{AppPath, AppPathError};
601    ///
602    /// fn setup_paths(config_override: Option<&str>) -> Result<(AppPath, AppPath), AppPathError> {
603    ///     let config = AppPath::try_with_override("config.toml", config_override)?;
604    ///     let data = AppPath::try_with_override("data", None::<&str>)?;
605    ///     Ok((config, data))
606    /// }
607    /// ```
608    #[inline]
609    pub fn try_with_override(
610        default: impl AsRef<Path>,
611        override_option: Option<impl AsRef<Path>>,
612    ) -> Result<Self, AppPathError> {
613        match override_option {
614            Some(override_path) => Self::try_new(override_path),
615            None => Self::try_new(default),
616        }
617    }
618
619    /// Creates a path with dynamic override support (fallible).
620    ///
621    /// This is the fallible version of [`AppPath::with_override_fn()`]. Use this method
622    /// when you need explicit error handling combined with dynamic override logic.
623    ///
624    /// **Most applications should use [`AppPath::with_override_fn()`] instead** for
625    /// cleaner, more idiomatic code.
626    ///
627    /// ## When to Use This Method
628    ///
629    /// - **Reusable libraries** with complex override logic that should handle errors gracefully
630    /// - **System-level tools** with dynamic configuration that need to handle broken environments
631    /// - **Applications with custom fallback strategies** for rare edge cases
632    ///
633    /// See [`AppPath::try_new()`] for detailed guidance on when to use fallible APIs.
634    ///
635    /// # Arguments
636    ///
637    /// * `default` - The default path to use if the override function returns `None`
638    /// * `override_fn` - A function that returns an optional override path
639    ///
640    /// # Returns
641    ///
642    /// * `Ok(AppPath)` - Successfully created AppPath with resolved path
643    /// * `Err(AppPathError)` - Failed to determine executable location
644    ///
645    /// # Examples
646    ///
647    /// ## Library with Complex Override Logic
648    ///
649    /// ```rust
650    /// use app_path::{AppPath, AppPathError};
651    /// use std::env;
652    ///
653    /// fn create_data_path() -> Result<AppPath, AppPathError> {
654    ///     AppPath::try_with_override_fn("data", || {
655    ///         // Complex override logic with multiple sources
656    ///         env::var("DATA_DIR").ok()
657    ///             .or_else(|| env::var("MYAPP_DATA_DIR").ok())
658    ///             .or_else(|| {
659    ///                 if env::var("DEVELOPMENT").is_ok() {
660    ///                     Some("dev_data".to_string())
661    ///                 } else {
662    ///                     None
663    ///                 }
664    ///             })
665    ///     })
666    /// }
667    /// ```
668    ///
669    /// ## Error Propagation with Dynamic Logic
670    ///
671    /// ```rust
672    /// use app_path::{AppPath, AppPathError};
673    ///
674    /// fn setup_logging() -> Result<AppPath, AppPathError> {
675    ///     AppPath::try_with_override_fn("logs/app.log", || {
676    ///         // Dynamic override based on multiple conditions
677    ///         if std::env::var("SYSLOG").is_ok() {
678    ///             Some("/var/log/myapp.log".to_string())
679    ///         } else if std::env::var("LOG_TO_TEMP").is_ok() {
680    ///             Some(std::env::temp_dir().join("myapp.log").to_string_lossy().into_owned())
681    ///         } else {
682    ///             None
683    ///         }
684    ///     })
685    /// }
686    /// ```
687    #[inline]
688    pub fn try_with_override_fn<F, P>(
689        default: impl AsRef<Path>,
690        override_fn: F,
691    ) -> Result<Self, AppPathError>
692    where
693        F: FnOnce() -> Option<P>,
694        P: AsRef<Path>,
695    {
696        match override_fn() {
697            Some(override_path) => Self::try_new(override_path),
698            None => Self::try_new(default),
699        }
700    }
701
702    /// Joins additional path segments to create a new AppPath.
703    ///
704    /// This creates a new `AppPath` by joining the current path with additional segments.
705    /// The new path inherits the same resolution behavior as the original.
706    ///
707    /// # Examples
708    ///
709    /// ```rust
710    /// use app_path::AppPath;
711    ///
712    /// let data_dir = AppPath::new("data");
713    /// let users_db = data_dir.join("users.db");
714    /// let backups = data_dir.join("backups").join("daily");
715    ///
716    /// // Chain operations for complex paths
717    /// let log_file = AppPath::new("logs")
718    ///     .join("2024")
719    ///     .join("app.log");
720    /// ```
721    #[inline]
722    pub fn join(&self, path: impl AsRef<Path>) -> Self {
723        Self::new(self.full_path.join(path))
724    }
725
726    /// Returns the parent directory as an AppPath, if it exists.
727    ///
728    /// Returns `None` if this path is a root directory or has no parent.
729    ///
730    /// # Examples
731    ///
732    /// ```rust
733    /// use app_path::AppPath;    ///
734    /// let config = AppPath::new("config/app.toml");
735    /// let config_dir = config.parent().unwrap();
736    ///
737    /// let logs_dir = AppPath::new("logs");
738    /// let _app_dir = logs_dir.parent(); // Points to exe directory
739    /// ```
740    #[inline]
741    pub fn parent(&self) -> Option<Self> {
742        self.full_path.parent().map(Self::new)
743    }
744
745    /// Creates a new AppPath with the specified file extension.
746    ///
747    /// If the path has an existing extension, it will be replaced.
748    /// If no extension exists, the new extension will be added.
749    ///
750    /// # Examples
751    ///
752    /// ```rust
753    /// use app_path::AppPath;
754    ///
755    /// let config = AppPath::new("config");
756    /// let config_toml = config.with_extension("toml");
757    /// let config_json = config.with_extension("json");
758    ///
759    /// let log_file = AppPath::new("app.log");
760    /// let backup_file = log_file.with_extension("bak");
761    /// ```
762    #[inline]
763    pub fn with_extension(&self, ext: &str) -> Self {
764        Self::new(self.full_path.with_extension(ext))
765    }
766
767    /// Returns the file name of this path as an `OsStr`, if it exists.
768    ///
769    /// This is a convenience method that delegates to the underlying `Path`.
770    ///
771    /// # Examples
772    ///
773    /// ```rust
774    /// use app_path::AppPath;
775    ///
776    /// let config = AppPath::new("config/app.toml");
777    /// assert_eq!(config.file_name().unwrap(), "app.toml");
778    /// ```
779    #[inline]
780    pub fn file_name(&self) -> Option<&std::ffi::OsStr> {
781        self.full_path.file_name()
782    }
783
784    /// Returns the file stem of this path as an `OsStr`, if it exists.
785    ///
786    /// This is a convenience method that delegates to the underlying `Path`.
787    ///
788    /// # Examples
789    ///
790    /// ```rust
791    /// use app_path::AppPath;
792    ///
793    /// let config = AppPath::new("config/app.toml");
794    /// assert_eq!(config.file_stem().unwrap(), "app");
795    /// ```
796    #[inline]
797    pub fn file_stem(&self) -> Option<&std::ffi::OsStr> {
798        self.full_path.file_stem()
799    }
800
801    /// Returns the extension of this path as an `OsStr`, if it exists.
802    ///
803    /// This is a convenience method that delegates to the underlying `Path`.
804    ///
805    /// # Examples
806    ///
807    /// ```rust
808    /// use app_path::AppPath;
809    ///
810    /// let config = AppPath::new("config/app.toml");
811    /// assert_eq!(config.extension().unwrap(), "toml");
812    /// ```
813    #[inline]
814    pub fn extension(&self) -> Option<&std::ffi::OsStr> {
815        self.full_path.extension()
816    }
817
818    /// Returns `true` if this path points to a directory.
819    ///
820    /// This is a convenience method that delegates to the underlying `Path`.
821    ///
822    /// # Examples
823    ///
824    /// ```rust
825    /// use app_path::AppPath;
826    ///
827    /// let data_dir = AppPath::new("data");
828    /// if data_dir.is_dir() {
829    ///     println!("Data directory exists");
830    /// }
831    /// ```
832    #[inline]
833    pub fn is_dir(&self) -> bool {
834        self.full_path.is_dir()
835    }
836
837    /// Returns `true` if this path points to a regular file.
838    ///
839    /// This is a convenience method that delegates to the underlying `Path`.
840    ///
841    /// # Examples
842    ///
843    /// ```rust
844    /// use app_path::AppPath;
845    ///
846    /// let config = AppPath::new("config.toml");
847    /// if config.is_file() {
848    ///     println!("Config file exists");
849    /// }
850    /// ```
851    #[inline]
852    pub fn is_file(&self) -> bool {
853        self.full_path.is_file()
854    }
855}