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