Linus
Linus

Reputation: 2819

Execute command and stream results

I want to execute a system command (git clone) and stream the output to the user through a channel in Phoenix?

Can I somehow make System.cmd stream the results instead of waiting until it's done?

Or, can I write the output to a file and stream the content from there, as it's being appended?

Upvotes: 6

Views: 3014

Answers (3)

Nicolas Goy
Nicolas Goy

Reputation: 1430

If you don't want to use Porcelain, which has a go executable dependency, you can use port, like so:

  def long do
    port = Port.open({:spawn_executable, /path/to/command"}, [:stderr_to_stdout, :binary, :exit_status, args: ["arg1"]])
    stream_output(port)
  end

  defp stream_output(port) do
    receive do
      {^port, {:data, data}} ->
        Logger.info(data) # send to phoenix channel
        stream_output(port)
      {^port, {:exit_status, 0}} ->
        Logger.info("Command success")
      {^port, {:exit_status, status}} ->
        Logger.info("Command error, status #{status}")
    end
  end

Upvotes: 3

sobolevn
sobolevn

Reputation: 18080

It is possible to override output with:

System.cmd "git", ["clone", "YOUR_GIT"], into: IO.stream(:stdio, :line)

Result:

Cloning into 'YOUR_GIT'... remote: Counting objects: 1665, done. remote: Compressing objects: 0% (1/979) remote: Compressing objects: 100% (979/979), done. Receiving objects: 0% (1/166remote: Total 1665 (delta 855), reused 1331 (delta 597) Receiving objects: 92% (Receiving objects: 100% (1665/1665), 5.25 MiB | 376.00 KiB/s, done. Resolving delResolving deltas: 100% (855/855), done. Checking connectivity... done.
{%IO.Stream{device: :standard_io, line_or_bytes: :line, raw: false}, 0}

docs

Edit:

To help you achieve your specific task, redirecting the local standard stream into external one, there's a library porcelain, which handles it perfectly.

Upvotes: 3

manukall
manukall

Reputation: 1462

I would advice on using Porcelain for this. Specifically check https://github.com/alco/porcelain#messages.

You can use Porcelain.spawn_shell to run your command with the out: {:send, self()} option and implement matching handle_info callbacks.

Upvotes: 3

Related Questions