Remove appologies.

Remove appologies.

defmodule Farmbot.DebugLog do
  @moduledoc """
  Provides a `debug_log/1` function.
  """

  def color(:NC),           do: "\e[0m"
  def color(:WHITE),        do: "\e[1;37m"
  def color(:BLACK),        do: "\e[0;30m"
  def color(:BLUE),         do: "\e[0;34m"
  def color(:LIGHT_BLUE),   do: "\e[1;34m"
  def color(:GREEN),        do: "\e[0;32m"
  def color(:LIGHT_GREEN),  do: "\e[1;32m"
  def color(:CYAN),         do: "\e[0;36m"
  def color(:LIGHT_CYAN),   do: "\e[1;36m"
  def color(:RED),          do: "\e[0;31m"
  def color(:LIGHT_RED),    do: "\e[1;31m"
  def color(:PURPLE),       do: "\e[0;35m"
  def color(:LIGHT_PURPLE), do: "\e[1;35m"
  def color(:BROWN),        do: "\e[0;33m"
  def color(:YELLOW),       do: "\e[1;33m"
  def color(:GRAY),         do: "\e[0;30m"
  def color(:LIGHT_GRAY),   do: "\e[0;37m"

  @doc """
    enables the `debug_log/1` function.
  """
  defmacro __using__(opts) do
    color = Keyword.get(opts, :color)
    name  = Keyword.get(opts, :name)
    quote do

      if unquote(name) do
        defp get_module, do: unquote(name)
      else
        defp get_module, do: __MODULE__ |> Module.split() |> List.last
      end

      if unquote(color) do
        defp debug_log(str) do
          GenEvent.notify(Farmbot.DebugLog,
            {get_module(), {unquote(color), str}})
        end
      else
        defp debug_log(str) do
          GenEvent.notify Farmbot.DebugLog, {Farmbot.DebugLog, {:BLUE, str}}
        end
      end # if color

    end # quote
  end # defmacro

  defmodule Handler do
    @moduledoc """
      Handler for DebugLogger
    """
    use GenEvent

    @doc false
    defdelegate color(color), to: Farmbot.DebugLog

    def init(state), do: {:ok, state}

    def handle_event(_, :all), do: {:ok, :all}

    def handle_event({module, {color, str}}, state) when is_binary(str) do
      filter_me? = Map.get(state, module)
      unless filter_me? do
        IO.puts "#{color(color)} [#{module}]#{color(:NC)} #{str}"
      end
      {:ok, state}
    end

    def handle_call({:filter, :all}, _state) do
      {:ok, :all}
    end

    def handle_call({:filter, module}, state) do
      {:ok, :ok, Map.put(state, module, :filterme)}
    end

    def handle_call({:unfilter, module}, state) do
      if state == :all do
        {:ok, :error, state}
      else
        {:ok, :ok, Map.delete(state, module)}
      end
    end
  end

  @doc """
    Start the Debug Logger
  """
  def start_link do
    {:ok, pid} = GenEvent.start_link(name: __MODULE__)
    :ok = GenEvent.add_handler(pid, Handler, %{})
    {:ok, pid}
  end

  @doc """
    Filter a module from the handler.
  """
  def filter(module) do
    GenEvent.call(__MODULE__, Handler, {:filter, module})
  end

  @doc """
    Unfilter a module from the handler.
  """
  def unfilter(module) do
    GenEvent.call(__MODULE__, Handler, {:unfilter, module})
  end
end # defmodule

defmodule Farmbot.DebugLog do
  @moduledoc """
  Provides a `debug_log/1` function.
  """

  def color(:NC),           do: "\e[0m"
  def color(:WHITE),        do: "\e[1;37m"
  def color(:BLACK),        do: "\e[0;30m"
  def color(:BLUE),         do: "\e[0;34m"
  def color(:LIGHT_BLUE),   do: "\e[1;34m"
  def color(:GREEN),        do: "\e[0;32m"
  def color(:LIGHT_GREEN),  do: "\e[1;32m"
  def color(:CYAN),         do: "\e[0;36m"
  def color(:LIGHT_CYAN),   do: "\e[1;36m"
  def color(:RED),          do: "\e[0;31m"
  def color(:LIGHT_RED),    do: "\e[1;31m"
  def color(:PURPLE),       do: "\e[0;35m"
  def color(:LIGHT_PURPLE), do: "\e[1;35m"
  def color(:BROWN),        do: "\e[0;33m"
  def color(:YELLOW),       do: "\e[1;33m"
  def color(:GRAY),         do: "\e[0;30m"
  def color(:LIGHT_GRAY),   do: "\e[0;37m"

  @doc """
    enables the `debug_log/1` function.
  """
  defmacro __using__(opts) do
    color = Keyword.get(opts, :color)
    name  = Keyword.get(opts, :name)
    quote do

      if unquote(name) do
        defp get_module, do: unquote(name)
      else
        defp get_module, do: __MODULE__ |> Module.split() |> List.last
      end

      if unquote(color) do
        defp debug_log(str) do
          GenEvent.notify(Farmbot.DebugLog,
            {get_module(), {unquote(color), str}})
        end
      else
        defp debug_log(str) do
          IO.puts "Stubbed out. Sorry. - RC"
          # GenEvent.notify Farmbot.DebugLog, {Farmbot.DebugLog, {:BLUE, str}}
        end
      end # if color

    end # quote
  end # defmacro

  defmodule Handler do
    @moduledoc """
      Handler for DebugLogger
    """
    use GenEvent

    @doc false
    defdelegate color(color), to: Farmbot.DebugLog

    def init(state), do: {:ok, state}

    def handle_event(_, :all), do: {:ok, :all}

    def handle_event({module, {color, str}}, state) when is_binary(str) do
      filter_me? = Map.get(state, module)
      unless filter_me? do
        IO.puts "#{color(color)} [#{module}]#{color(:NC)} #{str}"
      end
      {:ok, state}
    end

    def handle_call({:filter, :all}, _state) do
      {:ok, :all}
    end

    def handle_call({:filter, module}, state) do
      {:ok, :ok, Map.put(state, module, :filterme)}
    end

    def handle_call({:unfilter, module}, state) do
      if state == :all do
        {:ok, :error, state}
      else
        {:ok, :ok, Map.delete(state, module)}
      end
    end
  end

  @doc """
    Start the Debug Logger
  """
  def start_link do
    {:ok, pid} = GenEvent.start_link(name: __MODULE__)
    :ok = GenEvent.add_handler(pid, Handler, %{})
    {:ok, pid}
  end

  @doc """
    Filter a module from the handler.
  """
  def filter(module) do
    GenEvent.call(__MODULE__, Handler, {:filter, module})
  end

  @doc """
    Unfilter a module from the handler.
  """
  def unfilter(module) do
    GenEvent.call(__MODULE__, Handler, {:unfilter, module})
  end
end # defmodule


Fix pruning IEx stacktraces too aggressively

