Current section
Files
Jump to
Current section
Files
lib/pi/eval.ex
defmodule Pi.Eval do
@moduledoc "Runs bounded Elixir evals inside the project BEAM."
@inspect_opts [charlists: :as_lists, limit: 50, pretty: true]
def run(code, opts \\ []) when is_binary(code) do
timeout = Keyword.get(opts, :timeout, 30_000)
parent = self()
reload_project()
{pid, ref} =
spawn_monitor(fn -> send(parent, {:result, eval_with_captured_io(code)}) end)
receive do
{:result, result} ->
Process.demonitor(ref, [:flush])
{:ok, result}
{:DOWN, ^ref, :process, ^pid, reason} ->
{:error, "Process exited: #{Exception.format_exit(reason)}"}
after
timeout ->
Process.demonitor(ref, [:flush])
Process.exit(pid, :brutal_kill)
{:error, "Evaluation timed out after #{timeout}ms"}
end
end
defp reload_project do
reloader = :"Elixir.Phoenix.CodeReloader"
if Code.ensure_loaded?(reloader) do
for endpoint <- endpoints() do
try do
apply(reloader, :reload, [endpoint])
rescue
_ -> :ok
end
end
else
Mix.Task.reenable("compile.elixir")
Mix.Task.run("compile.elixir")
end
end
defp eval_with_captured_io(code) do
{{success?, result}, io} =
capture_io(fn ->
try do
{result, _bindings} = Code.eval_string(code, [arguments: []], env())
{true, result}
catch
kind, reason -> {false, Exception.format(kind, reason, __STACKTRACE__)}
end
end)
case result do
:"do not show this result in output" -> io
_ when not success? -> result
_ when io == "" -> inspect(result, @inspect_opts)
_ -> "IO:\n\n#{io}\n\nResult:\n\n#{inspect(result, @inspect_opts)}"
end
end
defp env do
import IEx.Helpers, warn: false
__ENV__
end
defp capture_io(fun) do
{:ok, pid} = StringIO.open("")
original = Application.get_env(:elixir, :ansi_enabled)
Application.put_env(:elixir, :ansi_enabled, false)
original_gl = Process.group_leader()
Process.group_leader(self(), pid)
try do
result = fun.()
{_, content} = StringIO.contents(pid)
{result, content}
after
Process.group_leader(self(), original_gl)
StringIO.close(pid)
Application.put_env(:elixir, :ansi_enabled, original)
end
end
defp endpoints do
for {app, _, _} <- Application.started_applications(),
mod <- (Application.get_env(app, :phoenix_endpoint) || []) |> List.wrap() do
mod
end
end
end