Current section

Files

Jump to
dot_env src dot_env.gleam
Raw

src/dot_env.gleam

import gleam/bool
import gleam/io
import gleam/string
import gleam/result.{try}
import dot_env/internal/parser
import dot_env/env
import simplifile
pub type Opts {
/// Customized options for loading the .env file
Opts(
/// The path to the .env file relative to the project root eg. .env and src/.env are two different things, .env points to the root of the project, src/.env points to the src folder in the root of the project
path: String,
/// Print debug information if something goes wrong
debug: Bool,
/// Force all keys to be uppercase
capitalize: Bool,
/// In case the file is missing, ignore the error and continue
ignore_missing_file: Bool,
)
/// Default options for loading the .env file - see `default` constant
Default
}
pub opaque type DotEnv {
DotEnv(path: String, debug: Bool, capitalize: Bool, ignore_missing_file: Bool)
}
pub const default = DotEnv(
path: ".env",
debug: True,
capitalize: True,
ignore_missing_file: True,
)
///
/// Load the .env file at the default path (.env) and set the environment variables
///
/// Debug information will be printed to the console if something goes wrong and all keys will be capitalized
///
/// # Example
///
/// ```gleam
/// import dot_env
///
/// pub fn main() {
/// dot_env.load()
/// }
/// ```
pub fn load() {
load_with_opts(Default)
}
///
/// Load the .env file at the specified path and set the environment variables
///
/// Debug information and key capitalization can be customized
///
/// # Example
///
/// ```gleam
/// import dot_env
///
/// pub fn main() {
/// dot_env.load_with_opts(dot_env.Opts(path: "src/.env", debug: False, capitalize: False))
/// }
/// ```
pub fn load_with_opts(opts: Opts) {
let dotenv = case opts {
Opts(path, debug, capitalize, ignore_missing_file) ->
DotEnv(path, debug, capitalize, ignore_missing_file)
Default -> default
}
let state =
dotenv
|> load_and_return_error
case state {
Ok(_) -> Nil
Error(msg) -> {
use <- bool.guard(when: !dotenv.debug, return: Nil)
io.println_error(msg)
}
}
}
fn load_and_return_error(dotenv: DotEnv) -> Result(Nil, String) {
use content <- try(
read_file(dotenv)
|> handle_file_result(dotenv.ignore_missing_file),
)
use kv_pairs <- try(parser.parse(content))
dotenv
|> recursively_set_environment_variables(kv_pairs)
Ok(Nil)
}
fn handle_file_result(
res: Result(String, String),
ignore_error: Bool,
) -> Result(String, String) {
use <- bool.guard(when: result.is_error(res) && ignore_error, return: Ok(""))
res
}
fn set_env(config: DotEnv, pair: #(String, String)) {
let #(key, value) = pair
let key = {
use <- bool.guard(when: !config.capitalize, return: key)
string.uppercase(key)
}
env.set(key, value)
}
fn recursively_set_environment_variables(
config: DotEnv,
kv_pairs: parser.KVPairs,
) {
case kv_pairs {
[] -> Nil
[pair] -> set_env(config, pair)
[pair, ..rest] -> {
set_env(config, pair)
recursively_set_environment_variables(config, rest)
}
}
}
fn read_file(dotenv: DotEnv) -> Result(String, String) {
use is_file <- result.try(
simplifile.verify_is_file(dotenv.path)
|> result.map_error(with: fn(_) {
"Failed to access file, ensure the file exists and is a readable file"
}),
)
use <- bool.guard(
when: !is_file,
return: Error("Specified file at `" <> dotenv.path <> "` does not exist"),
)
use contents <- result.try(
simplifile.read(dotenv.path)
|> result.map_error(with: fn(_) {
let msg =
"Unable to read file at `"
<> dotenv.path
<> "`, ensure the file exists and is readable"
msg
}),
)
Ok(contents)
}