Integrate vt via port

ex-snapshot
Marcin Kulik 7 years ago
parent c99e2ccaff
commit c9a3bf044f

@ -0,0 +1,22 @@
defmodule Asciinema.Vt do
alias Asciinema.Vt.{Pool, Worker}
def with_vt(width, height, f) do
Pool.checkout(fn vt ->
:ok = new(vt, width, height)
f.(vt)
end)
end
def new(vt, width, height) do
Worker.new(vt, width, height)
end
def feed(vt, data) do
Worker.feed(vt, data)
end
def dump_screen(vt, timeout \\ 5_000) do
Worker.dump_screen(vt, timeout)
end
end

@ -0,0 +1,34 @@
defmodule Asciinema.Vt.Pool do
use Supervisor
def start_link do
Supervisor.start_link(__MODULE__, [])
end
def init([]) do
supervise([
:poolboy.child_spec(:vt_pool, pool_config())
], strategy: :one_for_one, name: __MODULE__)
end
def checkout(f, timeout \\ 5_000) do
:poolboy.transaction(:vt_pool, fn worker ->
try do
f.(worker)
catch :exit, reason ->
Process.exit(worker, :kill)
case reason do
{:timeout, _} -> {:error, :timeout}
_ -> {:error, :unknown}
end
end
end, timeout)
end
defp pool_config do
[name: {:local, :vt_pool},
worker_module: Asciinema.Vt.Worker,
size: 2,
max_overflow: 0]
end
end

@ -0,0 +1,71 @@
defmodule Asciinema.Vt.Worker do
use GenServer
@vt_script_path "vt/main.js"
# Client API
def start_link(_) do
GenServer.start_link(__MODULE__, nil, [])
end
def new(pid, width, height) do
GenServer.cast(pid, {:new, width, height})
end
def feed(pid, data) do
GenServer.cast(pid, {:feed, data})
end
def dump_screen(pid, timeout) do
GenServer.call(pid, :dump_screen, timeout)
end
# Server callbacks
def init(_) do
path = System.find_executable("node")
port = Port.open({:spawn_executable, path}, [:binary, args: [@vt_script_path]])
{:ok, port}
end
def handle_call(:dump_screen, _from, port) do
send_cmd(port, "dump-screen")
case read_stout_line(port) do
{:ok, line} ->
result = line |> Poison.decode! |> Map.get("result")
{:reply, {:ok, result}, port}
{:error, reason} ->
{:reply, {:error, reason}, port}
end
end
defp read_stout_line(port) do
read_stout_line(port, "")
end
defp read_stout_line(port, line) do
receive do
{^port, {:data, data}} ->
if String.ends_with?(data, "\n") do
{:ok, line <> String.trim_trailing(data)}
else
read_stout_line(port, line <> data)
end
end
end
def handle_cast({:new, width, height}, port) do
send_cmd(port, "new", %{width: width, height: height})
{:noreply, port}
end
def handle_cast({:feed, data}, port) do
send_cmd(port, "feed-str", %{str: data})
{:noreply, port}
end
defp send_cmd(port, cmd, data \\ %{}) do
json = Map.put(data, :cmd, cmd) |> Poison.encode!
true = Port.command(port, "#{json}\n")
end
end

@ -0,0 +1,39 @@
defmodule Asciinema.VtTest do
use ExUnit.Case
alias Asciinema.Vt
@moduletag :vt
test "Vt.Worker" do
{:ok, pid} = Vt.Worker.start_link(nil)
assert Vt.Worker.new(pid, 8, 3) == :ok
assert Vt.Worker.feed(pid, "foobar\r\n") == :ok
assert Vt.Worker.feed(pid, "baz") == :ok
assert Vt.Worker.feed(pid, "qux") == :ok
assert {:ok, screen} = Vt.Worker.dump_screen(pid, 1000)
assert %{"lines" => [_ | _],
"cursor" => %{"x" => 6,
"y" => 1,
"visible" => true}} = screen
assert Vt.Worker.new(pid, 120, 80) == :ok
Enum.each(1..3000, fn _ ->
:ok = Vt.Worker.feed(pid, "aaaaaaaaaaaaaaaaaaaaaaaa")
end)
assert {:ok, %{"cursor" => %{"x" => 120, "y" => 79}}} = Vt.Worker.dump_screen(pid, 10000)
end
test "Vt" do
{:ok, _pid} = Vt.Pool.start_link()
result = Vt.with_vt(8, 3, fn vt ->
:ok = Vt.feed(vt, "foobar\r\n")
:ok = Vt.feed(vt, "baz")
:ok = Vt.feed(vt, "qux")
Vt.dump_screen(vt)
end)
assert {:ok, %{"cursor" => %{"x" => 6, "y" => 1}}} = result
end
end

@ -1,4 +1,4 @@
ExUnit.configure exclude: [a2png: true]
ExUnit.configure exclude: [a2png: true, vt: true]
ExUnit.start
Ecto.Adapters.SQL.Sandbox.mode(Asciinema.Repo, :manual)

Loading…
Cancel
Save