respond to version 1.2

respond to version 1.2

defmodule ESx.API.Indices do
  import ESx.R

  alias ESx.API.Utils
  alias ESx.Transport

  def delete(ts, args \\ %{}) do
    method = "DELETE"
    path   = Utils.pathify Utils.listify(args[:index])
    params = Utils.extract_params args
    body   = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def exists(ts, args \\ %{}) do
    method = "HEAD"
    path   = Utils.listify(args[:index])
    params = Utils.extract_params args
    body   = nil

    status200? ts, method, path, params, body
  end

  def exists?(ts, args \\ %{}) do
    exists ts, args
  end

  def create(ts, %{index: index, body: body} = args) when is_map(body) do
    method = "PUT"
    path   = Utils.pathify [Utils.escape(index)]
    params = Utils.extract_params args
    body   = body

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def get_alias(ts, args \\ %{}) do
    method = "GET"
    path   = Utils.pathify [Utils.listify(args[:index]), "_alias", Utils.escape(args[:name])]
    params = Utils.extract_params args
    body   = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def get_aliases(ts, args \\ %{}) do
    method = "GET"
    path   = Utils.pathify [Utils.listify(args[:index]), "_aliases", Utils.listify(args[:name])]
    params = Utils.extract_params args
    body   = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def update_aliases(ts, %{body: body} = args) when is_map(body) do
    method = "POST"
    path   = "_aliases"
    params = Utils.extract_params args

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def put_alias(ts, %{name: name} = args) do
    method = "PUT"
    path   = Utils.pathify [Utils.listify(args[:index]), "_alias", Utils.escape(name)]
    params = Utils.extract_params args
    body   = args[:body]

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def delete_alias(ts, %{index: index, name: name} = args) do
    method = "DELETE"
    path   = Utils.pathify [Utils.listify(index), "_alias", Utils.escape(name)]
    params = Utils.extract_params args
    body   = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def exists_alias(ts, args \\ %{}) do
    method = "HEAD"
    path   = Utils.pathify [Utils.listify(args[:index]), "_alias", Utils.escape(args[:name])]
    params = Utils.extract_params args
    body   = nil

    status200? ts, method, path, params, body
  end

  def exists_alias?(ts, args \\ %{}) do
    exists_alias ts, args
  end

  def refresh(ts, args \\ %{}) do
    method = "POST"
    path   = Utils.pathify [Utils.listify(args[:index]), "_refresh"]
    params = Utils.extract_params args
    body   = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def get_mapping(ts, args \\ %{}) do
    method = "GET"
    path   = Utils.pathify [Utils.listify(args[:index]), "_mapping", Utils.listify(args[:type])]
    params = Utils.extract_params args
    body = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def get_settings(ts, args \\ %{}) do
    method = "GET"
    path   = Utils.pathify [Utils.listify(args[:index]), Utils.listify(args[:type]), args.delete(:prefix), "_settings", Utils.escape(args[:name])]
    params = Utils.extract_params args
    body   = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def get_template(ts, args \\ %{}) do
    method = "GET"
    path   = Utils.pathify ["_template", Utils.escape(args[:name])]
    params = Utils.extract_params args
    body   = args[:body]

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def put_template(ts, %{name: name, body: body} = args) do
    method = "PUT"
    path   = Utils.pathify "_template", Utils.escape(name)
    params = Utils.extract_params args
    body   = body

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def delete_template(ts, %{name: name} = args) do
    method = HTTP_DELETE
    path   = Utils.pathify "_template", Utils.escape(name)
    params = Utils.extract_params args
    body = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def exists_template(ts, %{name: name} = args) do
    method = HTTP_HEAD
    path   = Utils.pathify '_template', Utils.escape(name)
    params = Utils.extract_params args
    body = nil

    status200? ts, method, path, params, body
  end

  def exists_template?(ts, args \\ %{}) do
    exists_template ts, args
  end

  def upgrade(ts, args \\ %{}) do
    method = "POST"
    path   = "_upgrade"
    params = Utils.extract_params args
    body   = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

end

