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 /// # Behavior
377 ///
378 /// - **Creates the directory itself**: Unlike `ensure_parent_dirs()`, this creates the full path as a directory
379 /// - **Creates all parents**: Any missing parent directories are created automatically
380 /// - **Idempotent**: Safe to call multiple times - won't fail if directory already exists
381 /// - **Atomic-like**: Either all directories are created or the operation fails
382 ///
383 /// # Examples
384 ///
385 /// ## Basic Directory Creation
386 ///
387 /// ```rust
388 /// use app_path::AppPath;
389 /// use std::env;
390 ///
391 /// let temp_dir = env::temp_dir().join("app_path_dir_example");
392 ///
393 /// // Create a cache directory
394 /// let cache_dir = AppPath::new(temp_dir.join("cache"));
395 /// cache_dir.ensure_dir_exists()?; // Creates cache/ directory
396 /// assert!(cache_dir.exists());
397 /// assert!(cache_dir.is_dir());
398 ///
399 /// # std::fs::remove_dir_all(&temp_dir).ok();
400 /// # Ok::<(), Box<dyn std::error::Error>>(())
401 /// ```
402 ///
403 /// ## Nested Directory Structures
404 ///
405 /// ```rust
406 /// use app_path::AppPath;
407 /// use std::env;
408 ///
409 /// let temp_dir = env::temp_dir().join("app_path_nested_example");
410 ///
411 /// // Create deeply nested directories
412 /// let deep_dir = AppPath::new(temp_dir.join("data/backups/daily"));
413 /// deep_dir.ensure_dir_exists()?; // Creates data/backups/daily/ directories
414 /// assert!(deep_dir.exists());
415 /// assert!(deep_dir.is_dir());
416 ///
417 /// // All parent directories are also created
418 /// let backups_dir = AppPath::new(temp_dir.join("data/backups"));
419 /// assert!(backups_dir.exists());
420 /// assert!(backups_dir.is_dir());
421 ///
422 /// # std::fs::remove_dir_all(&temp_dir).ok();
423 /// # Ok::<(), Box<dyn std::error::Error>>(())
424 /// ```
425 ///
426 /// ## Practical Application Setup
427 ///
428 /// ```rust
429 /// use app_path::AppPath;
430 /// use std::env;
431 ///
432 /// let temp_dir = env::temp_dir().join("app_setup_example");
433 ///
434 /// // Set up application directory structure
435 /// let config_dir = AppPath::new(temp_dir.join("config"));
436 /// let data_dir = AppPath::new(temp_dir.join("data"));
437 /// let cache_dir = AppPath::new(temp_dir.join("cache"));
438 /// let logs_dir = AppPath::new(temp_dir.join("logs"));
439 ///
440 /// // Create all directories
441 /// config_dir.ensure_dir_exists()?;
442 /// data_dir.ensure_dir_exists()?;
443 /// cache_dir.ensure_dir_exists()?;
444 /// logs_dir.ensure_dir_exists()?;
445 ///
446 /// // Now create subdirectories
447 /// let daily_logs = logs_dir.join("daily");
448 /// daily_logs.ensure_dir_exists()?;
449 ///
450 /// // Verify structure
451 /// assert!(config_dir.is_dir());
452 /// assert!(data_dir.is_dir());
453 /// assert!(cache_dir.is_dir());
454 /// assert!(logs_dir.is_dir());
455 /// assert!(daily_logs.is_dir());
456 ///
457 /// # std::fs::remove_dir_all(&temp_dir).ok();
458 /// # Ok::<(), Box<dyn std::error::Error>>(())
459 /// ```
460 ///
461 /// ## Comparison with `ensure_parent_dirs()`
462 ///
463 /// ```rust
464 /// use app_path::AppPath;
465 /// use std::env;
466 ///
467 /// let temp_dir = env::temp_dir().join("app_comparison_example");
468 ///
469 /// let file_path = AppPath::new(temp_dir.join("logs/app.log"));
470 /// let dir_path = AppPath::new(temp_dir.join("logs"));
471 ///
472 /// // For files: prepare parent directories
473 /// file_path.ensure_parent_dirs()?; // Creates logs/ directory
474 /// assert!(dir_path.exists()); // logs/ directory exists
475 /// assert!(!file_path.exists()); // app.log file does NOT exist
476 ///
477 /// // For directories: create the directory itself
478 /// dir_path.ensure_dir_exists()?; // Creates logs/ directory (idempotent)
479 /// assert!(dir_path.exists()); // logs/ directory exists
480 /// assert!(dir_path.is_dir()); // and it's definitely a directory
481 ///
482 /// # std::fs::remove_dir_all(&temp_dir).ok();
483 /// # Ok::<(), Box<dyn std::error::Error>>(())
484 /// ```
485 #[inline]
486 pub fn ensure_dir_exists(&self) -> std::io::Result<()> {
487 std::fs::create_dir_all(self.path())
488 }
489
490 /// Creates all directories needed for this path.
491 ///
492 /// **DEPRECATED**: Use [`ensure_parent_dirs()`](Self::ensure_parent_dirs) for file paths
493 /// or [`ensure_dir_exists()`](Self::ensure_dir_exists) for directory paths instead.
494 /// This method name was confusing as it didn't always create directories for the path itself.
495 ///
496 /// This method intelligently determines whether the path represents a file
497 /// or directory and creates the appropriate directories:
498 /// - **For existing directories**: does nothing (already exists)
499 /// - **For existing files**: creates parent directories if needed
500 /// - **For non-existing paths**: treats as file path and creates parent directories
501 ///
502 /// # Migration Guide
503 ///
504 /// ```rust
505 /// use app_path::AppPath;
506 ///
507 /// let file_path = AppPath::new("logs/app.log");
508 /// let dir_path = AppPath::new("cache");
509 ///
510 /// // Old (deprecated):
511 /// // file_path.create_dir_all()?;
512 /// // dir_path.create_dir_all()?; // This was confusing!
513 ///
514 /// // New (clear):
515 /// file_path.ensure_parent_dirs()?; // Creates logs/ for the file
516 /// dir_path.ensure_dir_exists()?; // Creates cache/ directory
517 /// # Ok::<(), Box<dyn std::error::Error>>(())
518 /// ```
519 #[deprecated(
520 since = "0.2.2",
521 note = "Use `ensure_parent_dirs()` for file paths or `ensure_dir_exists()` for directory paths instead"
522 )]
523 #[inline]
524 pub fn create_dir_all(&self) -> std::io::Result<()> {
525 if let Some(parent) = self.full_path.parent() {
526 std::fs::create_dir_all(parent)?;
527 }
528 Ok(())
529 }
530
531 /// Creates a path with override support (infallible).
532 ///
533 /// This method provides a one-line solution for creating paths that can be overridden
534 /// by external configuration. If an override is provided, it takes precedence over
535 /// the default path. Otherwise, the default path is used with normal AppPath resolution.
536 ///
537 /// **This is the primary method for implementing configurable paths in applications.**
538 /// It combines the simplicity of [`AppPath::new()`] with the flexibility of external
539 /// configuration overrides.
540 ///
541 /// ## Common Use Cases
542 ///
543 /// - **Environment variable overrides**: Allow users to customize file locations
544 /// - **Command-line argument overrides**: CLI tools with configurable paths
545 ///
546 /// ## How It Works
547 ///
548 /// **If override is provided**: Use the override path directly (can be relative or absolute)
549 /// **If override is `None`**: Use the default path with normal AppPath resolution
550 ///
551 /// # Examples
552 ///
553 /// ```rust
554 /// use app_path::AppPath;
555 /// use std::env;
556 ///
557 /// // Environment variable override
558 /// let config = AppPath::with_override(
559 /// "config.toml",
560 /// env::var("APP_CONFIG").ok()
561 /// );
562 ///
563 /// // CLI argument override
564 /// fn get_config(cli_override: Option<&str>) -> AppPath {
565 /// AppPath::with_override("config.toml", cli_override)
566 /// }
567 ///
568 /// // Configuration file override
569 /// struct Config {
570 /// data_dir: Option<String>,
571 /// }
572 ///
573 /// let config = load_config();
574 /// let data_dir = AppPath::with_override("data", config.data_dir.as_deref());
575 /// # fn load_config() -> Config { Config { data_dir: None } }
576 /// ```
577 #[inline]
578 pub fn with_override(
579 default: impl AsRef<Path>,
580 override_option: Option<impl AsRef<Path>>,
581 ) -> Self {
582 match override_option {
583 Some(override_path) => Self::new(override_path),
584 None => Self::new(default),
585 }
586 }
587
588 /// Creates a path with dynamic override support.
589 ///
590 /// **Use this for complex override logic or lazy evaluation.** The closure is called once
591 /// to determine if an override should be applied.
592 ///
593 /// # Examples
594 ///
595 /// ```rust
596 /// use app_path::AppPath;
597 /// use std::env;
598 ///
599 /// // Multiple fallback sources
600 /// let config = AppPath::with_override_fn("config.toml", || {
601 /// env::var("APP_CONFIG").ok()
602 /// .or_else(|| env::var("CONFIG_FILE").ok())
603 /// .or_else(|| {
604 /// // Only check expensive operations if needed
605 /// if env::var("USE_SYSTEM_CONFIG").is_ok() {
606 /// Some("/etc/myapp/config.toml".to_string())
607 /// } else {
608 /// None
609 /// }
610 /// })
611 /// });
612 ///
613 /// // Development mode override
614 /// let data_dir = AppPath::with_override_fn("data", || {
615 /// if env::var("DEVELOPMENT").is_ok() {
616 /// Some("dev_data".to_string())
617 /// } else {
618 /// None
619 /// }
620 /// });
621 /// ```
622 #[inline]
623 pub fn with_override_fn<F, P>(default: impl AsRef<Path>, override_fn: F) -> Self
624 where
625 F: FnOnce() -> Option<P>,
626 P: AsRef<Path>,
627 {
628 match override_fn() {
629 Some(override_path) => Self::new(override_path),
630 None => Self::new(default),
631 }
632 }
633
634 /// Creates a path with override support (fallible).
635 ///
636 /// **Fallible version of [`Self::with_override()`].** Most applications should use the
637 /// infallible version instead for cleaner code.
638 ///
639 /// # Examples
640 ///
641 /// ```rust
642 /// use app_path::{AppPath, AppPathError};
643 /// use std::env;
644 ///
645 /// fn get_config() -> Result<AppPath, AppPathError> {
646 /// AppPath::try_with_override("config.toml", env::var("CONFIG").ok())
647 /// }
648 /// ```
649 /// cleaner, more idiomatic code.
650 ///
651 /// ## When to Use This Method
652 ///
653 /// - **Reusable libraries** that should handle errors gracefully
654 /// - **System-level tools** that need to handle broken environments
655 /// - **Applications with custom fallback strategies** for rare edge cases
656 ///
657 /// See [`AppPath::try_new()`] for detailed guidance on when to use fallible APIs.
658 ///
659 /// # Arguments
660 ///
661 /// * `default` - The default path to use if no override is provided
662 /// * `override_option` - Optional override path that takes precedence if provided
663 ///
664 /// # Returns
665 ///
666 /// * `Ok(AppPath)` - Successfully created AppPath with resolved path
667 /// * `Err(AppPathError)` - Failed to determine executable location
668 ///
669 /// # Examples
670 ///
671 /// ## Library with Error Handling
672 ///
673 /// ```rust
674 /// use app_path::{AppPath, AppPathError};
675 /// use std::env;
676 ///
677 /// fn create_config_path() -> Result<AppPath, AppPathError> {
678 /// let config_override = env::var("MYAPP_CONFIG").ok();
679 /// AppPath::try_with_override("config.toml", config_override.as_deref())
680 /// }
681 /// ```
682 ///
683 /// ## Error Propagation
684 ///
685 /// ```rust
686 /// use app_path::{AppPath, AppPathError};
687 ///
688 /// fn setup_paths(config_override: Option<&str>) -> Result<(AppPath, AppPath), AppPathError> {
689 /// let config = AppPath::try_with_override("config.toml", config_override)?;
690 /// let data = AppPath::try_with_override("data", None::<&str>)?;
691 /// Ok((config, data))
692 /// }
693 /// ```
694 #[inline]
695 pub fn try_with_override(
696 default: impl AsRef<Path>,
697 override_option: Option<impl AsRef<Path>>,
698 ) -> Result<Self, AppPathError> {
699 match override_option {
700 Some(override_path) => Self::try_new(override_path),
701 None => Self::try_new(default),
702 }
703 }
704
705 /// Creates a path with dynamic override support (fallible).
706 ///
707 /// This is the fallible version of [`AppPath::with_override_fn()`]. Use this method
708 /// when you need explicit error handling combined with dynamic override logic.
709 ///
710 /// **Most applications should use [`AppPath::with_override_fn()`] instead** for
711 /// cleaner, more idiomatic code.
712 ///
713 /// ## When to Use This Method
714 ///
715 /// - **Reusable libraries** with complex override logic that should handle errors gracefully
716 /// - **System-level tools** with dynamic configuration that need to handle broken environments
717 /// - **Applications with custom fallback strategies** for rare edge cases
718 ///
719 /// See [`AppPath::try_new()`] for detailed guidance on when to use fallible APIs.
720 ///
721 /// # Arguments
722 ///
723 /// * `default` - The default path to use if the override function returns `None`
724 /// * `override_fn` - A function that returns an optional override path
725 ///
726 /// # Returns
727 ///
728 /// * `Ok(AppPath)` - Successfully created AppPath with resolved path
729 /// * `Err(AppPathError)` - Failed to determine executable location
730 ///
731 /// # Examples
732 ///
733 /// ## Library with Complex Override Logic
734 ///
735 /// ```rust
736 /// use app_path::{AppPath, AppPathError};
737 /// use std::env;
738 ///
739 /// fn create_data_path() -> Result<AppPath, AppPathError> {
740 /// AppPath::try_with_override_fn("data", || {
741 /// // Complex override logic with multiple sources
742 /// env::var("DATA_DIR").ok()
743 /// .or_else(|| env::var("MYAPP_DATA_DIR").ok())
744 /// .or_else(|| {
745 /// if env::var("DEVELOPMENT").is_ok() {
746 /// Some("dev_data".to_string())
747 /// } else {
748 /// None
749 /// }
750 /// })
751 /// })
752 /// }
753 /// ```
754 ///
755 /// ## Error Propagation with Dynamic Logic
756 ///
757 /// ```rust
758 /// use app_path::{AppPath, AppPathError};
759 ///
760 /// fn setup_logging() -> Result<AppPath, AppPathError> {
761 /// AppPath::try_with_override_fn("logs/app.log", || {
762 /// // Dynamic override based on multiple conditions
763 /// if std::env::var("SYSLOG").is_ok() {
764 /// Some("/var/log/myapp.log".to_string())
765 /// } else if std::env::var("LOG_TO_TEMP").is_ok() {
766 /// Some(std::env::temp_dir().join("myapp.log").to_string_lossy().into_owned())
767 /// } else {
768 /// None
769 /// }
770 /// })
771 /// }
772 /// ```
773 #[inline]
774 pub fn try_with_override_fn<F, P>(
775 default: impl AsRef<Path>,
776 override_fn: F,
777 ) -> Result<Self, AppPathError>
778 where
779 F: FnOnce() -> Option<P>,
780 P: AsRef<Path>,
781 {
782 match override_fn() {
783 Some(override_path) => Self::try_new(override_path),
784 None => Self::try_new(default),
785 }
786 }
787
788 /// Joins additional path segments to create a new AppPath.
789 ///
790 /// This creates a new `AppPath` by joining the current path with additional segments.
791 /// The new path inherits the same resolution behavior as the original.
792 ///
793 /// # Examples
794 ///
795 /// ```rust
796 /// use app_path::AppPath;
797 ///
798 /// let data_dir = AppPath::new("data");
799 /// let users_db = data_dir.join("users.db");
800 /// let backups = data_dir.join("backups").join("daily");
801 ///
802 /// // Chain operations for complex paths
803 /// let log_file = AppPath::new("logs")
804 /// .join("2024")
805 /// .join("app.log");
806 /// ```
807 #[inline]
808 pub fn join(&self, path: impl AsRef<Path>) -> Self {
809 Self::new(self.full_path.join(path))
810 }
811
812 /// Returns the parent directory as an AppPath, if it exists.
813 ///
814 /// Returns `None` if this path is a root directory or has no parent.
815 ///
816 /// # Examples
817 ///
818 /// ```rust
819 /// use app_path::AppPath; ///
820 /// let config = AppPath::new("config/app.toml");
821 /// let config_dir = config.parent().unwrap();
822 ///
823 /// let logs_dir = AppPath::new("logs");
824 /// let _app_dir = logs_dir.parent(); // Points to exe directory
825 /// ```
826 #[inline]
827 pub fn parent(&self) -> Option<Self> {
828 self.full_path.parent().map(Self::new)
829 }
830
831 /// Creates a new AppPath with the specified file extension.
832 ///
833 /// If the path has an existing extension, it will be replaced.
834 /// If no extension exists, the new extension will be added.
835 ///
836 /// # Examples
837 ///
838 /// ```rust
839 /// use app_path::AppPath;
840 ///
841 /// let config = AppPath::new("config");
842 /// let config_toml = config.with_extension("toml");
843 /// let config_json = config.with_extension("json");
844 ///
845 /// let log_file = AppPath::new("app.log");
846 /// let backup_file = log_file.with_extension("bak");
847 /// ```
848 #[inline]
849 pub fn with_extension(&self, ext: &str) -> Self {
850 Self::new(self.full_path.with_extension(ext))
851 }
852
853 /// Returns the file name of this path as an `OsStr`, if it exists.
854 ///
855 /// This is a convenience method that delegates to the underlying `Path`.
856 ///
857 /// # Examples
858 ///
859 /// ```rust
860 /// use app_path::AppPath;
861 ///
862 /// let config = AppPath::new("config/app.toml");
863 /// assert_eq!(config.file_name().unwrap(), "app.toml");
864 /// ```
865 #[inline]
866 pub fn file_name(&self) -> Option<&std::ffi::OsStr> {
867 self.full_path.file_name()
868 }
869
870 /// Returns the file stem of this path as an `OsStr`, if it exists.
871 ///
872 /// This is a convenience method that delegates to the underlying `Path`.
873 ///
874 /// # Examples
875 ///
876 /// ```rust
877 /// use app_path::AppPath;
878 ///
879 /// let config = AppPath::new("config/app.toml");
880 /// assert_eq!(config.file_stem().unwrap(), "app");
881 /// ```
882 #[inline]
883 pub fn file_stem(&self) -> Option<&std::ffi::OsStr> {
884 self.full_path.file_stem()
885 }
886
887 /// Returns the extension of this path as an `OsStr`, if it exists.
888 ///
889 /// This is a convenience method that delegates to the underlying `Path`.
890 ///
891 /// # Examples
892 ///
893 /// ```rust
894 /// use app_path::AppPath;
895 ///
896 /// let config = AppPath::new("config/app.toml");
897 /// assert_eq!(config.extension().unwrap(), "toml");
898 /// ```
899 #[inline]
900 pub fn extension(&self) -> Option<&std::ffi::OsStr> {
901 self.full_path.extension()
902 }
903
904 /// Returns `true` if this path points to a directory.
905 ///
906 /// This is a convenience method that delegates to the underlying `Path`.
907 ///
908 /// # Examples
909 ///
910 /// ```rust
911 /// use app_path::AppPath;
912 ///
913 /// let data_dir = AppPath::new("data");
914 /// if data_dir.is_dir() {
915 /// println!("Data directory exists");
916 /// }
917 /// ```
918 #[inline]
919 pub fn is_dir(&self) -> bool {
920 self.full_path.is_dir()
921 }
922
923 /// Returns `true` if this path points to a regular file.
924 ///
925 /// This is a convenience method that delegates to the underlying `Path`.
926 ///
927 /// # Examples
928 ///
929 /// ```rust
930 /// use app_path::AppPath;
931 ///
932 /// let config = AppPath::new("config.toml");
933 /// if config.is_file() {
934 /// println!("Config file exists");
935 /// }
936 /// ```
937 #[inline]
938 pub fn is_file(&self) -> bool {
939 self.full_path.is_file()
940 }
941}