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