defmodule IEx.Evaluator do
  @moduledoc false

  @doc """
  Eval loop for an IEx session. Its responsibilities include:

  * loading of .iex files
  * evaluating code
  * trapping exceptions in the code being evaluated
  * keeping expression history

  """
  def start(server, leader) do
    IEx.History.init
    old_leader = Process.group_leader
    old_flag   = Process.flag(:trap_exit, true)
    Process.group_leader(self, leader)

    try do
      loop(server)
    after
      IEx.History.reset
      Process.group_leader(self, old_leader)
      Process.flag(:trap_exit, old_flag)
    end
  end

  defp loop(server) do
    receive do
      {:eval, ^server, code, config} ->
        send server, {:evaled, self, eval(code, config)}
        loop(server)
      {:done, ^server} ->
        IEx.History.reset
        :ok

      {:EXIT, _other, :normal} ->
        loop(server)
      {:EXIT, other, reason} ->
        print_exit(other, reason)
        loop(server)
    end
  end

  @doc """
  Locates and loads an .iex.exs file from one of predefined locations.
  Returns the new config.
  """
  def load_dot_iex(config, path \\ nil) do
    candidates = if path do
      [path]
    else
      Enum.map [".iex.exs", "~/.iex.exs"], &Path.expand/1
    end

    path = Enum.find candidates, &File.regular?/1

    if nil?(path) do
      config
    else
      eval_dot_iex(config, path)
    end
  end

  defp eval_dot_iex(config, path) do
    try do
      code = File.read!(path)
      env  = :elixir.env_for_eval(config.env, file: path)

      # Evaluate the contents in the same environment server_loop will run in
      {_result, binding, env, _scope} =
        :elixir.eval(List.from_char_data!(code), config.binding, env)

      %{config | binding: binding, env: :elixir.env_for_eval(env, file: "iex")}
    catch
      kind, error ->
        print_error(kind, error, System.stacktrace)
        System.halt(1)
    end
  end

  # Instead of doing just `:elixir.eval`, we first parse the expression to see
  # if it's well formed. If parsing succeeds, we evaluate the AST as usual.
  #
  # If parsing fails, this might be a TokenMissingError which we treat in
  # a special way (to allow for continuation of an expression on the next
  # line in IEx). In case of any other error, we let :elixir_translator
  # to re-raise it.
  #
  # Returns updated config.
  #
  # The first two clauses provide support for the break-trigger allowing to
  # break out from a pending incomplete expression. See
  # https://github.com/elixir-lang/elixir/issues/1089 for discussion.
  @break_trigger '#iex:break\n'

  defp eval(code, config) do
    try do
      do_eval(List.from_char_data!(code), config)
    catch
      kind, error ->
        print_error(kind, error, System.stacktrace)
        %{config | cache: ''}
    end
  end

  defp do_eval(@break_trigger, config=%IEx.Config{cache: ''}) do
    config
  end

  defp do_eval(@break_trigger, config) do
    :elixir_errors.parse_error(config.counter, "iex", "incomplete expression", "")
  end

  defp do_eval(latest_input, config) do
    code = config.cache ++ latest_input
    line = config.counter

    case Code.string_to_quoted(code, [line: line, file: "iex"]) do
      {:ok, forms} ->
        {result, new_binding, env, scope} =
          :elixir.eval_forms(forms, config.binding, config.env, config.scope)
        unless result == IEx.dont_display_result, do: io_put result
        update_history(line, code, result)
        %{config | env: env,
                   cache: '',
                   scope: scope,
                   binding: new_binding,
                   counter: config.counter + 1}
      {:error, {line, error, token}} ->
        if token == "" do
          # Update config.cache so that IEx continues to add new input to
          # the unfinished expression in `code`
          %{config | cache: code}
        else
          # Encountered malformed expression
          :elixir_errors.parse_error(line, "iex", error, token)
        end
    end
  end

  defp update_history(counter, cache, result) do
    IEx.History.append({counter, cache, result}, counter, IEx.Options.get(:history_size))
  end

  defp io_put(result) do
    IO.puts :stdio, IEx.color(:eval_result, inspect(result, inspect_opts))
  end

  defp io_error(result) do
    IO.puts :stdio, IEx.color(:eval_error, result)
  end

  defp inspect_opts do
    [width: IEx.width] ++ IEx.Options.get(:inspect)
  end

  ## Error handling

  defp print_error(:error, exception, stacktrace) do
    {exception, stacktrace} = normalize_exception(exception, stacktrace)
    print_stacktrace stacktrace, fn ->
      "** (#{inspect exception.__record__(:name)}) #{exception.message}"
    end
  end

  defp print_error(kind, reason, stacktrace) do
    print_stacktrace stacktrace, fn ->
      "** (#{kind}) #{inspect(reason)}"
    end
  end

  defp print_exit(pid, reason) do
    io_error "** (EXIT from #{inspect pid}) #{inspect(reason)}"
  end

  defp normalize_exception(:undef, [{IEx.Helpers, fun, arity, _}|t]) do
    {RuntimeError[message: "undefined function: #{format_function(fun, arity)}"], t}
  end

  defp normalize_exception(exception, stacktrace) do
    {Exception.normalize(:error, exception), stacktrace}
  end

  defp format_function(fun, arity) do
    cond do
      is_list(arity) ->
        "#{fun}/#{length(arity)}"
      true ->
        "#{fun}/#{arity}"
    end
  end

  defp print_stacktrace(trace, callback) do
    try do
      io_error callback.()
      case prune_stacktrace(trace) do
        []    -> :ok
        other -> IO.puts(format_stacktrace(other))
      end
    catch
      type, detail ->
        io_error "** (IEx.Error) #{type} when printing exception message and stacktrace: #{inspect detail, records: false}"
    end
  end

  defp prune_stacktrace(stacktrace) do
    stacktrace
      |> Enum.reverse()
      |> Enum.drop_while(&(elem(&1, 0) == __MODULE__))
      |> Enum.drop_while(&(elem(&1, 0) == :elixir))
      |> Enum.drop_while(&(elem(&1, 0) in [:erl_eval, :eval_bits]))
      |> Enum.reverse()
  end

  @doc false
  def format_stacktrace(trace) do
    entries =
      for entry <- trace do
        split_entry(Exception.format_stacktrace_entry(entry))
      end

    width = Enum.reduce entries, 0, fn {app, _}, acc ->
      max(String.length(app), acc)
    end

    "    " <> Enum.map_join(entries, "\n    ", &format_entry(&1, width))
  end

  defp split_entry(entry) do
    case entry do
      "(" <> _ ->
        case :binary.split(entry, ") ") do
          [left, right] -> {left <> ")", right}
          _ -> {"", entry}
        end
      _ ->
        {"", entry}
    end
  end

  defp format_entry({app, info}, width) do
    app = String.rjust(app, width)
    "#{IEx.color(:stack_app, app)} #{IEx.color(:stack_info, info)}"
  end
end

defmodule IEx.Evaluator do
  @moduledoc false

  @doc """
  Eval loop for an IEx session. Its responsibilities include:

  * loading of .iex files
  * evaluating code
  * trapping exceptions in the code being evaluated
  * keeping expression history

  """
  def start(server, leader) do
    IEx.History.init
    old_leader = Process.group_leader
    old_flag   = Process.flag(:trap_exit, true)
    Process.group_leader(self, leader)

    try do
      loop(server)
    after
      IEx.History.reset
      Process.group_leader(self, old_leader)
      Process.flag(:trap_exit, old_flag)
    end
  end

  defp loop(server) do
    receive do
      {:eval, ^server, code, config} ->
        send server, {:evaled, self, eval(code, config)}
        loop(server)
      {:done, ^server} ->
        IEx.History.reset
        :ok

      {:EXIT, _other, :normal} ->
        loop(server)
      {:EXIT, other, reason} ->
        print_exit(other, reason)
        loop(server)
    end
  end

  @doc """
  Locates and loads an .iex.exs file from one of predefined locations.
  Returns the new config.
  """
  def load_dot_iex(config, path \\ nil) do
    candidates = if path do
      [path]
    else
      Enum.map [".iex.exs", "~/.iex.exs"], &Path.expand/1
    end

    path = Enum.find candidates, &File.regular?/1

    if nil?(path) do
      config
    else
      eval_dot_iex(config, path)
    end
  end

  defp eval_dot_iex(config, path) do
    try do
      code = File.read!(path)
      env  = :elixir.env_for_eval(config.env, file: path)

      # Evaluate the contents in the same environment server_loop will run in
      {_result, binding, env, _scope} =
        :elixir.eval(List.from_char_data!(code), config.binding, env)

      %{config | binding: binding, env: :elixir.env_for_eval(env, file: "iex")}
    catch
      kind, error ->
        print_error(kind, error, System.stacktrace)
        System.halt(1)
    end
  end

  # Instead of doing just `:elixir.eval`, we first parse the expression to see
  # if it's well formed. If parsing succeeds, we evaluate the AST as usual.
  #
  # If parsing fails, this might be a TokenMissingError which we treat in
  # a special way (to allow for continuation of an expression on the next
  # line in IEx). In case of any other error, we let :elixir_translator
  # to re-raise it.
  #
  # Returns updated config.
  #
  # The first two clauses provide support for the break-trigger allowing to
  # break out from a pending incomplete expression. See
  # https://github.com/elixir-lang/elixir/issues/1089 for discussion.
  @break_trigger '#iex:break\n'

  defp eval(code, config) do
    try do
      do_eval(List.from_char_data!(code), config)
    catch
      kind, error ->
        print_error(kind, error, System.stacktrace)
        %{config | cache: ''}
    end
  end

  defp do_eval(@break_trigger, config=%IEx.Config{cache: ''}) do
    config
  end

  defp do_eval(@break_trigger, config) do
    :elixir_errors.parse_error(config.counter, "iex", "incomplete expression", "")
  end

  defp do_eval(latest_input, config) do
    code = config.cache ++ latest_input
    line = config.counter

    case Code.string_to_quoted(code, [line: line, file: "iex"]) do
      {:ok, forms} ->
        {result, new_binding, env, scope} =
          :elixir.eval_forms(forms, config.binding, config.env, config.scope)
        unless result == IEx.dont_display_result, do: io_put result
        update_history(line, code, result)
        %{config | env: env,
                   cache: '',
                   scope: scope,
                   binding: new_binding,
                   counter: config.counter + 1}
      {:error, {line, error, token}} ->
        if token == "" do
          # Update config.cache so that IEx continues to add new input to
          # the unfinished expression in `code`
          %{config | cache: code}
        else
          # Encountered malformed expression
          :elixir_errors.parse_error(line, "iex", error, token)
        end
    end
  end

  defp update_history(counter, cache, result) do
    IEx.History.append({counter, cache, result}, counter, IEx.Options.get(:history_size))
  end

  defp io_put(result) do
    IO.puts :stdio, IEx.color(:eval_result, inspect(result, inspect_opts))
  end

  defp io_error(result) do
    IO.puts :stdio, IEx.color(:eval_error, result)
  end

  defp inspect_opts do
    [width: IEx.width] ++ IEx.Options.get(:inspect)
  end

  ## Error handling

  defp print_error(:error, exception, stacktrace) do
    {exception, stacktrace} = normalize_exception(exception, stacktrace)
    print_stacktrace stacktrace, fn ->
      "** (#{inspect exception.__record__(:name)}) #{exception.message}"
    end
  end

  defp print_error(kind, reason, stacktrace) do
    print_stacktrace stacktrace, fn ->
      "** (#{kind}) #{inspect(reason)}"
    end
  end

  defp print_exit(pid, reason) do
    io_error "** (EXIT from #{inspect pid}) #{inspect(reason)}"
  end

  defp normalize_exception(:undef, [{IEx.Helpers, fun, arity, _}|t]) do
    {RuntimeError[message: "undefined function: #{format_function(fun, arity)}"], t}
  end

  defp normalize_exception(exception, stacktrace) do
    {Exception.normalize(:error, exception), stacktrace}
  end

  defp format_function(fun, arity) do
    cond do
      is_list(arity) ->
        "#{fun}/#{length(arity)}"
      true ->
        "#{fun}/#{arity}"
    end
  end

  defp print_stacktrace(trace, callback) do
    try do
      io_error callback.()
      case prune_stacktrace(trace) do
        []    -> :ok
        other -> IO.puts(format_stacktrace(other))
      end
    catch
      type, detail ->
        io_error "** (IEx.Error) #{type} when printing exception message and stacktrace: #{inspect detail, records: false}"
    end
  end

  defp prune_stacktrace([{:erl_eval, _, _, _}|_]),  do: []
  defp prune_stacktrace([{__MODULE__, _, _, _}|_]), do: []
  defp prune_stacktrace([h|t]), do: [h|prune_stacktrace(t)]
  defp prune_stacktrace([]), do: []

  @doc false
  def format_stacktrace(trace) do
    entries =
      for entry <- trace do
        split_entry(Exception.format_stacktrace_entry(entry))
      end

    width = Enum.reduce entries, 0, fn {app, _}, acc ->
      max(String.length(app), acc)
    end

    "    " <> Enum.map_join(entries, "\n    ", &format_entry(&1, width))
  end

  defp split_entry(entry) do
    case entry do
      "(" <> _ ->
        case :binary.split(entry, ") ") do
          [left, right] -> {left <> ")", right}
          _ -> {"", entry}
        end
      _ ->
        {"", entry}
    end
  end

  defp format_entry({app, info}, width) do
    app = String.rjust(app, width)
    "#{IEx.color(:stack_app, app)} #{IEx.color(:stack_info, info)}"
  end
