livebook/lib/livebook/utils.ex
Jonatan Kłosko 8dea99e600
Support inputs (#328)
* Render basic input

* Make each cell type a separate struct

* Add operation to set input value

* Prototype communication

* Make input name editable

* Refactoring

* Unify cell attribute setters

* IO tests

* Document input communication protocol in Runtime

* Add more tests

* Add persistence

* Fix status update on cell attributes change

* Rework persistence

* Integrate input cell with insert mode

* Add missing users keybinding

* Mimic autofocus for dynamically inserted elements

* Support input type selection

* Support URL input and set up validation

* Convert input error into a more meaningful io error

* Add missing client checks

* Consume every input only once per evaluation

* Fixes
2021-06-08 12:33:50 +02:00

118 lines
3.1 KiB
Elixir

defmodule Livebook.Utils do
@moduledoc false
@type id :: binary()
@doc """
Generates a random binary id.
"""
@spec random_id() :: binary()
def random_id() do
:crypto.strong_rand_bytes(20) |> Base.encode32(case: :lower)
end
@doc """
Generates a random short binary id.
"""
@spec random_short_id() :: binary()
def random_short_id() do
:crypto.strong_rand_bytes(5) |> Base.encode32(case: :lower)
end
@doc """
Generates a random cookie for a distributed node.
"""
@spec random_cookie() :: atom()
def random_cookie() do
:crypto.strong_rand_bytes(42) |> Base.url_encode64() |> String.to_atom()
end
@doc """
Converts the given name to node identifier.
"""
@spec node_from_name(String.t()) :: atom()
def node_from_name(name) do
if name =~ "@" do
String.to_atom(name)
else
# Default to the same host as the current node
:"#{name}@#{node_host()}"
end
end
@doc """
Returns the host part of a node.
"""
@spec node_host() :: binary()
def node_host do
[_, host] = node() |> Atom.to_string() |> :binary.split("@")
host
end
@doc """
Registers the given process under `name` for the time of `fun` evaluation.
"""
@spec temporarily_register(pid(), atom(), (... -> any())) :: any()
def temporarily_register(pid, name, fun) do
Process.register(pid, name)
fun.()
after
Process.unregister(name)
end
@doc """
Returns a function that accesses list items by the given id.
## Examples
iex> list = [%{id: 1, name: "Jake"}, %{id: 2, name: "Amy"}]
iex> get_in(list, [Livebook.Utils.access_by_id(2), Access.key(:name)])
"Amy"
iex> list = [%{id: 1, name: "Jake"}, %{id: 2, name: "Amy"}]
iex> put_in(list, [Livebook.Utils.access_by_id(2), Access.key(:name)], "Amy Santiago")
[%{id: 1, name: "Jake"}, %{id: 2, name: "Amy Santiago"}]
An error is raised if the accessed structure is not a list:
iex> get_in(%{}, [Livebook.Utils.access_by_id(1)])
** (RuntimeError) Livebook.Utils.access_by_id/1 expected a list, got: %{}
"""
@spec access_by_id(term()) ::
Access.access_fun(data :: struct() | map(), current_value :: term())
def access_by_id(id) do
fn
:get, data, next when is_list(data) ->
data
|> Enum.find(fn item -> item.id == id end)
|> next.()
:get_and_update, data, next when is_list(data) ->
case Enum.split_while(data, fn item -> item.id != id end) do
{prev, [item | cons]} ->
case next.(item) do
{get, update} ->
{get, prev ++ [update | cons]}
:pop ->
{item, prev ++ cons}
end
_ ->
{nil, data}
end
_op, data, _next ->
raise "Livebook.Utils.access_by_id/1 expected a list, got: #{inspect(data)}"
end
end
@doc """
Validates if the given URL is syntactically valid.
"""
@spec valid_url?(String.t()) :: boolean()
def valid_url?(url) do
uri = URI.parse(url)
uri.scheme != nil and uri.host != nil and uri.host =~ "."
end
end