defmodule ESx.API.Indices do
  import ESx.R

  alias ESx.API.Utils
  alias ESx.Transport

  def delete(ts, args \\ %{}) do
    method = "DELETE"
    path   = Utils.pathify Utils.listify(args[:index])
    params = Utils.extract_params args
    body   = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def exists(ts, args \\ %{}) do
    method = "HEAD"
    path   = Utils.listify(args[:index])
    params = Utils.extract_params args
    body   = nil

    status200? ts, method, path, params, body
  end

  defdelegate exists?(ts, args \\ %{}), to: __MODULE__, as: :exists

  def create(ts, %{index: index, body: body} = args) when is_map(body) do
    method = "PUT"
    path   = Utils.pathify [Utils.escape(index)]
    params = Utils.extract_params args
    body   = body

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def get_alias(ts, args \\ %{}) do
    method = "GET"
    path   = Utils.pathify [Utils.listify(args[:index]), "_alias", Utils.escape(args[:name])]
    params = Utils.extract_params args
    body   = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def get_aliases(ts, args \\ %{}) do
    method = "GET"
    path   = Utils.pathify [Utils.listify(args[:index]), "_aliases", Utils.listify(args[:name])]
    params = Utils.extract_params args
    body   = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def update_aliases(ts, %{body: body} = args) when is_map(body) do
    method = "POST"
    path   = "_aliases"
    params = Utils.extract_params args

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def put_alias(ts, %{name: name} = args) do
    method = "PUT"
    path   = Utils.pathify [Utils.listify(args[:index]), "_alias", Utils.escape(name)]
    params = Utils.extract_params args
    body   = args[:body]

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def delete_alias(ts, %{index: index, name: name} = args) do
    method = "DELETE"
    path   = Utils.pathify [Utils.listify(index), "_alias", Utils.escape(name)]
    params = Utils.extract_params args
    body   = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def exists_alias(ts, args \\ %{}) do
    method = "HEAD"
    path   = Utils.pathify [Utils.listify(args[:index]), "_alias", Utils.escape(args[:name])]
    params = Utils.extract_params args
    body   = nil

    status200? ts, method, path, params, body
  end

  defdelegate exists_alias?(ts, args \\ %{}), to: __MODULE__, as: :exists_alias

  def refresh(ts, args \\ %{}) do
    method = "POST"
    path   = Utils.pathify [Utils.listify(args[:index]), "_refresh"]
    params = Utils.extract_params args
    body   = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def get_mapping(ts, args \\ %{}) do
    method = "GET"
    path   = Utils.pathify [Utils.listify(args[:index]), "_mapping", Utils.listify(args[:type])]
    params = Utils.extract_params args
    body = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def get_settings(ts, args \\ %{}) do
    method = "GET"
    path   = Utils.pathify [Utils.listify(args[:index]), Utils.listify(args[:type]), args.delete(:prefix), "_settings", Utils.escape(args[:name])]
    params = Utils.extract_params args
    body   = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def get_template(ts, args \\ %{}) do
    method = "GET"
    path   = Utils.pathify ["_template", Utils.escape(args[:name])]
    params = Utils.extract_params args
    body   = args[:body]

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def put_template(ts, %{name: name, body: body} = args) do
    method = "PUT"
    path   = Utils.pathify "_template", Utils.escape(name)
    params = Utils.extract_params args
    body   = body

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def delete_template(ts, %{name: name} = args) do
    method = HTTP_DELETE
    path   = Utils.pathify "_template", Utils.escape(name)
    params = Utils.extract_params args
    body = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

  def exists_template(ts, %{name: name} = args) do
    method = HTTP_HEAD
    path   = Utils.pathify '_template', Utils.escape(name)
    params = Utils.extract_params args
    body = nil

    status200? ts, method, path, params, body
  end

  defdelegate exists_template?(ts, args \\ %{}), to: __MODULE__, as: :exists_template

  def upgrade(ts, args \\ %{}) do
    method = "POST"
    path   = "_upgrade"
    params = Utils.extract_params args
    body   = nil

    Transport.perform_request(ts, method, path, params, body)
    |> response
  end

end


only read adapter config when compile

only read adapter config when compile

