congen 0.2.0

congen helps you build configuration systems that support partial updates from structured changes and CLI input
Documentation
# congen


[![Crates.io](https://img.shields.io/crates/v/congen.svg)](https://crates.io/crates/congen)
[![docs.rs](https://img.shields.io/docsrs/congen)](https://docs.rs/congen)
[![Dependency status](https://deps.rs/repo/github/Wasabi375/congen/status.svg)](https://deps.rs/repo/github/Wasabi375/congen)


`congen` helps you build configuration systems that support partial updates from structured changes and CLI input.

It is designed around two ideas:

- your config type implements `Configuration`
- updates are represented by a companion `CongenChange` type

Most projects use the derive macros from `congen-derive`, so you usually do not need to write these trait impls manually.

## Feature overview

- Derive support for config structs via `#[derive(Configuration)]`.
- Automatic generation of a strongly typed change object (`<Config>Change`) for each config type.
- Field-level update verbs:
  - `set`
  - `unset` (for unsettable fields like `Option<_>` or `bool`)
  - `use-default` (when defaults are configured)
- Nested updates by path (`sub.field`, `outer.inner.value`, etc.).
- Optional field semantics with `Option<T>` support.
- Collection updates for:
  - `Vec<T>` (`append`, `update`, `remove`, `empty`)
  - `HashMap<K, T>` (`append`, `update`, `remove`, `empty`)
- Typed list/map keys with support for string, signed, and unsigned map keys.
- Built-in primitive support, including:
  - `bool`
  - integer and float numeric primitives
  - `String`, `CString`, and `OsString`
- `clap` bridge (`CongenClap<T>`) that generates subcommands from your configuration description.
- `ValueEnum` integration via `#[derive(ValueEnumConfiguration)]` for enum fields used as config values.

`clap` support is feature-gated behind the `clap` crate feature (enabled by default).

## Derive macros

`congen` re-exports derive macros from `congen-derive`:

- `Configuration` for structs
- `ValueEnumConfiguration` for `clap::ValueEnum` enums (requires the `clap` feature)

Example:

```rust
use congen::Configuration;

#[derive(Configuration, Debug)]
struct Config {
    port: u16,
    #[congen(default)]
    log_file: Option<String>,
}
```

## Default values

This crate does not use rusts [Default] trait to provide default values. This is done, because a default
value for a type might not be necessarialy translate into a good default value in a configuration, e.g. 
the default for all number types is zero, which is not a good default value for the number of threads 
some process is allowed to use.

Instead [CongenInternal::default] is used instead. The exact default value can be configured using 
the `#[congen(default)]` attribute. 

- `#[congen(default)]` use `CongenInternal::default` for the field
- `#[congen(rust_default)]` use `Default::default` for the field
- `#[congen(default = <expr>)]` (custom expression)

In addition `#[congen(inner_default = <expr>)]` can be used to control the default value of inner types 
in `Vec`, `HashMap` and `Option`.

## CLI integration with `CongenClap`

You can parse configuration changes with clap and apply them to an existing config instance.

## Typical application flow

The most common pattern is:

1. Read the current config from disk.
2. If the CLI command is `config`, parse and apply a `CongenChange`, then write the updated config back to disk.
3. Otherwise, run your normal program logic with the config that was read from disk.

`congen` is focused on step 2: creating and applying strongly typed config changes.

```rust
use clap::{Parser, Subcommand};
use congen::{Configuration, CongenClap, load_from_env};

#[derive(Configuration, Debug)]
struct Config {
    retries: u32,
    #[congen(default)]
    token: Option<String>,
}

#[derive(Parser)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    Config(CongenClap<Config>),
    Run,
}

fn main() {
    let cli = Cli::parse_from(["program_name", "config", "token", "unset"]);
    let mut config = read_config_from_file("config.toml");

    match cli.command {
        Commands::Config(change_args) => {
            config.apply_change(change_args.into_change());
            write_config_to_file("config.toml", &config);
        }
        Commands::Run => {
            let config_change = load_from_env::<Config>("CONFIG").unwrap();
            config.apply_change(config_change);
            run_program(config);
        }
    }
}

fn read_config_from_file(_path: &str) -> Config {
    Config {
        retries: 3,
        token: Some("abc".to_string()),
    }
}

fn write_config_to_file(_path: &str, _config: &Config) {
    // serialize and persist config
}

fn run_program(_config: Config) {
    // your actual application logic
}
```

Typical generated commands look like:

- `config retries set 5`
- `config token unset`
- `config token use-default`

Nested fields and collections are represented through nested subcommands (see workspace examples).

## Environment variable integration

Besides clap-based parsing, you can build a `CongenChange` from environment variables using `congen::env`:

- `env::load_from_env::<Config>("CONFIG")` reads from `std::env::vars_os()`.

Environment variable names use this shape:

- `<PREFIX>_<PATH>_<VERB>`

Where:

- `<PATH>` is the config field path in screaming snake case (`sub.e` -> `SUB_E`, `myValue` -> `MY_VALUE`).
- `<VERB>` is one of:
  - `SET` (value is parsed and applied)
  - `UNSET` (value is ignored)
  - `USE_DEFAULT` (also accepts `USE-DEFAULT` and `USEDEFAULT`; value is ignored)

## Workspace examples

This repository contains complete examples:

- `examples/basic` - nested structs, `Option`, defaults, and basic set/unset behavior.
- `examples/lists` - `Vec`/`HashMap` operations, nested collection updates, key handling, and `inner_default`.
- `examples/primitives` - primitive parsing and updates across supported base types.

## Changelog

A changelog exists at [CHANGELOG.md](CHANGELOG.md)