source-kv 0.3.0

A Serde serialization and deserialization library for Valve's Key-Value format.
Documentation
# source-kv

A straightforward, high-performance `serde` implementation for parsing and serializing Valve's textual Key-Values formats (such as `.vmf`, `.vmt`, `gameinfo.txt`, `GameConfig.txt`, etc.).

## Key Features

* **Full Serde Integration**: Seamlessly deserialize into custom Rust structs and serialize back using standard `#[derive(Deserialize, Serialize)]` attributes.
* **Duplicate Key Support**: Valve's format heavily relies on duplicate keys (especially in `.vmf` files). `source-kv` natively handles this—just map repeated keys to a `Vec<T>`.
* **Order Preservation**: Internally uses `IndexMap` to guarantee that the order of properties and blocks is preserved during serialization.
* **Syntax Tolerance**: Safely handles unquoted keys, C-style line comments (`//`), and implicitly converts integer/boolean strings into native Rust types.
* **Dynamic / Untyped API**: Don't want to write static structs? Parse files directly into a generic `source_kv::Value` (AST) and easily convert specific nodes to typed structs later using `from_value`.

## Quick Start

### 1. Parsing Strongly-Typed Structures (e.g., VMF Entities)

Repeated keys are easily captured using `Vec<T>` alongside `#[serde(rename = "...")]`.

```rust
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct MapData {
    // VMF files can contain multiple 'entity' blocks
    #[serde(rename = "entity", default)]
    entities: Vec<Entity>,
}

#[derive(Debug, Deserialize)]
struct Entity {
    id: u32,
    classname: String,
    origin: Option<String>,
    #[serde(default)]
    connections: Vec<Connection>,
}

#[derive(Debug, Deserialize)]
struct Connection {
    target: String,
    action: String,
}

fn main() -> Result<(), source_kv::Error> {
    let vmf_data = r#"
    entity
    {
        "id" "1337"
        "classname" "prop_physics"
        "origin" "0 0 64"
        
        "connections"
        {
            "target" "door_1"
            "action" "Open"
        }
    }
    "#;

    let parsed: MapData = source_kv::from_str(vmf_data)?;
    println!("{:#?}", parsed.entities);
    
    Ok(())
}
```

### 2. Using the Dynamic AST (`source_kv::Value`)

Sometimes you don't know the exact structure upfront. You can parse the document into an untyped tree and deserialize specific parts on demand.

```rust
use serde::Deserialize;
use source_kv::{Deserializer, Value, from_value};

#[derive(Debug, Deserialize)]
struct ToolConfig {
    #[serde(rename = "GameExe")]
    game_exe: String,
}

fn main() -> Result<(), source_kv::Error> {
    let input = r#"
        "Configs"
        {
            "Games"
            {
                "Portal 2"
                {
                    "GameExe" "portal2.exe"
                }
            }
        }
    "#;

    // Parse into an untyped Value tree
    let mut de = Deserializer::from_str(input);
    let root: Value = de.parse_root()?;

    // Traverse the AST natively
    if let Some(portal_config) = root.get("Configs")
        .and_then(|c| c.get("Games"))
        .and_then(|g| g.get("Portal 2")) 
    {
        // Convert the specific untyped block into a typed Rust struct
        let config: ToolConfig = from_value(portal_config.clone())?;
        println!("Executable: {}", config.game_exe);
    }

    Ok(())
}
```

## API Overview

- `source_kv::from_str` — Deserialize a KeyValues string directly into a typed struct `T`.
- `source_kv::to_string` — Serialize a typed struct `T` back into KeyValues format.
- `source_kv::Value` — An enum representing the AST (`Str` or `Obj`), offering ergonomic getters like `.get(key)` and `.get_string(key)`.
- `source_kv::from_value` — Convert an existing AST `Value` node into a Serde-compatible struct `T`.

## License
MIT License.