Current section

Files

Jump to
dot_env src dot_env.gleam
Raw

src/dot_env.gleam

import dot_env/env
import dot_env/internal/parser
import gleam/bool
import gleam/io
import gleam/result.{try}
import gleam/string
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,
)
/// Create a default DotEnv instance. This is designed to use used as the starting point for using any of the builder methods
pub fn new() -> DotEnv {
default
}
/// Create a new DotEnv instance with the specified path
pub fn new_with_path(path: String) -> DotEnv {
DotEnv(..default, path: path)
}
/// Set whether to print debug information in the current DotEnv instance
pub fn set_debug(instance: DotEnv, debug: Bool) -> DotEnv {
DotEnv(..instance, debug: debug)
}
/// Set whether to capitalize all keys in the current DotEnv instance
pub fn set_capitalize(instance: DotEnv, capitalize: Bool) -> DotEnv {
DotEnv(..instance, capitalize: capitalize)
}
/// Set whether to ignore missing file errors in the current DotEnv instance
pub fn set_ignore_missing_file(
instance: DotEnv,
ignore_missing_file: Bool,
) -> DotEnv {
DotEnv(..instance, ignore_missing_file: ignore_missing_file)
}
/// Set the path to the .env file in the current DotEnv instance
pub fn set_path(instance: DotEnv, path: String) -> DotEnv {
DotEnv(..instance, path: path)
}
/// Get the path to the .env file in the current DotEnv instance
pub fn path(instance: DotEnv) -> String {
instance.path
}
/// Load the .env file using the current DotEnv instance and set the environment variables
///
/// # Example
///
/// ```gleam
/// import dot_env as dot
///
/// pub fn main() {
/// dot.new()
/// |> dot.set_path("src/.env")
/// |> dot.set_debug(False)
/// |> dot.load
/// }
pub fn load(dotenv: DotEnv) -> Nil {
load_with_opts(Opts(
path: dotenv.path,
debug: dotenv.debug,
capitalize: dotenv.capitalize,
ignore_missing_file: dotenv.ignore_missing_file,
))
}
/// 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_default()
/// }
/// ```
pub fn load_default() -> Nil {
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)
}
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)) -> Result(Nil, String) {
let key = {
use <- bool.guard(when: !config.capitalize, return: pair.0)
string.uppercase(pair.0)
}
key
|> env.set(pair.1)
}
fn recursively_set_environment_variables(
config: DotEnv,
kv_pairs: parser.KVPairs,
) -> Result(Nil, String) {
case kv_pairs {
[] -> Ok(Nil)
[pair] -> set_env(config, pair)
[pair, ..rest] -> {
use _ <- result.try(set_env(config, pair))
recursively_set_environment_variables(config, rest)
}
}
}
fn read_file(dotenv: DotEnv) -> Result(String, String) {
use is_file <- result.try(
simplifile.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(_) {
"Unable to read file at `"
<> dotenv.path
<> "`, ensure the file exists and is readable"
}),
)
Ok(contents)
}