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//!
167//! ## Ecosystem Integration
168//!
169//! `app-path` integrates seamlessly with popular Rust path crates through standard trait implementations:
170//!
171//! ### UTF-8 Path Serialization (camino)
172//!
173//! ```rust
174//! use app_path::app_path;
175//! # // Conditional compilation for documentation without dependencies
176//! # /*
177//! use camino::Utf8PathBuf;
178//!
179//! let static_dir = app_path!("web/static", env = "STATIC_DIR");
180//! let utf8_static = Utf8PathBuf::try_from(static_dir)?; // Direct conversion
181//!
182//! // Safe JSON serialization with UTF-8 guarantees
183//! let config = serde_json::json!({ "static_files": utf8_static });
184//! # */
185//! # Ok::<(), Box<dyn std::error::Error>>(())
186//! ```
187//!
188//! ### Cross-Platform Path Types (typed-path)
189//!
190//! ```rust
191//! use app_path::app_path;
192//! # // Conditional compilation for documentation without dependencies
193//! # /*
194//! use typed_path::{WindowsPath, UnixPath};
195//!
196//! let dist_dir = app_path!("dist");
197//!
198//! // Platform-specific paths with proper separators
199//! let win_path = WindowsPath::new(&dist_dir);  // Uses \ on Windows  
200//! let unix_path = UnixPath::new(&dist_dir);    // Uses / on Unix
201//! # */
202//! # Ok::<(), Box<dyn std::error::Error>>(())
203//! ```
204//!
205//! ### Secure Path Operations (pathos)
206//!
207//! ```rust
208//! use app_path::app_path;
209//! # // Conditional compilation for documentation without dependencies
210//! # /*
211//! use pathos::PathExt;
212//!
213//! let plugin_dir = app_path!("plugins");
214//! let plugin_path = plugin_dir.as_path().join("user_plugin.wasm");
215//!
216//! // Automatic normalization and traversal protection
217//! let safe_path = plugin_path.normalize()?;
218//! if !safe_path.starts_with(&plugin_dir) {
219//!     return Err("Path traversal detected".into());
220//! }
221//! # */
222//! # Ok::<(), Box<dyn std::error::Error>>(())
223//! ```
224
225mod app_path;
226mod error;
227mod functions;
228mod traits;
229
230#[cfg(test)]
231mod tests;
232
233// Re-export the public API
234pub use app_path::AppPath;
235pub use error::AppPathError;
236pub use functions::{exe_dir, try_exe_dir};
237
238/// Convenience macro for creating `AppPath` instances with optional environment variable overrides.
239///
240/// This macro provides a more ergonomic way to create `AppPath` instances, especially when
241/// dealing with environment variable overrides.
242///
243/// # Syntax
244///
245/// - `app_path!(path)` - Simple path creation (equivalent to `AppPath::new(path)`)
246/// - `app_path!(path, env = "VAR_NAME")` - With environment variable override
247/// - `app_path!(path, override = expression)` - With any optional override expression
248/// - `app_path!(path, fn = function)` - With function-based override logic
249///
250/// # Examples
251///
252/// ```rust
253/// use app_path::{app_path, AppPath};
254///
255/// // Simple usage
256/// let config = app_path!("config.toml");
257/// assert_eq!(config.file_name().unwrap(), "config.toml");
258///
259/// // Environment variable override
260/// let data_dir = app_path!("data", env = "DATA_DIR");
261///
262/// // Custom override expression
263/// let log_file = app_path!("app.log", override = std::env::args().nth(1));
264///
265/// // Function-based override
266/// let config_dir = app_path!("config", fn = || {
267///     std::env::var("XDG_CONFIG_HOME")
268///         .or_else(|_| std::env::var("HOME").map(|h| format!("{h}/.config")))
269///         .ok()
270/// });
271/// ```
272#[macro_export]
273macro_rules! app_path {
274    ($path:expr) => {
275        $crate::AppPath::new($path)
276    };
277    ($path:expr, env = $env_var:expr) => {
278        $crate::AppPath::with_override($path, ::std::env::var($env_var).ok())
279    };
280    ($path:expr, override = $override_expr:expr) => {
281        $crate::AppPath::with_override($path, $override_expr)
282    };
283    ($path:expr, fn = $override_fn:expr) => {
284        $crate::AppPath::with_override_fn($path, $override_fn)
285    };
286}
287
288/// Fallible version of [`app_path!`] that returns a [`Result`] instead of panicking.
289///
290/// This macro provides the same convenient syntax as [`app_path!`] but returns
291/// [`Result<AppPath, AppPathError>`] for explicit error handling. Perfect for
292/// libraries and applications that need graceful error handling.
293///
294/// # Syntax
295///
296/// - `try_app_path!(path)` - Simple path creation (equivalent to `AppPath::try_new(path)`)
297/// - `try_app_path!(path, env = "VAR_NAME")` - With environment variable override
298/// - `try_app_path!(path, override = expression)` - With any optional override expression
299/// - `try_app_path!(path, fn = function)` - With function-based override logic
300///
301/// # Examples
302///
303/// ## Basic Usage
304///
305/// ```rust
306/// use app_path::{try_app_path, AppPathError};
307///
308/// fn setup_config() -> Result<(), AppPathError> {
309///     let config = try_app_path!("config.toml")?;
310///     let database = try_app_path!("data/users.db")?;
311///     
312///     // Use paths normally
313///     if config.exists() {
314///         println!("Config found at: {}", config.display());
315///     }
316///     
317///     Ok(())
318/// }
319/// ```
320///
321/// ## Environment Variable Overrides
322///
323/// ```rust
324/// use app_path::try_app_path;
325///
326/// fn setup_logging() -> Result<(), Box<dyn std::error::Error>> {
327///     // Uses "logs/app.log" by default, LOG_PATH env var if set
328///     let log_file = try_app_path!("logs/app.log", env = "LOG_PATH")?;
329///     log_file.create_parents()?;
330///     
331///     std::fs::write(&log_file, "Application started")?;
332///     Ok(())
333/// }
334/// ```
335///
336/// ## Custom Override Logic
337///
338/// ```rust
339/// use app_path::try_app_path;
340///
341/// fn get_data_dir() -> Option<String> {
342///     std::env::var("XDG_DATA_HOME")
343///         .or_else(|_| std::env::var("HOME").map(|h| format!("{h}/.local/share")))
344///         .ok()
345/// }
346///
347/// fn setup_data() -> Result<(), Box<dyn std::error::Error>> {
348///     let data_dir = try_app_path!("data", override = get_data_dir())?;
349///     data_dir.create_dir()?;
350///     Ok(())
351/// }
352/// ```
353///
354/// ## Function-Based Override
355///
356/// ```rust
357/// use app_path::try_app_path;
358///
359/// fn setup_cache() -> Result<(), Box<dyn std::error::Error>> {
360///     let cache_dir = try_app_path!("cache", fn = || {
361///         std::env::var("XDG_CACHE_HOME")
362///             .or_else(|_| std::env::var("HOME").map(|h| format!("{h}/.cache")))
363///             .ok()
364///     })?;
365///     cache_dir.create_dir()?;
366///     Ok(())
367/// }
368/// ```
369///
370/// ## Error Handling Patterns
371///
372/// ```rust
373/// use app_path::{try_app_path, AppPathError};
374///
375/// match try_app_path!("config.toml") {
376///     Ok(config) => {
377///         println!("Config path: {}", config.display());
378///     }
379///     Err(AppPathError::ExecutableNotFound(msg)) => {
380///         eprintln!("Cannot determine executable location: {msg}");
381///     }
382///     Err(AppPathError::InvalidExecutablePath(msg)) => {
383///         eprintln!("Invalid executable path: {msg}");
384///     }
385///     Err(AppPathError::IoError(msg)) => {
386///         eprintln!("I/O operation failed: {msg}");
387///     }
388/// }
389/// ```
390///
391/// ## Library Usage
392///
393/// ```rust
394/// use app_path::{try_app_path, AppPathError};
395///
396/// /// Library function that gracefully handles path errors
397/// pub fn load_user_config() -> Result<String, Box<dyn std::error::Error>> {
398///     let config_path = try_app_path!("config.toml", env = "USER_CONFIG")?;
399///         
400///     if !config_path.exists() {
401///         return Err("Config file not found".into());
402///     }
403///     
404///     let content = std::fs::read_to_string(&config_path)?;
405///     Ok(content)
406/// }
407/// ```
408///
409/// # Comparison with [`app_path!`]
410///
411/// | Feature | [`app_path!`] | [`try_app_path!`] |
412/// |---------|---------------|-------------------|
413/// | **Return type** | [`AppPath`] | [`Result<AppPath, AppPathError>`] |
414/// | **Error handling** | Panics on failure | Returns [`Err`] on failure |
415/// | **Use case** | Applications | Libraries, explicit error handling |
416/// | **Syntax** | Same | Same |
417/// | **Performance** | Same | Same |
418///
419/// # When to Use
420///
421/// - **Use [`try_app_path!`]** for libraries, when you need graceful error handling,
422///   or when integrating with other fallible operations
423/// - **Use [`app_path!`]** for applications where you want to fail fast on system errors
424///
425/// # See Also
426///
427/// - [`app_path!`] - Panicking version with identical syntax
428/// - [`AppPath::try_new`] - Constructor equivalent
429/// - [`AppPath::try_with_override`] - Constructor with override equivalent
430#[macro_export]
431macro_rules! try_app_path {
432    ($path:expr) => {
433        $crate::AppPath::try_new($path)
434    };
435    ($path:expr, env = $env_var:expr) => {
436        $crate::AppPath::try_with_override($path, ::std::env::var($env_var).ok())
437    };
438    ($path:expr, override = $override_expr:expr) => {
439        $crate::AppPath::try_with_override($path, $override_expr)
440    };
441    ($path:expr, fn = $override_fn:expr) => {
442        $crate::AppPath::try_with_override_fn($path, $override_fn)
443    };
444}