app-path 0.1.2

Create file paths relative to your executable for truly portable applications
Documentation

AppPath

Create paths relative to your executable for truly portable applications.

Crates.io Documentation License: MIT OR Apache-2.0 CI

๐ŸŽฏ The Problem

When building applications that need to access files (configs, templates, data), you typically have two choices:

  1. System directories (~/.config/, %APPDATA%, etc.) - Great for installed apps, but...

    • Requires installation
    • Spreads files across the system
    • Hard to backup/move
    • Needs admin rights on some systems
  2. Hardcoded paths - Simple but brittle and non-portable

โœจ The Solution

AppPath creates paths relative to your executable location, enabling truly portable applications where everything stays together.

use app_path::AppPath;
use std::path::PathBuf;

// Create paths relative to your executable - accepts any path-like type
let config = AppPath::try_new("config.toml")?;
let data = AppPath::try_new("data/users.db")?;

// Efficient ownership transfer for owned types
let log_file = "logs/app.log".to_string();
let logs = AppPath::try_new(log_file)?; // String is moved

let path_buf = PathBuf::from("cache/data.bin");
let cache = AppPath::try_new(path_buf)?; // PathBuf is moved

// Works with any path-like type
let from_path = AppPath::try_new(std::path::Path::new("temp.txt"))?;

// Alternative: Use TryFrom for string types
let settings = AppPath::try_from("settings.json")?;

// Absolute paths are used as-is (for system integration)
let system_log = AppPath::try_new("/var/log/app.log")?;
let windows_temp = AppPath::try_new(r"C:\temp\cache.dat")?;

// Get the paths for use with standard library functions
println!("Config: {}", config.path().display());
println!("Data: {}", data.path().display());

// Check existence and create directories
if !logs.exists() {
    logs.create_dir_all()?;
}

๐Ÿš€ Features

  • ๐Ÿš€ Zero dependencies - Uses only standard library
  • ๐ŸŒ Cross-platform - Windows, Linux, macOS support
  • ๐Ÿ›ก๏ธ Safe API - Uses try_new() following Rust conventions where new() implies infallible construction
  • ๐Ÿ”ง Easy testing - Override base directory with with_base() method
  • ๐Ÿ“ Smart path handling - Relative paths resolve to executable directory, absolute paths used as-is
  • โšก Efficient ownership - Accepts any path-like type with optimal ownership transfer
  • ๐ŸŽฏ Ergonomic conversions - TryFrom implementations for string types
  • ๐Ÿ“š Comprehensive docs - Extensive examples and clear API documentation

๏ฟฝ Path Resolution Behavior

AppPath handles different path types intelligently:

Relative Paths (Recommended for Portable Apps)

// These resolve relative to your executable's directory
let config = AppPath::try_new("config.toml")?;       // โ†’ exe_dir/config.toml
let data = AppPath::try_new("data/users.db")?;       // โ†’ exe_dir/data/users.db
let nested = AppPath::try_new("logs/app/debug.log")?; // โ†’ exe_dir/logs/app/debug.log

Absolute Paths (For System Integration)

// These are used as-is, ignoring the executable directory
let system_config = AppPath::try_new("/etc/myapp/config.toml")?;  // โ†’ /etc/myapp/config.toml
let windows_temp = AppPath::try_new(r"C:\temp\cache.dat")?;       // โ†’ C:\temp\cache.dat
let user_home = AppPath::try_new("/home/user/.myapp/settings")?;  // โ†’ /home/user/.myapp/settings

This design allows your application to:

  • โœ… Stay portable with relative paths for app-specific files
  • โœ… Integrate with system using absolute paths when needed
  • โœ… Be configurable - users can specify either type in config files

๐Ÿ“– Quick Start

Add to your Cargo.toml:

[dependencies]
app-path = "0.1"
use app_path::AppPath;
use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create paths relative to your executable
    let config = AppPath::try_new("config.toml")?;
    let templates = AppPath::try_new("templates")?;
    let logs = AppPath::try_new("logs/app.log")?;
    
    // Use them like normal paths
    if config.exists() {
        let content = fs::read_to_string(config.path())?;
        println!("Config: {}", content);
    }
    
    // Create directories automatically
    logs.create_dir_all()?;
    fs::write(logs.path(), "Application started\n")?;
    
    println!("Config: {}", config);      // Displays full path
    println!("Templates: {}", templates);
    
    Ok(())
}