defmodule Maxwell.Builder do
    @http_methods [:get, :head, :delete, :trace, :options, :post, :put, :patch]
    @method_without_body [{:get!, :get}, {:head!, :head}, {:delete!, :delete}, {:trace!, :trace}, {:options!, :options}]
    @method_with_body [{:post!, :post}, {:put!, :put}, {:patch!, :patch}]

  defmacro __using__(methods) do
    methods = Maxwell.Until.adjust_method_format(methods, @http_methods)
    Maxwell.Until.is_allow_methods(methods, @http_methods)

    method_defs = for {method_exception, method} <- @method_without_body, method in methods do
      quote location: :keep do
        @doc """
          Method without body: #{unquote(method)}

          Receives `[url: url_string, headers: headers_map, query: query_map, opts: opts_keyword_list]` or `%Maxwell{}`

          Returns `{:ok, %Maxwell{}}` or `{:error, reason_term}`
          ## Examples
               iex> url(request_url_string_or_char_list)
                    |> query(request_query_map)
                    |> headers(request_headers_map)
                    |> opts(request_opts_keyword_list)
                    |> YourClient.#{unquote(method)}

               {:ok, %Maxwell{
                      headers: reponse_headers_map,
                      status:  reponse_http_status_integer,
                      body:    reponse_body_term,
                      opts:    request_opts_keyword_list,
                      url:     request_urlwithquery_string,
                      query:   request_query_map
               }

          or
                {:error, {:conn_failed, {:error, :nxdomain}}}

          You can make asynchronous requests by passing `respond_to: pid` option:
                Maxwell.get(url: "http://example.org", respond_to: self)
                receive do
                  {:maxwell_response, res} -> res.status # => 200
                end
          """
        def unquote(method)(maxwell\\[])
        def unquote(method)(maxwell = %Maxwell{body: body})when is_nil(body) do
          %{maxwell| method: unquote(method)}
          |> call_middleware
        end
        def unquote(method)(maxwell)when is_list(maxwell) do
          url        = maxwell|> Keyword.get(:url, "")
          headers    = maxwell|> Keyword.get(:headers, %{})
          query      = maxwell|> Keyword.get(:query, %{})
          opts       = maxwell|> Keyword.get(:opts, [])

          opts =
          if respond_to = maxwell|> Keyword.get(:respond_to, nil) do
            [{:respond_to, respond_to} | opts]
          else
            opts
          end

          %Maxwell{
            method: unquote(method),
            headers: headers,
            opts: opts,
            url: Maxwell.Until.append_query_string(url, query)
          }
          |> call_middleware
        end
        @doc """
          Method without body: #{unquote(method_exception)}

          Receives `[url: url_string, headers: headers_map, query: query_map, opts: opts_keyword_list]` or `%Maxwell{}`

          Returns `%Maxwell{}` or raise `%MaxWell.Error{}`

          """
        def unquote(method_exception)(maxwell\\%Maxwell{})
        def unquote(method_exception)(maxwell) do
          case unquote(method)(maxwell) do
            {:ok, %Maxwell{} = result} ->
              result
            {:error, reason}  ->
              raise Maxwell.Error, value: reason,
                message: "method: #{unquote(method)} reason: #{inspect reason} url: #{maxwell.url}, module: #{__MODULE__}"
          end
        end
      end
    end

    method_defs_with_body = for {method_exception, method} <- @method_with_body, method in methods do
      quote location: :keep do
        @doc """
          Method: #{unquote(method)}

          Receives `[url: url_string, headers: headers_map, query: query_map, opts: opts_keyword_list, body: body_term]` or `%Maxwell{}`

          Returns `{:ok, %Maxwell{}}` or `{:error, reason}`
          ## Examples
               iex> url(request_url_string_or_char_list)
                    |> query(request_query_map)
                    |> headers(request_headers_map)
                    |> opts(request_opts_keyword_list)
                    |> body(request_body_term)
                    |> YourClient.#{unquote(method)}

               {:ok, %Maxwell{
                     headers: reponse_headers_map,
                     status:  reponse_http_status_integer,
                     body:    reponse_body_term,
                     opts:    request_opts_keyword_list
                     url:     request_urlwithquery_string,
               }
          or
               {:error, {:conn_failed, {:error, :timeout}}}
          If adapter supports it, you can make asynchronous requests by passing `respond_to: pid` option:
                Maxwell.get(url: "http://example.org", respond_to: self)
                receive do
                  {:maxwell_response, res} -> res.status # => 200
                end
          """
        def unquote(method)(maxwell\\%Maxwell{})
        def unquote(method)(maxwell = %Maxwell{}) do
          %{maxwell| method: unquote(method)}
          |> call_middleware
        end
        def unquote(method)(maxwell)when is_list(maxwell) do
          url        = maxwell |> Keyword.get(:url, "")
          headers    = maxwell |> Keyword.get(:headers, %{})
          query      = maxwell |> Keyword.get(:query, %{})
          opts       = maxwell |> Keyword.get(:opts, [])
          body       = maxwell |> Keyword.get(:body, %{})

          opts =
          if respond_to = maxwell|> Keyword.get(:respond_to, nil) do
            [{:respond_to, respond_to} | opts]
          else
            opts
          end

          body =
          if multipart = maxwell|> Keyword.get(:multipart, nil) do
            {:multipart, multipart}
          else
            body
          end

          %Maxwell{
            method: unquote(method),
            headers: headers,
            opts: opts,
            body: body,
            url: Maxwell.Until.append_query_string(url, query)
          }
          |> call_middleware
        end
        @doc """
          Method: #{unquote(method_exception)}

          Receives `[url: url_string, headers: headers_map, query: query_map, opts: opts_keyword_list, body: body_term]` or `%Maxwell{}`

          Return `%Maxwell{}` or raise `%Maxwell.Error{}`

          """
        def unquote(method_exception)(maxwell\\%Maxwell{})
        def unquote(method_exception)(maxwell) do
          case unquote(method)(maxwell) do
            {:ok, %Maxwell{} = result} ->
              result
            {:error, reason}  ->
              raise Maxwell.Error, value: reason,
                message: "method: #{unquote(method)} reason: #{inspect reason} url: #{maxwell.url}, module: #{__MODULE__}"
          end
        end
      end
    end

    help_methods =
      quote do
        def url(maxwell \\ %Maxwell{}, url)when is_binary(url) do
          %{maxwell| url: url}
        end
        def query(maxwell \\ %Maxwell{}, query)when is_map(query) do
          %{maxwell| url: Maxwell.Until.append_query_string(maxwell.url, query)}
        end
        def headers(maxwell \\ %Maxwell{}, headers)when is_map(headers) do
          %{maxwell| headers: Map.merge(maxwell.headers, headers)}
        end
        def opts(maxwell \\ %Maxwell{}, opts)when is_list(opts) do
          %{maxwell| opts: Keyword.merge(maxwell.opts, opts)}
        end
        def body(maxwell \\ %Maxwell{}, body) do
          %{maxwell| body: body}
        end
        def multipart(maxwell \\ %Maxwell{}, multipart) do
           %{maxwell| body: {:multipart, multipart}}
        end
        def respond_to(target_pid)when is_pid(target_pid) do
          respond_to(%Maxwell{}, target_pid)
        end
        def respond_to(%Maxwell{} = maxwell) do
          respond_to(maxwell, self)
        end
        def respond_to(maxwell, target_pid) do
          target_pid = unless target_pid, do: self, else: target_pid
          %{maxwell| opts: Keyword.merge(maxwell.opts, [{:respond_to, target_pid}])}
        end
      end

    quote do
      unquote(method_defs)
      unquote(method_defs_with_body)
      unquote(help_methods)

      import Maxwell.Middleware
      import Maxwell.Adapter

      Module.register_attribute(__MODULE__, :middleware, accumulate: true)
      @before_compile Maxwell.Builder
    end
  end

  defp generate_call_adapter(env) do
    case Module.get_attribute(env.module, :adapter) do
      nil ->
        quote do
          defp call_adapter(env) do
            unquote(Maxwell.Until.default_adapter).call(env) # default
          end
        end
      {:fn, _, _} = adapter ->
        quote do
          defp call_adapter(env) do
            unquote(adapter).(env)
          end
        end
      mod when is_atom(mod) ->
        quote do
          defp call_adapter(env) do
            unquote(mod).call(env)
          end
        end
       _ ->
         raise "Adapter must be Module or fn(env) -> env end"
    end
  end

  defp generate_call_middleware(env) do
    reduced =
      Module.get_attribute(env.module, :middleware)
      |> Enum.reduce(
         quote do
           call_adapter(env)
         end,
         fn({mid, args}, acc) ->
           args = Macro.escape(args)
           quote do
             unquote(mid).call(env, fn(env) -> unquote(acc) end, unquote(args))
           end
         end)

    quote do
      defp call_middleware(env) do
        unquote(reduced)
      end
    end
  end

  defmacro __before_compile__(env) do
    [
      generate_call_adapter(env),
      generate_call_middleware(env),
    ]
  end

