1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
|
defmodule Hex.Shell do
@moduledoc false
def info(output) do
validate_output!(output)
Mix.shell().info(output)
end
def warn(output) do
validate_output!(output)
Mix.shell().info([IO.ANSI.yellow(), output, IO.ANSI.reset()])
end
def error(output) do
validate_output!(output)
Mix.shell().error(output)
end
def debug(output) do
validate_output!(output)
if Mix.debug?() do
info(output)
end
end
def yes?(output) do
validate_output!(output)
Mix.shell().yes?(output)
end
def prompt(output) do
validate_output!(output)
Mix.shell().prompt(output)
end
def format(output, emit? \\ Hex.Shell.ansi_enabled?()) do
IO.ANSI.format(output, emit?)
end
def cmd(command, options \\ [], callback) when is_function(callback, 1) do
callback =
if Keyword.get(options, :quiet, false) do
fn x -> x end
else
callback
end
env = validate_env(Keyword.get(options, :env, []))
args =
if Keyword.get(options, :stderr_to_stdout, true) do
[:stderr_to_stdout]
else
[]
end
opts = [:stream, :binary, :exit_status, :hide, :use_stdio, {:env, env} | args]
port = Port.open({:spawn, shell_command(command)}, opts)
port_read(port, callback)
end
defp port_read(port, callback) do
receive do
{^port, {:data, data}} ->
_ = callback.(data)
port_read(port, callback)
{^port, {:exit_status, status}} ->
status
end
end
# Finding shell command logic from :os.cmd in OTP
# https://github.com/erlang/otp/blob/8deb96fb1d017307e22d2ab88968b9ef9f1b71d0/lib/kernel/src/os.erl#L184
defp shell_command(command) do
case :os.type() do
{:unix, _} ->
command =
command
|> String.replace("\"", "\\\"")
|> String.to_charlist()
~c"sh -c \"" ++ command ++ ~c"\""
{:win32, osname} ->
command = ~c"\"" ++ String.to_charlist(command) ++ ~c"\""
case {System.get_env("COMSPEC"), osname} do
{nil, :windows} -> ~c"command.com /s /c " ++ command
{nil, _} -> ~c"cmd /s /c " ++ command
{cmd, _} -> ~c"#{cmd} /s /c " ++ command
end
end
end
defp validate_env(enum) do
Enum.map(enum, fn
{k, nil} ->
{String.to_charlist(k), false}
{k, v} ->
{String.to_charlist(k), String.to_charlist(v)}
other ->
raise ArgumentError, "invalid environment key-value #{inspect(other)}"
end)
end
if Mix.env() == :test do
defp validate_output!(output) do
formatted_output = output |> IO.ANSI.format_fragment(true) |> IO.chardata_to_string()
unless String.printable?(formatted_output) do
raise ArgumentError, "string not printable"
end
end
else
defp validate_output!(_output), do: :ok
end
if Mix.env() == :test do
def ansi_enabled?(), do: false
else
def ansi_enabled?(), do: IO.ANSI.enabled?()
end
end
|