I want to do something like this in my project’s main .iex.exs
file:
import_file_if_available ".iex.#{Mix.env}.exs"
But that doesn’t work because import_file_if_available
requires a string literal.
I want to do something like this in my project’s main .iex.exs
file:
import_file_if_available ".iex.#{Mix.env}.exs"
But that doesn’t work because import_file_if_available
requires a string literal.
case Mix.env do
:dev -> import_file_if_available(".iex.dev.exs")
:test -> import_file_if_available(".iex.test.exs")
:prod -> import_file_if_available(".iex.prod.exs")
end
should work?
That doesn’t work. I believe the problem is that import
and all its variants, like import_file_if_available
, are lexically scoped, i.e. they’re only effective within the block in which they’re run. In your example, that would be only inside the three case
pattern match clauses, but not outside those clauses, so not in the iex
session either.
But you gave me an idea:
_ = File.copy ".iex.#{Mix.env}.exs", ".iex.env.exs"
import_file_if_available ".iex.env.exs"
Sadly, that didn’t work either – I’m guessing import_file_if_available
is called before the File.copy/2
call executes, which makes sense as I think the former is called at compile time.
I think there might be a way to create the .iex.env.exs
file when iex
is started, but before it compiles the .iex.exs
file.
Ok, my supervisor said I had to retry again after my first attempt crashed…
You’re totally right, I only tried it with IO.puts (a side effect) and not with bindings but I think I found a solution after browsing through mix github repo and googling. I’ve tried it in a blank project setup and it seems to work:
# .iex.exs
Code.eval_file(".iex.#{Mix.env()}.exs")
then having this:
#.iex.dev.exs
defmodule Hello do
def world do
"Hello World!"
end
end
Lets me just call Hello.world
in the IEx REPL, but not found a way yet to define aliases that then propagate into the REPL. (maybe fiddling with/looking into quick_alias code could make this work)
Please try it in for your case! Hope it helps.
I thought at first that “my supervisor” referred to a person! (I’m still not totally sure still whether it’s an Elixir/OTP/Elixir object or a human.)
That was a good idea. A friend had a similar one:
env_file = ".iex.#{Mix.env}.exs"
if File.exists?(env_file), do: Code.eval_file(env_file)
Unfortunately, Code.eval_file
doesn’t work. I’m guessing that’s because it doesn’t modify the current environment (Macro.Env
struct, accessible as __ENV__
in iex
):
iex()> h Code.eval_file
def eval_file(file, relative_to \\ nil)
@spec eval_file(binary(), nil | binary()) :: {term(), binding :: list()}
Evals the given file.
Accepts relative_to as an argument to tell where the file is located.
While require_file/2 and compile_file/2 return the loaded modules and their
bytecode, eval_file/2 simply evaluates the file contents and returns the
evaluation result and its bindings (exactly the same return value as
eval_string/3).
Note that eval_file
returns “its bindings”, i.e. those set by import
calls in the evaluated file.
Here’s my current – working! – solution:
‘Shadow’ the Mix compile
task (in mix.exs
):
defp aliases do
[
compile: [©_environment_iex_exs_file/1, "compile"],
...
]
end
defp copy_environment_iex_exs_file(_) do
File.rm ".iex.env.exs"
File.copy ".iex.#{Mix.env}.exs", ".iex.env.exs"
end
That should ensure the file .iex.env.exs
exists if – and only if – the relevant environment-specific file does.
In .iex.exs
:
# This file should be created by `mix compile`:
import_file_if_available ".iex.env.exs"
I also tried creating a macro (a custom sigil) to wrap import_file_if_available
:
defmodule MyApp.ImportWithInterpolationSigil do
import IEx.Helpers, only: [import_file_if_available: 1]
defmacro sigil_i(t, _modifiers) do
t_string = Macro.to_string(t)
{t_evaled, _} = Code.eval_string(t_string)
quote do
import_file_if_available unquote(t_evaled)
end
end
end
and then using it in the .iex.exs
file like this:
import MyApp.ImportWithInterpolationSigil
~i(".iex.#{Mix.env}.exs")
I checked, by replacing import_file_if_available
in the macro with IO.puts
and the output identifies the problem:
(
IO.puts("Evaluating `.iex.dev.exs` ...")
import(MyApp.SomeModule, only: [foo: 1, foo: 2])
)
The macro is surrounding the contents of my .iex.dev.exs
file with parentheses, so the import
calls in that file are inside a block and thus don’t affect the iex
environment.
After writing the above, it occurred to me that the problem with my macro might be that it’s a sigil. And I was right! A regular macro works just fine:
defmacro import_with_interpolation(t) do
t_string = Macro.to_string(t)
{t_evaled, _} = Code.eval_string(t_string)
quote do
import_file_if_available unquote(t_evaled)
end
end
Here’s how it can be called in the .iex.exs
file:
import Partially.Test.ImportWithInterpolation
import_with_interpolation ".iex.#{Mix.env}.exs"
I just posted to the Elixir mailing list to propose adding my macro (or updating the existing import_file_if_available
macro) to the IEx.Helpers
module directly: