Skip to content

Commit 805a4b1

Browse files
committed
Initial commit with parsing/output
0 parents  commit 805a4b1

File tree

11 files changed

+263
-0
lines changed

11 files changed

+263
-0
lines changed

.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# The directory Mix will write compiled artifacts to.
2+
/_build
3+
4+
# If you run "mix test --cover", coverage assets end up here.
5+
/cover
6+
7+
# The directory Mix downloads your dependencies sources to.
8+
/deps
9+
10+
# Where 3rd-party dependencies like ExDoc output generated docs.
11+
/doc
12+
13+
# If the VM crashes, it generates a dump, let's ignore it too.
14+
erl_crash.dump
15+
16+
# Also ignore archive artifacts (built via "mix archive.build").
17+
*.ez

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# AppBootstrap
2+
3+
**TODO: Add description**
4+
5+
## Installation
6+
7+
If [available in Hex](https://hex.pm/docs/publish), the package can be installed as:
8+
9+
1. Add `app_bootstrap` to your list of dependencies in `mix.exs`:
10+
11+
```elixir
12+
def deps do
13+
[{:app_bootstrap, "~> 0.1.0"}]
14+
end
15+
```
16+
17+
2. Ensure `app_bootstrap` is started before your application:
18+
19+
```elixir
20+
def application do
21+
[applications: [:app_bootstrap]]
22+
end
23+
```
24+

app_bootstrap

2.6 MB
Binary file not shown.

config/config.exs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# This file is responsible for configuring your application
2+
# and its dependencies with the aid of the Mix.Config module.
3+
use Mix.Config
4+
5+
# This configuration is loaded before any dependency and is restricted
6+
# to this project. If another project depends on this project, this
7+
# file won't be loaded nor affect the parent project. For this reason,
8+
# if you want to provide default values for your application for
9+
# 3rd-party users, it should be done in your "mix.exs" file.
10+
11+
# You can configure for your application as:
12+
#
13+
# config :app_bootstrap, key: :value
14+
#
15+
# And access this configuration in your application as:
16+
#
17+
# Application.get_env(:app_bootstrap, :key)
18+
#
19+
# Or configure a 3rd-party app:
20+
#
21+
# config :logger, level: :info
22+
#
23+
24+
# It is also possible to import configuration files, relative to this
25+
# directory. For example, you can emulate configuration per environment
26+
# by uncommenting the line below and defining dev.exs, test.exs and such.
27+
# Configuration from the imported file will override the ones defined
28+
# here (which is why it is important to import them last).
29+
#
30+
# import_config "#{Mix.env}.exs"

lib/app_bootstrap.ex

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
defmodule AppBootstrap do
2+
@switches [
3+
strict: [app: :string, env: :string, out: :string],
4+
aliases: [a: :app, e: :env, o: :out]
5+
]
6+
7+
def main(argv) do
8+
with {:ok, parsed} <- parse_args(argv),
9+
{:ok, app_env} <- read_app_env(parsed[:app] || "./app.json"),
10+
{:ok, local_env} <- read_local_env(parsed[:env] || "./.env"),
11+
new_dotenv = merge_env(local_env, app_env) |> format_dotenv,
12+
:ok <- File.write(parsed[:out], new_dotenv) do
13+
IO.puts "New env written to #{parsed[:out]}"
14+
exit(:normal)
15+
else
16+
{:error, invalid} ->
17+
exit(invalid)
18+
end
19+
end
20+
21+
@spec parse_args(OptionParser.argv) ::
22+
{:ok, OptionParser.parsed} | {:error, String.t}
23+
defp parse_args(argv) do
24+
case OptionParser.parse(argv, @switches) do
25+
{parsed, _, []} -> {:ok, parsed}
26+
{_, _, invalid} -> {:error, "Invalid arguments passed"}
27+
end
28+
end
29+
30+
@spec read_app_env(String.t) :: {:ok, map} | {:error, String.t}
31+
defp read_app_env(app_json_path) do
32+
with {:ok, json} <- File.read(app_json_path),
33+
{:ok, map} <- Poison.decode(json),
34+
env when is_map(env) <- Map.get(map, "env") do
35+
{:ok, env}
36+
else
37+
_ -> {:error, ~s(No "env" found in app.json)}
38+
end
39+
end
40+
41+
@spec read_local_env(String.t) :: {:ok, map} | {:error, String.t}
42+
defp read_local_env(env_path) do
43+
env_string =
44+
case File.read(env_path) do
45+
{:ok, env_string} -> env_string
46+
{:error, _} -> ""
47+
end
48+
49+
parse_dotenv(env_string)
50+
end
51+
52+
@spec parse_dotenv(String.t) :: {:ok, map} | {:error, String.t}
53+
defp parse_dotenv(env_string) do
54+
env_string
55+
|> String.split("\n")
56+
|> Enum.reduce_while({:ok, %{}}, fn line, {:ok, dotenv} ->
57+
case line do
58+
"" ->
59+
{:cont, {:ok, dotenv}}
60+
value_line ->
61+
case parse_line(line) do
62+
{:ok, {key, value}} ->
63+
{:cont, {:ok, Map.put(dotenv, key, value)}}
64+
error ->
65+
{:halt, error}
66+
end
67+
end
68+
end)
69+
end
70+
71+
@spec parse_line(String.t) :: {:ok, {String.t, String.t}} | {:error, String.t}
72+
defp parse_line(line) do
73+
case String.split(line, "=", parts: 2) do
74+
[key, value] ->
75+
{:ok, {key, strip_quotes(value)}}
76+
line ->
77+
{:error, ~s(Could not parse dotenv line: #{line})}
78+
end
79+
end
80+
81+
@spec strip_quotes(String.t) :: String.t
82+
defp strip_quotes(value) do
83+
if String.starts_with?(value, ~s(")) and String.ends_with?(value, ~s(")) do
84+
String.slice(value, 1..-2)
85+
else
86+
value
87+
end
88+
end
89+
90+
@spec merge_env(map, map) :: String.t
91+
defp merge_env(local, app) do
92+
Enum.reduce(app, local, fn ({key, descriptor}, local) ->
93+
case Map.get(local, key) do
94+
value when not is_nil(value) ->
95+
local
96+
nil ->
97+
Map.put(local, key, get_value(key, descriptor))
98+
end
99+
end)
100+
end
101+
102+
@spec get_value(String.t, map) :: String.t
103+
defp get_value(key, descriptor) do
104+
case descriptor do
105+
%{"development_required" => false} -> ""
106+
%{"required" => false} -> ""
107+
%{"development_value" => value} -> value
108+
%{"value" => value} -> value
109+
%{"description" => description} ->
110+
IO.puts ~s(Provide a value for "#{key}":)
111+
IO.puts ~s("#{description}")
112+
IO.gets("➜ ") |> String.trim_trailing
113+
end
114+
end
115+
116+
@spec format_dotenv(map) :: String.t
117+
defp format_dotenv(dotenv) do
118+
Enum.reduce(dotenv, "", fn ({key, value}, string) ->
119+
"#{string}#{key}=#{value}\n"
120+
end)
121+
end
122+
end

mix.exs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
defmodule AppBootstrap.Mixfile do
2+
use Mix.Project
3+
4+
def project do
5+
[app: :app_bootstrap,
6+
version: "0.1.0",
7+
elixir: "~> 1.3",
8+
escript: [main_module: AppBootstrap],
9+
deps: deps()]
10+
end
11+
12+
# Configuration for the OTP application
13+
#
14+
# Type "mix help compile.app" for more information
15+
def application do
16+
[applications: [:logger, :poison]]
17+
end
18+
19+
# Dependencies can be Hex packages:
20+
#
21+
# {:mydep, "~> 0.3.0"}
22+
#
23+
# Or git/path repositories:
24+
#
25+
# {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
26+
#
27+
# Type "mix help deps" for more examples and options
28+
defp deps do
29+
[{:poison, "~> 2.2.0"}]
30+
end
31+
end

mix.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
%{"poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []}}

test/app.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"env": {
3+
"REQUIRED": {
4+
"description": "Description of a required value"
5+
},
6+
"NOT_REQUIRED": {
7+
"description": "Description of a non-required value",
8+
"required": false
9+
},
10+
"NOT_DEV_REQUIRED": {
11+
"description": "Description of a non-required value",
12+
"development_required": false
13+
},
14+
"VALUE": {
15+
"description": "Description of a provided value",
16+
"value": "value"
17+
},
18+
"DEVELOPMENT_VALUE": {
19+
"description": "Description of a provided development value",
20+
"value": "development_value"
21+
}
22+
}
23+
}

test/app_bootstrap_test.exs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
defmodule AppBootstrapTest do
2+
use ExUnit.Case
3+
doctest AppBootstrap
4+
5+
test "the truth" do
6+
assert 1 + 1 == 2
7+
end
8+
end

test/dotenv.env

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
NOT_REQUIRED=not_required
2+
WRAPPED="wrapped in quotes"
3+
MULTI_EQUAL=foo=bar
4+
MULTI_EQUAL_QUOTES="foo=bar"
5+
DUPE=false
6+
DUPE=true

test/test_helper.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ExUnit.start()

0 commit comments

Comments
 (0)