end

defmodule Maxwell.Builder do
    @http_methods [:get, :head, :delete, :trace, :options, :post, :put, :patch]
    @method_without_body [{:get!, :get}, {:head!, :head}, {:delete!, :delete}, {:trace!, :trace}, {:options!, :options}]
    @method_with_body [{:post!, :post}, {:put!, :put}, {:patch!, :patch}]

  defmacro __using__(methods) do
    methods = Maxwell.Until.adjust_method_format(methods, @http_methods)
    Maxwell.Until.is_allow_methods(methods, @http_methods)

    method_defs = for {method_exception, method} <- @method_without_body, method in methods do
      quote location: :keep do
        @doc """
          Method without body: #{unquote(method)}

          Receives `[url: url_string, headers: headers_map, query: query_map, opts: opts_keyword_list]` or `%Maxwell{}`

          Returns `{:ok, %Maxwell{}}` or `{:error, reason_term}`
          ## Examples
               iex> url(request_url_string_or_char_list)
                    |> query(request_query_map)
                    |> headers(request_headers_map)
                    |> opts(request_opts_keyword_list)
                    |> YourClient.#{unquote(method)}

               {:ok, %Maxwell{
                      headers: reponse_headers_map,
                      status:  reponse_http_status_integer,
                      body:    reponse_body_term,
                      opts:    request_opts_keyword_list,
                      url:     request_urlwithquery_string,
                      query:   request_query_map
               }

          or
                {:error, {:conn_failed, {:error, :nxdomain}}}

          You can make asynchronous requests by passing `respond_to: pid` option:
                Maxwell.get(url: "http://example.org", respond_to: self)
                receive do
                  {:maxwell_response, res} -> res.status # => 200
                end
          """
        def unquote(method)(maxwell\\[])
        def unquote(method)(maxwell = %Maxwell{body: body})when is_nil(body) do
          %{maxwell| method: unquote(method)}
          |> call_middleware
        end
        def unquote(method)(maxwell)when is_list(maxwell) do
          url        = maxwell|> Keyword.get(:url, "")
          headers    = maxwell|> Keyword.get(:headers, %{})
          query      = maxwell|> Keyword.get(:query, %{})
          opts       = maxwell|> Keyword.get(:opts, [])

          opts =
          if respond_to = maxwell|> Keyword.get(:respond_to, nil) do
            [{:respond_to, respond_to} | opts]
          else
            opts
          end

          %Maxwell{
            method: unquote(method),
            headers: headers,
            opts: opts,
            url: Maxwell.Until.append_query_string(url, query)
          }
          |> call_middleware
        end
        @doc """
          Method without body: #{unquote(method_exception)}

          Receives `[url: url_string, headers: headers_map, query: query_map, opts: opts_keyword_list]` or `%Maxwell{}`

          Returns `%Maxwell{}` or raise `%MaxWell.Error{}`

          """
        def unquote(method_exception)(maxwell\\%Maxwell{})
        def unquote(method_exception)(maxwell) do
          case unquote(method)(maxwell) do
            {:ok, %Maxwell{} = result} ->
              result
            {:error, reason}  ->
              raise Maxwell.Error, value: reason,
                message: "method: #{unquote(method)} reason: #{inspect reason} url: #{maxwell.url}, module: #{__MODULE__}"
          end
        end
      end
    end

    method_defs_with_body = for {method_exception, method} <- @method_with_body, method in methods do
      quote location: :keep do
        @doc """
          Method: #{unquote(method)}

          Receives `[url: url_string, headers: headers_map, query: query_map, opts: opts_keyword_list, body: body_term]` or `%Maxwell{}`

          Returns `{:ok, %Maxwell{}}` or `{:error, reason}`
          ## Examples
               iex> url(request_url_string_or_char_list)
                    |> query(request_query_map)
                    |> headers(request_headers_map)
                    |> opts(request_opts_keyword_list)
                    |> body(request_body_term)
                    |> YourClient.#{unquote(method)}

               {:ok, %Maxwell{
                     headers: reponse_headers_map,
                     status:  reponse_http_status_integer,
                     body:    reponse_body_term,
                     opts:    request_opts_keyword_list
                     url:     request_urlwithquery_string,
               }
          or
               {:error, {:conn_failed, {:error, :timeout}}}
          If adapter supports it, you can make asynchronous requests by passing `respond_to: pid` option:
                Maxwell.get(url: "http://example.org", respond_to: self)
                receive do
                  {:maxwell_response, res} -> res.status # => 200
                end
          """
        def unquote(method)(maxwell\\%Maxwell{})
        def unquote(method)(maxwell = %Maxwell{}) do
          %{maxwell| method: unquote(method)}
          |> call_middleware
        end
        def unquote(method)(maxwell)when is_list(maxwell) do
          url        = maxwell |> Keyword.get(:url, "")
          headers    = maxwell |> Keyword.get(:headers, %{})
          query      = maxwell |> Keyword.get(:query, %{})
          opts       = maxwell |> Keyword.get(:opts, [])
          body       = maxwell |> Keyword.get(:body, %{})

          opts =
          if respond_to = maxwell|> Keyword.get(:respond_to, nil) do
            [{:respond_to, respond_to} | opts]
          else
            opts
          end

          body =
          if multipart = maxwell|> Keyword.get(:multipart, nil) do
            {:multipart, multipart}
          else
            body
          end

          %Maxwell{
            method: unquote(method),
            headers: headers,
            opts: opts,
            body: body,
            url: Maxwell.Until.append_query_string(url, query)
          }
          |> call_middleware
        end
        @doc """
          Method: #{unquote(method_exception)}

          Receives `[url: url_string, headers: headers_map, query: query_map, opts: opts_keyword_list, body: body_term]` or `%Maxwell{}`

          Return `%Maxwell{}` or raise `%Maxwell.Error{}`

          """
        def unquote(method_exception)(maxwell\\%Maxwell{})
        def unquote(method_exception)(maxwell) do
          case unquote(method)(maxwell) do
            {:ok, %Maxwell{} = result} ->
              result
            {:error, reason}  ->
              raise Maxwell.Error, value: reason,
                message: "method: #{unquote(method)} reason: #{inspect reason} url: #{maxwell.url}, module: #{__MODULE__}"
          end
        end
      end
    end

    help_methods =
      quote do
        def url(maxwell \\ %Maxwell{}, url)when is_binary(url) do
          %{maxwell| url: url}
        end
        def query(maxwell \\ %Maxwell{}, query)when is_map(query) do
          %{maxwell| url: Maxwell.Until.append_query_string(maxwell.url, query)}
        end
        def headers(maxwell \\ %Maxwell{}, headers)when is_map(headers) do
          %{maxwell| headers: Map.merge(maxwell.headers, headers)}
        end
        def opts(maxwell \\ %Maxwell{}, opts)when is_list(opts) do
          %{maxwell| opts: Keyword.merge(maxwell.opts, opts)}
        end
        def body(maxwell \\ %Maxwell{}, body) do
          %{maxwell| body: body}
        end
        def multipart(maxwell \\ %Maxwell{}, multipart) do
           %{maxwell| body: {:multipart, multipart}}
        end
        def respond_to(target_pid)when is_pid(target_pid) do
          respond_to(%Maxwell{}, target_pid)
        end
        def respond_to(%Maxwell{} = maxwell) do
          respond_to(maxwell, self)
        end
        def respond_to(maxwell, target_pid) do
          target_pid = unless target_pid, do: self, else: target_pid
          %{maxwell| opts: Keyword.merge(maxwell.opts, [{:respond_to, target_pid}])}
        end
      end

    quote do
      unquote(method_defs)
      unquote(method_defs_with_body)
      unquote(help_methods)

      import Maxwell.Middleware
      import Maxwell.Adapter

      Module.register_attribute(__MODULE__, :middleware, accumulate: true)
      @before_compile Maxwell.Builder
    end
  end

  defp generate_call_adapter(env) do
    case Module.get_attribute(env.module, :adapter) do
      nil ->
        quote do
          defp call_adapter(env) do
            Maxwell.Until.default_adapter.call(env) # default
          end
        end
      {:fn, _, _} = adapter ->
        quote do
          defp call_adapter(env) do
            unquote(adapter).(env)
          end
        end
      mod when is_atom(mod) ->
        quote do
          defp call_adapter(env) do
            unquote(mod).call(env)
          end
        end
       _ ->
         raise "Adapter must be Module or fn(env) -> env end"
    end
  end

  defp generate_call_middleware(env) do
    reduced =
      Module.get_attribute(env.module, :middleware)
      |> Enum.reduce(
         quote do
           call_adapter(env)
         end,
         fn({mid, args}, acc) ->
           args = Macro.escape(args)
           quote do
             unquote(mid).call(env, fn(env) -> unquote(acc) end, unquote(args))
           end
         end)

    quote do
      defp call_middleware(env) do
        unquote(reduced)
      end
    end
  end

  defmacro __before_compile__(env) do
    [
      generate_call_adapter(env),
      generate_call_middleware(env),
    ]
  end

