Packages

env

Read configuration from environment variables and .env files, with typed reads, required settings and defaults.

import "std:env"

std:env reads and sets environment variables and works with .env files. Use it for configuration that changes between machines: ports, API keys, database paths, a development or production mode. Secrets in the environment stay out of your source and your repository. The examples set their own variables; in a real program they come from the shell, a service manager or a .env file.

Read a variable#

env.get(name) returns the value as a string, or an empty string when it is unset. Give a second argument for a default. env.has(name) tells a variable that is set from one that isn't, even when it is set to an empty value:

main.osl
import "std:env"

env.set("DOCS_NAME", "ada").unwrap()

log env.get("DOCS_NAME")
log "[" ++ env.get("DOCS_UNSET") ++ "]"
log env.get("DOCS_UNSET", "fallback")
log env.has("DOCS_NAME") env.has("DOCS_UNSET")

env.unset("DOCS_NAME").unwrap()
log env.has("DOCS_NAME")
Click the code to edit it

env.set and env.unset change the variable for this program and the programs it starts. They don't change your shell. set returns a result because the system can refuse a name it doesn't allow.

Typed reads#

Environment variables are text. env.int, env.number and env.bool convert and take a fallback for an unset variable. A value that is set but isn't the right type is an error, which is better than silently using a default when someone mistyped a port:

main.osl
import "std:env"

env.set("DOCS_PORT", "8080").unwrap()
env.set("DOCS_RATIO", "0.75").unwrap()
env.set("DOCS_DEBUG", "yes").unwrap()
env.set("DOCS_BAD", "eighty").unwrap()

log env.int("DOCS_PORT", 3000).unwrap()
log env.int("DOCS_UNSET", 3000).unwrap()
log env.number("DOCS_RATIO", 1).unwrap()
log env.bool("DOCS_DEBUG", false).unwrap()

auto bad = env.int("DOCS_BAD", 3000)
log bad.isErr() bad.unwrapErr().kind
Click the code to edit it

Booleans accept 1, true, t, yes, y and on as true, and 0, false, f, no, n and off as false, in any case.

Required settings#

env.require(name) returns the value, or an error naming the missing variable. env.required(...) checks several at once, and env.missing(...) lists the ones that are absent, so a program can report every problem in one go and stop at the start instead of failing halfway:

main.osl
import "std:env"

env.set("DOCS_DB", "app.db").unwrap()

log env.require("DOCS_DB").unwrap()
log env.require("DOCS_TOKEN").unwrapErr().message
log env.missing("DOCS_DB", "DOCS_TOKEN", "DOCS_SECRET")

switch env.required("DOCS_DB", "DOCS_TOKEN") (
  case ok(_)
    log "configured"
  case err(e)
    log "cannot start:" e.message
)
Click the code to edit it

A variable set to an empty string counts as not set for require, required and missing.

Expanding references#

env.expand(text) replaces $NAME and ${NAME} with the variables' values, which is handy for paths from a config file:

main.osl
import "std:env"

env.set("DOCS_HOME", "/srv/app").unwrap()
env.set("DOCS_PORT", "8080").unwrap()

log env.expand("$DOCS_HOME/data on port ${DOCS_PORT}")
Click the code to edit it

Parsing .env text#

A .env file holds NAME=value lines, with comments after #, optional quotes and an optional export. env.parse(text) reads that text into a file handle without touching the real environment:

main.osl
import "std:env"

string text = "# database\nHOST=localhost\nNAME=\"my app\"\nURL=$HOST/db\nexport PORT=5432\n"
auto config = env.parse(text)

log config.keys()
log config.get("NAME")
log config.int("PORT", 0).unwrap()
log config.expand("URL")
log config.get("MISSING", "none")
log config.all()
Click the code to edit it

A file handle has the same reads as the package (get, int, number, bool, has, keys, expand). Values are kept as written, and expand(key) resolves references to other values in the file first, then to the environment.

Load a .env file#

env.load(path) reads a .env file and sets its values as environment variables. It leaves alone variables that are already set, so a real environment wins over the file. With no path it reads .env:

main.osl
import "std:env"
import "std:fs"

fs.writeFile(".env.demo", "DOCS_GREETING=hello\nDOCS_LEVEL=3\n").unwrap()

env.unset("DOCS_GREETING").unwrap()
env.set("DOCS_LEVEL", "9").unwrap()
env.load(".env.demo").unwrap()

log env.get("DOCS_GREETING")
log env.get("DOCS_LEVEL")

log env.load("does-not-exist.env").unwrapErr().kind
fs.remove(".env.demo", {force: true}).unwrap()
Click the code to edit it

DOCS_LEVEL kept its value of 9. To make the file win, parse it and call apply({override: true}):

main.osl
import "std:env"

env.set("DOCS_MODE", "old").unwrap()
auto file = env.parse("DOCS_MODE=new")

file.apply().unwrap()
log env.get("DOCS_MODE")
file.apply({override: true}).unwrap()
log env.get("DOCS_MODE")
Click the code to edit it

Never commit a .env file that holds secrets. Commit a .env.example with the names and no values.

Editing and saving a .env file#

A file handle can be changed and written back. set, unset and merge return the handle, so calls chain, and save() writes to the handle's path:

main.osl
import "std:env"
import "std:fs"

fs.remove(".env.edit", {force: true}).unwrap()

auto file = env.parse("A=1\nB=2\n")
file.set("C", 3).merge({D: "four words"}).unset("A")
log file.text()

file.setPath(".env.edit")
file.save().unwrap()

auto again = env.parseFile(".env.edit").unwrap()
log again.keys()
log again.path()
fs.remove(".env.edit", {force: true}).unwrap()
Click the code to edit it

env.stringify(object) formats an object as .env text without a handle.

Modes#

env.mode() reports the app's mode from APP_ENV, OSL_ENV or NODE_ENV, in that order, and is development when none is set. isDev(), isProd() and isTest() test it, which lets a program turn on debug output or strict settings without its own convention:

main.osl
import "std:env"

env.set("APP_ENV", "production").unwrap()
log env.mode()
log env.isProd() env.isDev() env.isTest()
Click the code to edit it
Quick check

DOCS_PORT is set to eighty. What does env.int("DOCS_PORT", 3000) give?

Your turn

Port from the environment

Print the port the program would use: the EX_PORT variable as an integer, or 8080 when unset. Then set it to 9000 and print again, then to abc and print 8080 for the bad value too.

main.osl
Ctrl Enter runs
Expected output
8080
9000
8080

API reference#

68 members, generated from the std:env source of OSL 9.3.0.

Functions

#env.all() object

all returns every environment variable as an object of strings.

#env.bool(string key, boolean fallback) result<boolean, error>

bool reads an environment variable as a boolean, returning fallback when it is unset or blank. 1, true, t, yes, y and on are true; 0, false, f, no, n and off are false, in any case. Fails with ParseError for anything else.

#env.cwd() string

Deprecated. Deprecated: use process.cwd() instead.

cwd returns the working directory, or "".

#env.expand(string text) string

expand replaces $NAME and ${NAME} in text with environment variables; unset ones become "".

#env.file(string path) *env.File

Deprecated. Deprecated: use env.from({}) and file.setPath(path), or env.parseFile(path) for a file that exists.

file returns an empty File handle for path.

#env.from(object values) *env.File

from builds a File handle with no path from an object; values become strings.

#env.get(string key, any ...fallback) string

get returns an environment variable's value. When it is unset or empty it returns fallback, or "" without one; use env.has to tell unset from empty.

#env.getBool(string key, boolean def) boolean

Deprecated. Deprecated: use env.bool(key, def) instead, which fails on malformed values.

getBool reads a boolean, or def when unset or malformed.

#env.getDefault(string key, string def) string

Deprecated. Deprecated: use env.get(key, def) instead.

getDefault returns a variable, or def when it is unset or empty.

#env.getFloat(string key, number def) number

Deprecated. Deprecated: use env.number(key, def) instead, which fails on malformed values.

