Usage
Main commands
Create a .env
file in your project. You can add environment-specific variables using the rule NAME=VALUE
. For example:
#.env file
USER = foo
PASSWORD = bar
Usually it is a good idea to put this file into your .gitignore
file, so secrets wouldn't leak to the public space. After that you can use it in your application
using ConfigEnv
dotenv() # loads environment variables from .env
This way ENV
obtains key values pairs you set in your .env
file.
julia> ENV["PASSWORD"]
"bar"
.env
definitions
Following rules are applied when you are writing .env
:
FOO = BAR
becomesENV["FOO"] = "BAR"
;- empty lines are skipped;
- lines starting with
#
are comments and ignored during parsing; - empty content is treated as an empty string, i.e.
EMPTY=
becomesENV["EMPTY"] = ""
; - external single and double quotes are removed, i.e.
SINGLE_QUOTE='quoted'
becomesENV["SINGLE_QUOTE"] = "quoted"
; - inside double quotes, new lines are expanded, i.e.
becomesMULTILINE = "new line"
ENV["MULTILINE"] = "new\nline"
; - inner quotes are automatically escaped, i.e.
JSON={"foo": "bar"}
becomesENV["JSON"] = "{\"foo\": \"bar\"}"
; - extra spaces are removed from both ends of the value, i.e.
FOO=" some value "
becomesENV["FOO"] = "some value"
;
Configuration
Main command is dotenv
which reads your .env file, parse the content, stores it to ENV
, and finally return a EnvProxyDict
.
julia> cfg = dotenv()
julia> println(cfg)
ConfigEnv.EnvProxyDict(Dict("FOO" => "BAR"))
EnvProxyDict
acts as a proxy to ENV
dictionary, if key
is not found in EnvProxyDict
it will try to return value from ENV
.
julia> ENV["XYZ"] = "ABC"
julia> cfg = dotenv()
julia> println(cfg)
ConfigEnv.EnvProxyDict(Dict("FOO" => "BAR"))
julia> cfg["FOO"]
"BAR"
julia> cfg["XYZ"]
"ABC"
By default dotenv
use local .env
file, but you can specify a custom path for your .env
file.
dotenv("custom.env") # Loads `custom.env` file
Overwriting and nonoverwriting functions
Take note that dotenv
function replace previous ENV
environment variables by default. If you want to keep original version of ENV
you should use overwrite
argument
ENV["FOO"] = "BAR"
cfg = dotenv(overwrite = false)
cfg["FOO"] # "BAZ"
ENV["FOO"] # "BAR"
Alternatively one can use function dotenvx
. This function is just an alias to dotenv(overwrite = false)
, but sometimes it can be more convenient to use.
ENV["FOO"] = "BAR"
cfg = dotenvx() # Same as `dotenv(overwrite = false)`
cfg["FOO"] # "BAZ"
ENV["FOO"] # "BAR"
Merging multiple environments
You can provide more than one configuration file and all of them will be uploaded to ENV
.
dotenv("custom1.env", "custom2.env")
Alternatively, you can combine different configuration files together using merge
function or multiplication sign *
cfg1 = dotenv("custom1.env")
cfg2 = dotenv("custom2.env")
cfg = merge(cfg1, cfg2)
# or equivalently
cfg = cfg1 * cfg2
Take note that merge
not only combines dictionaries together, but also apply resulting dictionary to ENV
.
if duplicate keys encountered, then values from the rightmost dictionary is used.
Templating
One can use templates in .env
files, with the help of ${...}
construction. For example, this file
# .env
FOO = ZZZ
BAR = ${FOO}
is converted to
julia> dotenv();
julia> ENV["FOO"]
"ZZZ"
julia> ENV["BAR"]
"ZZZ"
Usage of {}
is mandatory, single $
is ignored, i.e.
# .env
FOO = ZZZ
BAR = $FOO
julia> dotenv();
julia> ENV["FOO"]
"ZZZ"
julia> ENV["BAR"]
"\$FOO"
Together with environments merging described in previous paragraph, templating can be very powerful tool to setup your ENV
in a very flexible way. For example, one can set global parameters in a .env
located in a root of the application and combine it with individual files located deeper inside the application file tree.
You can diagnose problems like unresolved templates and circular dependencies with isresolved
and unresolved_keys
. For example
# .env
FOO = ${BAR}
BAR = ${FOO}
ZZZ = ${YYY}
julia> cfg = dotenv();
julia> isresolved(cfg)
false
julia> unresolved_keys(cfg).circular
2-element Vector{Pair{String, String}}:
"FOO" => "\${BAR}"
"BAR" => "\${FOO}"
julia> unresolved_keys(cfg).undefined
1-element Vector{Pair{String, String}}:
"ZZZ" => "\${YYY}"
Nested templates
One can also use nested interpolations of an arbitrary depth to build more complicated environment constructions.
# .env
USER_1 = FOO
USER_2 = BAR
N = 1
USER = ${USER_${N}}
is translated to the following config
julia> dotenv();
julia> ENV["USER"]
"FOO"
IO
streaming
dotenv
function supports IO
objects, so one can download configuration from net if needed or read it any other way.
using ConfigEnv
using HTTP
cfg = HTTP.get("https://raw.githubusercontent.com/Arkoniak/ConfigEnv.jl/master/test/.env") |> x -> IOBuffer(x.body) |> dotenv
cfg["QWERTY"] # "ZXC"