end


Compile the target project and update the code path before benchmarking

defmodule Mix.Tasks.Bench do
  use Mix.Task

  @shortdoc "Benchmark your code"

  @moduledoc """
  ## Usage

      mix bench [options] [<path>...]

  When one or more arguments are supplied, each of them will be treated as a
  wildcard pattern and only those bench tests that match the pattern will be
  selected.

  By default, all files matching `bench/**/*_bench.exs` are run.

  The results of a test run are pretty-printed to the standard output.
  Additionally, the output in machine format is written to a snapshot file.

  ## Options

      -n, --no-pretty
          Instead of pretty-printing the output, print it in the format that
          can be parsed by bench.cmp and bench.graph.

      -q, --quiet
          Don't print progress report while the tests are running.

          Reports are printed to stderr so as not to interfere with output
          redirection.

      -d <duration>, --duration=<duration>
          Minimum duration of each test in seconds.

      -o <path>, --output=<path>
          Path to the directory in which to store snapshots. The directory will
          be created if necessary.

          Setting it to an empty value will prevent benchfella from creating
          any files or directories.

          Default: bench/snapshots.

      --no-compile
          Do not compile the target project before running benchmarks.

      -m, --mem-stats
          Gather memory usage statistics.

      --sys-mem-stats
          Gather system memory stats. Implies --mem-stats.

  """

  def run(args) do
    switches = [no_pretty: :boolean, quiet: :boolean, duration: :float,
                mem_stats: :boolean, sys_mem_stats: :boolean, output: :string,
                no_compile: :boolean]
    aliases = [n: :no_pretty, q: :quiet, d: :duration, m: :mem_stats, o: :output]
    {paths, options, no_compile} =
      case OptionParser.parse(args, strict: switches, aliases: aliases) do
        {opts, paths, []} -> {paths, opts}
        {_, _, [{opt, val}|_]} ->
          valstr = if val do "=#{val}" end
          Mix.raise "Invalid option: #{opt}#{valstr}"
      end
      |> normalize_options()

    prepare_mix_project(no_compile)

    Process.put(:"benchfella cli options", options)
    load_bench_files(paths)
  end

  defp prepare_mix_project(no_compile) do
    # Set up the target project's paths
    Mix.Project.get!
    unless no_compile, do: Mix.Task.run("compile", [])
    Mix.Task.run("loadpaths", [])
  end

  defp load_bench_files([]) do
    Path.wildcard("bench/**/*_bench.exs")
    |> do_load_bench_files
  end

  defp load_bench_files(paths) do
    Enum.flat_map(paths, &Path.wildcard/1)
    |> do_load_bench_files
  end

  defp do_load_bench_files([]), do: nil
  defp do_load_bench_files(files) do
    load_bench_helper()
    Kernel.ParallelRequire.files(files)
  end

  @helper_path "bench/bench_helper.exs"

  defp load_bench_helper() do
    if File.exists?(@helper_path) do
      Code.require_file(@helper_path)
    else
      Benchfella.start()
    end
  end

  defp normalize_options({paths, options}) do
    options =
      Enum.reduce(options, %{}, fn
        {:no_pretty, flag}, map -> Map.put(map, :format, pretty_to_format(!flag))
        {:quiet, flag}, map -> Map.put(map, :verbose, not flag)
        {:mem_stats, flag}, map -> Map.update(map, :mem_stats, flag, & &1)
        {:sys_mem_stats, true}, map -> Map.put(map, :mem_stats, :include_sys)
        {:sys_mem_stats, _}, map -> map
        {k, v}, map -> Map.put(map, k, v)
      end)
    {no_compile, options} = Map.pop(options, :no_compile)
    {paths, Enum.to_list(options), no_compile}
  end

  defp pretty_to_format(true), do: :pretty
  defp pretty_to_format(false), do: :machine
