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