dotenvor 0.2.0

Small, fast `.env` parser and loader for Rust
Documentation
# 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.