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 = barUsually 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 .envThis 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 = BARbecomesENV["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` fileOverwriting 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 * cfg2Take 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 = $FOOjulia> 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"