# cfgmatic-files

[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue)](https://gitlab.opentc.ru/opentc/rust-cfgmatic)
[![Unsafe](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
[![rustc](https://img.shields.io/badge/rustc-1.92+-lightgray.svg)](https://www.rust-lang.org)

Configuration file discovery and reading with multiple format support.

## Overview

`cfgmatic-files` provides a simple, cross-platform way to discover and parse configuration files following platform conventions (via `cfgmatic-paths`). It supports multiple serialization formats and handles configuration merging with proper priority handling.

## Features

- **Multiple Formats**: TOML, JSON, YAML (see note below)
- **Cross-platform**: Uses `cfgmatic-paths` for platform-specific directories
- **Priority-based Merging**: User > Local > System
- **Type-safe Parsing**: Deserialize directly into your structs
- **Flexible Discovery**: Find all files or just the first available

### YAML Support Notice

> **LEGACY**: The `yaml` feature is considered legacy. The `serde_yaml` crate is deprecated by its author and may be removed in future versions. See: https://github.com/dtolnay/serde-yaml/issues/344

## Installation

```toml
[dependencies]
cfgmatic-files = "0.1"
```

Enable features as needed:

```toml
[dependencies]
cfgmatic-files = { version = "0.1", features = ["toml", "json"] }
```

## Quick Start

### Finding Configuration Files

```rust
use cfgmatic_files::FileFinder;

// Find all config files for an application
let files = FileFinder::new("myapp").find()?;

for file in &files {
    println!("Found: {} ({:?})", file.path.display(), file.tier);
}
```

### Parsing Configurations

```rust
use cfgmatic_files::FileFinder;
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Config {
    timeout: u32,
    host: String,
}

let files = FileFinder::new("myapp").find()?;

if let Some(file) = files.first() {
    let config: Config = file.parse()?;
    println!("Timeout: {}, Host: {}", config.timeout, config.host);
}
```

### Loading and Merging

```rust
use cfgmatic_files::load_merged;
use serde::Deserialize;

#[derive(Debug, Deserialize, Default)]
struct Config {
    port: u16,
    host: String,
}

// Load and merge all config files (User > Local > System)
let config: Config = load_merged("myapp")?;
```

### Finding First Available

```rust
use cfgmatic_files::{load_first, FileFinder};

// Load the first available config file
let config: Option<Config> = load_first("myapp")?;

// Or find the file first
let files = FileFinder::new("myapp").find()?;
if let Some(file) = files.first() {
    // Use file.path() to access the path
    println!("Config file: {}", file.path().display());
}
```

## Configuration Priority

Files are searched and prioritized by tier (highest to lowest):

1. **User** (`ConfigTier::User`): User-specific configs
   - Unix/Linux: `~/.config/myapp/`
   - macOS: `~/Library/Application Support/myapp/` (GUI apps)
   - Windows: `%APPDATA%\myapp\`

2. **Local** (`ConfigTier::Local`): Machine-specific configs
   - Unix/Linux: `/usr/local/etc/myapp/`, `/etc/myapp/`
   - macOS: `/Library/Application Support/myapp/`
   - Windows: `%PROGRAMDATA%\myapp\`

3. **System** (`ConfigTier::System`): System-wide configs
   - Unix/Linux: `/etc/myapp/`, `/etc/xdg/myapp/`
   - Windows: `%PROGRAMDATA%\myapp\`

When merging configurations, higher tiers override values from lower tiers.

## Supported Formats

### TOML (default)

```toml
# config.toml
timeout = 30
host = "localhost"
```

```rust
let files = FileFinder::new("myapp")
    .formats(&[Format::Toml])
    .find()?;
```

### JSON (default)

```json
{
  "timeout": 30,
  "host": "localhost"
}
```

```rust
let files = FileFinder::new("myapp")
    .formats(&[Format::Json])
    .find()?;
```

### YAML (legacy feature)

```yaml
timeout: 30
host: localhost
```

```rust
let files = FileFinder::new("myapp")
    .formats(&[Format::Yaml])
    .find()?;
```

## Advanced Usage

### Custom Format Selection

```rust
use cfgmatic_files::{FileFinder, Format};

// Search for TOML and JSON, preferring TOML
let files = FileFinder::new("myapp")
    .formats(&[Format::Toml, Format::Json])
    .find()?;
```

### Tier-specific Searching

```rust
use cfgmatic_files::{FileFinder, ConfigTier};

// Only search user directories
let files = FileFinder::new("myapp")
    .tiers(&[ConfigTier::User])
    .find()?;
```

### Merging Custom Types

```rust
use cfgmatic_files::{ConfigFiles, Mergeable};
use serde::Deserialize;

#[derive(Debug, Deserialize, Default)]
struct Config {
    port: u16,
    #[serde(default)]
    database: DatabaseConfig,
}

#[derive(Debug, Deserialize, Default)]
struct DatabaseConfig {
    url: String,
    pool_size: u32,
}

let files = FileFinder::new("myapp").find()?;
let config: Config = files.merge()?;
```

## Examples

```bash
# Find and display all config files
cargo run --example find_files

# Load and parse TOML config
cargo run --example load_toml

# Merge multiple config files
cargo run --example merge_configs
```

## Feature Flags

- **`default`**: Enables `toml` and `json` features
- **`toml`**: TOML format support (via `toml` crate)
- **`json`**: JSON format support (via `serde_json`)
- **`yaml`**: YAML format support (via `serde_yaml`, **legacy**)
- **`async`**: Async support via `tokio`

## Relationship to cfgmatic

This crate is part of the `cfgmatic` configuration framework:

- **cfgmatic-paths**: Platform-specific path discovery
- **cfgmatic-files**: File discovery and parsing (this crate)
- **cfgmatic**: High-level API with environment variables and validation

## 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.

## Contributing

Contributions are welcome! Please see [CONTRIBUTING.md](../CONTRIBUTING.md) for details.
