Current section
Files
Jump to
Current section
Files
lib/mix/tasks/bex_config.ex
defmodule Mix.Tasks.Bex.Config do
@shortdoc "Amends `config/config.exs` with all the mocked by `bex` behaviours"
@moduledoc """
Collects the information about all bex-patched behaviours _and_ amends the config file
### Allowed arguments
- **`--dir: :string`** _[optional, default: `lib/bex`]_ the directory where to look
for generated modules
- **`--patch`** _[optional, default: `true`]_ if `false`, no attempt to patch found occurences
of calls to the behavioured function(s) would be made
### Example
```sh
mix bex.config --no-patch
```
"""
use Mix.Task
@bex_default_path "lib/bex"
@config_dir "config"
@config_file Path.join(@config_dir, "config.exs")
@impl Mix.Task
@doc false
def run(args) do
{opts, _pass_thru, []} =
OptionParser.parse(args,
strict: [
dir: :string,
patch: :boolean
]
)
bex_modules =
opts
|> Keyword.get(:dir, @bex_default_path)
|> Path.join("/**/*.ex")
|> Path.wildcard()
|> Enum.flat_map(&mocks_from_file/1)
bex_mox_impls = Map.new(bex_modules, &{&1, Module.concat([:Bex, :Behaviours, &1, :Mox])})
bex_impls = Map.new(bex_modules, &{&1, Module.concat([:Bex, :Behaviours, :Impls, &1])})
bex_impls_ast =
case bex_modules do
[] ->
[]
[_ | _] ->
quote do
config :bex,
:impls,
if(Mix.env() == :test,
do: unquote(Macro.escape(bex_mox_impls)),
else: unquote(Macro.escape(bex_impls))
)
end
end
old_config_content =
with true <- File.exists?(@config_file), do: File.read!(@config_file)
config_content =
if old_config_content do
old_config_content
|> Sourceror.parse_string!()
|> Macro.postwalk(fn
{:config, _meta_config,
[
{:__block__, _bex_meta, [:bex]},
{:__block__, _impls_meta, [:impls]},
_old_impls
]} ->
bex_impls_ast
ast ->
ast
end)
|> Sourceror.to_string()
else
with true <- bex_impls_ast != [],
do: Enum.join(["import Config", "", Sourceror.to_string(bex_impls_ast)], "\n")
end
if old_config_content != config_content do
File.mkdir_p(@config_dir)
File.write!(@config_file, config_content)
Mix.shell().info([
[:bright, :blue, "✓ #{@config_file}", :reset],
" has been altered"
])
else
Mix.shell().info([
[:blue, "✗ #{@config_file}", :reset],
" has not been changed"
])
end
end
defp mocks_from_file(file) when is_binary(file) do
file
|> File.read!()
|> Sourceror.parse_string()
|> case do
{:ok, term} -> mocks_from_file(term)
{:error, _error} -> []
end
end
defp mocks_from_file(term) when is_tuple(term) do
term
|> Macro.prewalk([], fn
{{:., _call_meta, [{:__aliases__, _mox_meta, [:Mox]}, :defmock]}, _meta,
[
[
{{:__block__, _block_meta, [:for]},
{:__aliases__, _aliases_meta, [:Bex, :Behaviours | aliases]}}
]
]} = ast,
acc ->
{ast, [Module.concat(aliases) | acc]}
ast, acc ->
{ast, acc}
end)
|> elem(1)
end
end