tomldir 0.1.7

Lean TOML loader with runtime dot-path access and flattening into maps, optimized for tools and dynamic configs
Documentation

tomldir

Crates.io Docs CI License

tomldir is a lean crate for loading TOML configs into map-based structures with fast dot-path access, deterministic flattening, and easy composition - ideal for CLIs and runtime-driven configuration.

Features

  • Runtime-friendly: Load and query config via string key paths like "server.port".
  • Flattened output: Nested tables become dot-separated keys for easy iteration or overlay with env/flags.
  • Map-first storage: Uses a HashMap by default, but easily swap in IndexMap or BTreeMap.
  • Thread-safe: Designed for use with Arc and concurrent access.
  • Flexible flattening: Flatten to any supported map or container with flatten_into().

If you’re coming from Go, tomldir offers a Viper-like workflow: load TOML into a map, access values via dot paths, and flatten configs for runtime use.

Why tomldir?

Use tomldir when you want:

  • Runtime access via string paths like database.host
  • Easy flattening for CLI flags or environment overlays
  • Flexible configs without rigid structs

tomldir intentionally prioritizes simplicity over strong typing.

Usage

Add to Cargo.toml:

[dependencies]
tomldir = "0.1"
# Optional: for order-preserving keys
indexmap = "2.0"

Basic: Setting up a runtime config

use tomldir::Config;

fn main() -> tomldir::Result<()> {
    let toml_data = r#"
        [database]
        host = "localhost"
        port = 5432
    "#;

    // Load into default HashMap
    let cfg = Config::from_toml(toml_data)?;

    
    // Thread-safe access (explicit cheap clone)
    let cfg = cfg.shared(); 

    assert_eq!(cfg.get_string("database.host"), Some("localhost"));

    // Or even convert config into env-style keys
    for (k, v) in cfg.flatten() {
        println!("APP_{}={}", k.to_uppercase().replace('.', "_"), v);
    }

    Ok(())
}

Advanced: Preserving Order with IndexMap

If you need to iterate over keys in the order they were defined (e.g., for help text generation or consistent output), use IndexMap.

use tomldir::{Config, Value};
use indexmap::IndexMap;

fn main() -> tomldir::Result<()> {
    let toml_data = r#"
        first = 1
        second = 2
    "#;

    // Specify storage type explicitly
    let cfg = Config::<IndexMap<String, Value>>::from_toml_with(toml_data)?;

    let keys: Vec<_> = cfg.flatten_into::<Vec<(String, String)>>()
        .into_iter()
        .map(|(k, _)| k)
        .collect();
    assert_eq!(keys, vec!["first", "second"]);
    
    Ok(())
}

You can get an iterator over all flattened key-value pairs as strings:

for (key, value) in cfg.flatten() {
    println!("{key} = {value}");
}

Or collect them into a specific map (like HashMap<String, String>) using flatten_into():

let flat: HashMap<String, String> = cfg.flatten_into();

Or flatten into any custom type using flatten_into():

use std::collections::BTreeMap;

// Flatten to BTreeMap (sorted keys)
let flat_sorted: BTreeMap<String, String> = cfg.flatten_into();

// Flatten to Vec
let flat_vec: Vec<(String, String)> = cfg.flatten_into();

Comparison

Feature serde + toml config tomldir
Strong typing ⚠️
Dot-path runtime access ⚠️
Flattening support ⚠️
Minimal boilerplate ⚠️

License

MIT