Integrate vt via port
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
|
ExUnit.start
|
||||||
|
|
||||||
Ecto.Adapters.SQL.Sandbox.mode(Asciinema.Repo, :manual)
|
Ecto.Adapters.SQL.Sandbox.mode(Asciinema.Repo, :manual)
|
||||||
|
Loading…
Reference in New Issue