pkgsrc-kv-derive 0.2.0

Derive macro for KEY=VALUE parsing
Documentation

Derive macro for parsing KEY=VALUE formats.

This crate provides [macro@Kv] for automatically implementing parsers for structs from KEY=VALUE formatted input.

Field Types

Rust Type Attribute Behavior
T Required single value
Option<T> Optional single value
Vec<T> Whitespace-separated values on single line
Option<Vec<T>> Optional whitespace-separated values
Vec<T> #[kv(multiline)] Multiple lines collected into Vec
Option<Vec<T>> #[kv(multiline)] Optional multiple lines
HashMap<String, String> #[kv(collect)] Collects unhandled keys

Container Attributes

  • #[kv(allow_unknown)] - Ignore unknown keys instead of returning an error

Field Attributes

  • #[kv(variable = "KEY")] - Use custom key name instead of uppercased field name
  • #[kv(multiline)] - Collect multiple lines with the same key into a Vec
  • #[kv(collect)] - Collect all unhandled keys into this HashMap<String, String>

Duplicate Key Behavior

For non-multiline fields, duplicate keys overwrite the previous value. For multiline fields, each occurrence appends to the Vec.

Examples

use indoc::indoc;
use pkgsrc::kv::{KvError, Kv};
use pkgsrc::PkgName;

#[derive(Kv)]
pub struct Package {
    pkgname: PkgName,
    #[kv(variable = "SIZE_PKG")]
    size: u64,
    #[kv(multiline)]
    description: Vec<String>,
    homepage: Option<String>,
}

let input = indoc! {"
    PKGNAME=foo-1.0
    SIZE_PKG=1234
    DESCRIPTION=A package that does
    DESCRIPTION=many interesting things.
"};
let pkg = Package::parse(input)?;
assert_eq!(pkg.pkgname.pkgbase(), "foo");
assert_eq!(pkg.size, 1234);
assert_eq!(pkg.description, vec!["A package that does", "many interesting things."]);
assert_eq!(pkg.homepage, None);

// Missing required fields return an error.
assert!(Package::parse("PKGNAME=bar-1.0\n").is_err());
# Ok::<(), KvError>(())

Use collect to collect unhandled keys into a HashMap, for example when parsing +BUILD_INFO where arbitrary variables will be present:

use indoc::indoc;
use std::collections::HashMap;
use pkgsrc::kv::{KvError, Kv};

#[derive(Kv)]
pub struct BuildInfo {
    build_host: Option<String>,
    machine_arch: Option<String>,
    #[kv(collect)]
    vars: HashMap<String, String>,
}

let input = indoc! {"
    BUILD_DATE=2025-01-15 10:30:00 +0000
    BUILD_HOST=builder.example.com
    MACHINE_ARCH=x86_64
    PKGPATH=devel/example
"};
let info = BuildInfo::parse(input)?;
assert_eq!(info.build_host, Some("builder.example.com".to_string()));
assert_eq!(info.machine_arch, Some("x86_64".to_string()));
assert_eq!(info.vars.get("PKGPATH"), Some(&"devel/example".to_string()));
assert_eq!(info.vars.get("VARBASE"), None);
# Ok::<(), KvError>(())