env

Load .env files into your EZ scripts. Parsing, validation, required-key checks, type coercion.

0 downloads owners: imabd645

Install

ez install env

Dependencies (1.0.0)

PackageRange
No dependencies.

Readme

env — .env file loading for EZ

Version: 1.0.0 Import: use "env" File: C:\ezlib\env\main.ez Requires: nothing — pure EZ, no FFI, no DLLs

Overview

env loads .env files into your EZ scripts. A .env file is a plain text
file of KEY=value pairs, one per line, that keeps configuration — database
URLs, API keys, debug flags — out of source code.

line numbers and filenames

The library is modular, following the same architecture as sqlite:

env/
  package.ez         ← manifest
  main.ez            ← public surface (load, load_string, load_files)
  src/
    errors.ez        ← structured error builders
    parser.ez        ← the only file that reads raw .env content
    store.ez         ← Env model: get/set/has/require/typed accessors
  test_env.ez        ← test suite

Quick Start

use "env"

env = load()                              # loads .env from current directory
port = env.getInt("PORT", 8080)
debug = env.getBool("DEBUG", false)
dbUrl = env.get("DATABASE_URL")

out "Starting on port " + str(port)

Example .env file

# Database
DATABASE_URL=postgres://localhost:5432/myapp
DB_POOL_SIZE=10

# Server
PORT=3000
HOST=0.0.0.0
DEBUG=true

# API keys
API_KEY="sk-abc123def456"
SECRET='keep-it-literal'

# Comma-separated list
ALLOWED_ORIGINS=http://localhost:3000,https://myapp.com

Loading

load(path = ".env", options = nil)Env

Load a .env file from disk.

env = load()                                    # .env
env = load(".env.production")                   # specific file
env = load(".env", { "required": ["DB_HOST"] }) # with validation
env = load(".env", { "defaults": { "PORT": "8080" } })

Options:

KeyTypeDescription
requiredarrayKeys that must be present. Throws KeyError on the first miss.
defaultsdictFallback values for keys not found in the file. File values always win.

load_string(content, options = nil)Env

Parse from a string instead of a file. Useful for testing or when .env data
arrives from a network response.

env = load_string("PORT=3000\nDEBUG=true")

load_files(paths, priority = "first")Env

Load multiple files and merge them. Missing files are silently skipped.

# First file wins (default) — .env is the base, .env.local fills in gaps
env = load_files([".env", ".env.local"])

# Last file wins — .env.production overrides everything
env = load_files([".env", ".env.production"], "last")

The Env Model

Every load* function returns an Env instance.

Core Accessors

MethodReturnsDescription
env.get(key, fallback = nil)string or fallbackGet a value. Returns fallback if the key is not set.
env.set(key, value)selfSet a value (in memory only, never writes to disk).
env.has(key)boolCheck if a key exists.
env.remove(key)selfDelete a key.
env.all()dictReturn all key-value pairs as a dictionary.
env.keys()arrayReturn all keys.
env.size()intNumber of variables loaded.

Typed Accessors

Every value in a .env file is a string. These helpers coerce to the
requested type and throw TypeError if the value cannot be converted.

MethodReturnsRecognised values
env.getInt(key, fallback)integerAny string num() can parse as an integer
env.getFloat(key, fallback)floatAny string num() can parse
env.getBool(key, fallback)booltrue/1/yes/on → true; false/0/no/off/"" → false
env.getList(key, delimiter, fallback)arraySplits on delimiter (default ",") and trims each element
use "env"
env = load()

port    = env.getInt("PORT", 8080)
debug   = env.getBool("DEBUG", false)
pi      = env.getFloat("PRECISION", 3.14)
hosts   = env.getList("ALLOWED_HOSTS")         # ["a", "b", "c"]
origins = env.getList("CORS", ";")             # custom delimiter

Required Keys

env = load()
env.require(["DATABASE_URL", "SECRET_KEY", "API_KEY"])
env.validate()     # throws KeyError on the first missing key

Or inline at load time:

env = load(".env", { "required": ["DATABASE_URL", "SECRET_KEY"] })

Merge

base = load(".env")
overrides = load_string("PORT=9000\nNEW_KEY=hello")

base.merge(overrides, false)    # new keys added, existing keys kept
base.merge(overrides, true)     # new keys added, existing keys overwritten

.env Syntax

SyntaxMeaning
KEY=valueSimple key-value pair
KEY = valueSpaces around = are stripped
KEY="value"Double-quoted: \n \t \\ \" are unescaped
KEY='value'Single-quoted: taken verbatim, no escaping
` KEY=value `Backtick-quoted: taken verbatim
export KEY=valueexport prefix ignored (bash compatibility)
# commentFull-line comment
KEY=value # commentInline comment (unquoted values only)
KEY=Empty value (empty string, not nil)

Rules:


Errors

All errors are thrown as formatted strings with a structured prefix.

ErrorThrown when
ParseErrorMalformed .env syntax: empty key, invalid character in key, unterminated quote, unreadable file. Includes line number and filename.
KeyErrorA required key is missing after validate().
TypeErrorA typed accessor (getInt, getBool, getFloat) cannot coerce the value.
try {
    env = load(".env", { "required": ["MISSING_KEY"] })
} catch e {
    out e    # "env: KeyError: required environment variable 'MISSING_KEY' is not set"
}

Patterns

Web app configuration

use "env"
use "web"

env = load(".env", { "required": ["SECRET_KEY", "DATABASE_URL"] })

app = WebApp()
app.config_set("secret", env.get("SECRET_KEY"))
app.config_set("debug", env.getBool("DEBUG", false))

app.run(env.getInt("PORT", 8080))

Per-environment layering

.env                ← base config, committed to git
.env.local          ← local overrides, gitignored
.env.production     ← production secrets, gitignored
use "env"

mode = "production"   # or read from argv
env = load_files([".env", ".env.local", ".env." + mode], "last")

Defaults for development

env = load(".env", {
    "defaults": {
        "PORT": "3000",
        "HOST": "127.0.0.1",
        "DEBUG": "true",
        "LOG_LEVEL": "debug"
    }
})

Tests

cd C:\ezlib\env
ez test_env.ez

55+ assertions covering: basic parsing, spaces, comments, inline comments,
all three quoting styles, escape sequences, export prefix, empty values,
duplicate keys, typed accessors (int, float, bool, list), fallbacks,
set/remove/has, require/validate, merge, defaults, and error handling
(ParseError, KeyError, TypeError).


Architecture

Following the same modular pattern as sqlite:

FileResponsibility
main.ezPublic surface — load(), load_string(), load_files(). No logic.
src/parser.ezThe only file that reads raw .env content. Handles all quoting, escaping, comments, and key validation.
src/store.ezThe Env model. All runtime API: accessors, typed coercion, require/validate, merge.
src/errors.ezStructured error builders. Every failure is diagnosable by type.

Not covered

process environment (getenv/setenv). It is an in-memory store seeded
from files.

This is a deliberate omission — interpolation interacts badly with quoting
and with values that contain $ literally (API keys).

Use \n inside double quotes instead.


License

MIT

Versions

VersionSizeDownloadsPublished
1.0.0 10.9 KB 0 1 hour ago

Integrity

sha256  338e30a9afff127e442d85a03be779fbacee116020d3b5e525ed0dcf284b1d73