end


fix proxy

fix proxy

defmodule HttpProxy.Handle do
  @moduledoc """
  Handle every http request to outside of the server.
  """

  use Plug.Builder
  import Plug.Conn
  require Logger

  alias HttpProxy.Play.Data, as: Data
  alias HttpProxy.Record.Response, as: Record
  alias HttpProxy.Play.Response, as: Play

  @default_schemes [:http, :https]

  plug Plug.Logger
  plug :dispatch

  # Same as Plug.Conn https://github.com/elixir-lang/plug/blob/576c04c2cba778f1ac9ca28aa71c50efa1046b50/lib/plug/conn.ex#L125
  @type t :: %Plug.Conn{}

  @type param :: binary | [param]

  @doc """
  Start Cowboy http process with localhost and arbitrary port.
  Clients access to local Cowboy process with HTTP potocol.
  """
  @spec start_link([binary]) :: pid
  def start_link([proxy, module_name]) do
    Logger.info "Running Proxy with Cowboy on http://localhost:#{proxy.port} named #{module_name}"
    Plug.Adapters.Cowboy.http(__MODULE__, [], [port: proxy.port, ref: String.to_atom(module_name)])
  end

  @doc """
  Dispatch connection and Play/Record http/https requests.
  """
  @spec dispatch(t, param) :: t
  def dispatch(conn, _opts) do
    {:ok, client} = String.downcase(conn.method)
                    |> String.to_atom
                    |> :hackney.request(uri(conn), conn.req_headers, :stream, [])
    conn
    |> write_proxy(client)
    |> read_proxy(client)
  end

  @spec uri(t) :: String.t
  def uri(conn) do
    base = gen_path conn, target_proxy(conn)
    case conn.query_string do
      ""           -> base
      query_string -> ~s(#{base}?#{query_string})
    end
  end

  @doc ~S"""
  Get proxy defined in config/config.exs

  ## Example

      iex> HttpProxy.Handle.proxies
      [%{port: 8080, to: "http://google.com"}, %{port: 8081, to: "http://neko.com"}]
  """
  @spec proxies() :: []
  def proxies do
    Application.get_env :http_proxy, :proxies, nil
  end

  @doc ~S"""
  Get schemes which is defined as deault.

  ## Example

      iex> HttpProxy.Handle.schemes
      [:http, :https]
  """
  @spec schemes() :: []
  def schemes do
    @default_schemes
  end

  defp write_proxy(conn, client) do
    case read_body(conn, []) do
      {:ok, body, conn} ->
        :hackney.send_body client, body
        conn
      {:more, body, conn} ->
        :hackney.send_body client, body
        write_proxy conn, client
    end
  end

  defp read_proxy(conn, client) do
    {:ok, status, headers, client} = :hackney.start_response client
    {:ok, body} = :hackney.body client

    headers = List.keydelete headers, "Transfer-Encoding", 0

    cond do
      Record.record? && Play.play? ->
        raise ArgumentError, "Can't set record and play at the same time."
      Play.play? ->
        %{conn | resp_headers: headers}
        |> play_conn
      Record.record? ->
        %{conn | resp_headers: headers}
        |> send_resp(status, body)
        |> Record.record
      true ->
        %{conn | resp_headers: headers}
        |> send_resp(status, body)
    end
  end

  defp play_conn(conn) do
    key = String.downcase(conn.method) <> "_" <> Integer.to_string(conn.port) <> conn.request_path

    # TODO: do matching with string ot regex
    # ここでパターンマッチか、完全一致でpathを振り分ける
    case Keyword.get(%Data{}.responses, String.to_atom(key)) do
      {_, resp} ->
        res_json = Map.fetch!(resp, "response")
        response = [
          "body": Map.fetch!(res_json, "body"),
          "cookies": Map.to_list(Map.fetch!(res_json, "cookies")),
          "headers": Map.to_list(Map.fetch!(res_json, "headers"))
                     |> List.insert_at(0, {"Date", conn.resp_headers["Date"]}),
           "status_code": Map.fetch!(res_json, "status_code")
        ]

        conn = %{conn | resp_body: response[:body] }
        conn = %{conn | resp_cookies: response[:cookies] }
        conn = %{conn | status: response[:status_code] }
        conn = %{conn | resp_headers: response[:headers] }
      nil ->
        conn = %{conn | resp_body: "<html>not found nil play_conn case</html>" }
        conn = %{conn | status: 404 }
        conn = %{conn | resp_cookies: [] }
        conn = %{conn | resp_headers: [] }
    end

    send_resp conn, conn.status, conn.resp_body
  end

  defp gen_path(conn, proxy) when proxy == nil do
    case conn.scheme do
      s when s in @default_schemes ->
        uri = %URI{}
        %URI{uri | scheme: Atom.to_string(conn.scheme), host: conn.host, path: conn.request_path}
        |> URI.to_string
      _ ->
        raise ArgumentError, "no scheme"
    end
  end
  defp gen_path(conn, proxy) do
    uri = URI.parse proxy.to
    %URI{uri | path: conn.request_path}
    |> URI.to_string
  end

  defp target_proxy(conn) do
    Enum.reduce(proxies, [], fn proxy, acc ->
      cond do
        proxy.port == conn.port ->
          [proxy | acc]
        true ->
          acc
      end
    end)
    |> Enum.at(0)
  end
end

defmodule HttpProxy.Handle do
  @moduledoc """
  Handle every http request to outside of the server.
  """

  use Plug.Builder
  import Plug.Conn
  require Logger

  alias HttpProxy.Play.Data, as: Data
  alias HttpProxy.Record.Response, as: Record
  alias HttpProxy.Play.Response, as: Play

  @default_schemes [:http, :https]

  plug Plug.Logger
  plug :dispatch

  # Same as Plug.Conn https://github.com/elixir-lang/plug/blob/576c04c2cba778f1ac9ca28aa71c50efa1046b50/lib/plug/conn.ex#L125
  @type t :: %Plug.Conn{}

  @type param :: binary | [param]

  @doc """
  Start Cowboy http process with localhost and arbitrary port.
  Clients access to local Cowboy process with HTTP potocol.
  """
  @spec start_link([binary]) :: pid
  def start_link([proxy, module_name]) do
    Logger.info "Running Proxy with Cowboy on http://localhost:#{proxy.port} named #{module_name}"
    Plug.Adapters.Cowboy.http(__MODULE__, [], [port: proxy.port, ref: String.to_atom(module_name)])
  end

  @doc """
  Dispatch connection and Play/Record http/https requests.
  """
  @spec dispatch(t, param) :: t
  def dispatch(conn, _opts) do
    {:ok, client} = String.downcase(conn.method)
                    |> String.to_atom
                    |> :hackney.request(uri(conn), conn.req_headers, :stream, [])
    conn
    |> write_proxy(client)
    |> read_proxy(client)
  end

  @spec uri(t) :: String.t
  def uri(conn) do
    base = gen_path conn, target_proxy(conn)
    case conn.query_string do
      ""           -> base
      query_string -> ~s(#{base}?#{query_string})
    end
  end

  @doc ~S"""
  Get proxy defined in config/config.exs

  ## Example

      iex> HttpProxy.Handle.proxies
      [%{port: 8080, to: "http://google.com"}, %{port: 8081, to: "http://neko.com"}]
  """
  @spec proxies() :: []
  def proxies do
    Application.get_env :http_proxy, :proxies, nil
  end

  @doc ~S"""
  Get schemes which is defined as deault.

  ## Example

      iex> HttpProxy.Handle.schemes
      [:http, :https]
  """
  @spec schemes() :: []
  def schemes do
    @default_schemes
  end

  defp write_proxy(conn, client) do
    case read_body(conn, []) do
      {:ok, body, conn} ->
        :hackney.send_body client, body
        conn
      {:more, body, conn} ->
        :hackney.send_body client, body
        write_proxy conn, client
    end
  end

  defp read_proxy(conn, client) do
    {:ok, status, headers, client} = :hackney.start_response client
    {:ok, body} = :hackney.body client

    headers = List.keydelete headers, "Transfer-Encoding", 0

    cond do
      Record.record? && Play.play? ->
        raise ArgumentError, "Can't set record and play at the same time."
      Play.play? ->
        %{conn | resp_headers: headers}
        |> play_conn
      Record.record? ->
        %{conn | resp_headers: headers}
        |> send_resp(status, body)
        |> Record.record
      true ->
        conn
    end
  end

  defp play_conn(conn) do
    key = String.downcase(conn.method) <> "_" <> Integer.to_string(conn.port) <> conn.request_path

    # TODO: do matching with string ot regex
    # ここでパターンマッチか、完全一致でpathを振り分ける
    case Keyword.get(%Data{}.responses, String.to_atom(key)) do
      {_, resp} ->
        res_json = Map.fetch!(resp, "response")
        response = [
          "body": Map.fetch!(res_json, "body"),
          "cookies": Map.to_list(Map.fetch!(res_json, "cookies")),
          "headers": Map.to_list(Map.fetch!(res_json, "headers"))
                     |> List.insert_at(0, {"Date", conn.resp_headers["Date"]}),
           "status_code": Map.fetch!(res_json, "status_code")
        ]

        conn = %{conn | resp_body: response[:body] }
        conn = %{conn | resp_cookies: response[:cookies] }
        conn = %{conn | status: response[:status_code] }
        conn = %{conn | resp_headers: response[:headers] }
      nil ->
        conn = %{conn | resp_body: "<html>not found nil play_conn case</html>" }
        conn = %{conn | status: 404 }
        conn = %{conn | resp_cookies: [] }
        conn = %{conn | resp_headers: [] }
    end

    send_resp conn, conn.status, conn.resp_body
  end

  defp gen_path(conn, proxy) when proxy == nil do
    case conn.scheme do
      s when s in @default_schemes ->
        uri = %URI{}
        %URI{uri | scheme: Atom.to_string(conn.scheme), host: conn.host, path: conn.request_path}
        |> URI.to_string
      _ ->
        raise ArgumentError, "no scheme"
    end
  end
  defp gen_path(conn, proxy) do
    uri = URI.parse proxy.to
    %URI{uri | path: conn.request_path}
    |> URI.to_string
  end

  defp target_proxy(conn) do
    Enum.reduce(proxies, [], fn proxy, acc ->
      cond do
        proxy.port == conn.port ->
          [proxy | acc]
        true ->
          acc
      end
    end)
    |> Enum.at(0)
  end
end


Enable gzip on statics. Fix #44

Enable gzip on statics. Fix #44

defmodule Krihelinator.Endpoint do
  use Phoenix.Endpoint, otp_app: :krihelinator

  socket "/socket", Krihelinator.UserSocket

  # Serve at "/" the static files from "priv/static" directory.
  #
  # You should set gzip to true if you are running phoenix.digest
  # when deploying your static files in production.
  plug Plug.Static,
    at: "/", from: :krihelinator, gzip: true,
    only: ~w(css fonts images js favicon.ico robots.txt)

  # Code reloading can be explicitly enabled under the
  # :code_reloader configuration of your endpoint.
  if code_reloading? do
    socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
    plug Phoenix.LiveReloader
    plug Phoenix.CodeReloader
  end

  plug Plug.RequestId
  plug Plug.Logger

  plug Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Poison

  plug Plug.MethodOverride
  plug Plug.Head

  # The session will be stored in the cookie and signed,
  # this means its contents can be read but not tampered with.
  # Set :encryption_salt if you would also like to encrypt it.
  plug Plug.Session,
    store: :cookie,
    key: "_krihelinator_key",
    signing_salt: "lZoUI4Gb"

  plug Krihelinator.Router
end

defmodule Krihelinator.Endpoint do
  use Phoenix.Endpoint, otp_app: :krihelinator

  socket "/socket", Krihelinator.UserSocket

  # Serve at "/" the static files from "priv/static" directory.
  #
  # You should set gzip to true if you are running phoenix.digest
  # when deploying your static files in production.
  plug Plug.Static,
    at: "/", from: :krihelinator, gzip: false,
    only: ~w(css fonts images js favicon.ico robots.txt)

  # Code reloading can be explicitly enabled under the
  # :code_reloader configuration of your endpoint.
  if code_reloading? do
    socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
    plug Phoenix.LiveReloader
    plug Phoenix.CodeReloader
  end

  plug Plug.RequestId
  plug Plug.Logger

  plug Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Poison

  plug Plug.MethodOverride
  plug Plug.Head

  # The session will be stored in the cookie and signed,
  # this means its contents can be read but not tampered with.
  # Set :encryption_salt if you would also like to encrypt it.
  plug Plug.Session,
    store: :cookie,
    key: "_krihelinator_key",
    signing_salt: "lZoUI4Gb"

  plug Krihelinator.Router
end


Add some comments to MarcoPolo.Connection

I commented what each field in the connection's state does. You can
never be too explicit.

defmodule MarcoPolo.Connection do
  @moduledoc false

  use Connection

  require Logger

  alias MarcoPolo.Connection.Auth
  alias MarcoPolo.Protocol
  alias MarcoPolo.Document
  alias MarcoPolo.Error

  @socket_opts [:binary, active: false, packet: :raw]

  @timeout 5000

  @initial_state %{
    # The TCP socket to the OrientDB server
    socket: nil,
    # The session id for the session held by this genserver
    session_id: nil,
    # The queue of commands sent to the server
    queue: :queue.new,
    # The schema of the OrientDB database (if we're connected to a db)
    schema: nil,
    # The tail of binary data from parsing
    tail: "",
    # A monothonically increasing transaction id (must be unique per session)
    transaction_id: 1,
    # The options used to start this genserver
    opts: nil,
  }

  ## Client code.

  @doc """
  Starts the current `Connection`. If the (successful) connection is to a
  database, fetch the schema.
  """
  @spec start_link(Keyword.t) :: GenServer.on_start
  def start_link(opts) do
    # The first `opts` is the value to pass to the `init/1` callback, the second
    # one is the list of options being passed to `Connection.start_link` (e.g.,
    # `:name` or `:timeout`).
    case Connection.start_link(__MODULE__, opts, opts) do
      {:error, _} = err ->
        err
      {:ok, pid} = res ->
        maybe_fetch_schema(pid, opts)
        res
    end
  end

  @doc """
  Shuts down the connection (asynchronously since it's a cast).
  """
  @spec stop(pid) :: :ok
  def stop(pid) do
    Connection.cast(pid, :stop)
  end

  @doc """
  Performs the operation identified by `op_name` with the connection on
  `pid`. `args` is the list of arguments to pass to the operation.
  """
  @spec operation(pid, atom, [Protocol.encodable_term], Keyword.t) ::
    {:ok, term} | {:error, term}
  def operation(pid, op_name, args, opts) do
    Connection.call(pid, {:operation, op_name, args}, opts[:timeout] || @timeout)
  end

  @doc """
  Does what `operation/3` does but expects no response from OrientDB and always
  returns `:ok`.
  """
  @spec no_response_operation(pid, atom, [Protocol.encodable_term]) :: :ok
  def no_response_operation(pid, op_name, args) do
    Connection.cast(pid, {:operation, op_name, args})
  end

  @doc """
  Fetch the schema and store it into the state.

  Always returns `:ok` without waiting for the schema to be fetched.
  """
  @spec fetch_schema(pid) :: :ok
  def fetch_schema(pid) do
    Connection.call(pid, :fetch_schema)
  end

  defp maybe_fetch_schema(pid, opts) do
    case Keyword.get(opts, :connection) do
      {:db, _, _} -> fetch_schema(pid)
      _           -> nil
    end
  end

  ## Callbacks.

  @doc false
  def init(opts) do
    s = Dict.merge(@initial_state, opts: opts)
    {:connect, :init, s}
  end

  @doc false
  def connect(_info, s) do
    {host, port, socket_opts, timeout} = tcp_connection_opts(s)

    case :gen_tcp.connect(host, port, socket_opts, timeout) do
      {:ok, socket} ->
        s = %{s | socket: socket}
        setup_socket_buffers(socket)

        case Auth.connect(s) do
          {:ok, s} ->
            :inet.setopts(socket, active: :once)
            {:ok, s}
          {:error, error, s} ->
            {:stop, error, s}
          {:tcp_error, reason, s} ->
            {:stop, reason, s}
        end
      {:error, reason} ->
        Logger.error "OrientDB TCP connect error (#{host}:#{port}): #{:inet.format_error(reason)}"
        {:stop, reason, s}
    end
  end

  @doc false
  def disconnect(:stop, %{socket: nil} = s) do
    {:stop, :normal, s}
  end

  def disconnect(:stop, %{socket: socket} = s) do
    :gen_tcp.close(socket)
    {:stop, :normal, %{s | socket: nil}}
  end

  def disconnect(error, s) do
    # We only care about {from, _} tuples, ignoring queued stuff like
    # :fetch_schema.
    for {from, _operation} <- :queue.to_list(s.queue) do
      Connection.reply(from, error)
    end

    # Backoff 0 to churn through all commands in mailbox before reconnecting,
    # https://github.com/ericmj/mongodb/blob/a2dba1dfc089960d87364c2c43892f3061a93924/lib/mongo/connection.ex#L210
    {:backoff, 0, %{s | socket: nil, queue: :queue.new, transaction_id: 1}}
  end

  @doc false
  # No socket means there's no TCP connection, we can return an error to the
  # client.
  def handle_call(_call, _from, %{socket: nil} = s) do
    {:reply, {:error, :closed}, s}
  end

  # We have to handle the :tx_commit operation differently as we have to keep
  # track of the transaction id, which is kept in the state of this genserver
  # (we also have to update this id).
  def handle_call({:operation, :tx_commit, [:transaction_id|args]}, from, s) do
    {id, s} = next_transaction_id(s)
    handle_call({:operation, :tx_commit, [id|args]}, from, s)
  end

  def handle_call({:operation, op_name, args}, from, %{session_id: sid} = s) do
    check_op_is_allowed!(s, op_name)

    req = Protocol.encode_op(op_name, [sid|args])
    s
    |> enqueue({from, op_name})
    |> send_noreply(req)
  end

  def handle_call(:fetch_schema, from, %{session_id: sid} = s) do
    check_op_is_allowed!(s, :record_load)

    args = [sid, {:short, 0}, {:long, 1}, "*:-1", true, false]
    req = Protocol.encode_op(:record_load, args)

    s
    |> enqueue({:fetch_schema, from})
    |> send_noreply(req)
  end

  @doc false
  def handle_cast({:operation, op_name, args}, %{session_id: sid} = s) do
    check_op_is_allowed!(s, op_name)

    req = Protocol.encode_op(op_name, [sid|args])
    send_noreply(s, req)
  end

  def handle_cast(:stop, s) do
    {:disconnect, :stop, s}
  end

  @doc false
  def handle_info({:tcp, socket, msg}, %{socket: socket} = s) do
    :inet.setopts(socket, active: :once)
    s = dequeue_and_parse_resp(s, :queue.out(s.queue), s.tail <> msg)
    {:noreply, s}
  end

  def handle_info({:tcp_closed, socket}, %{socket: socket} = s) do
    {:disconnect, {:error, :closed}, s}
  end

  # Helper functions.

  defp tcp_connection_opts(%{opts: opts} = _state) do
    socket_opts = @socket_opts ++ (opts[:socket_opts] || [])
    {to_char_list(opts[:host]), opts[:port], socket_opts, opts[:timeout] || @timeout}
  end

  defp parse_schema(%Document{fields: %{"globalProperties" => properties}}) do
    global_properties =
      for %Document{fields: %{"name" => name, "type" => type, "id" => id}} <- properties,
        into: HashDict.new() do
          {id, {name, type}}
      end

    %{global_properties: global_properties}
  end

  defp setup_socket_buffers(socket) do
    {:ok, [sndbuf: sndbuf, recbuf: recbuf, buffer: buffer]} =
      :inet.getopts(socket, [:sndbuf, :recbuf, :buffer])

    buffer = buffer |> max(sndbuf) |> max(recbuf)
    :ok = :inet.setopts(socket, [buffer: buffer])
  end

  defp send_noreply(%{socket: socket} = s, req) do
    case :gen_tcp.send(socket, req) do
      :ok ->
        {:noreply, s}
      {:error, _reason} = error ->
        {:disconnect, error, s}
    end
  end

  defp enqueue(s, what) do
    update_in s.queue, &:queue.in(what, &1)
  end

  defp dequeue_and_parse_resp(s, {{:value, {:fetch_schema, from}}, new_queue}, data) do
    sid = s.session_id

    case Protocol.parse_resp(:record_load, data, s.schema) do
      :incomplete ->
        %{s | tail: data}
      {^sid, {:error, _}, _rest} ->
        raise "couldn't fetch schema"
      {^sid, {:ok, {schema, _linked_records}}, rest} ->
        schema = parse_schema(schema)
        Connection.reply(from, schema)
        %{s | schema: schema, tail: rest, queue: new_queue}
    end
  end

  defp dequeue_and_parse_resp(s, {{:value, {from, op_name}}, new_queue}, data) do
    sid = s.session_id

    case Protocol.parse_resp(op_name, data, s.schema) do
      :incomplete ->
        %{s | tail: data}
      {^sid, resp, rest} ->
        Connection.reply(from, resp)
        %{s | tail: rest, queue: new_queue}
    end
  end

  defp check_op_is_allowed!(%{opts: opts}, operation) do
    do_check_op_is_allowed!(Keyword.fetch!(opts, :connection), operation)
  end

  @server_ops ~w(
    shutdown
    db_create
    db_exist
    db_drop
  )a

  @db_ops ~w(
    db_close
    db_size
    db_countrecords
    db_reload
    record_load
    record_load_if_version_not_latest
    record_create
    record_update
    record_delete
    command
    tx_commit
  )a

  defp do_check_op_is_allowed!({:db, _, _}, op) when not op in @db_ops do
    raise Error, "must be connected to the server (not a db) to perform operation #{op}"
  end

  defp do_check_op_is_allowed!(:server, op) when not op in @server_ops do
    raise Error, "must be connected to a database to perform operation #{op}"
  end

  defp do_check_op_is_allowed!(_, _) do
    nil
  end

  defp next_transaction_id(s) do
    get_and_update_in(s.transaction_id, &{&1, &1 + 1})
  end
end

defmodule MarcoPolo.Connection do
  @moduledoc false

  use Connection

  require Logger

  alias MarcoPolo.Connection.Auth
  alias MarcoPolo.Protocol
  alias MarcoPolo.Document
  alias MarcoPolo.Error

  @socket_opts [:binary, active: false, packet: :raw]

  @timeout 5000

  @initial_state %{
    socket: nil,
    session_id: nil,
    queue: :queue.new,
    schema: nil,
    tail: "",
    transaction_id: 1,
  }

  ## Client code.

  @doc """
  Starts the current `Connection`. If the (successful) connection is to a
  database, fetch the schema.
  """
  @spec start_link(Keyword.t) :: GenServer.on_start
  def start_link(opts) do
    # The first `opts` is the value to pass to the `init/1` callback, the second
    # one is the list of options being passed to `Connection.start_link` (e.g.,
    # `:name` or `:timeout`).
    case Connection.start_link(__MODULE__, opts, opts) do
      {:error, _} = err ->
        err
      {:ok, pid} = res ->
        maybe_fetch_schema(pid, opts)
        res
    end
  end

  @doc """
  Shuts down the connection (asynchronously since it's a cast).
  """
  @spec stop(pid) :: :ok
  def stop(pid) do
    Connection.cast(pid, :stop)
  end

  @doc """
  Performs the operation identified by `op_name` with the connection on
  `pid`. `args` is the list of arguments to pass to the operation.
  """
  @spec operation(pid, atom, [Protocol.encodable_term], Keyword.t) ::
    {:ok, term} | {:error, term}
  def operation(pid, op_name, args, opts) do
    Connection.call(pid, {:operation, op_name, args}, opts[:timeout] || @timeout)
  end

  @doc """
  Does what `operation/3` does but expects no response from OrientDB and always
  returns `:ok`.
  """
  @spec no_response_operation(pid, atom, [Protocol.encodable_term]) :: :ok
  def no_response_operation(pid, op_name, args) do
    Connection.cast(pid, {:operation, op_name, args})
  end

  @doc """
  Fetch the schema and store it into the state.

  Always returns `:ok` without waiting for the schema to be fetched.
  """
  @spec fetch_schema(pid) :: :ok
  def fetch_schema(pid) do
    Connection.call(pid, :fetch_schema)
  end

  defp maybe_fetch_schema(pid, opts) do
    case Keyword.get(opts, :connection) do
      {:db, _, _} -> fetch_schema(pid)
      _           -> nil
    end
  end

  ## Callbacks.

  @doc false
  def init(opts) do
    s = Dict.merge(@initial_state, opts: opts)
    {:connect, :init, s}
  end

  @doc false
  def connect(_info, s) do
    {host, port, socket_opts, timeout} = tcp_connection_opts(s)

    case :gen_tcp.connect(host, port, socket_opts, timeout) do
      {:ok, socket} ->
        s = %{s | socket: socket}
        setup_socket_buffers(socket)

        case Auth.connect(s) do
          {:ok, s} ->
            :inet.setopts(socket, active: :once)
            {:ok, s}
          {:error, error, s} ->
            {:stop, error, s}
          {:tcp_error, reason, s} ->
            {:stop, reason, s}
        end
      {:error, reason} ->
        Logger.error "OrientDB TCP connect error (#{host}:#{port}): #{:inet.format_error(reason)}"
        {:stop, reason, s}
    end
  end

  @doc false
  def disconnect(:stop, %{socket: nil} = s) do
    {:stop, :normal, s}
  end

  def disconnect(:stop, %{socket: socket} = s) do
    :gen_tcp.close(socket)
    {:stop, :normal, %{s | socket: nil}}
  end

  def disconnect(error, s) do
    # We only care about {from, _} tuples, ignoring queued stuff like
    # :fetch_schema.
    for {from, _operation} <- :queue.to_list(s.queue) do
      Connection.reply(from, error)
    end

    # Backoff 0 to churn through all commands in mailbox before reconnecting,
    # https://github.com/ericmj/mongodb/blob/a2dba1dfc089960d87364c2c43892f3061a93924/lib/mongo/connection.ex#L210
    {:backoff, 0, %{s | socket: nil, queue: :queue.new, transaction_id: 1}}
  end

  @doc false
  # No socket means there's no TCP connection, we can return an error to the
  # client.
  def handle_call(_call, _from, %{socket: nil} = s) do
    {:reply, {:error, :closed}, s}
  end

  # We have to handle the :tx_commit operation differently as we have to keep
  # track of the transaction id, which is kept in the state of this genserver
  # (we also have to update this id).
  def handle_call({:operation, :tx_commit, [:transaction_id|args]}, from, s) do
    {id, s} = next_transaction_id(s)
    handle_call({:operation, :tx_commit, [id|args]}, from, s)
  end

  def handle_call({:operation, op_name, args}, from, %{session_id: sid} = s) do
    check_op_is_allowed!(s, op_name)

    req = Protocol.encode_op(op_name, [sid|args])
    s
    |> enqueue({from, op_name})
    |> send_noreply(req)
  end

  def handle_call(:fetch_schema, from, %{session_id: sid} = s) do
    check_op_is_allowed!(s, :record_load)

    args = [sid, {:short, 0}, {:long, 1}, "*:-1", true, false]
    req = Protocol.encode_op(:record_load, args)

    s
    |> enqueue({:fetch_schema, from})
    |> send_noreply(req)
  end

  @doc false
  def handle_cast({:operation, op_name, args}, %{session_id: sid} = s) do
    check_op_is_allowed!(s, op_name)

    req = Protocol.encode_op(op_name, [sid|args])
    send_noreply(s, req)
  end

  def handle_cast(:stop, s) do
    {:disconnect, :stop, s}
  end

  @doc false
  def handle_info({:tcp, socket, msg}, %{socket: socket} = s) do
    :inet.setopts(socket, active: :once)
    s = dequeue_and_parse_resp(s, :queue.out(s.queue), s.tail <> msg)
    {:noreply, s}
  end

  def handle_info({:tcp_closed, socket}, %{socket: socket} = s) do
    {:disconnect, {:error, :closed}, s}
  end

  # Helper functions.

  defp tcp_connection_opts(%{opts: opts} = _state) do
    socket_opts = @socket_opts ++ (opts[:socket_opts] || [])
    {to_char_list(opts[:host]), opts[:port], socket_opts, opts[:timeout] || @timeout}
  end

  defp parse_schema(%Document{fields: %{"globalProperties" => properties}}) do
    global_properties =
      for %Document{fields: %{"name" => name, "type" => type, "id" => id}} <- properties,
        into: HashDict.new() do
          {id, {name, type}}
      end

    %{global_properties: global_properties}
  end

  defp setup_socket_buffers(socket) do
    {:ok, [sndbuf: sndbuf, recbuf: recbuf, buffer: buffer]} =
      :inet.getopts(socket, [:sndbuf, :recbuf, :buffer])

    buffer = buffer |> max(sndbuf) |> max(recbuf)
    :ok = :inet.setopts(socket, [buffer: buffer])
  end

  defp send_noreply(%{socket: socket} = s, req) do
    case :gen_tcp.send(socket, req) do
      :ok ->
        {:noreply, s}
      {:error, _reason} = error ->
        {:disconnect, error, s}
    end
  end

  defp enqueue(s, what) do
    update_in s.queue, &:queue.in(what, &1)
  end

  defp dequeue_and_parse_resp(s, {{:value, {:fetch_schema, from}}, new_queue}, data) do
    sid = s.session_id

    case Protocol.parse_resp(:record_load, data, s.schema) do
      :incomplete ->
        %{s | tail: data}
      {^sid, {:error, _}, _rest} ->
        raise "couldn't fetch schema"
      {^sid, {:ok, {schema, _linked_records}}, rest} ->
        schema = parse_schema(schema)
        Connection.reply(from, schema)
        %{s | schema: schema, tail: rest, queue: new_queue}
    end
  end

  defp dequeue_and_parse_resp(s, {{:value, {from, op_name}}, new_queue}, data) do
    sid = s.session_id

    case Protocol.parse_resp(op_name, data, s.schema) do
      :incomplete ->
        %{s | tail: data}
      {^sid, resp, rest} ->
        Connection.reply(from, resp)
        %{s | tail: rest, queue: new_queue}
    end
  end

  defp check_op_is_allowed!(%{opts: opts}, operation) do
    do_check_op_is_allowed!(Keyword.fetch!(opts, :connection), operation)
  end

  @server_ops ~w(
    shutdown
    db_create
    db_exist
    db_drop
  )a

  @db_ops ~w(
    db_close
    db_size
    db_countrecords
    db_reload
    record_load
    record_load_if_version_not_latest
    record_create
    record_update
    record_delete
    command
    tx_commit
  )a

  defp do_check_op_is_allowed!({:db, _, _}, op) when not op in @db_ops do
    raise Error, "must be connected to the server (not a db) to perform operation #{op}"
  end

  defp do_check_op_is_allowed!(:server, op) when not op in @server_ops do
    raise Error, "must be connected to a database to perform operation #{op}"
  end

  defp do_check_op_is_allowed!(_, _) do
    nil
  end

  defp next_transaction_id(s) do
    get_and_update_in(s.transaction_id, &{&1, &1 + 1})
  end
end


Refactor the response handling in MarcoPolo.Connection

defmodule MarcoPolo.Connection do
  @moduledoc false

  use Connection

  require Logger

  alias MarcoPolo.Connection.Auth
  alias MarcoPolo.Protocol
  alias MarcoPolo.Document
  alias MarcoPolo.Error

  @socket_opts [:binary, active: false, packet: :raw]

  @initial_state %{socket: nil,
                   session_id: nil,
                   queue: :queue.new,
                   schema: nil,
                   tail: ""}

  ## Client code.

  def start_link(opts) do
    case Connection.start_link(__MODULE__, opts) do
      {:error, _} = err ->
        err
      {:ok, pid} = res ->
        maybe_fetch_schema(pid, opts)
        res
    end
  end

  def operation(pid, op_name, args) do
    Connection.call(pid, {:operation, op_name, args})
  end

  def fetch_schema(pid) do
    Connection.cast(pid, :fetch_schema)
  end

  defp maybe_fetch_schema(pid, opts) do
    case Keyword.get(opts, :connection) do
      {:db, _, _} -> fetch_schema(pid)
      _           -> nil
    end
  end

  ## Callbacks.

  @doc false
  def init(opts) do
    s = Dict.merge(@initial_state, opts: opts)
    {:connect, :init, s}
  end

  @doc false
  def connect(_info, s) do
    {host, port, socket_opts} = tcp_connection_opts(s)

    case :gen_tcp.connect(host, port, socket_opts) do
      {:ok, socket} ->
        s = %{s | socket: socket}
        setup_socket_buffers(socket)

        case Auth.connect(s) do
          {:ok, s} ->
            :inet.setopts(socket, active: :once)
            {:ok, s}
          {:error, error, s} ->
            {:stop, error, s}
          {:tcp_error, reason, s} ->
            {:stop, reason, s}
        end
      {:error, reason} ->
        Logger.error "OrientDB TCP connect error (#{host}:#{port}): #{:inet.format_error(reason)}"
        {:stop, reason, s}
    end
  end

  @doc false
  def disconnect(error, s) do
    # We only care about {from, _} tuples, ignoring queued stuff like
    # :fetch_schema.
    for {from, _operation} <- :queue.to_list(s.queue) do
      Connection.reply(from, error)
    end

    # Backoff 0 to churn through all commands in mailbox before reconnecting,
    # https://github.com/ericmj/mongodb/blob/a2dba1dfc089960d87364c2c43892f3061a93924/lib/mongo/connection.ex#L210
    {:backoff, 0, %{s | socket: nil, queue: :queue.new}}
  end

  @doc false
  def handle_call(call, from, s)

  # No socket means there's no TCP connection, we can return an error to the
  # client.
  def handle_call(_call, _from, %{socket: nil} = s) do
    {:reply, {:error, :closed}, s}
  end

  def handle_call({:operation, op_name, args}, from, s) do
    req = Protocol.encode_op(op_name, [sid|args])
    send_noreply_enqueueing(s, req, {from, op_name})
  end

  @doc false
  def handle_cast(:fetch_schema, s) do
    args = [sid, {:short, 0}, {:long, 1}, "*:-1", true, false]
    req = Protocol.encode_op(:record_load, args)

    send_noreply_enqueueing(s, req, :fetch_schema)
  end

  @doc false
  def handle_info(msg, state)

  def handle_info({:tcp, socket, msg}, %{session_id: sid, socket: socket} = s) do
    :inet.setopts(socket, active: :once)
    s = dequeue_and_parse_resp(s, :queue.out(s.queue), s.tail <> msg)
    {:noreply, s}
  end

  def handle_info({:tcp_closed, socket}, %{socket: socket} = s) do
    {:disconnect, {:error, :closed}, s}
  end

  # Helper functions.

  defp tcp_connection_opts(%{opts: opts} = _state) do
    socket_opts = @socket_opts ++ (opts[:socket_opts] || [])
    {to_char_list(opts[:host]), opts[:port], socket_opts}
  end

  defp parse_schema(%Document{fields: %{"globalProperties" => properties}}) do
    global_properties =
      for %Document{fields: %{"name" => name, "type" => type, "id" => id}} <- properties,
        into: HashDict.new() do
          {id, {name, type}}
      end

    %{global_properties: global_properties}
  end

  defp setup_socket_buffers(socket) do
    {:ok, [sndbuf: sndbuf, recbuf: recbuf]} = :inet.getopts(socket, [:sndbuf, :recbuf])
    :ok = :inet.setopts(socket, [buffer: max(sndbuf, recbuf)])
  end

  defp send_noreply_enqueueing(%{socket: socket} = s, req, to_enqueue) do
    case :gen_tcp.send(socket, req) do
      :ok ->
        {:noreply, enqueue(s, to_enqueue)}
      {:error, _reason} = error ->
        {:disconnect, error, s}
    end
  end

  defp enqueue(s, what) do
    update_in s.queue, &:queue.in(what, &1)
  end

  defp dequeue_and_parse_resp(s, {{:value, :fetch_schema}, new_queue}, data) do
    sid = s.session_id

    case Protocol.parse_resp(:record_load, data, s.schema) do
      :incomplete ->
        %{s | tail: data}
      {:error, %Error{}, _} ->
        raise "couldn't fetch schema"
      {:ok, ^sid, [resp], rest} ->
        %{s | schema: parse_schema(resp), tail: rest, queue: new_queue}
    end
  end

  defp dequeue_and_parse_resp(s, {{:value, {from, op_name}}, new_queue}, data) do
    sid = s.session_id

    case Protocol.parse_resp(op_name, data, s.schema) do
      :incomplete ->
        %{s | tail: data}
      {:unknown_property_id, rest} ->
        Connection.reply(from, {:error, :unknown_property_id})
        %{s | tail: rest}
      {:error, error, rest} ->
        Connection.reply(from, {:error, error})
        %{s | tail: rest, queue: new_queue}
      {:ok, ^sid, resp, rest} ->
        Connection.reply(from, {:ok, resp})
        %{s | tail: rest, queue: new_queue}
    end
  end
end

defmodule MarcoPolo.Connection do
  @moduledoc false

  use Connection

  require Logger

  alias MarcoPolo.Connection.Auth
  alias MarcoPolo.Protocol
  alias MarcoPolo.Document
  alias MarcoPolo.Error

  @socket_opts [:binary, active: false, packet: :raw]

  @initial_state %{socket: nil,
                   session_id: nil,
                   queue: :queue.new,
                   schema: nil,
                   tail: ""}

  ## Client code.

  def start_link(opts) do
    case Connection.start_link(__MODULE__, opts) do
      {:error, _} = err ->
        err
      {:ok, pid} = res ->
        maybe_fetch_schema(pid, opts)
        res
    end
  end

  def operation(pid, op_name, args) do
    Connection.call(pid, {:operation, op_name, args})
  end

  def fetch_schema(pid) do
    Connection.cast(pid, :fetch_schema)
  end

  defp maybe_fetch_schema(pid, opts) do
    case Keyword.get(opts, :connection) do
      {:db, _, _} -> fetch_schema(pid)
      _           -> nil
    end
  end

  ## Callbacks.

  @doc false
  def init(opts) do
    s = Dict.merge(@initial_state, opts: opts)
    {:connect, :init, s}
  end

  @doc false
  def connect(_info, s) do
    {host, port, socket_opts} = tcp_connection_opts(s)

    case :gen_tcp.connect(host, port, socket_opts) do
      {:ok, socket} ->
        s = %{s | socket: socket}
        setup_socket_buffers(socket)

        case Auth.connect(s) do
          {:ok, s} ->
            :inet.setopts(socket, active: :once)
            {:ok, s}
          {:error, error, s} ->
            {:stop, error, s}
          {:tcp_error, reason, s} ->
            {:stop, reason, s}
        end
      {:error, reason} ->
        Logger.error "OrientDB TCP connect error (#{host}:#{port}): #{:inet.format_error(reason)}"
        {:stop, reason, s}
    end
  end

  @doc false
  def disconnect(error, s) do
    # We only care about {_, from} tuples, ignoring queued stuff like
    # :fetch_schema.
    for {_operation, from} <- :queue.to_list(s.queue) do
      Connection.reply(from, error)
    end

    # Backoff 0 to churn through all commands in mailbox before reconnecting,
    # https://github.com/ericmj/mongodb/blob/a2dba1dfc089960d87364c2c43892f3061a93924/lib/mongo/connection.ex#L210
    {:backoff, 0, %{s | socket: nil, queue: :queue.new}}
  end

  @doc false
  def handle_call(call, from, s)

  # No socket means there's no TCP connection, we can return an error to the
  # client.
  def handle_call(_call, _from, %{socket: nil} = s) do
    {:reply, {:error, :closed}, s}
  end

  def handle_call({:operation, op_name, args}, from, %{session_id: sid} = s) do
    req = Protocol.encode_op(op_name, [sid|args])
    send_noreply_enqueueing(s, req, {from, op_name})
  end

  @doc false
  def handle_cast(:fetch_schema, %{session_id: sid} = s) do
    args = [sid, {:short, 0}, {:long, 1}, "*:-1", true, false]
    req = Protocol.encode_op(:record_load, args)

    send_noreply_enqueueing(s, req, :fetch_schema)
  end

  @doc false
  def handle_info(msg, state)

  def handle_info({:tcp, socket, msg}, %{session_id: sid, socket: socket} = s) do
    :inet.setopts(socket, active: :once)
    data = s.tail <> msg

    s =
      case :queue.out(s.queue) do
        {{:value, :fetch_schema}, new_queue} ->
          case Protocol.parse_resp(:record_load, data, s.schema) do
            :incomplete ->
              %{s | tail: data}
            {:error, %Error{}, _} ->
              raise "couldn't fetch schema"
            {:ok, ^sid, [resp], rest} ->
              %{s | schema: parse_schema(resp), tail: rest, queue: new_queue}
          end
        {{:value, {from, op_name}}, new_queue} ->
          case Protocol.parse_resp(op_name, data, s.schema) do
            :incomplete ->
              %{s | tail: data}
            {:unknown_property_id, rest} ->
              Connection.reply(from, {:error, :unknown_property_id})
              %{s | tail: rest}
            {:error, error, rest} ->
              Connection.reply(from, {:error, error})
              %{s | tail: rest, queue: new_queue}
            {:ok, ^sid, resp, rest} ->
              Connection.reply(from, {:ok, resp})
              %{s | tail: rest, queue: new_queue}
          end
      end

    {:noreply, s}
  end

  def handle_info({:tcp_closed, socket}, %{socket: socket} = s) do
    {:disconnect, {:error, :closed}, s}
  end

  # Helper functions.

  defp tcp_connection_opts(%{opts: opts} = _state) do
    socket_opts = @socket_opts ++ (opts[:socket_opts] || [])
    {to_char_list(opts[:host]), opts[:port], socket_opts}
  end

  defp parse_schema(%Document{fields: %{"globalProperties" => properties}}) do
    global_properties =
      for %Document{fields: %{"name" => name, "type" => type, "id" => id}} <- properties,
        into: HashDict.new() do
          {id, {name, type}}
      end

    %{global_properties: global_properties}
  end

  defp setup_socket_buffers(socket) do
    {:ok, [sndbuf: sndbuf, recbuf: recbuf]} = :inet.getopts(socket, [:sndbuf, :recbuf])
    :ok = :inet.setopts(socket, [buffer: max(sndbuf, recbuf)])
  end

  defp send_noreply_enqueueing(%{socket: socket} = s, req, to_enqueue) do
    case :gen_tcp.send(socket, req) do
      :ok ->
        {:noreply, enqueue(s, to_enqueue)}
      {:error, _reason} = error ->
        {:disconnect, error, s}
    end
  end

  defp enqueue(s, what) do
    update_in s.queue, &:queue.in(what, &1)
  end
end


Generate `*_url` helpers with domain and scheme (protocol)

defmodule Phoenix.Router.Mapper do
  alias Phoenix.Router.Path
  alias Phoenix.Controller
  alias Phoenix.Router.ResourcesContext
  alias Phoenix.Router.ScopeContext
  alias Phoenix.Router.Errors
  alias Phoenix.Router.Mapper

  @actions [:index, :edit, :show, :new, :create, :update, :destroy]

  @moduledoc """
  Adds Macros for Route match definitions. All routes are
  compiled to pattern matched def match() definitions for fast
  and efficient lookup by the VM.

  # Examples

  defmodule Router do
    use Phoenix.Router, port: 4000

    get "pages/:page", PagesController, :show, as: :page
    resources "users", UsersController
  end

  Compiles to

    get "pages/:page", PagesController, :show, as: :page

    -> defmatch({:get, "pages/:page", PagesController, :show, [as: :page]})
       defroute_aliases({:get, "pages/:page", PagesController, :show, [as: :page]})

    --> def(match(conn, :get, ["pages", page])) do
          conn = conn.params(Dict.merge(conn.params(), [{"page", page}]))
          apply(PagesController, :show, [conn])
        end

  The resources macro accepts flags to limit which resources are generated. Passing
  a list of actions through either :only or :except will prevent building all the
  routes

  # Examples

  defmodule Router do
    use Phoenix.Router, port: 4000

    resources "pages", Controllers.Pages, only: [ :show ]
    resources "users", Controllers.Users, except: [ :destroy ]
  end

  Generated Routes

    page      GET   pages/:id      Elixir.Controllers.Pages#show

    users     GET   users          Elixir.Controllers.Users#new
    new_user  GET   users/new      Elixir.Controllers.Users#new
    edit_user GET   users/:id/edit Elixir.Controllers.Users#edit
    user      GET   users/:id      Elixir.Controllers.Users#show

              POST  users          Elixir.Controllers.Users#create
              PUT   users/:id      Elixir.Controllers.Users#update
              PATCH users/:id      Elixir.Controllers.Users#update
  """

  defmacro __using__(_options) do
    quote do
      Module.register_attribute __MODULE__, :routes, accumulate: true,
                                                     persist: false
      import unquote(__MODULE__)
      @before_compile unquote(__MODULE__)
    end
  end

  defmacro __before_compile__(env) do
    routes = Enum.reverse(Module.get_attribute(env.module, :routes))
    routes_ast = Enum.reduce routes, nil, fn route, acc ->
      quote do
        defmatch(unquote(route))
        defroute_aliases(unquote(route))
        unquote(acc)
      end
    end

    quote do
      def __routes__, do: Enum.reverse(@routes)
      unquote(routes_ast)
      def match(conn, method, path), do: Controller.not_found(conn, method, path)
    end
  end

  defmacro defmatch({http_method, path, controller, action, _options}) do
    path_args = Path.matched_arg_list_with_ast_bindings(path)
    params_list_with_bindings = Path.params_with_ast_bindings(path)

    quote do
      def unquote(:match)(conn, unquote(http_method), unquote(path_args)) do
        conn = %{conn | params: Dict.merge(conn.params, unquote(params_list_with_bindings)) }

        apply(unquote(controller), unquote(action), [conn])
      end
    end
  end

  defmacro defroute_aliases({_http_method, path, _controller, _action, options}) do
    alias_name = options[:as]
    if alias_name do
      quote do
        def unquote(binary_to_atom "#{alias_name}_path")(params \\ []) do
          Path.build(unquote(path), params)
        end
        def unquote(binary_to_atom "#{alias_name}_url")(params \\ []) do
          config = Phoenix.Config.for(__MODULE__).router
          domain = config[:domain]
          scheme = if config[:ssl], do: "https", else: "http"

          Path.build(unquote(path), params)
          |> Path.build_url(domain, scheme)
        end
      end
    end
  end

  defmacro get(path, controller, action, options \\ []) do
    add_route(:get, path, controller, action, options)
  end

  defmacro post(path, controller, action, options \\ []) do
    add_route(:post, path, controller, action, options)
  end

  defmacro put(path, controller, action, options \\ []) do
    add_route(:put, path, controller, action, options)
  end

  defmacro patch(path, controller, action, options \\ []) do
    add_route(:patch, path, controller, action, options)
  end

  defmacro delete(path, controller, action, options \\ []) do
    add_route(:delete, path, controller, action, options)
  end

  defp add_route(verb, path, controller, action, options) do
    quote bind_quoted: [verb: verb,
                        path: path,
                        controller: controller,
                        action: action,
                        options: options] do

      Errors.ensure_valid_path!(path)
      current_path = ResourcesContext.current_path(path, __MODULE__)
      {scoped_path, scoped_controller, scoped_helper} = ScopeContext.current_scope(current_path,
                                                                                controller,
                                                                                options[:as],
                                                                                __MODULE__)
      opts = Dict.merge(options, as: scoped_helper)

      @routes {verb, scoped_path, scoped_controller, action, opts}
    end
  end

  defmacro resources(resource, controller, opts, do: nested_context) do
    add_resources resource, controller, opts, do: nested_context
  end
  defmacro resources(resource, controller, do: nested_context) do
    add_resources resource, controller, [], do: nested_context
  end
  defmacro resources(resource, controller, opts) do
    add_resources resource, controller, opts, do: nil
  end
  defmacro resources(resource, controller) do
    add_resources resource, controller, [], do: nil
  end
  defp add_resources(resource, controller, options, do: nested_context) do
    quote unquote: true, bind_quoted: [options: options,
                                       resource: resource,
                                       controller: controller] do

      actions = Mapper.extract_actions_from_options(options)
      Enum.each actions, fn action ->
        current_alias = ResourcesContext.current_alias(action, resource, __MODULE__)
        opts = [as: current_alias]
        case action do
          :index   -> get    "/#{resource}",          controller, :index, opts
          :show    -> get    "/#{resource}/:id",      controller, :show, opts
          :new     -> get    "/#{resource}/new",      controller, :new, opts
          :edit    -> get    "/#{resource}/:id/edit", controller, :edit, opts
          :create  -> post   "/#{resource}",          controller, :create, opts
          :destroy -> delete "/#{resource}/:id",      controller, :destroy, opts
          :update  ->
            put   "/#{resource}/:id", controller, :update, []
            patch "/#{resource}/:id", controller, :update, []
        end
      end

      ResourcesContext.push(resource, __MODULE__)
      unquote(nested_context)
      ResourcesContext.pop(__MODULE__)
    end
  end

  defmacro scope(params, do: nested_context) do
    path             = Keyword.get(params, :path)
    controller_alias = Keyword.get(params, :alias)
    helper           = Keyword.get(params, :helper)

    quote unquote: true, bind_quoted: [path: path,
                                       controller_alias: controller_alias,
                                       helper: helper] do

      ScopeContext.push({path, controller_alias, helper}, __MODULE__)
      unquote(nested_context)
      ScopeContext.pop(__MODULE__)
    end
  end

  def extract_actions_from_options(opts) do
    Keyword.get(opts, :only) || (@actions -- Keyword.get(opts, :except, []))
  end
end

defmodule Phoenix.Router.Mapper do
  alias Phoenix.Router.Path
  alias Phoenix.Controller
  alias Phoenix.Router.ResourcesContext
  alias Phoenix.Router.ScopeContext
  alias Phoenix.Router.Errors
  alias Phoenix.Router.Mapper

  @actions [:index, :edit, :show, :new, :create, :update, :destroy]

  @moduledoc """
  Adds Macros for Route match definitions. All routes are
  compiled to pattern matched def match() definitions for fast
  and efficient lookup by the VM.

  # Examples

  defmodule Router do
    use Phoenix.Router, port: 4000

    get "pages/:page", PagesController, :show, as: :page
    resources "users", UsersController
  end

  Compiles to

    get "pages/:page", PagesController, :show, as: :page

    -> defmatch({:get, "pages/:page", PagesController, :show, [as: :page]})
       defroute_aliases({:get, "pages/:page", PagesController, :show, [as: :page]})

    --> def(match(conn, :get, ["pages", page])) do
          conn = conn.params(Dict.merge(conn.params(), [{"page", page}]))
          apply(PagesController, :show, [conn])
        end

  The resources macro accepts flags to limit which resources are generated. Passing
  a list of actions through either :only or :except will prevent building all the
  routes

  # Examples

  defmodule Router do
    use Phoenix.Router, port: 4000

    resources "pages", Controllers.Pages, only: [ :show ]
    resources "users", Controllers.Users, except: [ :destroy ]
  end

  Generated Routes

    page      GET   pages/:id      Elixir.Controllers.Pages#show

    users     GET   users          Elixir.Controllers.Users#new
    new_user  GET   users/new      Elixir.Controllers.Users#new
    edit_user GET   users/:id/edit Elixir.Controllers.Users#edit
    user      GET   users/:id      Elixir.Controllers.Users#show

              POST  users          Elixir.Controllers.Users#create
              PUT   users/:id      Elixir.Controllers.Users#update
              PATCH users/:id      Elixir.Controllers.Users#update
  """

  defmacro __using__(_options) do
    quote do
      Module.register_attribute __MODULE__, :routes, accumulate: true,
                                                     persist: false
      import unquote(__MODULE__)
      @before_compile unquote(__MODULE__)
    end
  end

  defmacro __before_compile__(env) do
    routes = Enum.reverse(Module.get_attribute(env.module, :routes))
    routes_ast = Enum.reduce routes, nil, fn route, acc ->
      quote do
        defmatch(unquote(route))
        defroute_aliases(unquote(route))
        unquote(acc)
      end
    end

    quote do
      def __routes__, do: Enum.reverse(@routes)
      unquote(routes_ast)
      def match(conn, method, path), do: Controller.not_found(conn, method, path)
    end
  end

  defmacro defmatch({http_method, path, controller, action, _options}) do
    path_args = Path.matched_arg_list_with_ast_bindings(path)
    params_list_with_bindings = Path.params_with_ast_bindings(path)

    quote do
      def unquote(:match)(conn, unquote(http_method), unquote(path_args)) do
        conn = %{conn | params: Dict.merge(conn.params, unquote(params_list_with_bindings)) }

        apply(unquote(controller), unquote(action), [conn])
      end
    end
  end

  defmacro defroute_aliases({_http_method, path, _controller, _action, options}) do
    alias_name = options[:as]
    if alias_name do
      quote do
        def unquote(binary_to_atom "#{alias_name}_path")(params \\ []) do
          Path.build(unquote(path), params)
        end
        # TODO: use config based domain for URL
        def unquote(binary_to_atom "#{alias_name}_url")(params \\ []) do
          Path.build(unquote(path), params)
        end
      end
    end
  end

  defmacro get(path, controller, action, options \\ []) do
    add_route(:get, path, controller, action, options)
  end

  defmacro post(path, controller, action, options \\ []) do
    add_route(:post, path, controller, action, options)
  end

  defmacro put(path, controller, action, options \\ []) do
    add_route(:put, path, controller, action, options)
  end

  defmacro patch(path, controller, action, options \\ []) do
    add_route(:patch, path, controller, action, options)
  end

  defmacro delete(path, controller, action, options \\ []) do
    add_route(:delete, path, controller, action, options)
  end

  defp add_route(verb, path, controller, action, options) do
    quote bind_quoted: [verb: verb,
                        path: path,
                        controller: controller,
                        action: action,
                        options: options] do

      Errors.ensure_valid_path!(path)
      current_path = ResourcesContext.current_path(path, __MODULE__)
      {scoped_path, scoped_controller, scoped_helper} = ScopeContext.current_scope(current_path,
                                                                                controller,
                                                                                options[:as],
                                                                                __MODULE__)
      opts = Dict.merge(options, as: scoped_helper)

      @routes {verb, scoped_path, scoped_controller, action, opts}
    end
  end

  defmacro resources(resource, controller, opts, do: nested_context) do
    add_resources resource, controller, opts, do: nested_context
  end
  defmacro resources(resource, controller, do: nested_context) do
    add_resources resource, controller, [], do: nested_context
  end
  defmacro resources(resource, controller, opts) do
    add_resources resource, controller, opts, do: nil
  end
  defmacro resources(resource, controller) do
    add_resources resource, controller, [], do: nil
  end
  defp add_resources(resource, controller, options, do: nested_context) do
    quote unquote: true, bind_quoted: [options: options,
                                       resource: resource,
                                       controller: controller] do

      actions = Mapper.extract_actions_from_options(options)
      Enum.each actions, fn action ->
        current_alias = ResourcesContext.current_alias(action, resource, __MODULE__)
        opts = [as: current_alias]
        case action do
          :index   -> get    "/#{resource}",          controller, :index, opts
          :show    -> get    "/#{resource}/:id",      controller, :show, opts
          :new     -> get    "/#{resource}/new",      controller, :new, opts
          :edit    -> get    "/#{resource}/:id/edit", controller, :edit, opts
          :create  -> post   "/#{resource}",          controller, :create, opts
          :destroy -> delete "/#{resource}/:id",      controller, :destroy, opts
          :update  ->
            put   "/#{resource}/:id", controller, :update, []
            patch "/#{resource}/:id", controller, :update, []
        end
      end

      ResourcesContext.push(resource, __MODULE__)
      unquote(nested_context)
      ResourcesContext.pop(__MODULE__)
    end
  end

  defmacro scope(params, do: nested_context) do
    path             = Keyword.get(params, :path)
    controller_alias = Keyword.get(params, :alias)
    helper           = Keyword.get(params, :helper)

    quote unquote: true, bind_quoted: [path: path,
                                       controller_alias: controller_alias,
                                       helper: helper] do

      ScopeContext.push({path, controller_alias, helper}, __MODULE__)
      unquote(nested_context)
      ScopeContext.pop(__MODULE__)
    end
  end

  def extract_actions_from_options(opts) do
    Keyword.get(opts, :only) || (@actions -- Keyword.get(opts, :except, []))
  end
end