Packages

Run programs as functions in Elixir

Current section

Files

Jump to
sh lib sh.ex
Raw

lib/sh.ex

defmodule Sh do
defmodule CommandNotFound do
defexception [:command]
def message(%{command: command}) do
"Command not found: #{command}"
end
end
defmodule AbnormalExit do
defexception [:output, :status]
def message(%{status: status}) do
"exited with non-zero status (#{status})"
end
end
def unquote(:"$handle_undefined_function")(program, args) do
command = to_string(program)
|> String.replace("_", "-")
|> System.find_executable
|| raise CommandNotFound, command: program
if is_list(List.last(args)) do
{ args, [opts] } = Enum.split(args, -1)
args = process_options(opts) ++ args
end
# http://www.erlang.org/doc/man/erlang.html#open_port-2
opts = ~w(exit_status stderr_to_stdout in binary eof hide)a ++ [args: args]
port = Port.open({ :spawn_executable, command }, opts)
loop(port, "")
end
defp loop(port, acc) do
receive do
{ ^port, { :data, data } } ->
loop(port, acc <> data)
{ ^port, :eof } ->
send port, { self, :close }
receive do
{ ^port, { :exit_status, 0 } } ->
acc
{ ^port, { :exit_status, status } } ->
raise %AbnormalExit{output: acc, status: status}
end
end
end
defp process_options(opts) do
Enum.reduce Enum.reverse(opts), [], fn { key, value }, acc ->
key = to_string(key)
if String.length(key) == 1 do
if value do
if value != true do
["-#{key}", to_string(value) | acc]
else
["-#{key}" | acc]
end
else
acc
end
else
key = String.replace(key, "_", "-")
if value do
if value != true do
["--#{key}=#{value}" | acc]
else
["--#{key}" | acc]
end
else
acc
end
end
end
end
end