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}