cfgmatic-paths 1.0.0

Cross-platform configuration path discovery following XDG and platform conventions
Documentation

cfgmatic-paths

Crates.io Docs.rs License Unsafe

Cross-platform configuration path discovery following XDG and platform conventions.

Overview

This crate provides a platform-agnostic way to discover configuration directories following the conventions of each operating system:

  • Unix/Linux: XDG Base Directory Specification
  • macOS CLI: XDG (same as Unix)
  • macOS GUI: Application Support directories (with macos-gui feature)
  • Windows: Known Folder IDs (AppData, ProgramData)

Architecture

crates/cfgmatic-paths/
├── Cargo.toml
├── src/
│   ├── lib.rs           # Public API and convenience functions
│   ├── builder.rs       # PathsBuilder and PathFinder
│   ├── core/            # Core domain types
│   │   ├── mod.rs       # Module exports
│   │   ├── app_type.rs  # AppType (CLI, GUI, Service)
│   │   ├── config_tier.rs # ConfigTier (User, Local, System)
│   │   ├── discovery.rs # ConfigDiscovery, ConfigCandidate, PathStatus
│   │   └── pattern.rs   # FilePattern for matching config files
│   ├── platform/        # Platform-specific implementations
│   │   ├── mod.rs       # DirectoryFinder trait
│   │   ├── unix.rs      # XDG for Unix/Linux/macOS CLI
│   │   ├── windows.rs   # Windows Known Folders
│   │   └── macos_gui.rs # macOS Application Support
│   ├── env/             # Environment abstraction for testing
│   │   ├── mod.rs       # Env trait
│   │   └── std_env.rs   # StdEnv implementation
│   └── filesystem/      # Filesystem abstraction for testing
│       ├── mod.rs       # Fs trait
│       └── std_fs.rs    # StdFs implementation

Usage

Basic Usage

use cfgmatic_paths::PathsBuilder;

// Create a path finder for your application
let finder = PathsBuilder::new("myapp").build();

// Get user config directories
let user_dirs = finder.user_dirs();
println!("User config dirs: {:?}", user_dirs);

// Ensure the primary user config directory exists
if let Ok(config_dir) = finder.ensure_user_config_dir() {
    println!("Config dir: {}", config_dir.display());
}

Configuration Discovery

The crate provides comprehensive configuration discovery with full diagnostics:

use cfgmatic_paths::{discover_config, PathsBuilder};

// Quick discovery
let discovery = discover_config("myapp");

println!("Preferred path for new config: {}", discovery.preferred_path.display());

if let Some(found) = &discovery.found_path {
    println!("Found existing config at: {}", found.display());
}

// See all candidates that were searched
for candidate in discovery.candidates {
    println!("  - {:?}: {} ({:?})",
        candidate.tier,
        candidate.path.display(),
        candidate.status
    );
}

// Check for config fragments (conf.d style)
for fragment in &discovery.fragments {
    println!("Fragment: {}", fragment.display());
}

Path Without Existence Check

Get the preferred configuration path without checking if it exists:

use cfgmatic_paths::{config_path, config_file_path};

// Get the preferred config directory
let dir = config_path("myapp");
println!("Config directory: {}", dir.display());

// Get a specific config file path
let file = config_file_path("myapp", "config.toml");
println!("Config file: {}", file.display());

Multi-File Configuration Discovery

Find all configuration files matching a pattern:

use cfgmatic_paths::{PathsBuilder, FilePattern};

let finder = PathsBuilder::new("myapp").build();

// Find all config files with common extensions
let pattern = FilePattern::extensions("config", &["toml", "yaml", "json"]);
let configs = finder.find_config_files(&pattern);

for config in configs {
    if config.exists() {
        println!("Found config: {} ({:?})",
            config.path.display(),
            config.tier
        );
    }
}

Conf.d Style Fragments

Find configuration fragments from conf.d-style directories:

use cfgmatic_paths::{PathsBuilder, FilePattern};

let finder = PathsBuilder::new("myapp").build();

// Find all .conf files in conf.d directories
let pattern = FilePattern::glob("*.conf");
let fragments = finder.find_fragments(&pattern, "conf.d");

for fragment in fragments {
    println!("Fragment: {}", fragment.display());
}

Convenience Functions

use cfgmatic_paths::{find_existing_dir, ensure_config_dir, get_all_dirs};

// Find the first existing config directory
if let Some(dir) = find_existing_dir("myapp") {
    println!("Found: {}", dir.display());
}

// Ensure user config directory exists
let config_dir = ensure_config_dir("myapp")?;

// Get all directories with metadata
for info in get_all_dirs("myapp") {
    println!("{:?}: {} (exists: {})",
        info.tier, info.path.display(), info.exists);
}

Configuration Tiers

Configuration directories are organized into three tiers:

  1. User (ConfigTier::User): User-specific configs in home directory. Highest priority, typically writable.

  2. Local (ConfigTier::Local): Machine-specific configs. Medium priority, may be writable.

  3. System (ConfigTier::System): System-wide configs. Lowest priority, typically read-only.

Platform Details

Unix/Linux (XDG)

  • User: $XDG_CONFIG_HOME/<app>/ (default: ~/.config/<app>/)
  • Legacy: ~/.<app>rc (optional, enabled by default)
  • System: $XDG_CONFIG_DIRS/<app>/ (default: /etc/xdg/<app>/)

macOS

CLI Applications (default)

Uses XDG (same as Unix/Linux).

GUI Applications (with macos-gui feature)

  • User: ~/Library/Application Support/<bundle-id>/
  • System: /Library/Application Support/<bundle-id>/

Windows

  • User Roaming: %APPDATA%\<Company>\<App>\
  • User Local: %LOCALAPPDATA%\<Company>\<App>\
  • System: %PROGRAMDATA%\<Company>\<App>\

Testing

The crate provides abstractions for testing:

  • Env: Mock environment variables
  • Fs: Mock filesystem operations

These allow testing without modifying the actual environment or filesystem.

Features

  • default: No additional features enabled.
  • macos-gui: Use macOS Application Support directories instead of XDG.
  • test-helpers: Additional utilities for testing (future).

Relationship to cfgmatic

This crate is the foundation for the cfgmatic configuration framework:

  • cfgmatic-paths: Path discovery (this crate)
  • cfgmatic: File search, merging, watching (uses cfgmatic-paths)

The separation allows:

  1. Independent use of path discovery
  2. Testing without filesystem dependencies
  3. Platform-specific optimizations

License

This project is licensed under either of

at your option.