end

defmodule Mix.Tasks.Bench do
  use Mix.Task

  @shortdoc "Benchmark your code"

  @moduledoc """
  ## Usage

      mix bench [options] [<path>...]

  When one or more arguments are supplied, each of them will be treated as a
  wildcard pattern and only those bench tests that match the pattern will be
  selected.

  By default, all files matching `bench/**/*_bench.exs` are run.

  The results of a test run are pretty-printed to the standard output.
  Additionally, the output in machine format is written to a snapshot file.

  ## Options

      -n, --no-pretty
          Instead of pretty-printing the output, print it in the format that
          can be parsed by bench.cmp and bench.graph.

      -q, --quiet
          Don't print progress report while the tests are running.

          Reports are printed to stderr so as not to interfere with output
          redirection.

      -d <duration>, --duration=<duration>
          Minimum duration of each test in seconds.

      -o <path>, --output=<path>
          Path to the directory in which to store snapshots. The directory will
          be created if necessary.

          Setting it to an empty value will prevent benchfella from creating
          any files or directories.

          Default: bench/snapshots.

      -m, --mem-stats
          Gather memory usage statistics.

      --sys-mem-stats
          Gather system memory stats. Implies --mem-stats.

  """

  def run(args) do
    switches = [no_pretty: :boolean, quiet: :boolean, duration: :float,
                mem_stats: :boolean, sys_mem_stats: :boolean, output: :string]
    aliases = [n: :no_pretty, q: :quiet, d: :duration, m: :mem_stats, o: :output]
    {paths, options} =
      case OptionParser.parse(args, strict: switches, aliases: aliases) do
        {opts, paths, []} -> {paths, opts}
        {_, _, [{opt, val}|_]} ->
          valstr = if val do "=#{val}" end
          Mix.raise "Invalid option: #{opt}#{valstr}"
      end
      |> normalize_options()
    Process.put(:"benchfella cli options", options)
    load_bench_files(paths)
  end

  defp load_bench_files([]) do
    Path.wildcard("bench/**/*_bench.exs")
    |> do_load_bench_files
  end

  defp load_bench_files(paths) do
    Enum.flat_map(paths, &Path.wildcard/1)
    |> do_load_bench_files
  end

  defp do_load_bench_files([]), do: nil
  defp do_load_bench_files(files) do
    load_bench_helper()
    Kernel.ParallelRequire.files(files)
  end

  @helper_path "bench/bench_helper.exs"

  defp load_bench_helper() do
    if File.exists?(@helper_path) do
      Code.require_file(@helper_path)
    else
      Benchfella.start()
    end
  end

  defp normalize_options({paths, options}) do
    options =
      Enum.reduce(options, %{}, fn
        {:no_pretty, flag}, map -> Map.put(map, :format, pretty_to_format(!flag))
        {:quiet, flag}, map -> Map.put(map, :verbose, not flag)
        {:mem_stats, flag}, map -> Map.update(map, :mem_stats, flag, & &1)
        {:sys_mem_stats, true}, map -> Map.put(map, :mem_stats, :include_sys)
        {:sys_mem_stats, _}, map -> map
        {k, v}, map -> Map.put(map, k, v)
      end)
      |> Enum.to_list()
    {paths, options}
  end

  defp pretty_to_format(true), do: :pretty
  defp pretty_to_format(false), do: :machine
end