Add example for Agent.cast/2 (#9142)

Add example for Agent.cast/2 (#9142)

defmodule Agent do
  @moduledoc """
  Agents are a simple abstraction around state.

  Often in Elixir there is a need to share or store state that
  must be accessed from different processes or by the same process
  at different points in time.

  The `Agent` module provides a basic server implementation that
  allows state to be retrieved and updated via a simple API.

  ## Examples

  For example, the following agent implements a counter:

      defmodule Counter do
        use Agent

        def start_link(initial_value) do
          Agent.start_link(fn -> initial_value end, name: __MODULE__)
        end

        def value do
          Agent.get(__MODULE__, & &1)
        end

        def increment do
          Agent.update(__MODULE__, &(&1 + 1))
        end
      end

  Usage would be:

      Counter.start_link(0)
      #=> {:ok, #PID<0.123.0>}

      Counter.value()
      #=> 0

      Counter.increment()
      #=> :ok

      Counter.increment()
      #=> :ok

      Counter.value()
      #=> 2

  Thanks to the agent server process, the counter can be safely incremented
  concurrently.

  Agents provide a segregation between the client and server APIs (similar to
  `GenServer`s). In particular, the functions passed as arguments to the calls to
  `Agent` functions are invoked inside the agent (the server). This distinction
  is important because you may want to avoid expensive operations inside the
  agent, as they will effectively block the agent until the request is
  fulfilled.

  Consider these two examples:

      # Compute in the agent/server
      def get_something(agent) do
        Agent.get(agent, fn state -> do_something_expensive(state) end)
      end

      # Compute in the agent/client
      def get_something(agent) do
        Agent.get(agent, & &1) |> do_something_expensive()
      end

  The first function blocks the agent. The second function copies all the state
  to the client and then executes the operation in the client. One aspect to
  consider is whether the data is large enough to require processing in the server,
  at least initially, or small enough to be sent to the client cheaply. Another
  factor is whether the data needs to be processed atomically: getting the
  state and calling `do_something_expensive(state)` outside of the agent means
  that the agent's state can be updated in the meantime. This is specially
  important in case of updates as computing the new state in the client rather
  than in the server can lead to race conditions if multiple clients are trying
  to update the same state to different values.

  ## How to supervise

  An `Agent` is most commonly started under a supervision tree.
  When we invoke `use Agent`, it automatically defines a `child_spec/1`
  function that allows us to start the agent directly under a supervisor.
  To start an agent under a supervisor with an initial counter of 0,
  one may do:

      children = [
        {Counter, 0}
      ]

      Supervisor.start_link(children, strategy: :one_for_all)

  While one could also simply pass the `Counter` as a child to the supervisor,
  such as:

      children = [
        Counter # Same as {Counter, []}
      ]

      Supervisor.start_link(children, strategy: :one_for_all)

  The definition above wouldn't work for this particular example,
  as it would attempt to start the counter with an initial value
  of an empty list. However, this may be a viable option in your
  own agents. A common approach is to use a keyword list, as that
  would allow setting the initial value and giving a name to the
  counter process, for example:

      def start_link(opts) do
        {initial_value, opts} = Keyword.pop(opts, :initial_value, 0)
        Agent.start_link(fn -> initial_value end, opts)
      end

  and then you can use `Counter`, `{Counter, name: :my_counter}` or
  even `{Counter, initial_value: 0, name: :my_counter}` as a child
  specification.

  `use Agent` also accepts a list of options which configures the
  child specification and therefore how it runs under a supervisor.
  The generated `child_spec/1` can be customized with the following options:

    * `:id` - the child specification identifier, defaults to the current module
    * `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
    * `:restart` - when the child should be restarted, defaults to `:permanent`
    * `:shutdown` - how to shut down the child, either immediately or by giving it time to shut down

  For example:

      use Agent, restart: :transient, shutdown: 10_000

  See the "Child specification" section in the `Supervisor` module for more
  detailed information. The `@doc` annotation immediately preceding
  `use Agent` will be attached to the generated `child_spec/1` function.

  ## Name registration

  An agent is bound to the same name registration rules as GenServers.
  Read more about it in the `GenServer` documentation.

  ## A word on distributed agents

  It is important to consider the limitations of distributed agents. Agents
  provide two APIs, one that works with anonymous functions and another
  that expects an explicit module, function, and arguments.

  In a distributed setup with multiple nodes, the API that accepts anonymous
  functions only works if the caller (client) and the agent have the same
  version of the caller module.

  Keep in mind this issue also shows up when performing "rolling upgrades"
  with agents. By rolling upgrades we mean the following situation: you wish
  to deploy a new version of your software by *shutting down* some of your
  nodes and replacing them with nodes running a new version of the software.
  In this setup, part of your environment will have one version of a given
  module and the other part another version (the newer one) of the same module.

  The best solution is to simply use the explicit module, function, and arguments
  APIs when working with distributed agents.

  ## Hot code swapping

  An agent can have its code hot swapped live by simply passing a module,
  function, and arguments tuple to the update instruction. For example, imagine
  you have an agent named `:sample` and you want to convert its inner state
  from a keyword list to a map. It can be done with the following
  instruction:

      {:update, :sample, {:advanced, {Enum, :into, [%{}]}}}

  The agent's state will be added to the given list of arguments (`[%{}]`) as
  the first argument.
  """

  @typedoc "Return values of `start*` functions"
  @type on_start :: {:ok, pid} | {:error, {:already_started, pid} | term}

  @typedoc "The agent name"
  @type name :: atom | {:global, term} | {:via, module, term}

  @typedoc "The agent reference"
  @type agent :: pid | {atom, node} | name

  @typedoc "The agent state"
  @type state :: term

  @doc """
  Returns a specification to start an agent under a supervisor.

  See the "Child specification" section in the `Supervisor` module for more detailed information.
  """
  @doc since: "1.5.0"
  def child_spec(arg) do
    %{
      id: Agent,
      start: {Agent, :start_link, [arg]}
    }
  end

  @doc false
  defmacro __using__(opts) do
    quote location: :keep, bind_quoted: [opts: opts] do
      if Module.get_attribute(__MODULE__, :doc) == nil do
        @doc """
        Returns a specification to start this module under a supervisor.

        See `Supervisor`.
        """
      end

      def child_spec(arg) do
        default = %{
          id: __MODULE__,
          start: {__MODULE__, :start_link, [arg]}
        }

        Supervisor.child_spec(default, unquote(Macro.escape(opts)))
      end

      defoverridable child_spec: 1
    end
  end

  @doc """
  Starts an agent linked to the current process with the given function.

  This is often used to start the agent as part of a supervision tree.

  Once the agent is spawned, the given function `fun` is invoked in the server
  process, and should return the initial agent state. Note that `start_link/2`
  does not return until the given function has returned.

  ## Options

  The `:name` option is used for registration as described in the module
  documentation.

  If the `:timeout` option is present, the agent is allowed to spend at most
  the given number of milliseconds on initialization or it will be terminated
  and the start function will return `{:error, :timeout}`.

  If the `:debug` option is present, the corresponding function in the
  [`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.

  If the `:spawn_opt` option is present, its value will be passed as options
  to the underlying process as in `Process.spawn/4`.

  ## Return values

  If the server is successfully created and initialized, the function returns
  `{:ok, pid}`, where `pid` is the PID of the server. If an agent with the
  specified name already exists, the function returns
  `{:error, {:already_started, pid}}` with the PID of that process.

  If the given function callback fails, the function returns `{:error, reason}`.

  ## Examples

      iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
      iex> Agent.get(pid, fn state -> state end)
      42

      iex> {:error, {exception, _stacktrace}} = Agent.start(fn -> raise "oops" end)
      iex> exception
      %RuntimeError{message: "oops"}

  """
  @spec start_link((() -> term), GenServer.options()) :: on_start
  def start_link(fun, options \\ []) when is_function(fun, 0) do
    GenServer.start_link(Agent.Server, fun, options)
  end

  @doc """
  Starts an agent linked to the current process.

  Same as `start_link/2` but a module, function, and arguments are expected
  instead of an anonymous function; `fun` in `module` will be called with the
  given arguments `args` to initialize the state.
  """
  @spec start_link(module, atom, [any], GenServer.options()) :: on_start
  def start_link(module, fun, args, options \\ []) do
    GenServer.start_link(Agent.Server, {module, fun, args}, options)
  end

  @doc """
  Starts an agent process without links (outside of a supervision tree).

  See `start_link/2` for more information.

  ## Examples

      iex> {:ok, pid} = Agent.start(fn -> 42 end)
      iex> Agent.get(pid, fn state -> state end)
      42

  """
  @spec start((() -> term), GenServer.options()) :: on_start
  def start(fun, options \\ []) when is_function(fun, 0) do
    GenServer.start(Agent.Server, fun, options)
  end

  @doc """
  Starts an agent without links with the given module, function, and arguments.

  See `start_link/4` for more information.
  """
  @spec start(module, atom, [any], GenServer.options()) :: on_start
  def start(module, fun, args, options \\ []) do
    GenServer.start(Agent.Server, {module, fun, args}, options)
  end

  @doc """
  Gets an agent value via the given anonymous function.

  The function `fun` is sent to the `agent` which invokes the function
  passing the agent state. The result of the function invocation is
  returned from this function.

  `timeout` is an integer greater than zero which specifies how many
  milliseconds are allowed before the agent executes the function and returns
  the result value, or the atom `:infinity` to wait indefinitely. If no result
  is received within the specified time, the function call fails and the caller
  exits.

  ## Examples

      iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
      iex> Agent.get(pid, fn state -> state end)
      42

  """
  @spec get(agent, (state -> a), timeout) :: a when a: var
  def get(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
    GenServer.call(agent, {:get, fun}, timeout)
  end

  @doc """
  Gets an agent value via the given function.

  Same as `get/3` but a module, function, and arguments are expected
  instead of an anonymous function. The state is added as first
  argument to the given list of arguments.
  """
  @spec get(agent, module, atom, [term], timeout) :: any
  def get(agent, module, fun, args, timeout \\ 5000) do
    GenServer.call(agent, {:get, {module, fun, args}}, timeout)
  end

  @doc """
  Gets and updates the agent state in one operation via the given anonymous
  function.

  The function `fun` is sent to the `agent` which invokes the function
  passing the agent state. The function must return a tuple with two
  elements, the first being the value to return (that is, the "get" value)
  and the second one being the new state of the agent.

  `timeout` is an integer greater than zero which specifies how many
  milliseconds are allowed before the agent executes the function and returns
  the result value, or the atom `:infinity` to wait indefinitely. If no result
  is received within the specified time, the function call fails and the caller
  exits.

  ## Examples

      iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
      iex> Agent.get_and_update(pid, fn state -> {state, state + 1} end)
      42
      iex> Agent.get(pid, fn state -> state end)
      43

  """
  @spec get_and_update(agent, (state -> {a, state}), timeout) :: a when a: var
  def get_and_update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
    GenServer.call(agent, {:get_and_update, fun}, timeout)
  end

  @doc """
  Gets and updates the agent state in one operation via the given function.

  Same as `get_and_update/3` but a module, function, and arguments are expected
  instead of an anonymous function. The state is added as first
  argument to the given list of arguments.
  """
  @spec get_and_update(agent, module, atom, [term], timeout) :: any
  def get_and_update(agent, module, fun, args, timeout \\ 5000) do
    GenServer.call(agent, {:get_and_update, {module, fun, args}}, timeout)
  end

  @doc """
  Updates the agent state via the given anonymous function.

  The function `fun` is sent to the `agent` which invokes the function
  passing the agent state. The return value of `fun` becomes the new
  state of the agent.

  This function always returns `:ok`.

  `timeout` is an integer greater than zero which specifies how many
  milliseconds are allowed before the agent executes the function and returns
  the result value, or the atom `:infinity` to wait indefinitely. If no result
  is received within the specified time, the function call fails and the caller
  exits.

  ## Examples

      iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
      iex> Agent.update(pid, fn state -> state + 1 end)
      :ok
      iex> Agent.get(pid, fn state -> state end)
      43

  """
  @spec update(agent, (state -> state), timeout) :: :ok
  def update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
    GenServer.call(agent, {:update, fun}, timeout)
  end

  @doc """
  Updates the agent state via the given function.

  Same as `update/3` but a module, function, and arguments are expected
  instead of an anonymous function. The state is added as first
  argument to the given list of arguments.
  """
  @spec update(agent, module, atom, [term], timeout) :: :ok
  def update(agent, module, fun, args, timeout \\ 5000) do
    GenServer.call(agent, {:update, {module, fun, args}}, timeout)
  end

  @doc """
  Performs a cast (*fire and forget*) operation on the agent state.

  The function `fun` is sent to the `agent` which invokes the function
  passing the agent state. The return value of `fun` becomes the new
  state of the agent.

  Note that `cast` returns `:ok` immediately, regardless of whether `agent` (or
  the node it should live on) exists.

  ## Examples

      iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
      iex> Agent.cast(pid, fn state -> state + 1 end)
      :ok
      iex> Agent.get(pid, fn state -> state end)
      43

  """
  @spec cast(agent, (state -> state)) :: :ok
  def cast(agent, fun) when is_function(fun, 1) do
    GenServer.cast(agent, {:cast, fun})
  end

  @doc """
  Performs a cast (*fire and forget*) operation on the agent state.

  Same as `cast/2` but a module, function, and arguments are expected
  instead of an anonymous function. The state is added as first
  argument to the given list of arguments.
  """
  @spec cast(agent, module, atom, [term]) :: :ok
  def cast(agent, module, fun, args) do
    GenServer.cast(agent, {:cast, {module, fun, args}})
  end

  @doc """
  Synchronously stops the agent with the given `reason`.

  It returns `:ok` if the agent terminates with the given
  reason. If the agent terminates with another reason, the call will
  exit.

  This function keeps OTP semantics regarding error reporting.
  If the reason is any other than `:normal`, `:shutdown` or
  `{:shutdown, _}`, an error report will be logged.

  ## Examples

      iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
      iex> Agent.stop(pid)
      :ok

  """
  @spec stop(agent, reason :: term, timeout) :: :ok
  def stop(agent, reason \\ :normal, timeout \\ :infinity) do
    GenServer.stop(agent, reason, timeout)
  end
end

defmodule Agent do
  @moduledoc """
  Agents are a simple abstraction around state.

  Often in Elixir there is a need to share or store state that
  must be accessed from different processes or by the same process
  at different points in time.

  The `Agent` module provides a basic server implementation that
  allows state to be retrieved and updated via a simple API.

  ## Examples

  For example, the following agent implements a counter:

      defmodule Counter do
        use Agent

        def start_link(initial_value) do
          Agent.start_link(fn -> initial_value end, name: __MODULE__)
        end

        def value do
          Agent.get(__MODULE__, & &1)
        end

        def increment do
          Agent.update(__MODULE__, &(&1 + 1))
        end
      end

  Usage would be:

      Counter.start_link(0)
      #=> {:ok, #PID<0.123.0>}

      Counter.value()
      #=> 0

      Counter.increment()
      #=> :ok

      Counter.increment()
      #=> :ok

      Counter.value()
      #=> 2

  Thanks to the agent server process, the counter can be safely incremented
  concurrently.

  Agents provide a segregation between the client and server APIs (similar to
  `GenServer`s). In particular, the functions passed as arguments to the calls to
  `Agent` functions are invoked inside the agent (the server). This distinction
  is important because you may want to avoid expensive operations inside the
  agent, as they will effectively block the agent until the request is
  fulfilled.

  Consider these two examples:

      # Compute in the agent/server
      def get_something(agent) do
        Agent.get(agent, fn state -> do_something_expensive(state) end)
      end

      # Compute in the agent/client
      def get_something(agent) do
        Agent.get(agent, & &1) |> do_something_expensive()
      end

  The first function blocks the agent. The second function copies all the state
  to the client and then executes the operation in the client. One aspect to
  consider is whether the data is large enough to require processing in the server,
  at least initially, or small enough to be sent to the client cheaply. Another
  factor is whether the data needs to be processed atomically: getting the
  state and calling `do_something_expensive(state)` outside of the agent means
  that the agent's state can be updated in the meantime. This is specially
  important in case of updates as computing the new state in the client rather
  than in the server can lead to race conditions if multiple clients are trying
  to update the same state to different values.

  ## How to supervise

  An `Agent` is most commonly started under a supervision tree.
  When we invoke `use Agent`, it automatically defines a `child_spec/1`
  function that allows us to start the agent directly under a supervisor.
  To start an agent under a supervisor with an initial counter of 0,
  one may do:

      children = [
        {Counter, 0}
      ]

      Supervisor.start_link(children, strategy: :one_for_all)

  While one could also simply pass the `Counter` as a child to the supervisor,
  such as:

      children = [
        Counter # Same as {Counter, []}
      ]

      Supervisor.start_link(children, strategy: :one_for_all)

  The definition above wouldn't work for this particular example,
  as it would attempt to start the counter with an initial value
  of an empty list. However, this may be a viable option in your
  own agents. A common approach is to use a keyword list, as that
  would allow setting the initial value and giving a name to the
  counter process, for example:

      def start_link(opts) do
        {initial_value, opts} = Keyword.pop(opts, :initial_value, 0)
        Agent.start_link(fn -> initial_value end, opts)
      end

  and then you can use `Counter`, `{Counter, name: :my_counter}` or
  even `{Counter, initial_value: 0, name: :my_counter}` as a child
  specification.

  `use Agent` also accepts a list of options which configures the
  child specification and therefore how it runs under a supervisor.
  The generated `child_spec/1` can be customized with the following options:

    * `:id` - the child specification identifier, defaults to the current module
    * `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
    * `:restart` - when the child should be restarted, defaults to `:permanent`
    * `:shutdown` - how to shut down the child, either immediately or by giving it time to shut down

  For example:

      use Agent, restart: :transient, shutdown: 10_000

  See the "Child specification" section in the `Supervisor` module for more
  detailed information. The `@doc` annotation immediately preceding
  `use Agent` will be attached to the generated `child_spec/1` function.

  ## Name registration

  An agent is bound to the same name registration rules as GenServers.
  Read more about it in the `GenServer` documentation.

  ## A word on distributed agents

  It is important to consider the limitations of distributed agents. Agents
  provide two APIs, one that works with anonymous functions and another
  that expects an explicit module, function, and arguments.

  In a distributed setup with multiple nodes, the API that accepts anonymous
  functions only works if the caller (client) and the agent have the same
  version of the caller module.

  Keep in mind this issue also shows up when performing "rolling upgrades"
  with agents. By rolling upgrades we mean the following situation: you wish
  to deploy a new version of your software by *shutting down* some of your
  nodes and replacing them with nodes running a new version of the software.
  In this setup, part of your environment will have one version of a given
  module and the other part another version (the newer one) of the same module.

  The best solution is to simply use the explicit module, function, and arguments
  APIs when working with distributed agents.

  ## Hot code swapping

  An agent can have its code hot swapped live by simply passing a module,
  function, and arguments tuple to the update instruction. For example, imagine
  you have an agent named `:sample` and you want to convert its inner state
  from a keyword list to a map. It can be done with the following
  instruction:

      {:update, :sample, {:advanced, {Enum, :into, [%{}]}}}

  The agent's state will be added to the given list of arguments (`[%{}]`) as
  the first argument.
  """

  @typedoc "Return values of `start*` functions"
  @type on_start :: {:ok, pid} | {:error, {:already_started, pid} | term}

  @typedoc "The agent name"
  @type name :: atom | {:global, term} | {:via, module, term}

  @typedoc "The agent reference"
  @type agent :: pid | {atom, node} | name

  @typedoc "The agent state"
  @type state :: term

  @doc """
  Returns a specification to start an agent under a supervisor.

  See the "Child specification" section in the `Supervisor` module for more detailed information.
  """
  @doc since: "1.5.0"
  def child_spec(arg) do
    %{
      id: Agent,
      start: {Agent, :start_link, [arg]}
    }
  end

  @doc false
  defmacro __using__(opts) do
    quote location: :keep, bind_quoted: [opts: opts] do
      if Module.get_attribute(__MODULE__, :doc) == nil do
        @doc """
        Returns a specification to start this module under a supervisor.

        See `Supervisor`.
        """
      end

      def child_spec(arg) do
        default = %{
          id: __MODULE__,
          start: {__MODULE__, :start_link, [arg]}
        }

        Supervisor.child_spec(default, unquote(Macro.escape(opts)))
      end

      defoverridable child_spec: 1
    end
  end

  @doc """
  Starts an agent linked to the current process with the given function.

  This is often used to start the agent as part of a supervision tree.

  Once the agent is spawned, the given function `fun` is invoked in the server
  process, and should return the initial agent state. Note that `start_link/2`
  does not return until the given function has returned.

  ## Options

  The `:name` option is used for registration as described in the module
  documentation.

  If the `:timeout` option is present, the agent is allowed to spend at most
  the given number of milliseconds on initialization or it will be terminated
  and the start function will return `{:error, :timeout}`.

  If the `:debug` option is present, the corresponding function in the
  [`:sys` module](http://www.erlang.org/doc/man/sys.html) will be invoked.

  If the `:spawn_opt` option is present, its value will be passed as options
  to the underlying process as in `Process.spawn/4`.

  ## Return values

  If the server is successfully created and initialized, the function returns
  `{:ok, pid}`, where `pid` is the PID of the server. If an agent with the
  specified name already exists, the function returns
  `{:error, {:already_started, pid}}` with the PID of that process.

  If the given function callback fails, the function returns `{:error, reason}`.

  ## Examples

      iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
      iex> Agent.get(pid, fn state -> state end)
      42

      iex> {:error, {exception, _stacktrace}} = Agent.start(fn -> raise "oops" end)
      iex> exception
      %RuntimeError{message: "oops"}

  """
  @spec start_link((() -> term), GenServer.options()) :: on_start
  def start_link(fun, options \\ []) when is_function(fun, 0) do
    GenServer.start_link(Agent.Server, fun, options)
  end

  @doc """
  Starts an agent linked to the current process.

  Same as `start_link/2` but a module, function, and arguments are expected
  instead of an anonymous function; `fun` in `module` will be called with the
  given arguments `args` to initialize the state.
  """
  @spec start_link(module, atom, [any], GenServer.options()) :: on_start
  def start_link(module, fun, args, options \\ []) do
    GenServer.start_link(Agent.Server, {module, fun, args}, options)
  end

  @doc """
  Starts an agent process without links (outside of a supervision tree).

  See `start_link/2` for more information.

  ## Examples

      iex> {:ok, pid} = Agent.start(fn -> 42 end)
      iex> Agent.get(pid, fn state -> state end)
      42

  """
  @spec start((() -> term), GenServer.options()) :: on_start
  def start(fun, options \\ []) when is_function(fun, 0) do
    GenServer.start(Agent.Server, fun, options)
  end

  @doc """
  Starts an agent without links with the given module, function, and arguments.

  See `start_link/4` for more information.
  """
  @spec start(module, atom, [any], GenServer.options()) :: on_start
  def start(module, fun, args, options \\ []) do
    GenServer.start(Agent.Server, {module, fun, args}, options)
  end

  @doc """
  Gets an agent value via the given anonymous function.

  The function `fun` is sent to the `agent` which invokes the function
  passing the agent state. The result of the function invocation is
  returned from this function.

  `timeout` is an integer greater than zero which specifies how many
  milliseconds are allowed before the agent executes the function and returns
  the result value, or the atom `:infinity` to wait indefinitely. If no result
  is received within the specified time, the function call fails and the caller
  exits.

  ## Examples

      iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
      iex> Agent.get(pid, fn state -> state end)
      42

  """
  @spec get(agent, (state -> a), timeout) :: a when a: var
  def get(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
    GenServer.call(agent, {:get, fun}, timeout)
  end

  @doc """
  Gets an agent value via the given function.

  Same as `get/3` but a module, function, and arguments are expected
  instead of an anonymous function. The state is added as first
  argument to the given list of arguments.
  """
  @spec get(agent, module, atom, [term], timeout) :: any
  def get(agent, module, fun, args, timeout \\ 5000) do
    GenServer.call(agent, {:get, {module, fun, args}}, timeout)
  end

  @doc """
  Gets and updates the agent state in one operation via the given anonymous
  function.

  The function `fun` is sent to the `agent` which invokes the function
  passing the agent state. The function must return a tuple with two
  elements, the first being the value to return (that is, the "get" value)
  and the second one being the new state of the agent.

  `timeout` is an integer greater than zero which specifies how many
  milliseconds are allowed before the agent executes the function and returns
  the result value, or the atom `:infinity` to wait indefinitely. If no result
  is received within the specified time, the function call fails and the caller
  exits.

  ## Examples

      iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
      iex> Agent.get_and_update(pid, fn state -> {state, state + 1} end)
      42
      iex> Agent.get(pid, fn state -> state end)
      43

  """
  @spec get_and_update(agent, (state -> {a, state}), timeout) :: a when a: var
  def get_and_update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
    GenServer.call(agent, {:get_and_update, fun}, timeout)
  end

  @doc """
  Gets and updates the agent state in one operation via the given function.

  Same as `get_and_update/3` but a module, function, and arguments are expected
  instead of an anonymous function. The state is added as first
  argument to the given list of arguments.
  """
  @spec get_and_update(agent, module, atom, [term], timeout) :: any
  def get_and_update(agent, module, fun, args, timeout \\ 5000) do
    GenServer.call(agent, {:get_and_update, {module, fun, args}}, timeout)
  end

  @doc """
  Updates the agent state via the given anonymous function.

  The function `fun` is sent to the `agent` which invokes the function
  passing the agent state. The return value of `fun` becomes the new
  state of the agent.

  This function always returns `:ok`.

  `timeout` is an integer greater than zero which specifies how many
  milliseconds are allowed before the agent executes the function and returns
  the result value, or the atom `:infinity` to wait indefinitely. If no result
  is received within the specified time, the function call fails and the caller
  exits.

  ## Examples

      iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
      iex> Agent.update(pid, fn state -> state + 1 end)
      :ok
      iex> Agent.get(pid, fn state -> state end)
      43

  """
  @spec update(agent, (state -> state), timeout) :: :ok
  def update(agent, fun, timeout \\ 5000) when is_function(fun, 1) do
    GenServer.call(agent, {:update, fun}, timeout)
  end

  @doc """
  Updates the agent state via the given function.

  Same as `update/3` but a module, function, and arguments are expected
  instead of an anonymous function. The state is added as first
  argument to the given list of arguments.
  """
  @spec update(agent, module, atom, [term], timeout) :: :ok
  def update(agent, module, fun, args, timeout \\ 5000) do
    GenServer.call(agent, {:update, {module, fun, args}}, timeout)
  end

  @doc """
  Performs a cast (*fire and forget*) operation on the agent state.

  The function `fun` is sent to the `agent` which invokes the function
  passing the agent state. The return value of `fun` becomes the new
  state of the agent.

  Note that `cast` returns `:ok` immediately, regardless of whether `agent` (or
  the node it should live on) exists.
  """
  @spec cast(agent, (state -> state)) :: :ok
  def cast(agent, fun) when is_function(fun, 1) do
    GenServer.cast(agent, {:cast, fun})
  end

  @doc """
  Performs a cast (*fire and forget*) operation on the agent state.

  Same as `cast/2` but a module, function, and arguments are expected
  instead of an anonymous function. The state is added as first
  argument to the given list of arguments.
  """
  @spec cast(agent, module, atom, [term]) :: :ok
  def cast(agent, module, fun, args) do
    GenServer.cast(agent, {:cast, {module, fun, args}})
  end

  @doc """
  Synchronously stops the agent with the given `reason`.

  It returns `:ok` if the agent terminates with the given
  reason. If the agent terminates with another reason, the call will
  exit.

  This function keeps OTP semantics regarding error reporting.
  If the reason is any other than `:normal`, `:shutdown` or
  `{:shutdown, _}`, an error report will be logged.

  ## Examples

      iex> {:ok, pid} = Agent.start_link(fn -> 42 end)
      iex> Agent.stop(pid)
      :ok

  """
  @spec stop(agent, reason :: term, timeout) :: :ok
  def stop(agent, reason \\ :normal, timeout \\ :infinity) do
    GenServer.stop(agent, reason, timeout)
  end
end


Erlangism

Erlangism

defmodule Mail.SMTPServer do
    require Logger
    @behaviour :gen_smtp_server_session

    def init(hostname, session_count, address, options) do
	if session_count > 40 do
	    Logger.warn('SMTP server connection limit exceeded')
	    {:stop, :normal, ['421 ', hostname, ' is too busy to accept mail right now']}
	else
	    banner = [hostname, " ESMTP"]
	    state = %{}
	    {:ok, banner, state}
	end
    end

    def handle_HELO(hostname, state), do: {:ok, state}

    def handle_EHLO(hostname, extensions, state) do
	my_extensions = extensions ++ [{'AUTH', 'PLAIN LOGIN CRAM-MD5'}, {'STARTTLS', true}]
	{:ok, my_extensions, state}
    end

    def handle_MAIL(from, state) do
	{:ok, state}
    end

    def handle_RCPT(to, state) do
	{:ok, state}
    end

    def handle_DATA(from, to, data, state) do
	Mail.Receive.receive_message(from, to, data)
	{:ok, "", state}
    end

    def handle_RSET(state), do: state

    def handle_VRFY(_, state) do
	{:error, "252 VRFY disabled by policy, just send some mail", state}
    end

    def handle_other(verb, _, state) do
	{["500 Error: command not recognized : '", verb, "'"], state}
    end

    def handle_STARTTLS(state), do: state

    def handle_info(info, state), do: {:noreply, state}

    def code_change(_OldVsn, state, _Extra), do: {:ok, state}

    def terminate(reason, state), do: {:ok, reason, state}
end

defmodule Mail.SMTPServer do
    require Logger
    @behaviour :gen_smtp_server_session

    def init(hostname, session_count, address, options) do
	if session_count > 40 do
	    Logger.warn("SMTP server connection limit exceeded")
	    {:stop, :normal, ["421 ", hostname, " is too busy to accept mail right now"]}
	else
	    banner = [hostname, " ESMTP"]
	    state = %{}
	    {:ok, banner, state}
	end
    end

    def handle_HELO(hostname, state), do: {:ok, state}

    def handle_EHLO(hostname, extensions, state) do
	my_extensions = extensions ++ [{"AUTH", "PLAIN LOGIN CRAM-MD5"}, {"STARTTLS", true}]
	{:ok, my_extensions, state}
    end

    def handle_MAIL(from, state) do
	{:ok, state}
    end

    def handle_RCPT(to, state) do
	{:ok, state}
    end

    def handle_DATA(from, to, data, state) do
	Mail.Receive.receive_message(from, to, data)
	{:ok, "", state}
    end

    def handle_RSET(state), do: state

    def handle_VRFY(_, state) do
	{:error, "252 VRFY disabled by policy, just send some mail", state}
    end

    def handle_other(verb, _, state) do
	{["500 Error: command not recognized : '", verb, "'"], state}
    end

    def handle_STARTTLS(state), do: state

    def handle_info(info, state), do: {:noreply, state}

    def code_change(_OldVsn, state, _Extra), do: {:ok, state}

    def terminate(reason, state), do: {:ok, reason, state}
end


Run formatter

Run formatter

defmodule Mix.Task do
  @moduledoc """
  Provides conveniences for creating, loading, and manipulating Mix tasks.

  A Mix task can be defined by using `Mix.Task` in a module whose name
  starts with `Mix.Tasks.` and which defines the `run/1` function.
  Typically, task modules live inside the `lib/mix/tasks/` directory,
  and their file names use dot separators instead of underscores
  (e.g. `deps.clean.ex`) - although ultimately the file name is not
  relevant.

  For example:

      # lib/mix/tasks/echo.ex
      defmodule Mix.Tasks.Echo do
        @moduledoc "Printed when the user requests `mix help echo`"
        @shortdoc "Echoes arguments"

        use Mix.Task

        @impl Mix.Task
        def run(args) do
          Mix.shell().info(Enum.join(args, " "))
        end
      end

  The command name will correspond to the portion of the module
  name following `Mix.Tasks.`. For example, a module name of
  `Mix.Tasks.Deps.Clean` corresponds to a task name of `deps.clean`.

  The `run/1` function will receive a list of all command line
  arguments passed, according to the user's terminal.

  For example, if the `args` in the above `echo` task were
  inspected, you might see something like this:

      mix echo 'A and B' C --test
      ["A and B", "C", "--test"]

  Define the `@shortdoc` attribute if you wish to make the task
  publicly visible on `mix help`. Omit this attribute if you do
  not want your task to be listed on `mix help`.

  If a task has requirements, they can be listed using the
  `@requirements` attribute. For example:

      @requirements ["compile"]

  Tasks typically depend on the `"compile"` task, when they need
  to access code from the current project, or the "app.start" task,
  which compiles and starts the current app:

      @requirements ["app.start"]

  Tasks can also be run directly by using `run/2`.

  ## Attributes

  There are a few attributes available in Mix tasks to
  configure them in Mix:

    * `@shortdoc`  - makes the task public with a short description that appears
      on `mix help`
    * `@recursive` - runs the task recursively in umbrella projects
    * `@requirements` - list of required tasks to be run before the task
    * `@preferred_cli_env` - recommends environment to run task. It is used
      only if `MIX_ENV` is not yet set. Note `@preferred_cli_env` is not loaded
      from dependencies as we need to know the environment in order to load
      the depenencies themselves. In those cases, you can set the `preferred_cli_env`
      cocnfiguration under `def project` in your `mix.exs`

  ## Documentation

  Users can read the documentation for public Mix tasks by running `mix help my_task`.
  The documentation that will be shown is the `@moduledoc` of the task's module.
  """

  @type task_name :: String.t() | atom
  @type task_module :: atom

  @doc """
  A task needs to implement `run` which receives
  a list of command line args.
  """
  @callback run(command_line_args :: [binary]) :: any

  @doc false
  defmacro __using__(_opts) do
    quote do
      Enum.each(
        Mix.Task.supported_attributes(),
        &Module.register_attribute(__MODULE__, &1, persist: true)
      )

      @behaviour Mix.Task
    end
  end

  @doc false
  def supported_attributes do
    [:shortdoc, :recursive, :preferred_cli_env, :requirements]
  end

  @doc """
  Loads all tasks in all code paths.
  """
  @spec load_all() :: [task_module]
  def load_all, do: load_tasks(:code.get_path())

  @doc """
  Loads all tasks in the given `paths`.
  """
  @spec load_tasks([List.Chars.t()]) :: [task_module]
  def load_tasks(dirs) do
    # We may get duplicate modules because we look through the
    # entire load path so make sure we only return unique modules.
    for dir <- dirs,
        file <- safe_list_dir(to_charlist(dir)),
        mod = task_from_path(file),
        uniq: true,
        do: mod
  end

  defp safe_list_dir(path) do
    case File.ls(path) do
      {:ok, paths} -> paths
      {:error, _} -> []
    end
  end

  @prefix_size byte_size("Elixir.Mix.Tasks.")
  @suffix_size byte_size(".beam")

  defp task_from_path(filename) do
    base = Path.basename(filename)
    part = byte_size(base) - @prefix_size - @suffix_size

    case base do
      <<"Elixir.Mix.Tasks.", rest::binary-size(part), ".beam">> ->
        mod = :"Elixir.Mix.Tasks.#{rest}"
        ensure_task?(mod) && mod

      _ ->
        nil
    end
  end

  @doc """
  Returns all loaded task modules.

  Modules that are not yet loaded won't show up.
  Check `load_all/0` if you want to preload all tasks.
  """
  @spec all_modules() :: [task_module]
  def all_modules do
    for {module, _} <- :code.all_loaded(), task?(module), do: module
  end

  @doc """
  Gets the moduledoc for the given task `module`.

  Returns the moduledoc or `nil`.
  """
  @spec moduledoc(task_module) :: String.t() | nil | false
  def moduledoc(module) when is_atom(module) do
    case Code.fetch_docs(module) do
      {:docs_v1, _, _, _, %{"en" => moduledoc}, _, _} -> moduledoc
      {:docs_v1, _, _, _, :hidden, _, _} -> false
      _ -> nil
    end
  end

  @doc """
  Gets the shortdoc for the given task `module`.

  Returns the shortdoc or `nil`.
  """
  @spec shortdoc(task_module) :: String.t() | nil
  def shortdoc(module) when is_atom(module) do
    case List.keyfind(module.__info__(:attributes), :shortdoc, 0) do
      {:shortdoc, [shortdoc]} -> shortdoc
      _ -> nil
    end
  end

  @doc """
  Checks if the task should be run recursively for all sub-apps in
  umbrella projects.

  Returns `true` or `false`.
  """
  @spec recursive(task_module) :: boolean
  def recursive(module) when is_atom(module) do
    case List.keyfind(module.__info__(:attributes), :recursive, 0) do
      {:recursive, [setting]} -> setting
      _ -> false
    end
  end

  @doc """
  Indicates if the current task is recursing.

  This returns true if a task is marked as recursive
  and it is being executed inside an umbrella project.
  """
  @doc since: "1.8.0"
  @spec recursing?() :: boolean
  def recursing?() do
    Mix.ProjectStack.recursing() != nil
  end

  @doc """
  Gets preferred CLI environment for the task.

  Returns environment (for example, `:test`, or `:prod`), or `nil`.
  """
  @spec preferred_cli_env(task_name) :: atom | nil
  def preferred_cli_env(task) when is_atom(task) or is_binary(task) do
    case get(task) do
      nil ->
        nil

      module ->
        case List.keyfind(module.__info__(:attributes), :preferred_cli_env, 0) do
          {:preferred_cli_env, [setting]} -> setting
          _ -> nil
        end
    end
  end

  @doc """
  Gets the list of requirements for the given task.

  Returns a list of strings, where the string is expected
  to be a task optionally followed by its arguments.
  """
  @spec requirements(task_module) :: []
  def requirements(module) when is_atom(module) do
    {:requirements, requirements} =
      List.keyfind(module.__info__(:attributes), :requirements, 0, {:requirements, []})

    requirements
  end

  @doc """
  Returns the task name for the given `module`.
  """
  @spec task_name(task_module) :: task_name
  def task_name(module) when is_atom(module) do
    Mix.Utils.module_name_to_command(module, 2)
  end

  @doc """
  Checks if an alias called `task` exists.

  For more information about task aliasing, take a look at the "Aliasing"
  section in the docs for `Mix`.
  """
  @spec alias?(task_name) :: boolean
  def alias?(task) when is_binary(task) do
    alias?(String.to_atom(task))
  end

  def alias?(task) when is_atom(task) do
    Keyword.has_key?(Mix.Project.config()[:aliases], task)
  end

  @doc """
  Receives a task name and returns the task module if found.

  Otherwise returns `nil` in case the module
  exists, but it isn't a task or cannot be found.
  """
  @spec get(task_name) :: task_module | nil
  def get(task) do
    case fetch(task) do
      {:ok, module} -> module
      {:error, _} -> nil
    end
  end

  @doc """
  Receives a task name and retrieves the task module.

  ## Exceptions

    * `Mix.NoTaskError`      - raised if the task could not be found
    * `Mix.InvalidTaskError` - raised if the task is not a valid `Mix.Task`

  """
  @spec get!(task_name) :: task_module
  def get!(task) do
    case fetch(task) do
      {:ok, module} ->
        module

      {:error, :invalid} ->
        raise Mix.InvalidTaskError, task: task

      {:error, :not_found} ->
        raise Mix.NoTaskError, task: task
    end
  end

  defp fetch(task) when is_binary(task) or is_atom(task) do
    case Mix.Utils.command_to_module(to_string(task), Mix.Tasks) do
      {:module, module} ->
        if task?(module), do: {:ok, module}, else: {:error, :invalid}

      {:error, _} ->
        {:error, :not_found}
    end
  end

  @doc """
  Runs a `task` with the given `args`.

  If the task was not yet invoked, it runs the task and
  returns the result.

  If there is an alias with the same name, the alias
  will be invoked instead of the original task.

  If the task or alias were already invoked, it does not
  run them again and simply aborts with `:noop`.

  It may raise an exception if an alias or a task can't
  be found or the task is invalid. Check `get!/1` for more
  information.
  """
  @spec run(task_name, [any]) :: any
  def run(task, args \\ [])

  def run(task, args) when is_atom(task) do
    run(Atom.to_string(task), args)
  end

  def run(task, args) when is_binary(task) do
    proj = Mix.Project.get()
    alias = Mix.Project.config()[:aliases][String.to_atom(task)]

    cond do
      alias && Mix.TasksServer.run({:alias, task, proj}) ->
        res = run_alias(List.wrap(alias), args, task, :ok)
        Mix.TasksServer.put({:task, task, proj})
        res

      Mix.TasksServer.run({:task, task, proj}) ->
        run_task(proj, task, args)

      true ->
        :noop
    end
  end

  defp run_task(proj, task, args) do
    if Mix.debug?(), do: output_task_debug_info(task, args, proj)

    # 1. If the task is available, we run it.
    # 2. Otherwise we compile and load dependencies
    # 3. Finally, we compile the current project in hope it is available.
    module =
      get_task_or_run(proj, task, fn -> Mix.Task.run("deps.loadpaths") end) ||
        get_task_or_run(proj, task, fn -> Mix.Task.run("compile", []) end) ||
        get!(task)

    run_requirements(module)

    recursive = recursive(module)

    cond do
      recursive && Mix.Project.umbrella?() ->
        Mix.ProjectStack.recur(fn ->
          recur(fn _ -> run(task, args) end)
        end)

      not recursive && Mix.ProjectStack.recursing() ->
        Mix.ProjectStack.on_recursing_root(fn -> run(task, args) end)

      true ->
        Mix.TasksServer.put({:task, task, proj})

        try do
          module.run(args)
        rescue
          e in OptionParser.ParseError ->
            Mix.raise("Could not invoke task #{inspect(task)}: " <> Exception.message(e))
        end
    end
  end

  def run_requirements(module) do
    Enum.each(requirements(module), fn requirement ->
      [task | args] = OptionParser.split(requirement)
      Mix.Task.run(task, args)
    end)
  end

  defp output_task_debug_info(task, args, proj) do
    Mix.shell().info("** Running mix " <> task_to_string(task, args) <> project_to_string(proj))
  end

  defp project_to_string(nil), do: ""
  defp project_to_string(proj), do: " (inside #{inspect(proj)})"

  defp task_to_string(task, []), do: task
  defp task_to_string(task, args), do: task <> " " <> Enum.join(args, " ")

  defp get_task_or_run(proj, task, fun) do
    cond do
      module = get(task) ->
        module

      proj ->
        fun.()
        nil

      true ->
        nil
    end
  end

  defp run_alias([h | t], alias_args, original_task, _res) when is_binary(h) do
    case OptionParser.split(h) do
      [^original_task | args] ->
        res = Mix.Task.run(original_task, args ++ alias_args)
        run_alias(t, [], original_task, res)

      [task | args] ->
        res = Mix.Task.run(task, join_args(args, alias_args, t))
        run_alias(t, alias_args, original_task, res)
    end
  end

  defp run_alias([h | t], alias_args, original_task, _res) when is_function(h, 1) do
    res = h.(join_args([], alias_args, t))
    run_alias(t, alias_args, original_task, res)
  end

  defp run_alias([], _alias_task, _original_task, res) do
    res
  end

  defp join_args(args, alias_args, []), do: args ++ alias_args
  defp join_args(args, _alias_args, _), do: args

  @doc """
  Clears all invoked tasks, allowing them to be reinvoked.

  This operation is not recursive.
  """
  @spec clear :: :ok
  def clear do
    Mix.TasksServer.clear()
  end

  @doc """
  Reenables a given task so it can be executed again down the stack.

  Both alias and the regular stack are reenabled when this function
  is called.

  If an umbrella project reenables a task, it is reenabled for all
  child projects.
  """
  @spec reenable(task_name) :: :ok
  def reenable(task) when is_binary(task) or is_atom(task) do
    task = to_string(task)
    proj = Mix.Project.get()
    recursive = (module = get(task)) && recursive(module)

    Mix.TasksServer.delete_many([{:task, task, proj}, {:alias, task, proj}])

    cond do
      recursive && Mix.Project.umbrella?() ->
        recur(fn proj ->
          Mix.TasksServer.delete_many([{:task, task, proj}, {:alias, task, proj}])
        end)

      proj = !recursive && Mix.ProjectStack.recursing() ->
        Mix.TasksServer.delete_many([{:task, task, proj}, {:alias, task, proj}])

      true ->
        :ok
    end

    :ok
  end

  defp recur(fun) do
    # Get all dependency configuration but not the deps path
    # as we leave the control of the deps path still to the
    # umbrella child.
    config = Mix.Project.deps_config() |> Keyword.delete(:deps_path)

    for %Mix.Dep{app: app, opts: opts} <- Mix.Dep.Umbrella.cached() do
      Mix.Project.in_project(app, opts[:path], [inherit_parent_config_files: true] ++ config, fun)
    end
  end

  @doc """
  Reruns `task` with the given arguments.

  This function reruns the given task; to do that, it first re-enables the task
  and then runs it as normal.
  """
  @spec rerun(task_name, [any]) :: any
  def rerun(task, args \\ []) do
    reenable(task)
    run(task, args)
  end

  @doc """
  Returns `true` if given module is a task.
  """
  @spec task?(task_module) :: boolean
  def task?(module) when is_atom(module) do
    match?('Elixir.Mix.Tasks.' ++ _, Atom.to_charlist(module)) and ensure_task?(module)
  end

  defp ensure_task?(module) do
    Code.ensure_loaded?(module) and function_exported?(module, :run, 1)
  end
end


fix(webpack): digest npm run build

fix(webpack): digest npm run build

defmodule Mix.Tasks.WhistlerNewsReader.Digest do
  use Mix.Task

  def run(args) do
    Mix.Shell.IO.cmd "npm run build:production"
    :ok = Mix.Tasks.Phoenix.Digest.run(args)
  end
end

defmodule Mix.Tasks.WhistlerNewsReader.Digest do
  use Mix.Task

  def run(args) do
    Mix.Shell.IO.cmd "npm run webpack:build:production"
    :ok = Mix.Tasks.Phoenix.Digest.run(args)
  end
end


[DevServer] Use Project.Loader to load serum.exs

defmodule Serum.DevServer do
  @moduledoc """
  The Serum development server.
  """

  alias Serum.DevServer.{Looper, Service}
  alias Serum.Project
  alias Serum.Project.Loader, as: ProjectLoader
  alias Serum.Result
  alias Serum.SiteBuilder

  @doc """
  Starts the Serum development server.
  """
  @spec run(binary, pos_integer) :: no_return()
  def run(dir, port) do
    import Supervisor.Spec

    uniq = Base.url_encode64(:crypto.strong_rand_bytes(6))
    site = "/tmp/serum_" <> uniq

    case ProjectLoader.load(dir, site) do
      {:error, _} = error ->
        Result.show(error)

      {:ok, %Project{} = proj} ->
        {:ok, builder} = SiteBuilder.start_link(dir, site)
        base = proj.base_url
        ms_callbacks = [Microscope.Logger]

        ms_options = [
          port: port,
          base: base,
          callbacks: ms_callbacks,
          index: true,
          extra_routes: [
            {"/serum_live_reloader", Serum.DevServer.LiveReloadHandler, nil}
          ]
        ]

        children = [
          worker(Service, [builder, dir, site, port]),
          worker(Microscope, [site, ms_options])
        ]

        opts = [strategy: :one_for_one, name: Serum.DevServer.Supervisor]
        Supervisor.start_link(children, opts)
        Looper.looper()
    end
  end
end

defmodule Serum.DevServer do
  @moduledoc """
  The Serum development server.
  """

  alias Serum.DevServer.{Looper, Service}
  alias Serum.Result
  alias Serum.SiteBuilder

  @doc """
  Starts the Serum development server.
  """
  @spec run(binary, pos_integer) :: no_return()
  def run(dir, port) do
    import Supervisor.Spec

    uniq = Base.url_encode64(:crypto.strong_rand_bytes(6))
    site = "/tmp/serum_" <> uniq

    {:ok, pid_builder} = SiteBuilder.start_link(dir, site)

    case SiteBuilder.load_info(pid_builder) do
      {:error, _} = error ->
        Result.show(error)

      {:ok, proj} ->
        base = proj.base_url
        ms_callbacks = [Microscope.Logger]

        ms_options = [
          port: port,
          base: base,
          callbacks: ms_callbacks,
          index: true,
          extra_routes: [
            {"/serum_live_reloader", Serum.DevServer.LiveReloadHandler, nil}
          ]
        ]

        children = [
          worker(Service, [pid_builder, dir, site, port]),
          worker(Microscope, [site, ms_options])
        ]

        opts = [strategy: :one_for_one, name: Serum.DevServer.Supervisor]
        Supervisor.start_link(children, opts)
        Looper.looper()
    end
  end
end


No need to pass the name as parameter.

No need to pass the name as parameter.

module Record do
  # Main entry point for records definition.
  def defrecord(name, values, opts) do
    as = Orddict.fetch(opts, :as, name)

    quote do
      # Use `module NAME, do: CONTENTS` syntax which is
      # the same as `module NAME do CONTENTS end`. We need
      # to wrap this in a block so this module declaration
      # do not affect the outer module one.
      module __MODULE__ :: unquote(name) do
        require ::Record
        Record.getters_and_setters(unquote(values), 1, [])
        Record.initializers(unquote(values))
      end

      require __MODULE__ :: unquote(name), as: unquote(as)
    end
  end

  # Define initializers methods. For a declaration like:
  #
  #     defrecord FileInfo, atime: nil, mtime: nil
  #
  # It will define three methods:
  #
  #     def new() do
  #       new([])
  #     end
  #
  #     def new([]) do
  #       { FileInfo, nil, nil }
  #     end
  #
  #     def new(opts) do
  #       { FileInfo, Orddict.fetch(opts, :atime, nil), Orddict.fetch(opts, :mtime, nil) }
  #     end
  #
  defmacro initializers(values) do
    # Get default values from the dictionary.
    defaults = Orddict.values(values)

    # For each value, define a piece of code that will receive
    # an ordered dict of options (opts) and it will try to fetch
    # the given key from the ordered dict, falling back to the
    # default value if one does not exist.
    selective = List.map values, fn({k,v}) {
      quote { Orddict.fetch(opts, unquote(k), unquote(v)) }
    }

    quote do
      def new(), do: new([])
      def new([]), do: { __MODULE__, unquote_splice(defaults) }
      def new(opts), do: { __MODULE__, unquote_splice(selective) }
    end
  end

  # Implement getters and setters for each attribute.
  # For a declaration like:
  #
  #     defrecord FileInfo, atime: nil, mtime: nil
  #
  # It will define four methods:
  #
  #     def :atime.(record) do
  #       element(2, record)
  #     end
  #
  #     def :atime.(record, value) do
  #       setelement(2, value, record)
  #     end
  #
  #     def :mtime.(record) do
  #       element(3, record)
  #     end
  #
  #     def :mtime.(record, value) do
  #       setelement(3, value, record)
  #     end
  #
  # `element` and `setelement` will simply get and set values
  # from the record tuple. Notice that `:atime.(record)` is just
  # a dynamic way to say `atime(record)`. We need to use this
  # syntax as `unquote(key)(record)` wouldn't be valid (as Elixir
  # allows you to parenthesis just on specific cases as `foo()`
  # and `foo.bar()`)
  defmacro getters_and_setters([{ key, default }|t], i, acc) do
    i = i + 1

    contents = quote do
      def unquote(key).(record) do
        element(unquote(i), record)
      end

      def unquote(key).(value, record) do
        setelement(unquote(i), record, value)
      end
    end

    typed = typed_functions(key, default, i)
    getters_and_setters(t, i, [contents, typed | acc])
  end

  defmacro getters_and_setters([], _i, acc), do: acc

  ## Private

  defp typed_functions(key, default, i) when is_list(default) do
    bin_key = atom_to_binary(key, :utf8)
    append  = :"append_#{bin_key}"
    prepend = :"prepend_#{bin_key}"
    merge   = :"merge_#{bin_key}"

    quote do
      def unquote(append).(value, record) do
        current = element(unquote(i), record)
        setelement(unquote(i), record, List.append(current, value))
      end

      def unquote(prepend).(value, record) do
        current = element(unquote(i), record)
        setelement(unquote(i), record, List.append(value, current))
      end

      def unquote(merge).(value, record) do
        current = element(unquote(i), record)
        setelement(unquote(i), record, Orddict.merge(current, value))
      end
    end
  end

  defp typed_functions(key, default, i) when is_number(default) do
    bin_key   = atom_to_binary(key, :utf8)
    increment = :"increment_#{bin_key}"

    quote do
      def unquote(increment).(value // 1, record) do
        current = element(unquote(i), record)
        setelement(unquote(i), record, current + value)
      end
    end
  end

  defp typed_functions(_, _, _), do: nil
end

module Record do
  # Main entry point for records definition.
  def defrecord(name, values, opts) do
    as = Orddict.fetch(opts, :as, name)

    quote do
      # Use `module NAME, do: CONTENTS` syntax which is
      # the same as `module NAME do CONTENTS end`. We need
      # to wrap this in a block so this module declaration
      # do not affect the outer module one.
      module __MODULE__ :: unquote(name) do
        require ::Record
        Record.getters_and_setters(unquote(values), 1, [])
        Record.initializers(__MODULE__, unquote(values))
      end

      require __MODULE__ :: unquote(name), as: unquote(as)
    end
  end

  # Define initializers methods. For a declaration like:
  #
  #     defrecord FileInfo, atime: nil, mtime: nil
  #
  # It will define three methods:
  #
  #     def new() do
  #       new([])
  #     end
  #
  #     def new([]) do
  #       { FileInfo, nil, nil }
  #     end
  #
  #     def new(opts) do
  #       { FileInfo, Orddict.fetch(opts, :atime, nil), Orddict.fetch(opts, :mtime, nil) }
  #     end
  #
  defmacro initializers(name, values) do
    # Get default values from the dictionary.
    defaults = Orddict.values(values)

    # For each value, define a piece of code that will receive
    # an ordered dict of options (opts) and it will try to fetch
    # the given key from the ordered dict, falling back to the
    # default value if one does not exist.
    selective = List.map values, fn({k,v}) {
      quote { Orddict.fetch(opts, unquote(k), unquote(v)) }
    }

    quote do
      def new(), do: new([])
      def new([]), do: { unquote(name), unquote_splice(defaults) }
      def new(opts), do: { unquote(name), unquote_splice(selective) }
    end
  end

  # Implement getters and setters for each attribute.
  # For a declaration like:
  #
  #     defrecord FileInfo, atime: nil, mtime: nil
  #
  # It will define four methods:
  #
  #     def :atime.(record) do
  #       element(2, record)
  #     end
  #
  #     def :atime.(record, value) do
  #       setelement(2, value, record)
  #     end
  #
  #     def :mtime.(record) do
  #       element(3, record)
  #     end
  #
  #     def :mtime.(record, value) do
  #       setelement(3, value, record)
  #     end
  #
  # `element` and `setelement` will simply get and set values
  # from the record tuple. Notice that `:atime.(record)` is just
  # a dynamic way to say `atime(record)`. We need to use this
  # syntax as `unquote(key)(record)` wouldn't be valid (as Elixir
  # allows you to parenthesis just on specific cases as `foo()`
  # and `foo.bar()`)
  defmacro getters_and_setters([{ key, default }|t], i, acc) do
    i = i + 1

    contents = quote do
      def unquote(key).(record) do
        element(unquote(i), record)
      end

      def unquote(key).(value, record) do
        setelement(unquote(i), record, value)
      end
    end

    typed = typed_functions(key, default, i)
    getters_and_setters(t, i, [contents, typed | acc])
  end

  defmacro getters_and_setters([], _i, acc), do: acc

  ## Private

  defp typed_functions(key, default, i) when is_list(default) do
    bin_key = atom_to_binary(key, :utf8)
    append  = :"append_#{bin_key}"
    prepend = :"prepend_#{bin_key}"
    merge   = :"merge_#{bin_key}"

    quote do
      def unquote(append).(value, record) do
        current = element(unquote(i), record)
        setelement(unquote(i), record, List.append(current, value))
      end

      def unquote(prepend).(value, record) do
        current = element(unquote(i), record)
        setelement(unquote(i), record, List.append(value, current))
      end

      def unquote(merge).(value, record) do
        current = element(unquote(i), record)
        setelement(unquote(i), record, Orddict.merge(current, value))
      end
    end
  end

  defp typed_functions(key, default, i) when is_number(default) do
    bin_key   = atom_to_binary(key, :utf8)
    increment = :"increment_#{bin_key}"

    quote do
      def unquote(increment).(value // 1, record) do
        current = element(unquote(i), record)
        setelement(unquote(i), record, current + value)
      end
    end
  end

  defp typed_functions(_, _, _), do: nil
end


add more cell tests

add more cell tests

defmodule CellTest do
  use ExUnit.Case, async: true
  import Enum, only: [member?: 2]
  doctest Cell

  test "start_link/1 starts a cell process with name `process` and registers it in the Registry" do
    position = {1, 0}
    {:ok, pid} = Cell.start_link(position)
    assert Registry.lookup(Cell.Registry, position) == [{pid, nil}]
    Supervisor.terminate_child(Cell.Supervisor, pid)
  end

  test "start_link/1 returns error if cell at `position` is already started" do
    position = {1, 1}
    {:ok, pid} = Cell.start_link(position)
    assert Cell.start_link(position) == {:error, {:already_started, pid}}
    Supervisor.terminate_child(Cell.Supervisor, pid)
  end

  test "create/1 creates a new cell a `position` in the Supervisor" do
    position = {1, 2}
    {:ok, pid} = Cell.create(position)
    # there may be more children in the Cell.Supervisor since these tests
    # are running asynchronously, so just check if our `pid` is one of them.
    assert member?(Supervisor.which_children(Cell.Supervisor), {:undefined, pid, :worker, [Cell]})
    Supervisor.terminate_child(Cell.Supervisor, pid)
  end

  test "create/1 returns error if cell with `position` already started" do
    position = {1, 3}
    {:ok, pid} = Cell.create(position)
    assert Cell.create(position) == {:error, {:already_started, pid}}
    Supervisor.terminate_child(Cell.Supervisor, pid)
  end

  test "destroy/1 terminates the cell with the given `position`" do
    position = {1, 4}
    {:ok, pid} = Cell.create(position)
    assert Cell.destroy(pid) == :ok
  end

  test "destroy/1 returns error if invalid `position` is given" do
    # try terminating the PID running this test,
    # which is not in the Cell.Supervisor
    assert Cell.destroy(self()) == {:error, :not_found}
  end

  test "tick/1 returns no cells to create and its own pid to destroy" do
    position = {1, 6}
    {:ok, pid} = Cell.create(position)
    assert Cell.tick(pid) == {[], [pid]}
    Supervisor.terminate_child(Cell.Supervisor, pid)
  end

  test "tick/1 returns no cells to create, no cells to destroy" do
    position1 = {3, 0}
    position2 = {3, 1}
    position3 = {4, 0}
    position4 = {4, 1}
    {:ok, pid1} = Cell.create(position1)
    {:ok, pid2} = Cell.create(position2)
    {:ok, pid3} = Cell.create(position3)
    {:ok, pid4} = Cell.create(position4)
    assert Cell.tick(pid2) == {[], []}
    Supervisor.terminate_child(Cell.Supervisor, pid1)
    Supervisor.terminate_child(Cell.Supervisor, pid2)
    Supervisor.terminate_child(Cell.Supervisor, pid3)
    Supervisor.terminate_child(Cell.Supervisor, pid4)
  end

  test "tick/1 returns cells to create, no cells to destroy" do
    position1 = {6, 0}
    position2 = {6, 1}
    position3 = {6, 2}
    {:ok, pid1} = Cell.create(position1)
    {:ok, pid2} = Cell.create(position2)
    {:ok, pid3} = Cell.create(position3)
    assert Cell.tick(pid2) == {[{5, 1}, {7, 1}], []}
    Supervisor.terminate_child(Cell.Supervisor, pid1)
    Supervisor.terminate_child(Cell.Supervisor, pid2)
    Supervisor.terminate_child(Cell.Supervisor, pid3)
  end

  test "tick/1 returns cells to create and its own `pid` to destroy" do
    position1 = {8, 0}
    position2 = {8, 1}
    position3 = {8, 2}
    {:ok, pid1} = Cell.create(position1)
    {:ok, pid2} = Cell.create(position2)
    {:ok, pid3} = Cell.create(position3)
    assert Cell.tick(pid1) == {[{7, 1}, {9, 1}], [pid1]}
    Supervisor.terminate_child(Cell.Supervisor, pid1)
    Supervisor.terminate_child(Cell.Supervisor, pid2)
    Supervisor.terminate_child(Cell.Supervisor, pid3)
  end
end

defmodule CellTest do
  use ExUnit.Case, async: true
  import Enum, only: [member?: 2]
  doctest Cell

  test "start_link/1 starts a cell process with name `process` and registers it in the Registry" do
    position = {1, 0}
    {:ok, pid} = Cell.start_link(position)
    assert Registry.lookup(Cell.Registry, position) == [{pid, nil}]
    Supervisor.terminate_child(Cell.Supervisor, pid)
  end

  test "start_link/1 returns error if cell at `position` is already started" do
    position = {1, 1}
    {:ok, pid} = Cell.start_link(position)
    assert Cell.start_link(position) == {:error, {:already_started, pid}}
    Supervisor.terminate_child(Cell.Supervisor, pid)
  end

  test "create/1 creates a new cell a `position` in the Supervisor" do
    position = {1, 2}
    {:ok, pid} = Cell.create(position)
    # there may be more children in the Cell.Supervisor since these tests
    # are running asynchronously, so just check if our `pid` is one of them.
    assert member?(Supervisor.which_children(Cell.Supervisor), {:undefined, pid, :worker, [Cell]})
    Supervisor.terminate_child(Cell.Supervisor, pid)
  end

  test "create/1 returns error if cell with `position` already started" do
    position = {1, 3}
    {:ok, pid} = Cell.create(position)
    assert Cell.create(position) == {:error, {:already_started, pid}}
    Supervisor.terminate_child(Cell.Supervisor, pid)
  end

  test "destroy/1 terminates the cell with the given `position`" do
    position = {1, 4}
    {:ok, pid} = Cell.create(position)
    assert Cell.destroy(pid) == :ok
  end

  test "destroy/1 returns error if invalid `position` is given" do
    # try terminating the PID running this test,
    # which is not in the Cell.Supervisor
    assert Cell.destroy(self()) == {:error, :not_found}
  end

  test "tick/1 returns no cells to create and its own pid to destroy" do
    position = {1, 6}
    {:ok, pid} = Cell.create(position)
    assert Cell.tick(pid) == {[], [pid]}
    Supervisor.terminate_child(Cell.Supervisor, pid)
  end

  # TODO test with cells to create, none to destory
  # TODO test with no cells to create, none to destroy
  # TODO test with cells to create, cell to destroy
end


fix test wrt. copy changes

fix test wrt. copy changes

defmodule TicketPoker.TeamControllerTest do
  use TicketPoker.ConnCase

  alias TicketPoker.{Team, Ticket}

  @find_attrs %{coders: [], name: "some content", points: []}
  @valid_attrs Map.put(@find_attrs, :points_string, "")
  @ticket_url "http://foo.bar.baz"
  @non_existent_ticket_id "35bf6df4-69a5-4bd3-a9e5-dddd7d55e3ea"
  @invalid_attrs %{}

  test "renders form for new resources", %{conn: conn} do
    conn = get conn, team_path(conn, :new)
    assert html_response(conn, 200) =~ "New to Ticket Poker?"
  end

  test "creates resource and redirects when data is valid", %{conn: conn} do
    conn = post conn, team_path(conn, :create), team: @valid_attrs
    team = Repo.one(from t in Team, select: t, limit: 1)
    assert redirected_to(conn) == team_path(conn, :show, team)
    assert Repo.get_by(Team, @find_attrs)
  end

  test "redirects to ticket page after create, if ticket created", %{conn: conn} do
    conn = post conn, team_path(conn, :create), team: Map.put(@valid_attrs, "new_ticket_url", @ticket_url)
    ticket = Repo.one(from t in Ticket, select: t, limit: 1)
    assert redirected_to(conn) == ticket_path(conn, :show, ticket)
    assert Repo.get_by(Team, @find_attrs)
  end

  test "does not create resource and renders errors when data is invalid", %{conn: conn} do
    conn = post conn, team_path(conn, :create), team: @invalid_attrs
    assert html_response(conn, 200) =~ "New to Ticket Poker?"
  end

  test "shows chosen resource", %{conn: conn} do
    team = Repo.insert! %Team{}
    conn = get conn, team_path(conn, :show, team)
    assert html_response(conn, 200) =~ "Team URL"
  end

  test "renders page not found when id is nonexistent", %{conn: conn} do
    assert_error_sent 404, fn ->
      get conn, team_path(conn, :show, @non_existent_ticket_id)
    end
  end

  test "renders form for editing chosen resource", %{conn: conn} do
    team = Repo.insert! %Team{}
    conn = get conn, team_path(conn, :show, team)
    assert html_response(conn, 200) =~ "Team URL"
  end

  test "redirects back to team/show, if no ticket created", %{conn: conn} do
    team = Repo.insert! %Team{}
    conn = put conn, team_path(conn, :update, team), team: @valid_attrs
    assert redirected_to(conn) == team_path(conn, :show, team)
    assert Repo.get_by(Team, @find_attrs)
  end

  test "redirects to ticket page, if ticket created", %{conn: conn} do
    team = Repo.insert! %Team{}
    conn = put conn, team_path(conn, :update, team), team: Map.put(@valid_attrs, "new_ticket_url", @ticket_url)
    ticket = Repo.one(from t in Ticket, select: t, limit: 1)
    assert redirected_to(conn) == ticket_path(conn, :show, ticket)
    assert Repo.get_by(Team, @find_attrs)
  end

  test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
    team = Repo.insert! %Team{}
    conn = put conn, team_path(conn, :update, team), team: @invalid_attrs
    assert html_response(conn, 200) =~ "Oops, something went wrong"
  end

end

defmodule TicketPoker.TeamControllerTest do
  use TicketPoker.ConnCase

  alias TicketPoker.{Team, Ticket}

  @find_attrs %{coders: [], name: "some content", points: []}
  @valid_attrs Map.put(@find_attrs, :points_string, "")
  @ticket_url "http://foo.bar.baz"
  @non_existent_ticket_id "35bf6df4-69a5-4bd3-a9e5-dddd7d55e3ea"
  @invalid_attrs %{}

  test "renders form for new resources", %{conn: conn} do
    conn = get conn, team_path(conn, :new)
    assert html_response(conn, 200) =~ "Please create your team"
  end

  test "creates resource and redirects when data is valid", %{conn: conn} do
    conn = post conn, team_path(conn, :create), team: @valid_attrs
    team = Repo.one(from t in Team, select: t, limit: 1)
    assert redirected_to(conn) == team_path(conn, :show, team)
    assert Repo.get_by(Team, @find_attrs)
  end

  test "redirects to ticket page after create, if ticket created", %{conn: conn} do
    conn = post conn, team_path(conn, :create), team: Map.put(@valid_attrs, "new_ticket_url", @ticket_url)
    ticket = Repo.one(from t in Ticket, select: t, limit: 1)
    assert redirected_to(conn) == ticket_path(conn, :show, ticket)
    assert Repo.get_by(Team, @find_attrs)
  end

  test "does not create resource and renders errors when data is invalid", %{conn: conn} do
    conn = post conn, team_path(conn, :create), team: @invalid_attrs
    assert html_response(conn, 200) =~ "Please create your team"
  end

  test "shows chosen resource", %{conn: conn} do
    team = Repo.insert! %Team{}
    conn = get conn, team_path(conn, :show, team)
    assert html_response(conn, 200) =~ "Team URL"
  end

  test "renders page not found when id is nonexistent", %{conn: conn} do
    assert_error_sent 404, fn ->
      get conn, team_path(conn, :show, @non_existent_ticket_id)
    end
  end

  test "renders form for editing chosen resource", %{conn: conn} do
    team = Repo.insert! %Team{}
    conn = get conn, team_path(conn, :show, team)
    assert html_response(conn, 200) =~ "Team URL"
  end

  test "redirects back to team/show, if no ticket created", %{conn: conn} do
    team = Repo.insert! %Team{}
    conn = put conn, team_path(conn, :update, team), team: @valid_attrs
    assert redirected_to(conn) == team_path(conn, :show, team)
    assert Repo.get_by(Team, @find_attrs)
  end

  test "redirects to ticket page, if ticket created", %{conn: conn} do
    team = Repo.insert! %Team{}
    conn = put conn, team_path(conn, :update, team), team: Map.put(@valid_attrs, "new_ticket_url", @ticket_url)
    ticket = Repo.one(from t in Ticket, select: t, limit: 1)
    assert redirected_to(conn) == ticket_path(conn, :show, ticket)
    assert Repo.get_by(Team, @find_attrs)
  end

  test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
    team = Repo.insert! %Team{}
    conn = put conn, team_path(conn, :update, team), team: @invalid_attrs
    assert html_response(conn, 200) =~ "Oops, something went wrong"
  end

end


Ensure dependency is loaded for PBKDF2

Ensure dependency is loaded for PBKDF2


updated examples in IEx on connect

updated examples in IEx on connect

defmodule Drab.Channel do
  require Logger
  @moduledoc false

  use Phoenix.Channel

  def join("__drab:" <> broadcast_topic, _, socket) do
    # socket already contains controller and action
    socket_with_topic = socket |> assign(:__broadcast_topic, broadcast_topic)

    {:ok, pid} = Drab.start_link(socket)

    socket_with_pid = assign(socket_with_topic, :__drab_pid, pid)

    {:ok, socket_with_pid}
  end

  def handle_in("execjs", %{"ok" => [sender_encrypted, reply]}, socket) do
    # sender contains PID of the process which sent the query
    # sender is waiting for the result
    send(sender(socket, sender_encrypted),
      { :got_results_from_client, :ok, reply })

    {:noreply, socket}
  end

  def handle_in("execjs", %{"error" => [sender_encrypted, reply]}, socket) do
    send(sender(socket, sender_encrypted),
      { :got_results_from_client, :error, reply })

    {:noreply, socket}
  end

  def handle_in("modal", %{"ok" => [sender_encrypted, reply]}, socket) do
    # sends { "button_name", %{"Param" => "value"}}
    send(sender(socket, sender_encrypted),
      {
        :got_results_from_client,
        :ok,
        {
          reply["button_clicked"] |> String.to_existing_atom,
          reply["params"] |> Map.delete("__drab_modal_hidden_input")
        }
      })

    {:noreply, socket}
  end

  def handle_in("waiter", %{"drab_waiter_token" => waiter_token, "sender" => sender}, socket) do
    {pid, ref} = Drab.Waiter.detokenize_waiter(socket, waiter_token)

    send(pid, {:waiter, ref, sender})

    {:noreply, socket}
  end

  def handle_in("onload", _, socket) do
    verify_and_cast(:onload, [], socket)
  end

  def handle_in("onconnect", payload, socket) do
    # IO.inspect payload

    Drab.set_socket(socket.assigns.__drab_pid, socket)
    # for debugging
    if IEx.started? do
      commander = Drab.get_commander(socket)
      modules = DrabModule.all_modules_for(commander.__drab__().modules)
      groupped = Enum.map(modules, fn module ->
        [_ | rest] = Module.split(module)
        Enum.join(rest, ".")
      end) |> Enum.join(", ")

      module_examples = %{
        Drab.Live   => "socket |> poke(count: 42)",
        Drab.Query  => "socket |> select(:htmls, from: \"h4\")",
        Drab.Modal  => "socket |> alert(\"Title\", \"Sure?\", buttons: [ok: \"Azaliż\", cancel: \"Poniechaj\"])",
        Drab.Core   => "socket |> exec_js(\"alert('hello from IEx!')\")"
      }
      examples = Enum.map(modules, fn module ->
        module_examples[module]
      end) |> Enum.filter(fn x -> !is_nil(x) end)

      p = inspect(socket.assigns.__drab_pid)
      pid_string = Regex.named_captures(~r/#PID<(?<pid>.*)>/, p) |> Map.get("pid")
      Logger.debug """

          Started Drab for #{socket.assigns.__broadcast_topic}, handling events in #{inspect(commander)}
          You may debug Drab functions in IEx by copy/paste the following:
      import Drab.{#{groupped}}
      socket = Drab.get_socket(pid("#{pid_string}"))

          Examples:
      #{examples |> Enum.join("\n")}
      """
    end

    verify_and_cast(:onconnect, [payload["payload"]], socket)
  end

  def handle_in("event", %{
      "event" => event_name,
      "payload" => payload,
      "event_handler_function" => event_handler_function,
      "reply_to" => reply_to
      }, socket) do
    # event name is currently not used (0.2.0)
    verify_and_cast(event_name, [payload, event_handler_function, reply_to], socket)
  end

  defp verify_and_cast(event_name, params, socket) do
    p = [event_name, socket] ++ params
    GenServer.cast(socket.assigns.__drab_pid, List.to_tuple(p))
    {:noreply, socket}
  end

  defp sender(socket, sender_encrypted) do
    Drab.detokenize(socket, sender_encrypted)
  end
end

defmodule Drab.Channel do
  require Logger
  @moduledoc false

  use Phoenix.Channel

  def join("__drab:" <> broadcast_topic, _, socket) do
    # socket already contains controller and action
    socket_with_topic = socket |> assign(:__broadcast_topic, broadcast_topic)

    {:ok, pid} = Drab.start_link(socket)

    socket_with_pid = assign(socket_with_topic, :__drab_pid, pid)

    {:ok, socket_with_pid}
  end

  def handle_in("execjs", %{"ok" => [sender_encrypted, reply]}, socket) do
    # sender contains PID of the process which sent the query
    # sender is waiting for the result
    send(sender(socket, sender_encrypted),
      { :got_results_from_client, :ok, reply })

    {:noreply, socket}
  end

  def handle_in("execjs", %{"error" => [sender_encrypted, reply]}, socket) do
    send(sender(socket, sender_encrypted),
      { :got_results_from_client, :error, reply })

    {:noreply, socket}
  end

  def handle_in("modal", %{"ok" => [sender_encrypted, reply]}, socket) do
    # sends { "button_name", %{"Param" => "value"}}
    send(sender(socket, sender_encrypted),
      {
        :got_results_from_client,
        :ok,
        {
          reply["button_clicked"] |> String.to_existing_atom,
          reply["params"] |> Map.delete("__drab_modal_hidden_input")
        }
      })

    {:noreply, socket}
  end

  def handle_in("waiter", %{"drab_waiter_token" => waiter_token, "sender" => sender}, socket) do
    {pid, ref} = Drab.Waiter.detokenize_waiter(socket, waiter_token)

    send(pid, {:waiter, ref, sender})

    {:noreply, socket}
  end

  def handle_in("onload", _, socket) do
    verify_and_cast(:onload, [], socket)
  end

  def handle_in("onconnect", payload, socket) do
    # IO.inspect payload

    Drab.set_socket(socket.assigns.__drab_pid, socket)
    # for debugging
    if IEx.started? do
      commander = Drab.get_commander(socket)
      modules = DrabModule.all_modules_for(commander.__drab__().modules)
      groupped = Enum.map(modules, fn module ->
        [_ | rest] = Module.split(module)
        Enum.join(rest, ".")
      end) |> Enum.join(", ")

      module_examples = %{
        Drab.Live   => "socket |> poke(count: 42)",
        Drab.Query  => "socket |> select(:htmls, from: \"h4\")",
        Drab.Modal  => "socket |> alert(\"Title\", \"Sure?\", buttons: [ok: \"Azaliż\", cancel: \"Poniechaj\"])",
        Drab.Core   => "socket |> exec_js(\"alert('hello from IEx!')\")"
      }
      examples = Enum.map(modules, fn module ->
        IO.inspect(module)
        module_examples[module]
      end)

      p = inspect(socket.assigns.__drab_pid)
      pid_string = Regex.named_captures(~r/#PID<(?<pid>.*)>/, p) |> Map.get("pid")
      Logger.debug """

          Started Drab for #{socket.assigns.__broadcast_topic}, handling events in #{inspect(commander)}
          You may debug Drab functions in IEx by copy/paste the following:
      import Drab.{#{groupped}}
      socket = Drab.get_socket(pid("#{pid_string}"))

          Examples:
      #{examples |> Enum.join("\n")}
      """
    end

    verify_and_cast(:onconnect, [payload["payload"]], socket)
  end

  def handle_in("event", %{
      "event" => event_name,
      "payload" => payload,
      "event_handler_function" => event_handler_function,
      "reply_to" => reply_to
      }, socket) do
    # event name is currently not used (0.2.0)
    verify_and_cast(event_name, [payload, event_handler_function, reply_to], socket)
  end

  defp verify_and_cast(event_name, params, socket) do
    p = [event_name, socket] ++ params
    GenServer.cast(socket.assigns.__drab_pid, List.to_tuple(p))
    {:noreply, socket}
  end

  defp sender(socket, sender_encrypted) do
    Drab.detokenize(socket, sender_encrypted)
  end
end


refactoring romans

refactoring romans

defmodule Roman do
  @nums [1000, 900, 500, 400, 100, 90, 80, 70, 60, 50, 40, 10, 9, 5, 4, 1]
  @numerals ["M", "CM", "D", "CD", "C", "XC", "LXXX", "LXX", "LX", "L", "XL", "X", "IX", "V", "IV", "I"]
  @map Enum.zip(@nums, @numerals) |> Enum.into(%{})

  @doc """
  Convert the number to a roman number.
  """
  @spec numerals(pos_integer) :: String.t
  def numerals(n), do: numerals(@nums, n) |> Enum.join("")
  defp numerals([], _), do: []
  defp numerals([h|t]=ls, n) when n - h >= 0, do: [@map[h] | numerals(ls, n-h)]
  defp numerals([_|t], n), do: numerals(t, n)
end

defmodule Roman do
  @ls [{1000, "M"},
       {900, "CM"},
       {500, "D"},
       {400, "CD"},
       {100, "C"},
       {90, "XC"},
       {80, "LXXX"},
       {70, "LXX"},
       {60, "LX"},
       {50, "L"},
       {40, "XL"},
       {10, "X"},
       {9, "IX"},
       {5, "V"},
       {4, "IV"},
       {1, "I"}]

  @keys @ls |> Enum.map(fn {k, _} -> k end)
  @map @ls |> Enum.into(%{})

  @doc """
  Convert the number to a roman number.
  """
  @spec numerals(pos_integer) :: String.t
  def numerals(number) do
    calc(@keys, number) |> Enum.join("")
  end

  defp calc([], _), do: []
  defp calc([k|t]=ls, number) do
    cond do
      number - k >= 0 -> [@map[k] | calc(ls, number-k)]
      true -> calc(t, number)
    end
  end
end


Extend lexer test coverage

Extend lexer test coverage

- list, map, set literals
- typedefs
- enum, struct, exception, service definitions

defmodule LexerTest do
  use ExUnit.Case

  defp tokenize(s) do
    {:ok, value, _} = :thrift_lexer.string(String.to_char_list(s))
    value
  end

  test "whitespace" do
    assert tokenize(" ") == []
  end

  test "comments" do
    assert tokenize("# foo") == []
    assert tokenize("// foo") == []
    assert tokenize("/* foo */") == []
    assert tokenize("
    /*
      foo
    */") == []
  end

  test "symbols" do
    assert tokenize(";") == [{:symbol, 1, ';'}]
    assert tokenize("=") == [{:symbol, 1, '='}]
  end

  test "keywords" do
    keywords = ~w(
      namespace include
      void bool byte i8 i16 i64 double string binary list map set
      typedef enum struct union exception
      const oneway extends throws service required optional
    )
    for keyword <- keywords do
      value = keyword |> tokenize |> Keyword.keys |> List.first
      assert value == String.to_atom(keyword)
    end
  end

  test "boolean literals" do
    assert tokenize("true") == [true: 1]
    assert tokenize("false") == [false: 1]
  end

  test "integer literals" do
    assert tokenize("0") == [{:int, 1, 0}]
    assert tokenize("10") == [{:int, 1, 10}]
    assert tokenize("+10") == [{:int, 1, 10}]
    assert tokenize("-10") == [{:int, 1, -10}]
  end

  test "hex literals" do
    assert tokenize("0x0") == [{:int, 1, 0}]
    assert tokenize("0xff") == [{:int, 1, 255}]
    assert tokenize("+0xc0c0c0") == [{:int, 1, 12632256}]
    assert tokenize("-0xc0c0c0") == [{:int, 1, -12632256}]
  end

  test "double literals" do
    assert tokenize("0.0") == [{:double, 1, 0.0}]
    assert tokenize("3.14") == [{:double, 1, 3.14}]
    assert tokenize("6.02e23") == [{:double, 1, 6.02e23}]
  end

  test "string literals" do
    assert tokenize("''") == [{:string, 1, ''}]
    assert tokenize("'foo'") == [{:string, 1, 'foo'}]
    assert tokenize("\"foo\"") == [{:string, 1, 'foo'}]
    assert tokenize("'foo\nbar'") == [{:string, 1, 'foo\nbar'}]
    assert tokenize("'\\'") == [{:string, 1, '\\'}]
    assert tokenize("'\"\"'") == [{:string, 1, '""'}]
  end

  test "list literals" do
    assert tokenize("[]") == [{:symbol, 1, '['}, {:symbol, 1, ']'}]
    assert tokenize("[1]") == [{:symbol, 1, '['}, {:int, 1, 1}, {:symbol, 1, ']'}]
    assert tokenize("[1,2]") == [
     {:symbol, 1, '['},
     {:int, 1, 1},
     {:symbol, 1, ','},
     {:int, 1, 2},
     {:symbol, 1, ']'}]
  end

  test "map literals" do
    assert tokenize("{}") == [{:symbol, 1, '{'}, {:symbol, 1, '}'}]
    assert tokenize("{'hello':'world'}") == [
      {:symbol, 1, '{'},
      {:string, 1, 'hello'}, {:symbol, 1, ':'}, {:string, 1, 'world'},
      {:symbol, 1, '}'}]
    assert tokenize("{'a':1,'b':2}") == [
      {:symbol, 1, '{'},
      {:string, 1, 'a'}, {:symbol, 1, ':'}, {:int, 1, 1},
      {:symbol, 1, ','},
      {:string, 1, 'b'}, {:symbol, 1, ':'}, {:int, 1, 2},
      {:symbol, 1, '}'}]
  end

  test "set literals" do
    assert tokenize("{}") == [{:symbol, 1, '{'}, {:symbol, 1, '}'}]
    assert tokenize("{1}") == [{:symbol, 1, '{'}, {:int, 1, 1}, {:symbol, 1, '}'}]
    assert tokenize("{1,2}") == [
      {:symbol, 1, '{'},
      {:int, 1, 1},
      {:symbol, 1, ','},
      {:int, 1, 2},
      {:symbol, 1, '}'}]
  end

  test "identifiers" do
    assert tokenize("abc_123.xzy") == [{:ident, 1, 'abc_123.xzy'}]
    assert tokenize("abc-") == [{:st_ident, 1, 'abc-'}]
  end

  test "typedef" do
    assert tokenize("typedef i32 Integer") == [{:typedef, 1}, {:i32, 1}, {:ident, 1, 'Integer'}]
  end

  test "enum definition" do
    assert tokenize("""
      enum Operation {
        ADD = 1,
        SUBTRACT = 2,
        MULTIPLY = 3,
        DIVIDE = 4
      }
    """) == [
      {:enum, 1}, {:ident, 1, 'Operation'},
      {:symbol, 1, '{'},
      {:ident, 2, 'ADD'}, {:symbol, 2, '='}, {:int, 2, 1},
      {:symbol, 2, ','},
      {:ident, 3, 'SUBTRACT'}, {:symbol, 3, '='}, {:int, 3, 2},
      {:symbol, 3, ','},
      {:ident, 4, 'MULTIPLY'}, {:symbol, 4, '='}, {:int, 4, 3},
      {:symbol, 4, ','},
      {:ident, 5, 'DIVIDE'}, {:symbol, 5, '='}, {:int, 5, 4},
      {:symbol, 6, '}'}]
  end

  test "struct definition" do
    assert tokenize("""
      struct Work {
        1: i32 num1 = 0,
        2: i32 num2,
        3: Operation op,
        4: optional string comment,
      }
    """) == [
      {:struct, 1}, {:ident, 1, 'Work'},
      {:symbol, 1, '{'},
      {:int, 2, 1}, {:symbol, 2, ':'}, {:i32, 2}, {:ident, 2, 'num1'}, {:symbol, 2, '='}, {:int, 2, 0},
      {:symbol, 2, ','},
      {:int, 3, 2}, {:symbol, 3, ':'}, {:i32, 3}, {:ident, 3, 'num2'},
      {:symbol, 3, ','},
      {:int, 4, 3}, {:symbol, 4, ':'}, {:ident, 4, 'Operation'}, {:ident, 4, 'op'},
      {:symbol, 4, ','},
      {:int, 5, 4}, {:symbol, 5, ':'}, {:optional, 5}, {:string, 5}, {:ident, 5, 'comment'},
      {:symbol, 5, ','},
      {:symbol, 6, '}'}]
  end

  test "exception definition" do
    assert tokenize("""
      exception InvalidOperation {
        1: i32 whatOp,
        2: string why
      }
    """) == [
      {:exception, 1}, {:ident, 1, 'InvalidOperation'},
      {:symbol, 1, '{'},
      {:int, 2, 1}, {:symbol, 2, ':'}, {:i32, 2}, {:ident, 2, 'whatOp'},
      {:symbol, 2, ','},
      {:int, 3, 2}, {:symbol, 3, ':'}, {:string, 3}, {:ident, 3, 'why'},
      {:symbol, 4, '}'}]
  end

  test "service definition" do
    assert tokenize("""
      service Calculator extends shared.SharedService {
        void ping(),
        i32 add(1:i32 num1, 2:i32 num2),
        oneway void zip()
      }
    """) == [
      {:service, 1}, {:ident, 1, 'Calculator'}, {:extends, 1}, {:ident, 1, 'shared.SharedService'},
      {:symbol, 1, '{'},
      {:void, 2}, {:ident, 2, 'ping'}, {:symbol, 2, '('}, {:symbol, 2, ')'},
      {:symbol, 2, ','},
      {:i32, 3}, {:ident, 3, 'add'},
        {:symbol, 3, '('},
          {:int, 3, 1}, {:symbol, 3, ':'}, {:i32, 3}, {:ident, 3, 'num1'},
          {:symbol, 3, ','},
          {:int, 3, 2}, {:symbol, 3, ':'}, {:i32, 3}, {:ident, 3, 'num2'},
        {:symbol, 3, ')'},
      {:symbol, 3, ','},
      {:oneway, 4}, {:void, 4}, {:ident, 4, 'zip'}, {:symbol, 4, '('}, {:symbol, 4, ')'},
      {:symbol, 5, '}'}]
  end
end

defmodule LexerTest do
  use ExUnit.Case

  defp tokenize(s) do
    {:ok, value, _} = :thrift_lexer.string(String.to_char_list(s))
    value
  end

  test "whitespace" do
    assert tokenize(" ") == []
  end

  test "comments" do
    assert tokenize("# foo") == []
    assert tokenize("// foo") == []
    assert tokenize("/* foo */") == []
    assert tokenize("
    /*
      foo
    */") == []
  end

  test "symbols" do
    assert tokenize(";") == [{:symbol, 1, ';'}]
    assert tokenize("=") == [{:symbol, 1, '='}]
  end

  test "keywords" do
    keywords = ~w(
      namespace include
      void bool byte i8 i16 i64 double string binary list map set
      typedef enum struct union exception
      const oneway extends throws service required optional
    )
    for keyword <- keywords do
      value = keyword |> tokenize |> Keyword.keys |> List.first
      assert value == String.to_atom(keyword)
    end
  end

  test "boolean literals" do
    assert tokenize("true") == [true: 1]
    assert tokenize("false") == [false: 1]
  end

  test "integer literals" do
    assert tokenize("0") == [{:int, 1, 0}]
    assert tokenize("10") == [{:int, 1, 10}]
    assert tokenize("+10") == [{:int, 1, 10}]
    assert tokenize("-10") == [{:int, 1, -10}]
  end

  test "hex literals" do
    assert tokenize("0x0") == [{:int, 1, 0}]
    assert tokenize("0xff") == [{:int, 1, 255}]
    assert tokenize("+0xc0c0c0") == [{:int, 1, 12632256}]
    assert tokenize("-0xc0c0c0") == [{:int, 1, -12632256}]
  end

  test "double literals" do
    assert tokenize("0.0") == [{:double, 1, 0.0}]
    assert tokenize("3.14") == [{:double, 1, 3.14}]
    assert tokenize("6.02e23") == [{:double, 1, 6.02e23}]
  end

  test "string literals" do
    assert tokenize("''") == [{:string, 1, ''}]
    assert tokenize("'foo'") == [{:string, 1, 'foo'}]
    assert tokenize("\"foo\"") == [{:string, 1, 'foo'}]
    assert tokenize("'foo\nbar'") == [{:string, 1, 'foo\nbar'}]
    assert tokenize("'\\'") == [{:string, 1, '\\'}]
    assert tokenize("'\"\"'") == [{:string, 1, '""'}]
  end

  test "identifiers" do
    assert tokenize("abc_123.xzy") == [{:ident, 1, 'abc_123.xzy'}]
    assert tokenize("abc-") == [{:st_ident, 1, 'abc-'}]
  end
end


update protocols_1

update protocols_1