Skip to content

Commit

Permalink
Merge branch 'support-other-dotenv-files'
Browse files Browse the repository at this point in the history
  • Loading branch information
keathley committed Jan 14, 2020
2 parents 048d2d4 + 1fa5997 commit 67c3d22
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 120 deletions.
57 changes: 48 additions & 9 deletions lib/vapor/providers/dotenv.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,60 @@ defmodule Vapor.Provider.Dotenv do
You can change this by setting the `overwrite` key to `true`:
%Dotenv{overwrite: true}
## File heirarchy
If no file is specified then the dotenv provider will load these files in this
order. Each proceeding file is loaded over the previous. In these examples `ENV`
will be the current mix environment: `dev`, `test`, or `prod`.
* `.env`
* `.env.ENV`
* `.env.local`
* `.env.ENV.local`
You should commit `.env` and `.env.ENV` files to your project and ignore any
`.local` files. This allows users to provide a custom setup if they need to
do that.
"""
defstruct filename: ".env", overwrite: false
defstruct filename: nil, overwrite: false

defimpl Vapor.Provider do
@mix_env Mix.env()

def load(%{filename: nil, overwrite: overwrite}) do
files = [".env", ".env.#{@mix_env}", ".env.local", ".env.#{@mix_env}.local"]

files
|> Enum.reduce(%{}, fn file, acc -> Map.merge(acc, load_file(file)) end)
|> put_vars(overwrite)

{:ok, %{}}
end

def load(%{filename: filename, overwrite: overwrite}) do
case File.read(filename) do
filename
|> load_file
|> put_vars(overwrite)

{:ok, %{}}
end

defp load_file(file) do
case File.read(file) do
{:ok, contents} ->
for {k, v} <- parse(contents) do
if overwrite || System.get_env(k) == nil do
System.put_env(k, v)
end
end
{:ok, %{}}
parse(contents)

_ ->
{:ok, %{}}
%{}
end
end

def put_vars(vars, overwrite) do
for {k, v} <- vars do
if overwrite || System.get_env(k) == nil do
System.put_env(k, v)
end
end
end

Expand All @@ -48,6 +86,7 @@ defmodule Vapor.Provider.Dotenv do
|> Enum.filter(&good_pair/1)
|> Enum.map(fn [key, value] -> {String.trim(key), String.trim(value)} end)
|> Enum.map(fn {key, value} -> {key, value} end)
|> Enum.into(%{})
end

defp comment?(line) do
Expand Down
233 changes: 122 additions & 111 deletions test/vapor/provider/dotenv_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,130 +3,141 @@ defmodule Vapor.Provider.DotenvTest do

alias Vapor.Provider.Dotenv

describe "default/0" do
setup do
File.rm(".env")
System.delete_env("FOO")
System.delete_env("BAR")
System.delete_env("BAZ")

on_exit fn ->
File.rm(".env")
end
setup do
System.delete_env("FOO")
System.delete_env("BAR")
System.delete_env("BAZ")

:ok
end
File.rm(".env")
File.rm(".env.test")
File.rm(".env.dev")

test "reads the file in as variables" do
contents = """
FOO=foo
BAR = bar
BAZ =this is a baz
"""
File.write(".env", contents)

plan = %Dotenv{}
assert {:ok, %{}} == Vapor.Provider.load(plan)
assert System.get_env("FOO") == "foo"
assert System.get_env("BAR") == "bar"
assert System.get_env("BAZ") == "this is a baz"
on_exit fn ->
File.rm(".env")
File.rm(".env.test")
File.rm(".env.dev")
end

test "returns correctly if the file doesn't exist" do
plan = %Dotenv{}
{:ok, envs} = Vapor.Provider.load(plan)
assert envs == %{}
end
:ok
end

test "ignores any malformed data" do
contents = """
FOO=foo
BAR
=this is a baz
"""
File.write(".env", contents)

plan = %Dotenv{}
Vapor.Provider.load(plan)
assert System.get_env("FOO") == "foo"
assert System.get_env("BAR") == nil
assert System.get_env("BAZ") == nil
end
test "reads the file in as variables" do
contents = """
FOO=foo
BAR = bar
BAZ =this is a baz
"""
File.write(".env", contents)

plan = %Dotenv{}
assert {:ok, %{}} == Vapor.Provider.load(plan)
assert System.get_env("FOO") == "foo"
assert System.get_env("BAR") == "bar"
assert System.get_env("BAZ") == "this is a baz"
end

test "ignores comment lines" do
contents = """
# This is a comment
FOO=foo
# BAR=bar
# BAZ=comment with indentation
"""
File.write(".env", contents)

plan = %Dotenv{}
Vapor.Provider.load(plan)
assert System.get_env("FOO") == "foo"
assert System.get_env("BAR") == nil
assert System.get_env("BAZ") == nil
end
test "returns correctly if the file doesn't exist" do
plan = %Dotenv{}
{:ok, envs} = Vapor.Provider.load(plan)
assert envs == %{}
end

test "does not overwrite existing env variables by default" do
contents = """
# This is a comment
FOO=foo
BAR=bar
"""
File.write(".env", contents)
System.put_env("FOO", "existing")

plan = %Dotenv{}
Vapor.Provider.load(plan)
assert System.get_env("FOO") == "existing"
assert System.get_env("BAR") == "bar"
assert System.get_env("BAZ") == nil
end
test "ignores any malformed data" do
contents = """
FOO=foo
BAR
=this is a baz
"""
File.write(".env", contents)

plan = %Dotenv{}
Vapor.Provider.load(plan)
assert System.get_env("FOO") == "foo"
assert System.get_env("BAR") == nil
assert System.get_env("BAZ") == nil
end

test "overwrites existing variables if specified" do
contents = """
# This is a comment
FOO=foo
BAR=bar
"""
File.write(".env", contents)
System.put_env("FOO", "existing")

plan = %Dotenv{overwrite: true}
Vapor.Provider.load(plan)
assert System.get_env("FOO") == "foo"
assert System.get_env("BAR") == "bar"
assert System.get_env("BAZ") == nil
end
test "ignores comment lines" do
contents = """
# This is a comment
FOO=foo
# BAR=bar
# BAZ=comment with indentation
"""
File.write(".env", contents)

plan = %Dotenv{}
Vapor.Provider.load(plan)
assert System.get_env("FOO") == "foo"
assert System.get_env("BAR") == nil
assert System.get_env("BAZ") == nil
end

describe "with_file/1" do
setup do
File.rm(".env.dev")
test "does not overwrite existing env variables by default" do
contents = """
# This is a comment
FOO=foo
BAR=bar
"""
File.write(".env", contents)
System.put_env("FOO", "existing foo")

plan = %Dotenv{}
Vapor.Provider.load(plan)
assert System.get_env("FOO") == "existing foo"
assert System.get_env("BAR") == "bar"
end

on_exit fn ->
File.rm(".env.dev")
end
test "overwrites existing variables if specified" do
contents = """
# This is a comment
FOO=foo
BAR=bar
"""
File.write(".env", contents)
System.put_env("FOO", "existing")

plan = %Dotenv{overwrite: true}
Vapor.Provider.load(plan)
assert System.get_env("FOO") == "foo"
assert System.get_env("BAR") == "bar"
assert System.get_env("BAZ") == nil
end

:ok
end
test "stacks multiple files together" do
base_contents = """
FOO=foo
BAR=bar
"""
File.write!(".env", base_contents)

test_contents = """
BAR=test bar
BAZ=test baz
"""
File.write!(".env.test", test_contents)

System.put_env("FOO", "existing")

Vapor.Provider.load(%Dotenv{})
assert System.get_env("FOO") == "existing"
assert System.get_env("BAR") == "test bar"
assert System.get_env("BAZ") == "test baz"
end

test "allows custom files" do
contents = """
FOO=foo
BAR = bar
BAZ =this is a baz
"""
File.write(".env.dev", contents)

plan = %Dotenv{filename: ".env.dev"}
Vapor.Provider.load(plan)
assert System.get_env("FOO") == "foo"
assert System.get_env("BAR") == "bar"
assert System.get_env("BAZ") == "this is a baz"
end
test "allows custom files" do
contents = """
FOO=foo
BAR = bar
BAZ =this is a baz
"""
File.write(".env.dev", contents)

plan = %Dotenv{filename: ".env.dev"}
Vapor.Provider.load(plan)
assert System.get_env("FOO") == "foo"
assert System.get_env("BAR") == "bar"
assert System.get_env("BAZ") == "this is a baz"
end
end

0 comments on commit 67c3d22

Please sign in to comment.