# cfgmatic-paths
[](https://crates.io/crates/cfgmatic-paths)
[](https://docs.rs/cfgmatic-paths)
[](LICENSE-MIT)
[](https://github.com/rust-secure-code/safety-dance/)
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
```rust
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:
```rust
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:
```rust
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:
```rust
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:
```rust
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
```rust
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
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.