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 | |
Option<T> |
#[kv(lenient)] |
Optional single value; an unparseable value becomes None instead of erroring |
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 |
Vec<KvWarning> |
#[kv(warnings)] |
Collects parse failures from lenient fields |
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 aVec#[kv(collect)]- Collect all unhandled keys into thisHashMap<String, String>#[kv(lenient)]- For anOption<T>field, treat a value that fails to parse asNonerather than erroring (recorded in a#[kv(warnings)]field if one is present)#[kv(warnings)]- Collect parse failures fromlenientfields into thisVec<KvWarning>
Duplicate Key Behavior
For non-multiline fields, duplicate keys overwrite the previous value.
For multiline fields, each occurrence appends to the Vec.
Examples
These examples are written against the pkgsrc-kv crate, which
re-exports this macro alongside the runtime it targets. They are marked
ignore here only because this engine crate does not depend on the
runtime; they run as written once pkgsrc-kv is a dependency.
use indoc::indoc;
use pkgsrc_kv::{Kv, KvError};
#[derive(Kv)]
pub struct Package {
pkgname: String,
#[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, "foo-1.0");
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::{Kv, KvError};
#[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>(())