<commit_before>defmodule Phoenix.Controller do
  import Plug.Conn
  alias Plug.Conn.AlreadySentError

  require Logger
  require Phoenix.Endpoint

  @unsent [:unset, :set]

  @moduledoc """
  Controllers are used to group common functionality in the same
  (pluggable) module.

  For example, the route:

      get "/users/:id", MyApp.UserController, :show

  will invoke the `show/2` action in the `MyApp.UserController`:

      defmodule MyApp.UserController do
        use MyApp.Web, :controller

        def show(conn, %{"id" => id}) do
          user = Repo.get(User, id)
          render conn, "show.html", user: user
        end
      end

  An action is just a regular function that receives the connection
  and the request parameters as arguments. The connection is a
  `Plug.Conn` struct, as specified by the Plug library.

  ## Connection

  A controller by default provides many convenience functions for
  manipulating the connection, rendering templates, and more.

  Those functions are imported from two modules:

    * `Plug.Conn` - a bunch of low-level functions to work with
      the connection

    * `Phoenix.Controller` - functions provided by Phoenix
      to support rendering, and other Phoenix specific behaviour

  ## Rendering and layouts

  One of the main features provided by controllers is the ability
  to do content negotiation and render templates based on
  information sent by the client. Read `render/3` to learn more.

  It is also important to not confuse `Phoenix.Controller.render/3`
  with `Phoenix.View.render/3` in the long term. The former expects
  a connection and relies on content negotiation while the latter is
  connection-agnostic and typically invoked from your views.

  ## Plug pipeline

  As with routers, controllers also have their own plug pipeline.
  However, different from routers, controllers have a single pipeline:

      defmodule MyApp.UserController do
        use MyApp.Web, :controller

        plug :authenticate, usernames: ["jose", "eric", "sonny"]

        def show(conn, params) do
          # authenticated users only
        end

        defp authenticate(conn, options) do
          if get_session(conn, :username) in options[:usernames] do
            conn
          else
            conn |> redirect(Router.root_path) |> halt
          end
        end
      end

  Check `Phoenix.Controller.Pipeline` for more information on `plug/2`
  and how to customize the plug pipeline.

  ## Options

  When used, the controller supports the following options:

    * `:namespace` - sets the namespace to properly inflect
      the layout view. By default it uses the base alias
      in your controller name

    * `:log` - the level to log. When false, disables controller
      logging

  ## Overriding `action/2` for custom arguments

  Phoenix injects an `action/2` plug in your controller which calls the
  function matched from the router. By default, it passes the conn and params.
  In some cases, overriding the `action/2` plug in your controller is a
  useful way to inject certain argument to your actions that you
  would otherwise need to fetch off the connection repeatedly. For example,
  imagine if you stored a `conn.assigns.current_user` in the connection
  and wanted quick access to the user for every action in your controller:

      def action(conn, _) do
        apply(__MODULE__, action_name(conn), [conn,
                                              conn.params,
                                              conn.assigns.current_user])
      end

      def index(conn, _params, user) do
        videos = Repo.all(user_videos(user))
        # ...
      end

      def delete(conn, %{"id" => id}, user) do
        video = Repo.get!(user_videos(user), id)
        # ...
      end
  """
  defmacro __using__(opts) do
    quote bind_quoted: [opts: opts] do
      import Plug.Conn
      import Phoenix.Controller

      use Phoenix.Controller.Pipeline

      plug Phoenix.Controller.Logger, opts
      plug :put_new_layout, {Phoenix.Controller.__layout__(__MODULE__, opts), :app}
      plug :put_new_view, Phoenix.Controller.__view__(__MODULE__)
    end
  end

  @doc """
  Returns the action name as an atom, raises if unavailable.
  """
  @spec action_name(Plug.Conn.t) :: atom
  def action_name(conn), do: conn.private.phoenix_action

  @doc """
  Returns the controller module as an atom, raises if unavailable.
  """
  @spec controller_module(Plug.Conn.t) :: atom
  def controller_module(conn), do: conn.private.phoenix_controller

  @doc """
  Returns the router module as an atom, raises if unavailable.
  """
  @spec router_module(Plug.Conn.t) :: atom
  def router_module(conn), do: conn.private.phoenix_router

  @doc """
  Returns the endpoint module as an atom, raises if unavailable.
  """
  @spec endpoint_module(Plug.Conn.t) :: atom
  def endpoint_module(conn), do: conn.private.phoenix_endpoint

  @doc """
  Returns the template name rendered in the view as a string
  (or nil if no template was rendered).
  """
  @spec view_template(Plug.Conn.t) :: binary | nil
  def view_template(conn) do
    conn.private[:phoenix_template]
  end

  defp get_json_encoder do
    Application.get_env(:phoenix, :format_encoders)
    |> Keyword.get(:json, Poison)
  end

  @doc """
  Sends JSON response.

  It uses the configured `:format_encoders` under the `:phoenix`
  application for `:json` to pick up the encoder module.

  ## Examples

      iex> json conn, %{id: 123}

  """
  @spec json(Plug.Conn.t, term) :: Plug.Conn.t
  def json(conn, data) do
    encoder = get_json_encoder()

    send_resp(conn, conn.status || 200, "application/json", encoder.encode_to_iodata!(data))
  end

  @doc """
  A plug that may convert a JSON response into a JSONP one.

  In case a JSON response is returned, it will be converted
  to a JSONP as long as the callback field is present in
  the query string. The callback field itself defaults to
  "callback" but may be configured with the callback option.

  In case there is no callback or the response is not encoded
  in JSON format, it is a no-op.

  Only alphanumeric characters and underscore are allowed in the
  callback name. Otherwise an exception is raised.

  ## Examples

      # Will convert JSON to JSONP if callback=someFunction is given
      plug :allow_jsonp

      # Will convert JSON to JSONP if cb=someFunction is given
      plug :allow_jsonp, callback: "cb"

  """
  @spec allow_jsonp(Plug.Conn.t, Keyword.t) :: Plug.Conn.t
  def allow_jsonp(conn, opts \\ []) do
    callback = Keyword.get(opts, :callback, "callback")
    case Map.fetch(conn.query_params, callback) do
      :error    -> conn
      {:ok, ""} -> conn
      {:ok, cb} ->
        validate_jsonp_callback!(cb)
        register_before_send(conn, fn conn ->
          if json_response?(conn) do
            conn
            |> put_resp_header("content-type", "application/javascript")
            |> resp(conn.status, jsonp_body(conn.resp_body, cb))
          else
            conn
          end
        end)
    end
  end

  defp json_response?(conn) do
    case get_resp_header(conn, "content-type") do
      ["application/json;" <> _] -> true
      ["application/json"] -> true
      _ -> false
    end
  end

  defp jsonp_body(data, callback) do
    body =
      data
      |> IO.iodata_to_binary()
      |> String.replace(<<0x2028::utf8>>, "\\u2028")
      |> String.replace(<<0x2029::utf8>>, "\\u2029")

    "/**/ typeof #{callback} === 'function' && #{callback}(#{body});"
  end

  defp validate_jsonp_callback!(<<h, t::binary>>)
    when h in ?0..?9 or h in ?A..?Z or h in ?a..?z or h == ?_,
    do: validate_jsonp_callback!(t)
  defp validate_jsonp_callback!(<<>>), do: :ok
  defp validate_jsonp_callback!(_),
    do: raise(ArgumentError, "the JSONP callback name contains invalid characters")

  @doc """
  Sends text response.

  ## Examples

      iex> text conn, "hello"

      iex> text conn, :implements_to_string

  """
  @spec text(Plug.Conn.t, String.Chars.t) :: Plug.Conn.t
  def text(conn, data) do
    send_resp(conn, conn.status || 200, "text/plain", to_string(data))
  end

  @doc """
  Sends html response.

  ## Examples

      iex> html conn, "<html><head>..."

  """
  @spec html(Plug.Conn.t, iodata) :: Plug.Conn.t
  def html(conn, data) do
    send_resp(conn, conn.status || 200, "text/html", data)
  end

  @doc """
  Sends redirect response to the given url.

  For security, `:to` only accepts paths. Use the `:external`
  option to redirect to any URL.

  ## Examples

      iex> redirect conn, to: "/login"

      iex> redirect conn, external: "http://elixir-lang.org"

  """
  def redirect(conn, opts) when is_list(opts) do
    url  = url(opts)
    html = Plug.HTML.html_escape(url)
    body = "<html><body>You are being <a href=\"#{html}\">redirected</a>.</body></html>"

    conn
    |> put_resp_header("location", url)
    |> send_resp(conn.status || 302, "text/html", body)
  end

  defp url(opts) do
    cond do
      to = opts[:to] ->
        case to do
          "//" <> _ -> raise_invalid_url()
          "/" <> _  -> to
          _         -> raise_invalid_url()
        end
      external = opts[:external] ->
        external
      true ->
        raise ArgumentError, "expected :to or :external option in redirect/2"
    end
  end
  defp raise_invalid_url do
    raise ArgumentError, "the :to option in redirect expects a path"
  end

  @doc """
  Stores the view for rendering.

  Raises `Plug.Conn.AlreadySentError` if the conn was already sent.
  """
  @spec put_view(Plug.Conn.t, atom) :: Plug.Conn.t
  def put_view(%Plug.Conn{state: state} = conn, module) when state in @unsent do
    put_private(conn, :phoenix_view, module)
  end

  def put_view(_conn, _module), do: raise AlreadySentError

  @doc """
  Stores the view for rendering if one was not stored yet.

  Raises `Plug.Conn.AlreadySentError` if the conn was already sent.
  """
  @spec put_new_view(Plug.Conn.t, atom) :: Plug.Conn.t
  def put_new_view(%Plug.Conn{state: state} = conn, module)
      when state in @unsent do
    update_in conn.private, &Map.put_new(&1, :phoenix_view, module)
  end

  def put_new_view(_conn, _module) do
    raise Plug.Conn.AlreadySentError
  end

  @doc """
  Retrieves the current view.
  """
  @spec view_module(Plug.Conn.t) :: atom
  def view_module(conn) do
    conn.private.phoenix_view
  end

  @doc """
  Stores the layout for rendering.

  The layout must be a tuple, specifying the layout view and the layout
  name, or false. In case a previous layout is set, `put_layout` also
  accepts the layout name to be given as a string or as an atom. If a
  string, it must contain the format. Passing an atom means the layout
  format will be found at rendering time, similar to the template in
  `render/3`. It can also be set to `false`. In this case, no layout
  would be used.

  ## Examples

      iex> layout(conn)
      false

      iex> conn = put_layout conn, {AppView, "application.html"}
      iex> layout(conn)
      {AppView, "application.html"}

      iex> conn = put_layout conn, "print.html"
      iex> layout(conn)
      {AppView, "print.html"}

      iex> conn = put_layout :print
      iex> layout(conn)
      {AppView, :print}

  Raises `Plug.Conn.AlreadySentError` if the conn was already sent.
  """
  @spec put_layout(Plug.Conn.t, {atom, binary} | binary | false) :: Plug.Conn.t
  def put_layout(%Plug.Conn{state: state} = conn, layout) do
    if state in @unsent do
      _put_layout(conn, layout)
    else
      raise Plug.Conn.AlreadySentError
    end
  end

  def _put_layout(conn, layout)

  def _put_layout(conn, false) do
    put_private(conn, :phoenix_layout, false)
  end

  def _put_layout(conn, {mod, layout}) when is_atom(mod) do
    put_private(conn, :phoenix_layout, {mod, layout})
  end

  def _put_layout(conn, layout) when is_binary(layout) or is_atom(layout) do
    update_in conn.private, fn private ->
      case Map.get(private, :phoenix_layout, false) do
        {mod, _} -> Map.put(private, :phoenix_layout, {mod, layout})
        false    -> raise "cannot use put_layout/2 with atom/binary when layout is false, use a tuple instead"
      end
    end
  end

  @doc """
  Stores the layout for rendering if one was not stored yet.

  Raises `Plug.Conn.AlreadySentError` if the conn was already sent.
  """
  @spec put_new_layout(Plug.Conn.t, {atom, binary} | false) :: Plug.Conn.t
  def put_new_layout(%Plug.Conn{state: state} = conn, layout)
      when (is_tuple(layout) and tuple_size(layout) == 2) or layout == false do
    if state in @unsent do
      update_in conn.private, &Map.put_new(&1, :phoenix_layout, layout)
    else
      raise AlreadySentError
    end
  end

  @doc """
  Sets which formats have a layout when rendering.

  ## Examples

      iex> layout_formats conn
      ["html"]

      iex> put_layout_formats conn, ["html", "mobile"]
      iex> layout_formats conn
      ["html", "mobile"]

  Raises `Plug.Conn.AlreadySentError` if the conn was already sent.
  """
  @spec put_layout_formats(Plug.Conn.t, [String.t]) :: Plug.Conn.t
  def put_layout_formats(%Plug.Conn{state: state} = conn, formats)<commit_msg>Fix dialyzer warning in put_new_layout<commit_after>defmodule Phoenix.Controller do
  import Plug.Conn
  alias Plug.Conn.AlreadySentError

  require Logger
  require Phoenix.Endpoint

  @unsent [:unset, :set]

  @moduledoc """
  Controllers are used to group common functionality in the same
  (pluggable) module.

  For example, the route:

      get "/users/:id", MyApp.UserController, :show

  will invoke the `show/2` action in the `MyApp.UserController`:

      defmodule MyApp.UserController do
        use MyApp.Web, :controller

        def show(conn, %{"id" => id}) do
          user = Repo.get(User, id)
          render conn, "show.html", user: user
        end
      end

  An action is just a regular function that receives the connection
  and the request parameters as arguments. The connection is a
  `Plug.Conn` struct, as specified by the Plug library.

  ## Connection

  A controller by default provides many convenience functions for
  manipulating the connection, rendering templates, and more.

  Those functions are imported from two modules:

    * `Plug.Conn` - a bunch of low-level functions to work with
      the connection

    * `Phoenix.Controller` - functions provided by Phoenix
      to support rendering, and other Phoenix specific behaviour

  ## Rendering and layouts

  One of the main features provided by controllers is the ability
  to do content negotiation and render templates based on
  information sent by the client. Read `render/3` to learn more.

  It is also important to not confuse `Phoenix.Controller.render/3`
  with `Phoenix.View.render/3` in the long term. The former expects
  a connection and relies on content negotiation while the latter is
  connection-agnostic and typically invoked from your views.

  ## Plug pipeline

  As with routers, controllers also have their own plug pipeline.
  However, different from routers, controllers have a single pipeline:

      defmodule MyApp.UserController do
        use MyApp.Web, :controller

        plug :authenticate, usernames: ["jose", "eric", "sonny"]

        def show(conn, params) do
          # authenticated users only
        end

        defp authenticate(conn, options) do
          if get_session(conn, :username) in options[:usernames] do
            conn
          else
            conn |> redirect(Router.root_path) |> halt
          end
        end
      end

  Check `Phoenix.Controller.Pipeline` for more information on `plug/2`
  and how to customize the plug pipeline.

  ## Options

  When used, the controller supports the following options:

    * `:namespace` - sets the namespace to properly inflect
      the layout view. By default it uses the base alias
      in your controller name

    * `:log` - the level to log. When false, disables controller
      logging

  ## Overriding `action/2` for custom arguments

  Phoenix injects an `action/2` plug in your controller which calls the
  function matched from the router. By default, it passes the conn and params.
  In some cases, overriding the `action/2` plug in your controller is a
  useful way to inject certain argument to your actions that you
  would otherwise need to fetch off the connection repeatedly. For example,
  imagine if you stored a `conn.assigns.current_user` in the connection
  and wanted quick access to the user for every action in your controller:

      def action(conn, _) do
        apply(__MODULE__, action_name(conn), [conn,
                                              conn.params,
                                              conn.assigns.current_user])
      end

      def index(conn, _params, user) do
        videos = Repo.all(user_videos(user))
        # ...
      end

      def delete(conn, %{"id" => id}, user) do
        video = Repo.get!(user_videos(user), id)
        # ...
      end
  """
  defmacro __using__(opts) do
    quote bind_quoted: [opts: opts] do
      import Plug.Conn
      import Phoenix.Controller

      use Phoenix.Controller.Pipeline

      plug Phoenix.Controller.Logger, opts
      plug :put_new_layout, {Phoenix.Controller.__layout__(__MODULE__, opts), :app}
      plug :put_new_view, Phoenix.Controller.__view__(__MODULE__)
    end
  end

  @doc """
  Returns the action name as an atom, raises if unavailable.
  """
  @spec action_name(Plug.Conn.t) :: atom
  def action_name(conn), do: conn.private.phoenix_action

  @doc """
  Returns the controller module as an atom, raises if unavailable.
  """
  @spec controller_module(Plug.Conn.t) :: atom
  def controller_module(conn), do: conn.private.phoenix_controller

  @doc """
  Returns the router module as an atom, raises if unavailable.
  """
  @spec router_module(Plug.Conn.t) :: atom
  def router_module(conn), do: conn.private.phoenix_router

  @doc """
  Returns the endpoint module as an atom, raises if unavailable.
  """
  @spec endpoint_module(Plug.Conn.t) :: atom
  def endpoint_module(conn), do: conn.private.phoenix_endpoint

  @doc """
  Returns the template name rendered in the view as a string
  (or nil if no template was rendered).
  """
  @spec view_template(Plug.Conn.t) :: binary | nil
  def view_template(conn) do
    conn.private[:phoenix_template]
  end

  defp get_json_encoder do
    Application.get_env(:phoenix, :format_encoders)
    |> Keyword.get(:json, Poison)
  end

  @doc """
  Sends JSON response.

  It uses the configured `:format_encoders` under the `:phoenix`
  application for `:json` to pick up the encoder module.

  ## Examples

      iex> json conn, %{id: 123}

  """
  @spec json(Plug.Conn.t, term) :: Plug.Conn.t
  def json(conn, data) do
    encoder = get_json_encoder()

    send_resp(conn, conn.status || 200, "application/json", encoder.encode_to_iodata!(data))
  end

  @doc """
  A plug that may convert a JSON response into a JSONP one.

  In case a JSON response is returned, it will be converted
  to a JSONP as long as the callback field is present in
  the query string. The callback field itself defaults to
  "callback" but may be configured with the callback option.

  In case there is no callback or the response is not encoded
  in JSON format, it is a no-op.

  Only alphanumeric characters and underscore are allowed in the
  callback name. Otherwise an exception is raised.

  ## Examples

      # Will convert JSON to JSONP if callback=someFunction is given
      plug :allow_jsonp

      # Will convert JSON to JSONP if cb=someFunction is given
      plug :allow_jsonp, callback: "cb"

  """
  @spec allow_jsonp(Plug.Conn.t, Keyword.t) :: Plug.Conn.t
  def allow_jsonp(conn, opts \\ []) do
    callback = Keyword.get(opts, :callback, "callback")
    case Map.fetch(conn.query_params, callback) do
      :error    -> conn
      {:ok, ""} -> conn
      {:ok, cb} ->
        validate_jsonp_callback!(cb)
        register_before_send(conn, fn conn ->
          if json_response?(conn) do
            conn
            |> put_resp_header("content-type", "application/javascript")
            |> resp(conn.status, jsonp_body(conn.resp_body, cb))
          else
            conn
          end
        end)
    end
  end

  defp json_response?(conn) do
    case get_resp_header(conn, "content-type") do
      ["application/json;" <> _] -> true
      ["application/json"] -> true
      _ -> false
    end
  end

  defp jsonp_body(data, callback) do
    body =
      data
      |> IO.iodata_to_binary()
      |> String.replace(<<0x2028::utf8>>, "\\u2028")
      |> String.replace(<<0x2029::utf8>>, "\\u2029")

    "/**/ typeof #{callback} === 'function' && #{callback}(#{body});"
  end

  defp validate_jsonp_callback!(<<h, t::binary>>)
    when h in ?0..?9 or h in ?A..?Z or h in ?a..?z or h == ?_,
    do: validate_jsonp_callback!(t)
  defp validate_jsonp_callback!(<<>>), do: :ok
  defp validate_jsonp_callback!(_),
    do: raise(ArgumentError, "the JSONP callback name contains invalid characters")

  @doc """
  Sends text response.

  ## Examples

      iex> text conn, "hello"

      iex> text conn, :implements_to_string

  """
  @spec text(Plug.Conn.t, String.Chars.t) :: Plug.Conn.t
  def text(conn, data) do
    send_resp(conn, conn.status || 200, "text/plain", to_string(data))
  end

  @doc """
  Sends html response.

  ## Examples

      iex> html conn, "<html><head>..."

  """
  @spec html(Plug.Conn.t, iodata) :: Plug.Conn.t
  def html(conn, data) do
    send_resp(conn, conn.status || 200, "text/html", data)
  end

  @doc """
  Sends redirect response to the given url.

  For security, `:to` only accepts paths. Use the `:external`
  option to redirect to any URL.

  ## Examples

      iex> redirect conn, to: "/login"

      iex> redirect conn, external: "http://elixir-lang.org"

  """
  def redirect(conn, opts) when is_list(opts) do
    url  = url(opts)
    html = Plug.HTML.html_escape(url)
    body = "<html><body>You are being <a href=\"#{html}\">redirected</a>.</body></html>"

    conn
    |> put_resp_header("location", url)
    |> send_resp(conn.status || 302, "text/html", body)
  end

  defp url(opts) do
    cond do
      to = opts[:to] ->
        case to do
          "//" <> _ -> raise_invalid_url()
          "/" <> _  -> to
          _         -> raise_invalid_url()
        end
      external = opts[:external] ->
        external
      true ->
        raise ArgumentError, "expected :to or :external option in redirect/2"
    end
  end
  defp raise_invalid_url do
    raise ArgumentError, "the :to option in redirect expects a path"
  end

  @doc """
  Stores the view for rendering.

  Raises `Plug.Conn.AlreadySentError` if the conn was already sent.
  """
  @spec put_view(Plug.Conn.t, atom) :: Plug.Conn.t
  def put_view(%Plug.Conn{state: state} = conn, module) when state in @unsent do
    put_private(conn, :phoenix_view, module)
  end

  def put_view(_conn, _module), do: raise AlreadySentError

  @doc """
  Stores the view for rendering if one was not stored yet.

  Raises `Plug.Conn.AlreadySentError` if the conn was already sent.
  """
  @spec put_new_view(Plug.Conn.t, atom) :: Plug.Conn.t
  def put_new_view(%Plug.Conn{state: state} = conn, module)
      when state in @unsent do
    update_in conn.private, &Map.put_new(&1, :phoenix_view, module)
  end

  def put_new_view(_conn, _module) do
    raise Plug.Conn.AlreadySentError
  end

  @doc """
  Retrieves the current view.
  """
  @spec view_module(Plug.Conn.t) :: atom
  def view_module(conn) do
    conn.private.phoenix_view
  end

  @doc """
  Stores the layout for rendering.

  The layout must be a tuple, specifying the layout view and the layout
  name, or false. In case a previous layout is set, `put_layout` also
  accepts the layout name to be given as a string or as an atom. If a
  string, it must contain the format. Passing an atom means the layout
  format will be found at rendering time, similar to the template in
  `render/3`. It can also be set to `false`. In this case, no layout
  would be used.

  ## Examples

      iex> layout(conn)
      false

      iex> conn = put_layout conn, {AppView, "application.html"}
      iex> layout(conn)
      {AppView, "application.html"}

      iex> conn = put_layout conn, "print.html"
      iex> layout(conn)
      {AppView, "print.html"}

      iex> conn = put_layout :print
      iex> layout(conn)
      {AppView, :print}

  Raises `Plug.Conn.AlreadySentError` if the conn was already sent.
  """
  @spec put_layout(Plug.Conn.t, {atom, binary} | binary | false) :: Plug.Conn.t
  def put_layout(%Plug.Conn{state: state} = conn, layout) do
    if state in @unsent do
      _put_layout(conn, layout)
    else
      raise Plug.Conn.AlreadySentError
    end
  end

  def _put_layout(conn, layout)

  def _put_layout(conn, false) do
    put_private(conn, :phoenix_layout, false)
  end

  def _put_layout(conn, {mod, layout}) when is_atom(mod) do
    put_private(conn, :phoenix_layout, {mod, layout})
  end

  def _put_layout(conn, layout) when is_binary(layout) or is_atom(layout) do
    update_in conn.private, fn private ->
      case Map.get(private, :phoenix_layout, false) do
        {mod, _} -> Map.put(private, :phoenix_layout, {mod, layout})
        false    -> raise "cannot use put_layout/2 with atom/binary when layout is false, use a tuple instead"
      end
    end
  end

  @doc """
  Stores the layout for rendering if one was not stored yet.

  Raises `Plug.Conn.AlreadySentError` if the conn was already sent.
  """
  @spec put_new_layout(Plug.Conn.t, {atom, binary | atom} | false) :: Plug.Conn.t
  def put_new_layout(%Plug.Conn{state: state} = conn, layout)
      when (is_tuple(layout) and tuple_size(layout) == 2) or layout == false do
    if state in @unsent do
      update_in conn.private, &Map.put_new(&1, :phoenix_layout, layout)
    else
      raise AlreadySentError
    end
  end

  @doc """
  Sets which formats have a layout when rendering.

  ## Examples

      iex> layout_formats conn
      ["html"]

      iex> put_layout_formats conn, ["html", "mobile"]
      iex> layout_formats conn
      ["html", "mobile"]

  Raises `Plug.Conn.AlreadySentError` if the conn was already sent.
  """
  @spec put_layout_formats(Plug.Conn.t, [String.t]) :: Plug.Conn.t
  def put_layout_formats(%Plug.Conn{state: state} = conn, formats)
{'fraction_non_alphanumeric': 0.10293112612954666, 'fraction_numerical': 0.003872731414686005, 'mean_word_length': 2.8865279622251734, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 129, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7556599', 'n_tokens_mistral': 8395, 'n_tokens_neox': 7989, 'n_words': 2786}