defmodule Irc.Plugin.TempRef do
@moduledoc """
This module allows to easily implement local temporary simple references for easy access from IRC.
For example, your plugin output could be acted on, and instead of giving the burden for the user to
write or copy that uuid, you could give them a small alphanumeric reference to use instead.
You can configure how many and for how long the references are kept.
## Usage
`import Irc.Plugin.TempRef`
```elixir
defmodule Irc.MyPlugin do
defstruct [:temprefs]
def init(_) do
# …
{:ok, %__MODULE__{temprefs: new_temp_refs()}
end
end
```
"""
defstruct [:refs, :max, :expire, :build_fun, :build_increase_fun, :build_options]
defmodule SimpleAlphaNumericBuilder do
def build(options) do
length = Keyword.get(options, :length, 3)
for _ <- 1..length, into: "", do: <<Enum.random('bcdfghjkmpqtrvwxy2346789')>>
end
def increase(options) do
Keyword.put(options, :length, Keyword.get(options, :length, 3) + 1)
end
end
def new_temp_refs(options \\ []) do
%__MODULE__{
refs: Keyword.get(options, :init_refs, []),
max: Keyword.get(options, :max, []),
expire: Keyword.get(options, :expire, :infinity),
build_fun: Keyword.get(options, :build_fun, &__MODULE__.SimpleAlphaNumericBuilder.build/1),
build_increase_fun: Keyword.get(options, :build_increase_fun, &__MODULE__.SimpleAlphaNumericBuilder.increase/1),
build_options: Keyword.get(options, :build_options, [length: 3])
}
end
def janitor_refs(state = %__MODULE__{}) do
if length(state.refs) > state.max do
%__MODULE__{refs: state.refs |> Enum.reverse() |> tl() |> Enum.reverse()}
else
state
end
end
def put_temp_ref(data, state = %__MODULE__{}) do
state = janitor_refs(state)
key = new_nonexisting_key(state)
if key do
ref = {key, DateTime.utc_now(), data}
{key, %__MODULE__{state | refs: [ref | state.refs]}}
else
{nil, state}
end
end
def lookup_temp_ref(key, state, default \\ nil) do
case List.keyfind(state.refs, key, 0) do
{_, _, data} -> data
_ -> default
end
end
defp new_nonexisting_key(state, i) when i > 50 do
nil
end
defp new_nonexisting_key(state = %__MODULE__{refs: refs}, i \\ 1) do
build_options = if rem(i, 5) == 0 do
state.build_increase_fun.(state.build_options)
else
state.build_options
end
key = state.build_fun.(state.build_options)
if !List.keymember?(refs, key, 0) do
key
else
new_nonexisting_key(state, i + 1)
end
end
end