getFloat reads a number, or def when unset or malformed.

#env.getInt(string key, int def) int

Deprecated. Deprecated: use env.int(key, def) instead, which fails on malformed values.

getInt reads a whole number, or def when unset or malformed.

#env.has(string key) boolean

has reports whether an environment variable is set, even to "".

#env.home() string

Deprecated. Deprecated: use sys.homeDir() instead.

home returns the home directory, or "".

#env.int(string key, int fallback) result<int, error>

int reads an environment variable as a whole number, returning fallback when it is unset or blank. Fails with ParseError when it is set to something else, such as PORT=abc.

#env.isDev() boolean

isDev reports whether env.mode() is "dev" or "development".

#env.isProd() boolean

isProd reports whether env.mode() is "prod" or "production".

#env.isTest() boolean

isTest reports whether env.mode() is "test" or "testing".

#env.keys() string[]

keys returns the names of every environment variable, sorted.

#env.load(any ...paths) result<unit, error>

load reads .env files (".env" when none are given) and sets their variables, keeping any that are already set. Use env.parseFile(path).apply({override: true}) to replace them. Every file that can be read is applied, in order, even when an earlier one is missing; then load fails with the first error: NotFoundError for a missing file, PermissionError or IOError for an unreadable one, and ValueError for a key the OS rejects.

#env.local() boolean

Deprecated. Deprecated: use env.load() instead.

local loads .env and reports whether it was read.

#env.localOverload() boolean

Deprecated. Deprecated: use env.parseFile(".env").apply({override: true}) instead.

localOverload loads .env, replacing set variables, and reports whether it was read.

#env.missing(any ...keys) string[]

missing returns the listed environment variables that are unset or empty, in the order given.

#env.mode() string

mode returns the app's mode from APP_ENV, OSL_ENV or NODE_ENV (the first that is set), lowercased, or "development".

#env.number(string key, number fallback) result<number, error>

number reads an environment variable as a number, returning fallback when it is unset or blank. Fails with ParseError when it isn't a number.

#env.overload(any ...paths) boolean

Deprecated. Deprecated: use env.parseFile(path).apply({override: true}) instead.

overload loads .env files, replacing variables that are already set, and reports whether every file was read.

#env.parse(string text) *env.File

parse reads .env text into a File handle with no path.

#env.parseFile(string path) result<*env.File, error>

parseFile reads a .env file into a File handle that remembers its path, so file.save() writes back to it. Fails with NotFoundError when the file doesn't exist, PermissionError or IOError.

#env.read(string path) *env.File

Deprecated. Deprecated: use env.parseFile(path) instead, which fails when the file can't be read.

read reads a .env file into a File handle, which is empty when the file can't be read.

#env.require(string key) result<string, error>

require returns an environment variable that must be set. Fails with NotFoundError when it is unset or empty.

#env.required(any ...keys) result<unit, error>

required checks that every listed environment variable is set and not empty. Fails with NotFoundError naming all the missing ones.

#env.set(string key, string value) result<unit, error>

set sets an environment variable for this program and the processes it starts. Spaces around key are ignored. Throws ValueError for an empty name or one containing "=".

#env.stringify(object values) string

stringify formats an object as .env text, one KEY=value line per key in sorted order, quoting values that need it.

#env.unset(string key) result<unit, error>

unset removes an environment variable. Removing one that isn't set does nothing. Throws ValueError for an empty name or one containing "=".

#env.value(string key) env.Value

Deprecated. Deprecated: use env.get, env.has, env.int, env.number or env.bool instead.

value returns a Value handle for a variable.

*env.Value

#value.bool(boolean def) boolean

Deprecated. Deprecated: use env.bool(key, def) instead.

bool returns the value as a boolean, or def.

#value.exists() boolean

Deprecated. Deprecated: use env.has(key) instead.

exists reports whether the variable is set.

#value.fallback(string def) string

Deprecated. Deprecated: use env.get(key, def) instead.

fallback returns the value, or def when it is unset or empty.

#value.float(number def) number

Deprecated. Deprecated: use env.number(key, def) instead.

