Skip to content

Instantly share code, notes, and snippets.

@lucca65
Created April 3, 2017 22:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lucca65/4e0ebb81e5ba2a0a58d446215a9956ab to your computer and use it in GitHub Desktop.
Save lucca65/4e0ebb81e5ba2a0a58d446215a9956ab to your computer and use it in GitHub Desktop.
defmodule Hub do
@moduledoc """
Publish/subscribe server.
Subscription is done with a pattern.
Example:
{:ok, _pid} = Hub.start_link(name: :hub)
Hub.subscribe(:hub, %{count: count} when count > 42)
Hub.publish(:hub, %{count: 45, message: "You rock!"})
"""
@doc """
Starts the Hub GenServer process
"""
@spec start_link(Genserver.options) :: GenServer.on_start
def start_link(options \\ []) do
GenServer.start_link(__MODULE__, :ok, options)
end
@doc """
Convenience macro for subscribing without the need to unquote the pattern.
example:
Hub.subscribe(:hub, %{count: count} when count > 42)
"""
defmacro subscribe(hub, pattern, pid \\ nil) do
quote do
pid = unquote(pid) || self()
Hub.subscribe_quoted(unquote(hub), unquote(Macro.escape(pattern)), pid)
end
end
@doc """
Subscribes the given pid to the quoted pattern
example:
Hub.subscribe(:hub, quote do: %{count: count} when count > 42)
"""
@spec subscribe_quoted(pid, any, pid) :: :ok
def subscribe_quoted(hub, quoted_pattern, pid \\ self()) do
GenServer.call(hub, {:subscribe, {pid, quoted_pattern}})
end
@doc """
Publishes the term to all subscribers that matches it
"""
@spec publish(pid, any) :: :ok
def publish(hub, term) do
GenServer.cast(hub, {:publish, term})
end
def init(:ok) do
{:ok, []}
end
def handle_call({:subscribe, subscriber}, _from, subscribers) do
{:reply, :ok, [subscriber | subscribers]}
end
def handle_cast({:publish, term}, subscribers) do
subscribers
|> Enum.each(fn {pid, pattern} ->
if pattern_match?(pattern, term) do
send(pid, term)
end
end)
{:noreply, subscribers}
end
defp pattern_match?(pattern, term) do
ast = {:case, [], [
Macro.escape(term), [
do: [
{:->, [], [[pattern], true]},
{:->, [], [[{:_, [], Elixir}], false]}
]
]
]}
{result, _} = Code.eval_quoted(ast)
result
end
end
def pattern_match?(quoted_pattern, term) do
ast = {:case, [], [
Macro.escape(term), [
do: [
{:->, [], [[quoted_pattern], true]},
{:->, [], [[{:_, [], Elixir}], false]}
]
]
]}
{result, _} = Code.eval_quoted(ast)
result
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment