5 releases
| 0.1.4 | Jan 8, 2026 |
|---|---|
| 0.1.3 | Jan 4, 2026 |
| 0.1.2 | Dec 28, 2025 |
| 0.1.1 | Dec 20, 2025 |
| 0.1.0 | Dec 15, 2025 |
#178 in Configuration
377 downloads per month
Used in 4 crates
(2 directly)
56KB
1K
SLoC
korni
An ultra-fast, nearly stateless, and failure-tolerant parser for .env files, written in Rust.
Designed for high-performance tooling (LSPs, linters, formatters) and applications that need deep introspection into environment configuration files.
Features
- 🚀 Blazingly Fast: Heavily optimized using nearly zero-copy parsing (
Cowstrings) and SIMD-friendly slice iterators. - 📍 Introspective: Tracks exact line and column positions (spans) for keys, values, and comments.
- 💬 Comment Support: First-class support for parsing and preserving comments, including commented-out key-value pairs.
- 🛡️ Failure Tolerant: Continues parsing after errors, collecting all issues instead of halting on the first one.
- 📦 Zero Runtime Dependencies: Lightweight and easy to drop into any project.
Installation
[dependencies]
korni = "0.1.4"
Quick Start
use korni::{parse, ParseOptions};
fn main() {
let input = r#"
# Database Configuration
DB_HOST=localhost
DB_PORT=5432
"#;
// Simple parsing (fast mode)
let entries = parse(input);
for entry in entries {
if let Some(pair) = entry.as_pair() {
println!("Key: {}, Value: {}", pair.key, pair.value);
}
}
// Advanced parsing with positions and comments
let full_entries = korni::parse_with_options(input, ParseOptions::full());
}
API Reference
Core Types
Entry<'a>
The main parsing result type representing a single line in an .env file:
pub enum Entry<'a> {
Comment(Span), // A comment line (# ...)
Pair(KeyValuePair<'a>), // A key-value pair (KEY=value)
Error(Error), // A parsing error
}
KeyValuePair<'a>
Represents a parsed key-value pair with full introspection:
pub struct KeyValuePair<'a> {
pub key: Cow<'a, str>, // The key name
pub key_span: Option<Span>, // Span of the key
pub value: Cow<'a, str>, // The parsed value
pub value_span: Option<Span>, // Span of the value
pub quote: QuoteType, // Single, Double, or None
pub open_quote_pos: Option<Position>,
pub close_quote_pos: Option<Position>,
pub equals_pos: Option<Position>, // Position of the '='
pub is_exported: bool, // Whether 'export' keyword was used
pub is_comment: bool, // Whether this was in a comment (# KEY=value)
}
ParseOptions
Configure parsing behavior:
pub struct ParseOptions {
pub include_comments: bool, // Parse & include comments in output
pub track_positions: bool, // Track line/col/offset positions
}
// Presets
ParseOptions::fast() // Default: no comments, no positions
ParseOptions::full() // Comments + positions enabled
Position and Span
For precise location tracking:
pub struct Position {
pub line: usize, // 0-indexed line number
pub col: usize, // 0-indexed column number
pub offset: usize, // Byte offset from file start
}
pub struct Span {
pub start: Position,
pub end: Position,
}
QuoteType
Indicates how a value was quoted:
pub enum QuoteType {
Single, // 'value'
Double, // "value"
None, // value (unquoted)
}
Parsing Functions
parse(input: &str) -> Vec<Entry>
Fast parsing - returns key-value pairs only, no position tracking:
let entries = korni::parse("KEY=value");
parse_with_options(input: &str, options: ParseOptions) -> Vec<Entry>
Configurable parsing with custom options:
let entries = korni::parse_with_options(input, ParseOptions::full());
Builder API
The builder API provides a fluent interface for parsing from various sources:
From String
use korni::Korni;
let env = Korni::from_str("KEY=value")
.preserve_comments()
.track_positions()
.parse()?;
println!("{}", env.get("KEY").unwrap());
From File
let env = Korni::from_file(".env")
.preserve_comments()
.parse()?;
Auto-discover File
Searches current directory and ancestors for the file:
let env = Korni::find_file(".env")?
.parse()?;
From Bytes
let bytes = b"KEY=value";
let env = Korni::from_bytes(bytes).parse()?;
From Reader
use std::io::Cursor;
let reader = Cursor::new("KEY=value");
let env = Korni::from_reader(reader).parse()?;
Environment API
The Environment struct provides a HashMap-like interface:
use korni::Korni;
let env = Korni::from_str("
DB_HOST=localhost
DB_PORT=5432
").parse()?;
// Get a value
let host = env.get("DB_HOST"); // Option<&str>
let port = env.get_or("DB_PORT", "3306"); // &str with default
// Get full entry with metadata
if let Some(entry) = env.get_entry("DB_HOST") {
println!("Quoted: {:?}", entry.quote);
println!("Exported: {}", entry.is_exported);
}
// Iterate all pairs
for pair in env.iter() {
println!("{} = {}", pair.key, pair.value);
}
// Check for errors
if env.has_errors() {
for error in env.errors() {
eprintln!("Error: {}", error);
}
}
// Export to HashMap<String, String>
let map = env.to_map();
Iterator API
Stream entries one at a time for memory efficiency:
use korni::{Parser, EnvIterator};
let parser = Parser::new("KEY1=a\nKEY2=b");
let iter = EnvIterator::new(parser);
for entry in iter {
// Process entry
}
Error Types
All parsing errors include byte offsets for precise error reporting:
pub enum Error {
InvalidUtf8 { offset: usize, reason: String },
UnclosedQuote { quote_type: &'static str, offset: usize },
InvalidKey { offset: usize, reason: String },
ForbiddenWhitespace { location: &'static str, offset: usize },
DoubleEquals { offset: usize },
InvalidBom { offset: usize },
Expected { offset: usize, expected: &'static str },
Generic { offset: usize, message: String },
Io(String),
}
// Get the byte offset of any error
let offset = error.offset();
Parsing Rules
Keys
- Must contain only ASCII alphanumeric characters and underscores (
[A-Za-z0-9_]) - Must NOT start with a digit
- No whitespace allowed between key and
=
VALID_KEY=value
_also_valid=value
# Invalid: 123_KEY=value (starts with digit)
# Invalid: KEY = value (whitespace around =)
Values
Unquoted Values
- Terminate at first whitespace or end of line
- Support line continuation with trailing backslash
KEY=simple_value
MULTI=line1\
line2
Single-Quoted Values
- Literal strings - no escape processing
- Must be closed on the same line
KEY='raw value with $VAR not expanded'
Double-Quoted Values
- Support escape sequences:
\n,\r,\t,\\,\",\$ - Unknown escapes are preserved literally
KEY="line1\nline2"
ESCAPED="contains \"quotes\""
Comments
- Lines starting with
#are comments - Inline comments:
KEY=value # comment(requires whitespace before#) - Commented-out pairs (
# KEY=value) are parsed withis_comment: true
Export Keyword
The optional export prefix is supported:
export DATABASE_URL=postgres://localhost/db
BOM Handling
- UTF-8 BOM (
\xEF\xBB\xBF) at file start is silently skipped - BOM in middle of file produces an error
Comparison with dotenvy
dotenvy is the standard, battle-tested crate for loading environment variables in Rust applications. korni serves a different, more specialized purpose.
| Feature | dotenvy |
korni |
|---|---|---|
| Primary Goal | Load env vars into std::env |
Parse env files into structured data |
| Output | Modifies process environment | AST / Structured Iterators |
| Introspection | None (opaque loading) | Full (Spans, Line/Col, Offsets) |
| Comments | Ignored | Parsed & Preserved |
| Error Handling | Stops on first error | Failure-tolerant (collects all errors) |
Modifies std::env |
✅ Yes | ❌ No (Pure parsing) |
| Use Case | Application Configuration | Tooling (IDEs, Linters), Complex Configs |
When to use dotenvy
- You just want
cargo runto pick up your.envfile. - You need standard, 12-factor app configuration.
When to use korni
- You are building an IDE plugin, linter, or formatter.
- You need to analyze the structure of an
.envfile (e.g. "where isDB_PORTdefined?"). - You need performance-critical parsing of massive files.
- You want to manually control how environment variables are applied.
Specification & Compliance
EDF Specification
This parser implements the EDF (Ecolog Dotenv File Format) 1.0.1 specification.
Compliance Statement
korni aims for EDF 1.0.1 compliance. Per the specification's compliance requirements:
A parser implementation claiming EDF 1.0.1 Compliance MUST adhere to ALL requirements specified in the specification. This is a strict, all-or-nothing compliance model.
Requirements Implemented
- ✅ Parsing Rules: Keys, values (quoted/unquoted), comments, and multiline handling per Section 4
- ✅ Error Handling: Failure-tolerant parsing with detailed error messages including byte offsets
- ✅ Security: UTF-8 validation, BOM handling, error message safety
- ✅ Edge Cases: Empty values, escape sequences, line continuations
Allowed Variations (per spec)
This implementation varies in:
- Performance: Heavily optimized with focus on zero-copy parsing (but it is not absolutely zero-copy) and SIMD-friendly iterators
- API Design: Rust-idiomatic with
Cowstrings, builders, and iterators - Additional Features: Position tracking, comment parsing, and
is_commentflag for commented-out pairs
Version Compatibility
- Specification Version: EDF 1.0.1
- Semantic Versioning: This library follows semver. Major version bumps indicate potential parsing behavior changes.
License
MIT