float returns the value as a number, or def.

#value.int(int def) int

Deprecated. Deprecated: use env.int(key, def) instead.

int returns the value as a whole number, or def.

#value.key() string

Deprecated. Deprecated: use env.get and env.has instead of value handles.

key returns the variable's name.

#value.string() string

Deprecated. Deprecated: use env.get(key) instead.

string returns the raw value.

*env.File

#file.all() object

all returns the values as an object of strings.

#file.apply(object? options) result<unit, error>

apply sets every value as an environment variable. Options: override (boolean, default false) also replaces variables that are already set. Fails with ValueError for a key the OS rejects, such as one containing "=", after setting the others.

#file.applyOverload() boolean

Deprecated. Deprecated: use file.apply({override: true}) instead.

applyOverload sets every value as an environment variable, replacing set ones.

#file.bool(string key, boolean fallback) result<boolean, error>

bool reads a value as a boolean, like env.bool.

#file.clear() *env.File

clear removes every key. It mutates the file and returns it, so calls can chain.

#file.expand(string key) string

expand returns a value with $NAME and ${NAME} references replaced, first from the file's own values (recursively, ignoring cycles) and then from the environment; \$ stays a literal $.

#file.get(string key, any ...fallback) string

get returns a value. When it is missing or empty it returns fallback, or "" without one.

#file.getBool(string key, boolean def) boolean

Deprecated. Deprecated: use file.bool(key, def) instead.

getBool reads a boolean, or def when missing or malformed.

#file.getDefault(string key, string def) string

Deprecated. Deprecated: use file.get(key, def) instead.

getDefault returns a value, or def when it is missing or empty.

#file.getFloat(string key, number def) number

Deprecated. Deprecated: use file.number(key, def) instead.

getFloat reads a number, or def when missing or malformed.

#file.getInt(string key, int def) int

Deprecated. Deprecated: use file.int(key, def) instead.

getInt reads a whole number, or def when missing or malformed.

#file.has(string key) boolean

has reports whether the file has key, even with an empty value.

#file.int(string key, int fallback) result<int, error>

int reads a value as a whole number, like env.int.

#file.keys() string[]

keys returns the names in the file, sorted.

#file.load() boolean

Deprecated. Deprecated: use file.read() and then file.apply() instead.

load re-reads the file and sets its values as environment variables, keeping set ones, and reports whether it was read.

#file.loaded() boolean

Deprecated. Deprecated: env.parseFile and file.read() now fail when the file can't be read.

loaded reports whether the last read succeeded.

#file.merge(object values) *env.File

merge sets every key of an object. It mutates the file and returns it, so calls can chain. Blank keys are skipped.

#file.number(string key, number fallback) result<number, error>

number reads a value as a number, like env.number.

#file.overload() boolean

Deprecated. Deprecated: use file.read() and then file.apply({override: true}) instead.

overload re-reads the file and sets its values as environment variables, replacing set ones, and reports whether it was read.

#file.path() string

path returns the file's path, or "" when it has none.

#file.read() result<unit, error>

read replaces the values with the contents of the file at file.path(). Fails with NotFoundError when it doesn't exist, PermissionError or IOError, leaving the values unchanged. Throws StateError when the file has no path.

#file.save() result<unit, error>

save writes the values to file.path() as .env text (see env.stringify). Fails with NotFoundError when the directory doesn't exist, PermissionError or IOError. Throws StateError when the file has no path.

#file.set(string key, any value) *env.File

set sets a value. It mutates the file and returns it, so calls can chain. Non-string values are stored as their string form. Throws ValueError for an empty key.

#file.setPath(string path) *env.File

setPath sets the path file.read() and file.save() use. It mutates the file and returns it, so calls can chain. An empty path means ".env".

#file.text() string

text formats the values as .env text (see env.stringify).

#file.unset(string key) *env.File

unset removes a key. It mutates the file and returns it, so calls can chain.

#file.value(string key) env.Value

Deprecated. Deprecated: use file.get, file.has, file.int, file.number or file.bool instead.

value returns a Value handle for key.