# dotenvor
`dotenvor` is a small, fast `.env` parser and loader for Rust.
It focuses on predictable behavior, low dependency overhead, and an ergonomic API (`EnvLoader` + convenience functions).
## Highlights
- Fast parser for common `.env` syntax
- Builder-style loader with multi-file precedence
- Built-in multi-environment stack helper (`.convention("development")`)
- Optional variable substitution (`$VAR`, `${VAR}`, `${VAR:-fallback}`)
- Optional upward search for `.env` files
- First-party `dotenv` CLI (`dotenv run ...`)
- Process-env or in-memory targets for safer tests
- Quiet/verbose logging controls
## Installation
```toml
[dependencies]
dotenvor = "0.2"
```
## MSRV
The minimum supported Rust version (MSRV) is **Rust 1.88.0+**.
We don't treat MSRV changes as breaking, it may be changed in any release.
## Quick Start
### safe load (in-memory)
```rust
use dotenvor::EnvLoader;
let env = EnvLoader::new().load()?.env;
println!("DATABASE_URL={:?}", env.get("DATABASE_URL"));
# Ok::<(), dotenvor::Error>(())
```
`EnvLoader::load()` is safe and returns an in-memory map.
Use builder options (`.path(...)`, `.required(false)`, etc.) when needed.
### process-env load (unsafe)
```rust
use dotenvor::dotenv;
let report = unsafe { dotenv()? };
println!("loaded={} skipped={}", report.loaded, report.skipped_existing);
# Ok::<(), dotenvor::Error>(())
```
`dotenv()` mutates process-wide state via `std::env::set_var` and is `unsafe`,
because callers must guarantee no concurrent process-environment access.
In concurrent code or isolated tests, prefer `EnvLoader::load()`.
### Attribute macro startup load
```rust
#[dotenvor::load]
fn main() -> Result<(), dotenvor::Error> {
Ok(())
}
```
Or, you can specify options:
```rust
#[dotenvor::load(path = ".env", required = true, override_existing = false, search_upward = false)]
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), dotenvor::Error> {
println!("inside async runtime");
Ok(())
}
```
`#[dotenvor::load]` injects the same process-env loading flow before the function runs.
The function must return `Result<_, _>`.
### Multi-environment stack convention
```rust
use dotenvor::EnvLoader;
let loaded = EnvLoader::new()
.convention("development")
.required(false)
.load()?;
println!("loaded={}", loaded.report.loaded);
# Ok::<(), dotenvor::Error>(())
```
Convention precedence (highest to lowest):
- `.env.development.local`
- `.env.local`
- `.env.development`
- `.env`
### Parse only
```rust
use dotenvor::parse_str;
let entries = parse_str("A=1\nB=\"hello\"\n")?;
assert_eq!(entries.len(), 2);
# Ok::<(), dotenvor::Error>(())
```
### CLI: run a command with dotenv files
```bash
cargo run --bin dotenv -- run -- printenv DATABASE_URL
```
Select files explicitly (repeat `-f` or use comma-separated paths):
```bash
cargo run --bin dotenv -- run -f ".env.local,.env" -- my-app
```
Useful flags:
- `-o`, `--override`: let file values override existing environment variables
- `-i`, `--ignore`: skip missing files
- `-u`, `--search-upward`: resolve relative files by walking parent directories
### Opt in to permissive key parsing
```rust
use dotenvor::{parse_str_with_mode, KeyParsingMode};
let entries = parse_str_with_mode(
"KEYS:CAN:HAVE_COLONS=1\n%TEMP%=/tmp\n",
KeyParsingMode::Permissive,
)?;
assert_eq!(entries.len(), 2);
# Ok::<(), dotenvor::Error>(())
```
## Implemented Behavior
### Parsing
- `KEY=VALUE` pairs
- Whitespace trimming around keys and values
- Empty values (`FOO=`)
- Comments with `#` outside quotes
- Single quotes, double quotes, and backticks
- Double-quoted escapes: `\n`, `\r`, `\t`, `\\`, `\"`
- Optional `export` prefix
- Duplicate keys: last value wins
- Reader, string, and bytes parsing APIs
- Multiline quoted values (including PEM-style blocks)
- Strict key mode by default, plus opt-in `KeyParsingMode::Permissive`
### Loading
- Multi-file loading with deterministic precedence
- Convention helper for environment stacks (`.convention("development")`)
- `override_existing(false)` by default
- `EnvLoader::load()` is safe and returns a memory map + report
- Process-env loading is available via unsafe APIs (`dotenv`, `from_path`,
`from_paths`, `from_filename`, `EnvLoader::load_and_modify`)
- Upward file search support
- `dotenv()` / `from_filename(...)`: upward search enabled
- `EnvLoader`: upward search disabled by default (enable with `.search_upward(true)`)
- Missing-file mode
- `required(true)` (default): missing files return `Error::Io`
- `required(false)`: missing files are skipped silently
- Configurable file decoding via `.encoding(...)`
- `Encoding::Utf8` (default)
- `Encoding::Latin1` (ISO-8859-1)
- CLI command execution (`dotenv run`)
- Defaults to `.env` when no file is selected
- Accepts `-f/--file` for file selection (repeatable and comma-separated)
- Supports `-o/--override` and `-i/--ignore`
### Substitution
- Optional mode: `SubstitutionMode::Expand`
- Expands `$VAR`, `${VAR}`, and `${VAR:-fallback}` (strict key mode)
- Supports chained and forward references
- Falls back to current target environment values when needed
- Treats single-quoted values and escaped dollars (`\$`) as literal in expand mode
### Logging
- `.verbose(true)` enables loader diagnostics on stderr
- `.quiet(true)` suppresses diagnostics
## License
Licensed under either of:
- MIT license ([LICENSE-MIT](LICENSE-MIT))
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
at your option.