///|
struct EnvMap {
/// The map of environment variables.
map : @hashmap.T[String, String]
}
///|
fn EnvMap::new() -> EnvMap {
{ map: @hashmap.new() }
}
///|
pub fn EnvMap::get(self : EnvMap, name : String) -> String? {
self.map.get(name)
}
///|
pub(all) enum EnvSequence {
/// Inherit the existing environment without loading from input.
EnvOnly
/// Inherit the existing environment, and then load from input, overriding existing values.
EnvThenInput
/// Load from input only.
InputOnly
/// Load from input and then inherit the existing environment. Values in the existing environment are not overwritten.
InputThenEnv
}
///|
pub impl Default for EnvSequence with default() {
EnvSequence::InputThenEnv
}
///|
pub struct EnvLoader {
mut path : String?
priv str : String
priv mut sequence : EnvSequence
}
///|
pub fn EnvLoader::from_path(path : String) -> EnvLoader raise DotenvError {
let str = (try? @fs.read_file_to_string(path))
.map_err(fn(e) { DotenvError::Io(e, path) })
.unwrap_or_error()
{ path: Some(path), str, sequence: EnvSequence::default() }
}
///|
pub fn EnvLoader::from_string(str : String) -> EnvLoader {
{ path: None, str, sequence: EnvSequence::default() }
}
///| Sets the path to the specified path.
///
/// This is useful when constructing with a reader, but still desiring a path to be used in the error message context.
///
/// If a reader exists and a path is specified, loading will be done using the reader.
pub fn EnvLoader::set_path(self : EnvLoader, path : String?) -> EnvLoader {
self.path = path
self
}
///| Sets the sequence in which to load environment variables.
pub fn EnvLoader::set_sequence(
self : EnvLoader,
sequence : EnvSequence,
) -> EnvLoader {
self.sequence = sequence
self
}
///|
fn EnvLoader::load_input(self : EnvLoader) -> EnvMap raise DotenvError {
let iter = DotenvIter::new(self.str)
let result = try? iter.load()
match result {
Ok(env_map) => env_map
Err(e) =>
match e {
ParseBufError::LineParse(line, pos) =>
raise DotenvError::LineParseError(line, pos)
}
}
}
///|
fn EnvLoader::load_input_and_modify(
self : EnvLoader,
) -> EnvMap raise DotenvError {
let iter = DotenvIter::new(self.str)
(try? iter.load_and_modify())
.map_err(fn(e) {
match e {
ParseBufError::LineParse(line, pos) =>
DotenvError::LineParseError(line, pos)
}
})
.unwrap_or_error()
}
///|
fn EnvLoader::load_input_and_modify_override(
self : EnvLoader,
) -> EnvMap raise DotenvError {
let iter = DotenvIter::new(self.str)
let result = try? iter.load_and_modify_override()
result
.map_err(fn(e) {
match e {
ParseBufError::LineParse(line, pos) =>
DotenvError::LineParseError(line, pos)
}
})
.unwrap_or_error()
}
///|
fn env_vars() -> @hashmap.T[String, String] {
let vars = @sys.get_env_vars()
@hashmap.from_iter(vars.iter())
}
///| Loads environment variables into a hash map.
///
/// This is the primary method for loading environment variables.
pub fn EnvLoader::load(self : EnvLoader) -> EnvMap raise DotenvError {
match self.sequence {
EnvOnly => { map: env_vars() }
EnvThenInput => {
let existing = env_vars()
let input = self.load_input()
input.map.each(fn(k, v) { existing.set(k, v) })
{ map: existing }
}
InputOnly => self.load_input()
InputThenEnv => {
let input = self.load_input()
let existing = env_vars()
existing.each(fn(k, v) { input.map.set(k, v) })
input
}
}
}
///| Shortcut for loading environment variables from a file.
pub fn load(
path~ : String = "./.env",
ignore_file_not_found~ : Bool = true,
) -> EnvMap raise DotenvError {
if @fs.path_exists(path) && (try? @fs.is_file(path)).or(false) {
EnvLoader::from_path(path).load()
} else if ignore_file_not_found {
EnvLoader::from_string("").set_sequence(EnvSequence::EnvOnly).load()
} else {
raise DotenvError::FileNotFound(path)
}
}
///| Loads environment variables into a hash map, modifying the existing environment.
///
/// This calls `@sys.set_env_var` internally.
pub fn EnvLoader::load_and_modify(self : EnvLoader) -> EnvMap raise DotenvError {
match self.sequence {
InputOnly => raise DotenvError::InvalidOp
EnvThenInput => {
let existing = env_vars()
let input = self.load_input_and_modify_override()
input.map.each(fn(k, v) { existing.set(k, v) })
{ map: existing }
}
EnvOnly => self.load_input_and_modify_override()
InputThenEnv => {
let existing = env_vars()
let input = self.load_input_and_modify()
input.map.each(fn(k, v) {
if not(existing.contains(k)) {
@sys.set_env_var(k, v)
}
})
existing.each(fn(k, v) { input.map.set(k, v) })
input
}
}
}
///| Shortcut for loading environment variables from a file and modifying the existing environment.
pub fn load_and_modify(
path~ : String = "./.env",
ignore_file_not_found~ : Bool = true,
) -> EnvMap raise DotenvError {
if @fs.path_exists(path) && (try? @fs.is_file(path)).or(false) {
EnvLoader::from_path(path).load_and_modify()
} else if ignore_file_not_found {
// If the file is not found, we do the same as in `load`.
EnvLoader::from_string("").set_sequence(EnvSequence::EnvOnly).load()
} else {
raise DotenvError::FileNotFound(path)
}
}