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:
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")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:
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().kindBooleans 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:
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
)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:
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}")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:
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()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:
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()DOCS_LEVEL kept its value of 9. To make the file win, parse it and call apply({override: true}):
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")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:
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()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:
import "std:env"
env.set("APP_ENV", "production").unwrap()
log env.mode()
log env.isProd() env.isDev() env.isTest()DOCS_PORT is set to eighty. What does env.int("DOCS_PORT", 3000) give?
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.
8080 9000 8080
API reference#
68 members, generated from the std:env source of OSL 9.3.0.
Functions
env.all() objectall 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() stringDeprecated. Deprecated: use process.cwd() instead.
cwd returns the working directory, or "".
env.expand(string text) stringexpand replaces $NAME and ${NAME} in text with environment variables; unset ones become "".
env.file(string path) *env.FileDeprecated. 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.Filefrom builds a File handle with no path from an object; values become strings.
env.get(string key, any ...fallback) stringget 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) booleanDeprecated. 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) stringDeprecated. 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) numberDeprecated. 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) intDeprecated. 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) booleanhas reports whether an environment variable is set, even to "".
env.home() stringDeprecated. 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() booleanisDev reports whether env.mode() is "dev" or "development".
env.isProd() booleanisProd reports whether env.mode() is "prod" or "production".
env.isTest() booleanisTest 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() booleanDeprecated. Deprecated: use env.load() instead.
local loads .env and reports whether it was read.
env.localOverload() booleanDeprecated. 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() stringmode 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) booleanDeprecated. 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.Fileparse 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.FileDeprecated. 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) stringstringify 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.ValueDeprecated. 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) booleanDeprecated. Deprecated: use env.bool(key, def) instead.
bool returns the value as a boolean, or def.
value.exists() booleanDeprecated. Deprecated: use env.has(key) instead.
exists reports whether the variable is set.
value.fallback(string def) stringDeprecated. Deprecated: use env.get(key, def) instead.
fallback returns the value, or def when it is unset or empty.
value.float(number def) numberDeprecated. Deprecated: use env.number(key, def) instead.
float returns the value as a number, or def.
value.int(int def) intDeprecated. Deprecated: use env.int(key, def) instead.
int returns the value as a whole number, or def.
value.key() stringDeprecated. Deprecated: use env.get and env.has instead of value handles.
key returns the variable's name.
value.string() stringDeprecated. Deprecated: use env.get(key) instead.
string returns the raw value.
*env.File
file.all() objectall 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() booleanDeprecated. 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.Fileclear removes every key. It mutates the file and returns it, so calls can chain.
file.expand(string key) stringexpand 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) stringget returns a value. When it is missing or empty it returns fallback, or "" without one.
file.getBool(string key, boolean def) booleanDeprecated. Deprecated: use file.bool(key, def) instead.
getBool reads a boolean, or def when missing or malformed.
file.getDefault(string key, string def) stringDeprecated. 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) numberDeprecated. Deprecated: use file.number(key, def) instead.
getFloat reads a number, or def when missing or malformed.
file.getInt(string key, int def) intDeprecated. Deprecated: use file.int(key, def) instead.
getInt reads a whole number, or def when missing or malformed.
file.has(string key) booleanhas 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() booleanDeprecated. 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() booleanDeprecated. 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.Filemerge 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() booleanDeprecated. 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() stringpath 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.Fileset 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.FilesetPath 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() stringtext formats the values as .env text (see env.stringify).
file.unset(string key) *env.Fileunset removes a key. It mutates the file and returns it, so calls can chain.
file.value(string key) env.ValueDeprecated. Deprecated: use file.get, file.has, file.int, file.number or file.bool instead.
value returns a Value handle for key.