๐Ÿ”„ Ownership and Performance

AppPath accepts any path-like type with optimal ownership handling:

use app_path::AppPath;
use std::path::{Path, PathBuf};

// String literals (no allocation)
let config = AppPath::try_new("config.toml")?;

// Owned String (moves ownership, no clone)
let filename = "data.db".to_string();
let data = AppPath::try_new(filename)?; // filename is moved

// PathBuf (moves ownership, no clone)
let path_buf = PathBuf::from("logs/app.log");
let logs = AppPath::try_new(path_buf)?; // path_buf is moved

// Path reference (efficient conversion)
let path_ref = Path::new("cache.json");
let cache = AppPath::try_new(path_ref)?;

// TryFrom for ergonomic string conversions
let settings = AppPath::try_from("settings.toml")?;
let from_string = AppPath::try_from("db.sqlite".to_string())?;

๐Ÿ—๏ธ Application Structure

Your portable application structure becomes:

myapp.exe          # Your executable
โ”œโ”€โ”€ config.toml    # AppPath::try_new("config.toml")
โ”œโ”€โ”€ templates/     # AppPath::try_new("templates")
โ”‚   โ”œโ”€โ”€ email.html
โ”‚   โ””โ”€โ”€ report.html
โ”œโ”€โ”€ data/          # AppPath::try_new("data")
โ”‚   โ””โ”€โ”€ cache.db
โ””โ”€โ”€ logs/          # AppPath::try_new("logs")
    โ””โ”€โ”€ app.log

๐Ÿงช Testing Support

Override the base directory for testing:

#[cfg(test)]
mod tests {
    use app_path::AppPath;
    use std::env;

    #[test]
    fn test_config_loading() {
        let temp = env::temp_dir().join("app_path_test");
        let config = AppPath::try_new("config.toml")
            .unwrap()
            .with_base(&temp);
        
        // Test with isolated temporary directory
        assert!(!config.exists());
    }
}

๐ŸŽฏ Why Choose AppPath?

vs. Standard Library (std::env::current_dir())

// โŒ Brittle - depends on where user runs the program
let config = std::env::current_dir()?.join("config.toml");

// โœ… Reliable - always relative to your executable
let config = AppPath::try_new("config.toml")?;

vs. System Directories (directories crate)

// โŒ Scattered across the system
use directories::ProjectDirs;
let proj_dirs = ProjectDirs::from("com", "MyOrg", "MyApp").unwrap();
let config = proj_dirs.config_dir().join("config.toml"); // ~/.config/MyApp/config.toml

// โœ… Everything together with your app
let config = AppPath::try_new("config.toml")?; // ./config.toml (next to exe)

vs. Manual Path Joining

// โŒ Verbose and error-prone
let exe_path = std::env::current_exe()?;
let exe_dir = exe_path.parent().ok_or("No parent")?;
let config = exe_dir.join("config.toml");

// โœ… Clean and simple
let config = AppPath::try_new("config.toml")?;

๐Ÿ“ Perfect For

  • Portable applications that travel on USB drives
  • Development tools that should work anywhere
  • Corporate environments where you can't install software
  • Containerized applications with predictable layouts
  • Embedded systems with simple file structures
  • Quick prototypes that need simple file access

๐Ÿ”„ Common Usage Patterns

Replace hardcoded paths:

// Instead of brittle hardcoded paths
let config = PathBuf::from("config.toml");  // Depends on working directory

// Use AppPath for reliable, portable paths
let config = AppPath::try_new("config.toml")?;  // Always relative to executable

Replace manual path construction:

// Instead of verbose manual construction
let exe = std::env::current_exe()?;
let exe_dir = exe.parent().unwrap();
let config = exe_dir.join("config.toml");

// Use AppPath for clean, simple code
let config = AppPath::try_new("config.toml")?;

๐Ÿ“„ License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.


AppPath: Keep it simple, keep it together. ๐ŸŽฏ