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_dir/config.toml
12//! let database = app_path!("data/users.db"); // → /path/to/exe_dir/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_dir/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//! ### Constructors
41//!
42//! - [`AppPath::new()`] - **Application base directory**: Returns the directory containing the executable
43//! - [`AppPath::with()`] - **Primary API**: Create paths relative to application base directory
44//! - [`AppPath::try_new()`] - **Libraries**: Fallible version for getting application base directory
45//! - [`AppPath::try_with()`] - **Libraries**: Fallible version for creating relative paths
46//! - [`AppPath::with_override()`] - **Deployment**: Environment-configurable paths
47//! - [`AppPath::try_with_override()`] - **Deployment (Fallible)**: Fallible environment-configurable paths
48//! - [`AppPath::with_override_fn()`] - **Advanced**: Function-based override logic
49//! - [`AppPath::try_with_override_fn()`] - **Advanced (Fallible)**: Fallible function-based override logic
50//!
51//! ### Directory Creation
52//!
53//! - [`AppPath::create_parents()`] - **Files**: Creates parent directories for files
54//! - [`AppPath::create_dir()`] - **Directories**: Creates directories (and parents)
55//!
56//! ### Path Operations & Traits
57//!
58//! - **All `Path` methods**: Available directly via `Deref<Target=Path>` (e.g., `exists()`, `is_file()`, `file_name()`, `extension()`)
59//! - [`AppPath::into_path_buf()`] - **Conversion**: Extract owned `PathBuf` from wrapper
60//! - [`AppPath::into_inner()`] - **Conversion**: Alias for `into_path_buf()` following Rust patterns
61//! - [`AppPath::to_bytes()`] - **Ecosystem**: Raw bytes for specialized libraries
62//! - [`AppPath::into_bytes()`] - **Ecosystem**: Owned bytes for specialized libraries
63//!
64//! ### Convenience Macros
65//!
66//! - [`app_path!`] - **Macro**: Convenient syntax with optional environment overrides
67//! - [`try_app_path!`] - **Macro (Fallible)**: Returns `Result` for explicit error handling
68//!
69//! ## Constructor Variants
70//!
71//! This crate provides both panicking and fallible variants for most operations:
72//!
73//! | Panicking (Recommended) | Fallible (Libraries) | Use Case |
74//! |------------------------|---------------------|----------|
75//! | [`AppPath::new()`] | [`AppPath::try_new()`] | Get application base directory |
76//! | [`AppPath::with()`] | [`AppPath::try_with()`] | Create relative paths |
77//! | [`AppPath::with_override()`] | [`AppPath::try_with_override()`] | Environment-configurable paths |
78//! | [`AppPath::with_override_fn()`] | [`AppPath::try_with_override_fn()`] | Function-based override logic |
79//! | [`app_path!`] | [`try_app_path!`] | Convenient macros |
80//!
81//! ## Macro Syntax Variants
82//!
83//! Both `app_path!` and `try_app_path!` macros support four syntax forms for maximum flexibility:
84//!
85//! ```rust
86//! # use app_path::{app_path, try_app_path};
87//! // 1. Direct value
88//! let config = app_path!("config.toml");
89//! // → /path/to/exe_dir/config.toml
90//!
91//! // 2. With environment override
92//! let config = app_path!("config.toml", env = "CONFIG_PATH");
93//! // → Uses CONFIG_PATH if set, otherwise /path/to/exe_dir/config.toml
94//!
95//! // 3. With optional override value
96//! let config = app_path!("config.toml", override = std::env::var("CONFIG_PATH").ok());
97//! // → Uses CONFIG_PATH if available, otherwise /path/to/exe_dir/config.toml
98//!
99//! // 4. With function-based override
100//! let config = app_path!("config.toml", fn = || {
101//!     std::env::var("CONFIG_PATH").ok()
102//! });
103//! // → Uses function result if Some, otherwise /path/to/exe_dir/config.toml
104//! ```
105//!
106//! ### Variable Capturing in Macros
107//!
108//! Both macros support variable capturing in complex expressions:
109//!
110//! ```rust
111//! # use app_path::app_path;
112//! let version = "1.0";
113//! let cache = app_path!(format!("cache-{version}"));
114//!
115//! let user_ids = vec![123, 456];
116//! let logs: Vec<_> = user_ids.iter()
117//!     .map(|id| app_path!(format!("logs/user-{id}.log")))
118//!     .collect();
119//! ```
120//!
121//! ## Ecosystem Integration
122//!
123//! AppPath works seamlessly with ecosystem crates through `Deref<Target=Path>`:
124//!
125//! ### Serde Integration
126//!
127//! ```rust
128//! use app_path::app_path;
129//! use serde::{Serialize, Deserialize};
130//!
131//! #[derive(Serialize, Deserialize)]
132//! struct Config {
133//!     db_path: String,
134//! }
135//!
136//! let config = Config {
137//!     db_path: app_path!("data/app.db").display().to_string(),
138//! };
139//! # Ok::<(), Box<dyn std::error::Error>>(())
140//! ```
141//!
142//! ### UTF-8 Path Serialization (camino)
143//!
144//! ```rust
145//! use app_path::app_path;
146//! use camino::Utf8PathBuf;
147//!
148//! let static_dir = app_path!("web/static");
149//! let utf8_static = Utf8PathBuf::from_path_buf(static_dir.into_path_buf())
150//!     .map_err(|_| "Invalid UTF-8 path")?;
151//! # Ok::<(), Box<dyn std::error::Error>>(())
152//! ```
153//!
154//! ### Cross-Platform Path Types (typed-path)
155//!
156//! ```rust
157//! use app_path::app_path;
158//! use typed_path::{WindowsPath, UnixPath};
159//!
160//! let dist_dir = app_path!("dist");
161//! let win_path = WindowsPath::new(&dist_dir.to_bytes());
162//! let unix_path = UnixPath::new(&dist_dir.to_bytes());
163//! # Ok::<(), Box<dyn std::error::Error>>(())
164//! ```
165//!
166//! ## Panic Conditions
167//!
168//! [`AppPath::new()`] panics only if executable location cannot be determined:
169//! - `std::env::current_exe()` fails (extremely rare system failure)
170//! - Executable path is empty (indicates system corruption)
171//!
172//! These represent unrecoverable system failures that occur at application startup.
173//! After the first successful call, the executable directory is cached and subsequent
174//! calls never panic.
175//!
176//! **For libraries or applications requiring graceful error handling**, use the fallible
177//! variant [`AppPath::try_new()`] instead.
178
179mod app_path;
180mod error;
181mod functions;
182
183#[cfg(test)]
184mod tests;
185
186// Re-export the public API
187pub use app_path::AppPath;
188pub use error::AppPathError;
189
190// Internal functions for tests and crate internals
191pub(crate) use functions::try_exe_dir;
192
193/// Convenience macro for creating `AppPath` instances with optional environment variable overrides.
194///
195/// # Syntax
196///
197/// - `app_path!()` - Application base directory (equivalent to `AppPath::new()`)
198/// - `app_path!(path)` - Simple path creation (equivalent to `AppPath::with(path)`)
199/// - `app_path!(path, env = "VAR_NAME")` - With environment variable override
200/// - `app_path!(path, override = expression)` - With optional override expression
201/// - `app_path!(path, fn = function)` - With function-based override logic
202///
203/// # Examples
204///
205/// ```rust
206/// use app_path::app_path;
207///
208/// let config = app_path!("config.toml");
209/// let data_dir = app_path!("data", env = "DATA_DIR");
210/// let log_file = app_path!("app.log", override = std::env::args().nth(1));
211/// ```
212#[macro_export]
213macro_rules! app_path {
214    () => {
215        $crate::AppPath::new()
216    };
217    ($path:expr) => {
218        $crate::AppPath::with($path)
219    };
220    ($path:expr, env = $env_var:expr) => {
221        $crate::AppPath::with_override($path, ::std::env::var($env_var).ok())
222    };
223    ($path:expr, override = $override_expr:expr) => {
224        $crate::AppPath::with_override($path, $override_expr)
225    };
226    ($path:expr, fn = $override_fn:expr) => {
227        $crate::AppPath::with_override_fn($path, $override_fn)
228    };
229}
230
231/// Fallible version of [`app_path!`] that returns a [`Result`] instead of panicking.
232///
233/// This macro provides the same convenient syntax as [`app_path!`] but returns
234/// [`Result<AppPath, AppPathError>`] for explicit error handling. Perfect for
235/// libraries and applications that need graceful error handling.
236///
237/// # Syntax
238///
239/// - `try_app_path!()` - Application base directory (equivalent to `AppPath::try_new()`)
240/// - `try_app_path!(path)` - Simple path creation (equivalent to `AppPath::try_with(path)`)
241/// - `try_app_path!(path, env = "VAR_NAME")` - With environment variable override
242/// - `try_app_path!(path, override = expression)` - With any optional override expression
243/// - `try_app_path!(path, fn = function)` - With function-based override logic
244///
245/// # Examples
246///
247/// ## Basic Usage
248///
249/// ```rust
250/// use app_path::try_app_path;
251///
252/// let config = try_app_path!("config.toml")?;
253/// let database = try_app_path!("data/users.db")?;
254/// # Ok::<(), Box<dyn std::error::Error>>(())
255/// ```
256///
257/// ## Environment Variable Overrides
258///
259/// ```rust
260/// use app_path::try_app_path;
261///
262/// let log_file = try_app_path!("logs/app.log", env = "LOG_PATH")?;
263/// log_file.create_parents()?;
264/// # Ok::<(), Box<dyn std::error::Error>>(())
265/// ```
266///
267/// ## Custom Override Logic
268///
269/// ```rust
270/// use app_path::try_app_path;
271///
272/// let custom_path = std::env::var("DATA_HOME").ok();
273/// let data_dir = try_app_path!("data", override = custom_path)?;
274/// # Ok::<(), Box<dyn std::error::Error>>(())
275/// ```
276///
277/// ## Function-Based Override
278///
279/// ```rust
280/// use app_path::try_app_path;
281///
282/// let cache_dir = try_app_path!("cache", fn = || std::env::var("CACHE_DIR").ok())?;
283/// # Ok::<(), Box<dyn std::error::Error>>(())
284/// ```
285///
286/// ## Error Handling
287///
288/// ```rust
289/// use app_path::{try_app_path, AppPathError};
290///
291/// match try_app_path!("config.toml") {
292///     Ok(config) => println!("Config: {}", config.display()),
293///     Err(AppPathError::ExecutableNotFound(msg)) => {
294///         eprintln!("Cannot find executable: {msg}");
295///     }
296///     Err(AppPathError::InvalidExecutablePath(msg)) => {
297///         eprintln!("Invalid executable path: {msg}");
298///     }
299///     Err(AppPathError::IoError(io_err)) => {
300///         eprintln!("I/O operation failed: {io_err}");
301///         // Access original error details for specific handling
302///         match io_err.kind() {
303///             std::io::ErrorKind::PermissionDenied => {
304///                 eprintln!("Permission denied - check file permissions");
305///             }
306///             _ => eprintln!("Other I/O error"),
307///         }
308///     }
309/// }
310/// ```
311///
312/// ## Library Usage
313///
314/// ```rust
315/// use app_path::try_app_path;
316///
317/// pub fn load_config() -> Result<String, Box<dyn std::error::Error>> {
318///     let config_path = try_app_path!("config.toml")?;
319///     std::fs::read_to_string(&config_path).map_err(Into::into)
320/// }
321/// ```
322///
323/// # Comparison with [`app_path!`]
324///
325/// | Feature | [`app_path!`] | [`try_app_path!`] |
326/// |---------|---------------|-------------------|
327/// | **Return type** | [`AppPath`] | [`Result<AppPath, AppPathError>`] |
328/// | **Error handling** | Panics on failure | Returns [`Err`] on failure |
329/// | **Use case** | Applications | Libraries, explicit error handling |
330/// | **Syntax** | Same | Same |
331/// | **Performance** | Same | Same |
332///
333/// # When to Use
334///
335/// - **Use [`try_app_path!`]** for libraries, when you need graceful error handling,
336///   or when integrating with other fallible operations
337/// - **Use [`app_path!`]** for applications where you want to fail fast on system errors
338///
339/// # See Also
340///
341/// - [`app_path!`] - Panicking version with identical syntax
342/// - [`AppPath::try_new()`] - Constructor equivalent
343/// - [`AppPath::try_with_override()`] - Constructor with override equivalent
344/// - [`AppPath::try_with_override_fn()`] - Constructor with function-based override equivalent
345#[macro_export]
346macro_rules! try_app_path {
347    () => {
348        $crate::AppPath::try_new()
349    };
350    ($path:expr) => {
351        $crate::AppPath::try_with($path)
352    };
353    ($path:expr, env = $env_var:expr) => {
354        $crate::AppPath::try_with_override($path, ::std::env::var($env_var).ok())
355    };
356    ($path:expr, override = $override_expr:expr) => {
357        $crate::AppPath::try_with_override($path, $override_expr)
358    };
359    ($path:expr, fn = $override_fn:expr) => {
360        $crate::AppPath::try_with_override_fn($path, $override_fn)
361    };
362}