Packages
igniter_new
0.5.34
0.5.34
0.5.33
0.5.32
0.5.31
0.5.30
0.5.29
0.5.28
0.5.27
0.5.26
0.5.25
0.5.24
0.5.23
0.5.22
0.5.21
0.5.20
0.5.19
0.5.18
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Create a new mix project with igniter, and run igniter installers in one command!
Current section
Files
Jump to
Current section
Files
lib/loading.ex
# SPDX-FileCopyrightText: 2024 igniter contributors <https://github.com/ash-project/igniter/graphs/contributors>
#
# SPDX-License-Identifier: MIT
defmodule Igniter.Installer.Loading do
@moduledoc """
Utilities for doing operations with loading spinners.
"""
@spinners "⠁⠂⠄⡀⡁⡂⡄⡅⡇⡏⡗⡧⣇⣏⣗⣧⣯⣷⣿⢿⣻⢻⢽⣹⢹⢸⠸⢘⠘⠨⢈⠈⠐⠠⢀" |> String.graphemes()
@doc """
Runs the function with a loading spinner, suppressing all output.
"""
def with_spinner(name, fun, opts \\ []) do
if opts[:verbose?] do
Mix.shell().info(name <> ":")
result = fun.()
Mix.shell().info(name <> ": " <> "#{IO.ANSI.green()}✔#{IO.ANSI.reset()}")
result
else
shell = Mix.shell()
# I don't understand why I have to do this
if !Code.ensure_loaded?(Mix.Shell.ActuallyQuiet) do
Code.eval_quoted(
quote do
defmodule Mix.Shell.ActuallyQuiet do
@moduledoc false
@behaviour Mix.Shell
def print_app, do: :ok
def info(_message), do: :ok
def error(message) do
# Instead of discarding, send to stderr so it gets captured
IO.puts(:stderr, message)
:ok
end
def prompt(_message), do: :ok
def yes?(_message, _options \\ []), do: :ok
def cmd(command, opts \\ []) do
Mix.Shell.cmd(command, opts, fn data -> data end)
end
end
end
)
end
{:ok, _} = Igniter.Installer.CaptureServer.start_link([])
loader =
if shell == Mix.Shell.IO do
spawn_loader(name)
end
try do
Mix.shell(Mix.Shell.ActuallyQuiet)
{:ok, ref} =
Igniter.Installer.CaptureServer.device_capture_on(:standard_error, :unicode, "")
try do
with_log(fn ->
do_with_io(self(), [], fn ->
fun.()
end)
|> elem(0)
end)
|> elem(0)
catch
kind, reason ->
Process.put(:spinner_error, true)
Mix.shell(shell)
Mix.shell().info(Igniter.Installer.CaptureServer.device_output(:standard_error, ref))
:erlang.raise(kind, reason, __STACKTRACE__)
after
Mix.shell(shell)
Igniter.Installer.CaptureServer.device_capture_off(ref)
GenServer.stop(Igniter.Installer.CaptureServer)
end
after
if shell == Mix.Shell.IO do
loader_ref = make_ref()
error_occurred = Process.get(:spinner_error, false)
stop_message =
if error_occurred,
do: {:stop_error, self(), loader_ref},
else: {:stop_success, self(), loader_ref}
send(loader, stop_message)
receive do
{:loader_stopped, ^loader_ref} ->
nil
after
500 ->
:ok
end
end
end
end
end
defp do_with_io(pid, options, fun) when is_pid(pid) do
prompt_config = Keyword.get(options, :capture_prompt, true)
encoding = Keyword.get(options, :encoding, :unicode)
input = Keyword.get(options, :input, "")
{:group_leader, original_gl} =
Process.info(pid, :group_leader) || {:group_leader, Process.group_leader()}
{:ok, capture_gl} = StringIO.open(input, capture_prompt: prompt_config, encoding: encoding)
try do
Process.group_leader(pid, capture_gl)
do_capture_gl(capture_gl, fun)
after
Process.group_leader(pid, original_gl)
end
end
defp do_capture_gl(string_io, fun) do
try do
fun.()
catch
kind, reason ->
_ = StringIO.close(string_io)
:erlang.raise(kind, reason, __STACKTRACE__)
else
result ->
{:ok, {_input, output}} = StringIO.close(string_io)
{result, output}
end
end
def spawn_loader(task_name) do
spawn_link(fn ->
@spinners
|> Stream.cycle()
|> Stream.map(fn next ->
receive do
{:stop_success, pid, ref} ->
IO.puts("\r\e[K" <> task_name <> " " <> "#{IO.ANSI.green()}✔#{IO.ANSI.reset()}")
send(pid, {:loader_stopped, ref})
exit(:normal)
{:stop_error, pid, ref} ->
IO.puts("\r\e[K" <> task_name <> " " <> "#{IO.ANSI.red()}✗#{IO.ANSI.reset()}")
send(pid, {:loader_stopped, ref})
exit(:normal)
after
0 ->
IO.write("\r\e[K" <> task_name <> " " <> next)
:timer.sleep(50)
end
end)
|> Stream.run()
end)
end
def with_log(opts \\ [], fun) when is_list(opts) do
opts =
if opts[:level] == :warn do
IO.warn("level: :warn is deprecated, please use :warning instead")
Keyword.put(opts, :level, :warning)
else
opts
end
{:ok, string_io} = StringIO.open("")
try do
ref = Igniter.Installer.CaptureServer.log_capture_on(self(), string_io, opts)
try do
fun.()
after
:ok = Logger.flush()
:ok = Igniter.Installer.CaptureServer.log_capture_off(ref)
end
catch
kind, reason ->
_ = StringIO.close(string_io)
:erlang.raise(kind, reason, __STACKTRACE__)
else
result ->
{:ok, {_input, output}} = StringIO.close(string_io)
{result, output}
end
end
end