dotenvor 0.1.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.1"
```

## 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

### Load into the process environment

```rust
use dotenvor::dotenv;

// Looks for ".env" and searches parent directories if needed.
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 a loader configured with
`.target(TargetEnv::memory())`.

### Builder API with memory target

```rust
use dotenvor::{EnvLoader, KeyParsingMode, SubstitutionMode, TargetEnv};

let mut loader = EnvLoader::new()
    .path(".env")
    .search_upward(true)
    .required(false) // skip missing files instead of returning Error::Io
    .key_parsing_mode(KeyParsingMode::Strict)
    .substitution_mode(SubstitutionMode::Expand)
    .override_existing(false)
    .target(TargetEnv::memory());

let report = loader.load()?;
let env = loader.target_env().as_memory().expect("memory target");

println!("files_read={}", report.files_read);
println!("DATABASE_URL={:?}", env.get("DATABASE_URL"));
# Ok::<(), dotenvor::Error>(())
```

### Multi-environment stack convention

```rust
use dotenvor::{EnvLoader, TargetEnv};

let mut loader = EnvLoader::new()
    .convention("development")
    .required(false)
    .target(TargetEnv::memory());

loader.load()?;
# 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` defaults to `TargetEnv::memory()` for process-isolated loads
- Process-env loading is available via `unsafe { TargetEnv::process() }` and
  unsafe convenience functions (`dotenv`, `from_path`, `from_paths`,
  `from_filename`)
- 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.