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
205mod app_path;
206mod error;
207mod functions;
208mod traits;
209
210#[cfg(test)]
211mod tests;
212
213// Re-export the public API
214pub use app_path::AppPath;
215pub use error::AppPathError;
216pub use functions::{exe_dir, try_exe_dir};
217
218/// Convenience macro for creating `AppPath` instances with optional environment variable overrides.
219///
220/// This macro provides a more ergonomic way to create `AppPath` instances, especially when
221/// dealing with environment variable overrides.
222///
223/// # Syntax
224///
225/// - `app_path!(path)` - Simple path creation (equivalent to `AppPath::new(path)`)
226/// - `app_path!(path, env = "VAR_NAME")` - With environment variable override
227/// - `app_path!(path, override = expression)` - With any optional override expression
228/// - `app_path!(path, fn = function)` - With function-based override logic
229///
230/// # Examples
231///
232/// ```rust
233/// use app_path::{app_path, AppPath};
234///
235/// // Simple usage
236/// let config = app_path!("config.toml");
237/// assert_eq!(config.file_name().unwrap(), "config.toml");
238///
239/// // Environment variable override
240/// let data_dir = app_path!("data", env = "DATA_DIR");
241///
242/// // Custom override expression
243/// let log_file = app_path!("app.log", override = std::env::args().nth(1));
244///
245/// // Function-based override
246/// let config_dir = app_path!("config", fn = || {
247/// std::env::var("XDG_CONFIG_HOME")
248/// .or_else(|_| std::env::var("HOME").map(|h| format!("{h}/.config")))
249/// .ok()
250/// });
251/// ```
252#[macro_export]
253macro_rules! app_path {
254 ($path:expr) => {
255 $crate::AppPath::new($path)
256 };
257 ($path:expr, env = $env_var:expr) => {
258 $crate::AppPath::with_override($path, ::std::env::var($env_var).ok())
259 };
260 ($path:expr, override = $override_expr:expr) => {
261 $crate::AppPath::with_override($path, $override_expr)
262 };
263 ($path:expr, fn = $override_fn:expr) => {
264 $crate::AppPath::with_override_fn($path, $override_fn)
265 };
266}
267
268/// Fallible version of [`app_path!`] that returns a [`Result`] instead of panicking.
269///
270/// This macro provides the same convenient syntax as [`app_path!`] but returns
271/// [`Result<AppPath, AppPathError>`] for explicit error handling. Perfect for
272/// libraries and applications that need graceful error handling.
273///
274/// # Syntax
275///
276/// - `try_app_path!(path)` - Simple path creation (equivalent to `AppPath::try_new(path)`)
277/// - `try_app_path!(path, env = "VAR_NAME")` - With environment variable override
278/// - `try_app_path!(path, override = expression)` - With any optional override expression
279/// - `try_app_path!(path, fn = function)` - With function-based override logic
280///
281/// # Examples
282///
283/// ## Basic Usage
284///
285/// ```rust
286/// use app_path::{try_app_path, AppPathError};
287///
288/// fn setup_config() -> Result<(), AppPathError> {
289/// let config = try_app_path!("config.toml")?;
290/// let database = try_app_path!("data/users.db")?;
291///
292/// // Use paths normally
293/// if config.exists() {
294/// println!("Config found at: {}", config.display());
295/// }
296///
297/// Ok(())
298/// }
299/// ```
300///
301/// ## Environment Variable Overrides
302///
303/// ```rust
304/// use app_path::try_app_path;
305///
306/// fn setup_logging() -> Result<(), Box<dyn std::error::Error>> {
307/// // Uses "logs/app.log" by default, LOG_PATH env var if set
308/// let log_file = try_app_path!("logs/app.log", env = "LOG_PATH")?;
309/// log_file.create_parents()?;
310///
311/// std::fs::write(&log_file, "Application started")?;
312/// Ok(())
313/// }
314/// ```
315///
316/// ## Custom Override Logic
317///
318/// ```rust
319/// use app_path::try_app_path;
320///
321/// fn get_data_dir() -> Option<String> {
322/// std::env::var("XDG_DATA_HOME")
323/// .or_else(|_| std::env::var("HOME").map(|h| format!("{h}/.local/share")))
324/// .ok()
325/// }
326///
327/// fn setup_data() -> Result<(), Box<dyn std::error::Error>> {
328/// let data_dir = try_app_path!("data", override = get_data_dir())?;
329/// data_dir.create_dir()?;
330/// Ok(())
331/// }
332/// ```
333///
334/// ## Function-Based Override
335///
336/// ```rust
337/// use app_path::try_app_path;
338///
339/// fn setup_cache() -> Result<(), Box<dyn std::error::Error>> {
340/// let cache_dir = try_app_path!("cache", fn = || {
341/// std::env::var("XDG_CACHE_HOME")
342/// .or_else(|_| std::env::var("HOME").map(|h| format!("{h}/.cache")))
343/// .ok()
344/// })?;
345/// cache_dir.create_dir()?;
346/// Ok(())
347/// }
348/// ```
349///
350/// ## Error Handling Patterns
351///
352/// ```rust
353/// use app_path::{try_app_path, AppPathError};
354///
355/// match try_app_path!("config.toml") {
356/// Ok(config) => {
357/// println!("Config path: {}", config.display());
358/// }
359/// Err(AppPathError::ExecutableNotFound(msg)) => {
360/// eprintln!("Cannot determine executable location: {msg}");
361/// }
362/// Err(AppPathError::InvalidExecutablePath(msg)) => {
363/// eprintln!("Invalid executable path: {msg}");
364/// }
365/// Err(AppPathError::IoError(msg)) => {
366/// eprintln!("I/O operation failed: {msg}");
367/// }
368/// }
369/// ```
370///
371/// ## Library Usage
372///
373/// ```rust
374/// use app_path::{try_app_path, AppPathError};
375///
376/// /// Library function that gracefully handles path errors
377/// pub fn load_user_config() -> Result<String, Box<dyn std::error::Error>> {
378/// let config_path = try_app_path!("config.toml", env = "USER_CONFIG")?;
379///
380/// if !config_path.exists() {
381/// return Err("Config file not found".into());
382/// }
383///
384/// let content = std::fs::read_to_string(&config_path)?;
385/// Ok(content)
386/// }
387/// ```
388///
389/// # Comparison with [`app_path!`]
390///
391/// | Feature | [`app_path!`] | [`try_app_path!`] |
392/// |---------|---------------|-------------------|
393/// | **Return type** | [`AppPath`] | [`Result<AppPath, AppPathError>`] |
394/// | **Error handling** | Panics on failure | Returns [`Err`] on failure |
395/// | **Use case** | Applications | Libraries, explicit error handling |
396/// | **Syntax** | Same | Same |
397/// | **Performance** | Same | Same |
398///
399/// # When to Use
400///
401/// - **Use [`try_app_path!`]** for libraries, when you need graceful error handling,
402/// or when integrating with other fallible operations
403/// - **Use [`app_path!`]** for applications where you want to fail fast on system errors
404///
405/// # See Also
406///
407/// - [`app_path!`] - Panicking version with identical syntax
408/// - [`AppPath::try_new`] - Constructor equivalent
409/// - [`AppPath::try_with_override`] - Constructor with override equivalent
410#[macro_export]
411macro_rules! try_app_path {
412 ($path:expr) => {
413 $crate::AppPath::try_new($path)
414 };
415 ($path:expr, env = $env_var:expr) => {
416 $crate::AppPath::try_with_override($path, ::std::env::var($env_var).ok())
417 };
418 ($path:expr, override = $override_expr:expr) => {
419 $crate::AppPath::try_with_override($path, $override_expr)
420 };
421 ($path:expr, fn = $override_fn:expr) => {
422 $crate::AppPath::try_with_override_fn($path, $override_fn)
423 };
424}