app_path/
lib.rs

1//! # app-path
2//!
3//! Create portable applications that keep files together with the executable.
4//!
5//! ## Quick Start
6//!
7//! ```rust
8//! use app_path::app_path;
9//!
10//! // Files relative to your executable - not current directory!
11//! let config = app_path!("config.toml");        // → /path/to/exe/config.toml
12//! let database = app_path!("data/users.db");    // → /path/to/exe/data/users.db
13//!
14//! // Environment overrides for deployment
15//! let logs = app_path!("logs/app.log", env = "LOG_PATH");
16//! // → Uses LOG_PATH if set, otherwise /path/to/exe/logs/app.log
17//!
18//! // Works like standard paths - all Path methods available
19//! if config.exists() {
20//!     let content = std::fs::read_to_string(&config)?;
21//! }
22//!
23//! // Directory creation
24//! logs.create_parents()?;                 // Creates logs/ directory for the file
25//! app_path!("cache").create_dir()?;       // Creates cache/ directory itself
26//! # Ok::<(), Box<dyn std::error::Error>>(())
27//! ```
28//!
29//! ## Key Features
30//!
31//! - **Portable**: Relative paths resolve to executable directory  
32//! - **System integration**: Absolute paths work as-is
33//! - **Zero dependencies**: Only standard library
34//! - **High performance**: Static caching, minimal allocations
35//! - **Thread-safe**: Concurrent access safe
36//! - **Zero-cost**: All `Path` methods available via `Deref` (e.g., `exists()`, `is_file()`, `extension()`)
37//!
38//! ## API Design
39//!
40//! - [`AppPath::new()`] - **Recommended**: Simple constructor (panics on failure)
41//! - [`AppPath::try_new()`] - **Libraries**: Fallible version for error handling
42//! - [`AppPath::with_override()`] - **Deployment**: Environment-configurable paths
43//! - [`AppPath::with_override_fn()`] - **Advanced**: Function-based override logic
44//! - [`app_path!`] - **Macro**: Convenient syntax with optional environment overrides
45//! - [`try_app_path!`] - **Macro (Fallible)**: Returns `Result` for explicit error handling
46//! - [`AppPath::create_parents()`] - **Files**: Creates parent directories for files
47//! - [`AppPath::create_dir()`] - **Directories**: Creates directories (and parents)
48//! - [`exe_dir()`] - **Advanced**: Direct access to executable directory (panics on failure)
49//! - [`try_exe_dir()`] - **Libraries**: Fallible executable directory access
50//!
51//! ## Function Variants
52//!
53//! This crate provides both panicking and fallible variants for most operations:
54//!
55//! | Panicking (Recommended) | Fallible (Libraries) | Use Case |
56//! |------------------------|---------------------|----------|
57//! | [`AppPath::new()`] | [`AppPath::try_new()`] | Constructor methods |
58//! | [`app_path!`] | [`try_app_path!`] | Convenient macros |
59//! | [`exe_dir()`] | [`try_exe_dir()`] | Direct directory access |
60//!
61//! ### Macro Syntax Variants
62//!
63//! Both `app_path!` and `try_app_path!` macros support four syntax forms for maximum flexibility:
64//!
65//! ```rust
66//! # use app_path::{app_path, try_app_path};
67//! // 1. Direct value
68//! let config = app_path!("config.toml");
69//! // → /path/to/exe/config.toml
70//!
71//! // 2. With environment override
72//! let config = app_path!("config.toml", env = "CONFIG_PATH");
73//! // → Uses CONFIG_PATH if set, otherwise /path/to/exe/config.toml
74//!
75//! // 3. With optional override value
76//! let config = app_path!("config.toml", override = std::env::var("CONFIG_PATH").ok());
77//! // → Uses CONFIG_PATH if available, otherwise /path/to/exe/config.toml
78//!
79//! // 4. With function-based override
80//! let config = app_path!("config.toml", fn = || {
81//!     std::env::var("CONFIG_PATH").ok()
82//! });
83//! // → Uses function result if Some, otherwise /path/to/exe/config.toml
84//! ```
85//!
86//! ### Variable Capturing
87//!
88//! Both macros support variable capturing in complex expressions:
89//!
90//! ```rust
91//! # use app_path::{app_path, try_app_path};
92//! let version = "1.0";
93//! let cache = app_path!(format!("cache-{version}")); // Captures `version`
94//! // → /path/to/exe/cache-1.0
95//!
96//! // Useful in closures and async blocks
97//! async fn process_data(id: u32) {
98//!     let output = app_path!(format!("output-{id}.json")); // Captures `id`
99//!     // → /path/to/exe/output-123.json (where id = 123)
100//!     // ... async processing
101//! }
102//! ```
103//!
104//! ### Panic Conditions
105//!
106//! [`AppPath::new()`] and [`exe_dir()`] panic only if executable location cannot be determined:
107//! - `std::env::current_exe()` fails (extremely rare system failure)
108//! - Executable path is empty (indicates system corruption)
109//!
110//! These represent unrecoverable system failures that occur at application startup.
111//! After the first successful call, the executable directory is cached and subsequent
112//! calls never panic.
113//!
114//! **For libraries or applications requiring graceful error handling**, use the fallible
115//! variants [`AppPath::try_new()`] and [`try_exe_dir()`] instead.
116//!
117//! ## Ecosystem Integration
118//!
119//! `app-path` integrates seamlessly with popular Rust path crates through standard trait implementations:
120//!
121//! ### UTF-8 Path Serialization (camino)
122//!
123//! ```rust
124//! use app_path::app_path;
125//! # // Conditional compilation for documentation without dependencies
126//! # /*
127//! use camino::Utf8PathBuf;
128//!
129//! let static_dir = app_path!("web/static", env = "STATIC_DIR");
130//! let utf8_static = Utf8PathBuf::try_from(static_dir)?; // Direct conversion
131//!
132//! // Safe JSON serialization with UTF-8 guarantees
133//! let config = serde_json::json!({ "static_files": utf8_static });
134//! # */
135//! # Ok::<(), Box<dyn std::error::Error>>(())
136//! ```
137//!
138//! ### Cross-Platform Path Types (typed-path)
139//!
140//! ```rust
141//! use app_path::app_path;
142//! # // Conditional compilation for documentation without dependencies
143//! # /*
144//! use typed_path::{WindowsPath, UnixPath};
145//!
146//! let dist_dir = app_path!("dist");
147//!
148//! // Platform-specific paths with proper separators
149//! let win_path = WindowsPath::new(&dist_dir);  // Uses \ on Windows  
150//! let unix_path = UnixPath::new(&dist_dir);    // Uses / on Unix
151//! # */
152//! # Ok::<(), Box<dyn std::error::Error>>(())
153//! ```
154
155mod app_path;
156mod error;
157mod functions;
158
159#[cfg(test)]
160mod tests;
161
162// Re-export the public API
163pub use app_path::AppPath;
164pub use error::AppPathError;
165pub use functions::{exe_dir, try_exe_dir};
166
167/// Convenience macro for creating `AppPath` instances with optional environment variable overrides.
168///
169/// This macro provides a more ergonomic way to create `AppPath` instances, especially when
170/// dealing with environment variable overrides.
171///
172/// # Syntax
173///
174/// - `app_path!(path)` - Simple path creation (equivalent to `AppPath::new(path)`)
175/// - `app_path!(path, env = "VAR_NAME")` - With environment variable override
176/// - `app_path!(path, override = expression)` - With any optional override expression
177/// - `app_path!(path, fn = function)` - With function-based override logic
178///
179/// # Examples
180///
181/// ```rust
182/// use app_path::{app_path, AppPath};
183///
184/// // Simple usage
185/// let config = app_path!("config.toml");
186/// assert_eq!(config.file_name().unwrap(), "config.toml");
187///
188/// // Environment variable override
189/// let data_dir = app_path!("data", env = "DATA_DIR");
190///
191/// // Custom override expression
192/// let log_file = app_path!("app.log", override = std::env::args().nth(1));
193///
194/// // Function-based override
195/// let config_dir = app_path!("config", fn = || {
196///     std::env::var("XDG_CONFIG_HOME")
197///         .or_else(|_| std::env::var("HOME").map(|h| format!("{h}/.config")))
198///         .ok()
199/// });
200/// ```
201#[macro_export]
202macro_rules! app_path {
203    ($path:expr) => {
204        $crate::AppPath::new($path)
205    };
206    ($path:expr, env = $env_var:expr) => {
207        $crate::AppPath::with_override($path, ::std::env::var($env_var).ok())
208    };
209    ($path:expr, override = $override_expr:expr) => {
210        $crate::AppPath::with_override($path, $override_expr)
211    };
212    ($path:expr, fn = $override_fn:expr) => {
213        $crate::AppPath::with_override_fn($path, $override_fn)
214    };
215}
216
217/// Fallible version of [`app_path!`] that returns a [`Result`] instead of panicking.
218///
219/// This macro provides the same convenient syntax as [`app_path!`] but returns
220/// [`Result<AppPath, AppPathError>`] for explicit error handling. Perfect for
221/// libraries and applications that need graceful error handling.
222///
223/// # Syntax
224///
225/// - `try_app_path!(path)` - Simple path creation (equivalent to `AppPath::try_new(path)`)
226/// - `try_app_path!(path, env = "VAR_NAME")` - With environment variable override
227/// - `try_app_path!(path, override = expression)` - With any optional override expression
228/// - `try_app_path!(path, fn = function)` - With function-based override logic
229///
230/// # Examples
231///
232/// ## Basic Usage
233///
234/// ```rust
235/// use app_path::{try_app_path, AppPathError};
236///
237/// fn setup_config() -> Result<(), AppPathError> {
238///     let config = try_app_path!("config.toml")?;
239///     let database = try_app_path!("data/users.db")?;
240///     
241///     // Use paths normally
242///     if config.exists() {
243///         println!("Config found at: {}", config.display());
244///     }
245///     
246///     Ok(())
247/// }
248/// ```
249///
250/// ## Environment Variable Overrides
251///
252/// ```rust
253/// use app_path::try_app_path;
254///
255/// fn setup_logging() -> Result<(), Box<dyn std::error::Error>> {
256///     // Uses "logs/app.log" by default, LOG_PATH env var if set
257///     let log_file = try_app_path!("logs/app.log", env = "LOG_PATH")?;
258///     log_file.create_parents()?;
259///     
260///     std::fs::write(&log_file, "Application started")?;
261///     Ok(())
262/// }
263/// ```
264///
265/// ## Custom Override Logic
266///
267/// ```rust
268/// use app_path::try_app_path;
269///
270/// fn get_data_dir() -> Option<String> {
271///     std::env::var("XDG_DATA_HOME")
272///         .or_else(|_| std::env::var("HOME").map(|h| format!("{h}/.local/share")))
273///         .ok()
274/// }
275///
276/// fn setup_data() -> Result<(), Box<dyn std::error::Error>> {
277///     let data_dir = try_app_path!("data", override = get_data_dir())?;
278///     data_dir.create_dir()?;
279///     Ok(())
280/// }
281/// ```
282///
283/// ## Function-Based Override
284///
285/// ```rust
286/// use app_path::try_app_path;
287///
288/// fn setup_cache() -> Result<(), Box<dyn std::error::Error>> {
289///     let cache_dir = try_app_path!("cache", fn = || {
290///         std::env::var("XDG_CACHE_HOME")
291///             .or_else(|_| std::env::var("HOME").map(|h| format!("{h}/.cache")))
292///             .ok()
293///     })?;
294///     cache_dir.create_dir()?;
295///     Ok(())
296/// }
297/// ```
298///
299/// ## Error Handling Patterns
300///
301/// ```rust
302/// use app_path::{try_app_path, AppPathError};
303///
304/// match try_app_path!("config.toml") {
305///     Ok(config) => {
306///         println!("Config path: {}", config.display());
307///     }
308///     Err(AppPathError::ExecutableNotFound(msg)) => {
309///         eprintln!("Cannot determine executable location: {msg}");
310///     }
311///     Err(AppPathError::InvalidExecutablePath(msg)) => {
312///         eprintln!("Invalid executable path: {msg}");
313///     }
314///     Err(AppPathError::IoError(msg)) => {
315///         eprintln!("I/O operation failed: {msg}");
316///     }
317/// }
318/// ```
319///
320/// ## Library Usage
321///
322/// ```rust
323/// use app_path::{try_app_path, AppPathError};
324///
325/// /// Library function that gracefully handles path errors
326/// pub fn load_user_config() -> Result<String, Box<dyn std::error::Error>> {
327///     let config_path = try_app_path!("config.toml", env = "USER_CONFIG")?;
328///         
329///     if !config_path.exists() {
330///         return Err("Config file not found".into());
331///     }
332///     
333///     let content = std::fs::read_to_string(&config_path)?;
334///     Ok(content)
335/// }
336/// ```
337///
338/// # Comparison with [`app_path!`]
339///
340/// | Feature | [`app_path!`] | [`try_app_path!`] |
341/// |---------|---------------|-------------------|
342/// | **Return type** | [`AppPath`] | [`Result<AppPath, AppPathError>`] |
343/// | **Error handling** | Panics on failure | Returns [`Err`] on failure |
344/// | **Use case** | Applications | Libraries, explicit error handling |
345/// | **Syntax** | Same | Same |
346/// | **Performance** | Same | Same |
347///
348/// # When to Use
349///
350/// - **Use [`try_app_path!`]** for libraries, when you need graceful error handling,
351///   or when integrating with other fallible operations
352/// - **Use [`app_path!`]** for applications where you want to fail fast on system errors
353///
354/// # See Also
355///
356/// - [`app_path!`] - Panicking version with identical syntax
357/// - [`AppPath::try_new`] - Constructor equivalent
358/// - [`AppPath::try_with_override`] - Constructor with override equivalent
359#[macro_export]
360macro_rules! try_app_path {
361    ($path:expr) => {
362        $crate::AppPath::try_new($path)
363    };
364    ($path:expr, env = $env_var:expr) => {
365        $crate::AppPath::try_with_override($path, ::std::env::var($env_var).ok())
366    };
367    ($path:expr, override = $override_expr:expr) => {
368        $crate::AppPath::try_with_override($path, $override_expr)
369    };
370    ($path:expr, fn = $override_fn:expr) => {
371        $crate::AppPath::try_with_override_fn($path, $override_fn)
372    };
373}