Add --output to mix firmware to specify .fw path

defmodule Mix.Tasks.Firmware do
  use Mix.Task
  import Mix.Nerves.Utils
  alias Mix.Nerves.Preflight

  @shortdoc "Build a firmware bundle"

  @moduledoc """
  Build a firmware image for the selected target platform.

  This task builds the project, combines the generated OTP release with
  a Nerves system image, and creates a `.fw` file that may be written
  to an SDCard or sent to a device.

  ## Command line options

    * `--verbose` - produce detailed output about release assembly
    * `--output` - (Optional) The path to the .fw file used to write the patch
      firmware. Defaults to `Nerves.Env.firmware_path/1`
  ## Environment variables

    * `NERVES_SYSTEM`    - may be set to a local directory to specify the Nerves
      system image that is used

    * `NERVES_TOOLCHAIN` - may be set to a local directory to specify the
      Nerves toolchain (C/C++ crosscompiler) that is used
  """

  @switches [verbose: :boolean, output: :string]

  @impl true
  def run(args) do
    Preflight.check!()
    debug_info("Nerves Firmware Assembler")

    {opts, _, _} = OptionParser.parse(args, switches: @switches)

    system_path = check_nerves_system_is_set!()

    check_nerves_toolchain_is_set!()

    # Check for required files
    if use_distillery?() do
      rel_config =
        File.cwd!()
        |> Path.join("rel/config.exs")

      if !File.exists?(rel_config) do
        Mix.raise("""
          You are missing a release config file. Run  nerves.release.init task first
        """)
      end
    else
      vm_args =
        File.cwd!()
        |> Path.join("rel/vm.args.eex")

      if !File.exists?(vm_args) do
        Mix.raise("""
          rel/vm.args needs to be moved to rel/vm.args.eex
        """)
      end
    end

    # By this point, paths have already been loaded.
    # We just want to ensure any custom systems are compiled
    # via the precompile checks
    Mix.Task.run("nerves.precompile", ["--no-loadpaths"])
    Mix.Task.run("compile", [])

    Mix.Nerves.IO.shell_info("Building OTP Release...")

    if use_distillery?() do
      clean_distillery_release(opts)
      build_distillery_release(opts)
    else
      build_release()
    end

    config = Mix.Project.config()
    fw_out = opts[:out] || Nerves.Env.firmware_path(config)
    build_firmware(config, system_path, fw_out)
  end

  @doc false
  def result({_, 0}), do: nil

  def result({result, _}),
    do:
      Mix.raise("""
      Nerves encountered an error. #{inspect(result)}
      """)

  defp clean_distillery_release(opts) do
    verbosity = if opts[:verbose], do: "--verbose", else: "--silent"

    try do
      Mix.Task.run("distillery.release.clean", [verbosity, "--implode", "--no-confirm"])
    catch
      :exit, _ -> :noop
    end
  end

  defp build_release() do
    Mix.Task.run("release", [])
  end

  defp build_distillery_release(opts) do
    verbosity = if opts[:verbose], do: "--verbose", else: "--quiet"
    Mix.Task.run("distillery.release", [verbosity, "--no-tar"])
  end

  defp build_firmware(config, system_path, fw_out) do
    otp_app = config[:app]
    compiler_check()
    firmware_config = Application.get_env(:nerves, :firmware)

    rootfs_priorities =
      Nerves.Env.package(:nerves_system_br)
      |> rootfs_priorities()

    rel2fw_path = Path.join(system_path, "scripts/rel2fw.sh")
    cmd = "bash"
    args = [rel2fw_path]

    if firmware_config[:rootfs_additions] do
      Mix.shell().error(
        "The :rootfs_additions configuration option has been deprecated. Please use :rootfs_overlay instead."
      )
    end

    build_rootfs_overlay = Path.join([Mix.Project.build_path(), "nerves", "rootfs_overlay"])
    File.mkdir_p(build_rootfs_overlay)

    write_erlinit_config(build_rootfs_overlay)

    project_rootfs_overlay =
      case firmware_config[:rootfs_overlay] || firmware_config[:rootfs_additions] do
        nil ->
          []

        overlays when is_list(overlays) ->
          overlays

        overlay ->
          [Path.expand(overlay)]
      end

    rootfs_overlays =
      [build_rootfs_overlay | project_rootfs_overlay]
      |> Enum.map(&["-a", &1])
      |> List.flatten()

    fwup_conf =
      case firmware_config[:fwup_conf] do
        nil -> []
        fwup_conf -> ["-c", Path.join(File.cwd!(), fwup_conf)]
      end

    fw = ["-f", fw_out]
    release_path = Path.join(Mix.Project.build_path(), "rel/#{otp_app}")
    output = [release_path]
    args = args ++ fwup_conf ++ rootfs_overlays ++ fw ++ rootfs_priorities ++ output
    env = standard_fwup_variables(config)

    set_provisioning(firmware_config[:provisioning])

    config
    |> Nerves.Env.images_path()
    |> File.mkdir_p()

    shell(cmd, args, env: env)
    |> result
  end

  defp standard_fwup_variables(config) do
    # Assuming the fwup.conf file respects these variable like the official
    # systems do, this will set the .fw metadata to what's in the mix.exs.
    [
      {"NERVES_FW_VERSION", config[:version]},
      {"NERVES_FW_PRODUCT", config[:name] || to_string(config[:app])},
      {"NERVES_FW_DESCRIPTION", config[:description]},
      {"NERVES_FW_AUTHOR", config[:author]}
    ]
  end

  # Need to check min version for nerves_system_br to check if passing the
  # rootfs priorities option is supported. This was added in the 1.7.1 release
  # https://github.com/nerves-project/nerves_system_br/releases/tag/v1.7.1
  defp rootfs_priorities(%Nerves.Package{app: :nerves_system_br, version: vsn}) do
    case Version.compare(vsn, "1.7.1") do
      r when r in [:gt, :eq] ->
        rootfs_priorities_file =
          Path.join([Mix.Project.build_path(), "nerves", "rootfs.priorities"])

        if File.exists?(rootfs_priorities_file) do
          ["-p", rootfs_priorities_file]
        else
          []
        end

      _ ->
        []
    end
  end

  defp rootfs_priorities(_), do: []

  defp compiler_check() do
    with {:ok, otpc} <- module_compiler_version(:code),
         {:ok, otp_requirement} <- Version.parse_requirement("~> #{otpc.major}.#{otpc.minor}"),
         {:ok, elixirc} <- module_compiler_version(Kernel) do
      unless Version.match?(elixirc, otp_requirement) do
        Mix.raise("""
        The Erlang compiler that compiled Elixir is older than the compiler
        used to compile OTP.

        Elixir: #{elixirc.major}.#{elixirc.minor}.#{elixirc.patch}
        OTP:    #{otpc.major}.#{otpc.minor}.#{otpc.patch}

        Please use a version of Elixir that was compiled using the same major
        version of OTP.

        For example:

        If your target is running OTP 22, you should use a version of Elixir
        that was compiled using OTP 22.

        If you're using asdf to manage Elixir versions, run:

        asdf install elixir #{System.version()}-otp-#{system_otp_release()}
        asdf global elixir #{System.version()}-otp-#{system_otp_release()}
        """)
      end
    else
      error ->
        Mix.raise("""
        Nerves was unable to verify the Erlang compiler version.
        Error: #{error}
        """)
    end
  end

  def module_compiler_version(mod) do
    with {:file, path} <- :code.is_loaded(mod),
         {:ok, {_, [compile_info: compile_info]}} <- :beam_lib.chunks(path, [:compile_info]),
         {:ok, vsn} <- Keyword.fetch(compile_info, :version),
         vsn <- to_string(vsn) do
      parse_version(vsn)
    end
  end

  def system_otp_release do
    :erlang.system_info(:otp_release)
    |> to_string()
  end

  defp write_erlinit_config(build_overlay) do
    with user_opts <- Application.get_env(:nerves, :erlinit, []),
         {:ok, system_config_file} <- Nerves.Erlinit.system_config_file(Nerves.Env.system()),
         {:ok, system_config_file} <- File.read(system_config_file),
         system_opts <- Nerves.Erlinit.decode_config(system_config_file),
         erlinit_opts <- Nerves.Erlinit.merge_opts(system_opts, user_opts),
         erlinit_config <- Nerves.Erlinit.encode_config(erlinit_opts) do
      erlinit_config_file = Path.join(build_overlay, "etc/erlinit.config")

      Path.dirname(erlinit_config_file)
      |> File.mkdir_p()

      header = erlinit_config_header(user_opts)

      File.write(erlinit_config_file, header <> erlinit_config)
      {:ok, erlinit_config_file}
    else
      {:error, :no_config} ->
        Nerves.Utils.Shell.warn("There was no system erlinit.config found")
        :noop

      _e ->
        :noop
    end
  end

  def erlinit_config_header(opts) do
    """
    # Generated from rootfs_overlay/etc/erlinit.config
    """ <>
      if opts != [] do
        """
        # with overrides from the application config
        """
      else
        """
        """
      end
  end
end

defmodule Mix.Tasks.Firmware do
  use Mix.Task
  import Mix.Nerves.Utils
  alias Mix.Nerves.Preflight

  @shortdoc "Build a firmware bundle"

  @moduledoc """
  Build a firmware image for the selected target platform.

  This task builds the project, combines the generated OTP release with
  a Nerves system image, and creates a `.fw` file that may be written
  to an SDCard or sent to a device.

  ## Command line options

    * `--verbose` - produce detailed output about release assembly

  ## Environment variables

    * `NERVES_SYSTEM`    - may be set to a local directory to specify the Nerves
      system image that is used

    * `NERVES_TOOLCHAIN` - may be set to a local directory to specify the
      Nerves toolchain (C/C++ crosscompiler) that is used
  """

  @switches [verbose: :boolean]

  @impl true
  def run(args) do
    Preflight.check!()
    debug_info("Nerves Firmware Assembler")

    {opts, _, _} = OptionParser.parse(args, switches: @switches)

    system_path = check_nerves_system_is_set!()

    check_nerves_toolchain_is_set!()

    # Check for required files
    if use_distillery?() do
      rel_config =
        File.cwd!()
        |> Path.join("rel/config.exs")

      if !File.exists?(rel_config) do
        Mix.raise("""
          You are missing a release config file. Run  nerves.release.init task first
        """)
      end
    else
      vm_args =
        File.cwd!()
        |> Path.join("rel/vm.args.eex")

      if !File.exists?(vm_args) do
        Mix.raise("""
          rel/vm.args needs to be moved to rel/vm.args.eex
        """)
      end
    end

    # By this point, paths have already been loaded.
    # We just want to ensure any custom systems are compiled
    # via the precompile checks
    Mix.Task.run("nerves.precompile", ["--no-loadpaths"])
    Mix.Task.run("compile", [])

    Mix.Nerves.IO.shell_info("Building OTP Release...")

    if use_distillery?() do
      clean_distillery_release(opts)
      build_distillery_release(opts)
    else
      build_release()
    end

    build_firmware(system_path)
  end

  @doc false
  def result({_, 0}), do: nil

  def result({result, _}),
    do:
      Mix.raise("""
      Nerves encountered an error. #{inspect(result)}
      """)

  defp clean_distillery_release(opts) do
    verbosity = if opts[:verbose], do: "--verbose", else: "--silent"

    try do
      Mix.Task.run("distillery.release.clean", [verbosity, "--implode", "--no-confirm"])
    catch
      :exit, _ -> :noop
    end
  end

  defp build_release() do
    Mix.Task.run("release", [])
  end

  defp build_distillery_release(opts) do
    verbosity = if opts[:verbose], do: "--verbose", else: "--quiet"
    Mix.Task.run("distillery.release", [verbosity, "--no-tar"])
  end

  defp build_firmware(system_path) do
    config = Mix.Project.config()
    otp_app = config[:app]

    compiler_check()
    firmware_config = Application.get_env(:nerves, :firmware)

    rootfs_priorities =
      Nerves.Env.package(:nerves_system_br)
      |> rootfs_priorities()

    rel2fw_path = Path.join(system_path, "scripts/rel2fw.sh")
    cmd = "bash"
    args = [rel2fw_path]

    if firmware_config[:rootfs_additions] do
      Mix.shell().error(
        "The :rootfs_additions configuration option has been deprecated. Please use :rootfs_overlay instead."
      )
    end

    build_rootfs_overlay = Path.join([Mix.Project.build_path(), "nerves", "rootfs_overlay"])
    File.mkdir_p(build_rootfs_overlay)

    write_erlinit_config(build_rootfs_overlay)

    project_rootfs_overlay =
      case firmware_config[:rootfs_overlay] || firmware_config[:rootfs_additions] do
        nil ->
          []

        overlays when is_list(overlays) ->
          overlays

        overlay ->
          [Path.expand(overlay)]
      end

    rootfs_overlays =
      [build_rootfs_overlay | project_rootfs_overlay]
      |> Enum.map(&["-a", &1])
      |> List.flatten()

    fwup_conf =
      case firmware_config[:fwup_conf] do
        nil -> []
        fwup_conf -> ["-c", Path.join(File.cwd!(), fwup_conf)]
      end

    fw = ["-f", Nerves.Env.firmware_path(config)]
    release_path = Path.join(Mix.Project.build_path(), "rel/#{otp_app}")
    output = [release_path]
    args = args ++ fwup_conf ++ rootfs_overlays ++ fw ++ rootfs_priorities ++ output
    env = standard_fwup_variables(config)

    set_provisioning(firmware_config[:provisioning])

    config
    |> Nerves.Env.images_path()
    |> File.mkdir_p()

    shell(cmd, args, env: env)
    |> result
  end

  defp standard_fwup_variables(config) do
    # Assuming the fwup.conf file respects these variable like the official
    # systems do, this will set the .fw metadata to what's in the mix.exs.
    [
      {"NERVES_FW_VERSION", config[:version]},
      {"NERVES_FW_PRODUCT", config[:name] || to_string(config[:app])},
      {"NERVES_FW_DESCRIPTION", config[:description]},
      {"NERVES_FW_AUTHOR", config[:author]}
    ]
  end

  # Need to check min version for nerves_system_br to check if passing the
  # rootfs priorities option is supported. This was added in the 1.7.1 release
  # https://github.com/nerves-project/nerves_system_br/releases/tag/v1.7.1
  defp rootfs_priorities(%Nerves.Package{app: :nerves_system_br, version: vsn}) do
    case Version.compare(vsn, "1.7.1") do
      r when r in [:gt, :eq] ->
        rootfs_priorities_file =
          Path.join([Mix.Project.build_path(), "nerves", "rootfs.priorities"])

        if File.exists?(rootfs_priorities_file) do
          ["-p", rootfs_priorities_file]
        else
          []
        end

      _ ->
        []
    end
  end

  defp rootfs_priorities(_), do: []

  defp compiler_check() do
    with {:ok, otpc} <- module_compiler_version(:code),
         {:ok, otp_requirement} <- Version.parse_requirement("~> #{otpc.major}.#{otpc.minor}"),
         {:ok, elixirc} <- module_compiler_version(Kernel) do
      unless Version.match?(elixirc, otp_requirement) do
        Mix.raise("""
        The Erlang compiler that compiled Elixir is older than the compiler
        used to compile OTP.

        Elixir: #{elixirc.major}.#{elixirc.minor}.#{elixirc.patch}
        OTP:    #{otpc.major}.#{otpc.minor}.#{otpc.patch}

        Please use a version of Elixir that was compiled using the same major
        version of OTP.

        For example:

        If your target is running OTP 22, you should use a version of Elixir
        that was compiled using OTP 22.

        If you're using asdf to manage Elixir versions, run:

        asdf install elixir #{System.version()}-otp-#{system_otp_release()}
        asdf global elixir #{System.version()}-otp-#{system_otp_release()}
        """)
      end
    else
      error ->
        Mix.raise("""
        Nerves was unable to verify the Erlang compiler version.
        Error: #{error}
        """)
    end
  end

  def module_compiler_version(mod) do
    with {:file, path} <- :code.is_loaded(mod),
         {:ok, {_, [compile_info: compile_info]}} <- :beam_lib.chunks(path, [:compile_info]),
         {:ok, vsn} <- Keyword.fetch(compile_info, :version),
         vsn <- to_string(vsn) do
      parse_version(vsn)
    end
  end

  def system_otp_release do
    :erlang.system_info(:otp_release)
    |> to_string()
  end

  defp write_erlinit_config(build_overlay) do
    with user_opts <- Application.get_env(:nerves, :erlinit, []),
         {:ok, system_config_file} <- Nerves.Erlinit.system_config_file(Nerves.Env.system()),
         {:ok, system_config_file} <- File.read(system_config_file),
         system_opts <- Nerves.Erlinit.decode_config(system_config_file),
         erlinit_opts <- Nerves.Erlinit.merge_opts(system_opts, user_opts),
         erlinit_config <- Nerves.Erlinit.encode_config(erlinit_opts) do
      erlinit_config_file = Path.join(build_overlay, "etc/erlinit.config")

      Path.dirname(erlinit_config_file)
      |> File.mkdir_p()

      header = erlinit_config_header(user_opts)

      File.write(erlinit_config_file, header <> erlinit_config)
      {:ok, erlinit_config_file}
    else
      {:error, :no_config} ->
        Nerves.Utils.Shell.warn("There was no system erlinit.config found")
        :noop

      _e ->
        :noop
    end
  end

  def erlinit_config_header(opts) do
    """
    # Generated from rootfs_overlay/etc/erlinit.config
    """ <>
      if opts != [] do
        """
        # with overrides from the application config
        """
      else
        """
        """
      end
  end
end


Optimize ensure content type implementation

defmodule Phoenix.Controller do
  import Plug.Conn

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

  For example, the route:

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

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

      defmodule UserController do
        use Phoenix.Controller

        plug :action

        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 routers, controllers also have their own plug pipeline. However,
  different from routers, controllers have a single pipeline:

      defmodule UserController do
        use Phoenix.Controller

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

        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

  The `:action` plug must always be invoked and it represents the action
  to be dispatched to.

  Check `Phoenix.Controller.Pipeline` for more information on `plug/2`
  and how to customize the plug pipeline.
  """
  defmacro __using__(_options) do
    quote do
      import Plug.Conn
      import Phoenix.Controller

      use Phoenix.Controller.Pipeline

      plug Phoenix.Controller.Logger
      plug :put_new_layout, {Phoenix.Controller.__layout__(__MODULE__), :application}
      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 """
  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 =
      Application.get_env(:phoenix, :format_encoders)
      |> Keyword.get(:json, Poison)

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

  @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, "..."

  """
  @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)
    {:safe, html} = Phoenix.HTML.html_escape(url)
    body = "You are being redirected."

    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
          "/" <> _ -> to
          _        -> raise ArgumentError, "the :to option in redirect expects a path"
        end
      external = opts[:external] ->
        external
      true ->
        raise ArgumentError, "expected :to or :external option in redirect/2"
    end
  end

  @doc """
  Stores the view for rendering.
  """
  @spec put_view(Plug.Conn.t, atom) :: Plug.Conn.t
  def put_view(conn, module) do
    put_private(conn, :phoenix_view, module)
  end

  @doc """
  Stores the view for rendering if one was not stored yet.
  """
  @spec put_new_view(Plug.Conn.t, atom) :: Plug.Conn.t
  def put_new_view(conn, module) do
    update_in conn.private, &Map.put_new(&1, :phoenix_view, module)
  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`.

  ## Examples

      iex> layout(conn)
      false

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

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

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

  """
  @spec put_layout(Plug.Conn.t, {atom, binary} | binary | false) :: Plug.Conn.t
  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.
  """
  @spec put_new_layout(Plug.Conn.t, {atom, binary} | false) :: Plug.Conn.t
  def put_new_layout(conn, layout)
      when tuple_size(layout) == 2
      when layout == false do
    update_in conn.private, &Map.put_new(&1, :phoenix_layout, layout)
  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"]

  """
  @spec put_layout_formats(Plug.Conn.t, [String.t]) :: Plug.Conn.t
  def put_layout_formats(conn, formats) when is_list(formats) do
    put_private(conn, :phoenix_layout_formats, formats)
  end

  @doc """
  Retrieves current layout formats.
  """
  @spec layout_formats(Plug.Conn.t) :: [String.t]
  def layout_formats(conn) do
    Map.get(conn.private, :phoenix_layout_formats, ~w(html))
  end

  @doc """
  Retrieves the current layout.
  """
  @spec layout(Plug.Conn.t) :: {atom, String.t} | false
  def layout(conn), do: conn.private |> Map.get(:phoenix_layout, false)

  @doc """
  Render the given template or the default template
  specified by the current action with the given assigns.

  See `render/3` for more information.
  """
  @spec render(Plug.Conn.t, Dict.t | binary | atom) :: Plug.Conn.t
  def render(conn, template_or_assigns \\ [])

  def render(conn, template) when is_binary(template) or is_atom(template) do
    render(conn, template, [])
  end

  def render(conn, assigns) do
    render(conn, action_name(conn), assigns)
  end

  @doc """
  Renders the given `template` and `assigns` based on the `conn` information.

  Once the template is rendered, the template format is set as the response
  content type (for example, an HTML template will set "text/html" as response
  content type) and the data is sent to the client with default status of 200.

  ## Arguments

    * `conn` - the `Plug.Conn` struct

    * `template` - which may be an atom or a string. If an atom, like `:index`,
      it will render a template with the same format as the one found in
      `conn.params["format"]`. For example, for an HTML request, it will render
      the "index.html" template. If the template is a string, it must contain
      the extension too, like "index.json"

    * `assigns` - a dictionary with the assigns to be used in the view. Those
      assigns are merged and have higher precedence than the connection assigns
      (`conn.assigns`)

  ## Examples

      defmodule MyApp.UserController do
        use Phoenix.Controller

        plug :action

        def show(conn) do
          render conn, "show.html", message: "Hello"
        end
      end

  The example above renders a template "show.html" from the `MyApp.UserView`
  and sets the response content type to "text/html".

  In many cases, you may want the template format to be set dynamically based
  on the request. To do so, you can pass the template name as an atom (without
  the extension):

      def show(conn) do
        render conn, :show, message: "Hello"
      end

  In order for the example above to work, we need to do content negotiation with
  the accepts plug before rendering. You can do so by adding the following to your
  pipeline (in the router):

      plug :accepts, ["html"]

  ## Views

  By default, Controllers render templates in a view with a similar name to the
  controller. For example, `MyApp.UserController` will render templates inside
  the `MyApp.UserView`. This information can be changed any time by using the
  `put_view/2` function:

      def show(conn) do
        conn
        |> put_view(MyApp.SpecialView)
        |> render(:show, message: "Hello")
      end
.
  `put_view/2` can also be used as a plug:

      defmodule MyApp.UserController do
        use Phoenix.Controller

        plug :put_view, MyApp.SpecialView
        plug :action

        def show(conn) do
          render conn, :show, message: "Hello"
        end
      end

  ## Layouts

  Templates are often rendered inside layouts. By default, Phoenix
  will render layouts for html requests. For example:

      defmodule MyApp.UserController do
        use Phoenix.Controller

        plug :action

        def show(conn) do
          render conn, "show.html", message: "Hello"
        end
      end

  will render the  "show.html" template inside an "application.html"
  template specified in `MyApp.LayoutView`. `put_layout/2` can be used
  to change the layout, similar to how `put_view/2` can be used to change
  the view.

  `layout_formats/2` and `put_layout_formats/2` can be used to configure
  which formats support/require layout rendering (defaults to "html" only).
  """
  @spec render(Plug.Conn.t, binary | atom, Dict.t) :: Plug.Conn.t
  def render(conn, template, assigns) when is_atom(template) do
    format =
      conn.params["format"] ||
      raise "cannot render template #{inspect template} because conn.params[\"format\"] is not set. " <>
            "Please set `plug :accepts, %w(html json ...)` in your pipeline."
    render(conn, template_name(template, format), format, assigns)
  end

  def render(conn, template, assigns) when is_binary(template) do
    case Path.extname(template) do
      "." <> format ->
        render(conn, template, format, assigns)
      "" ->
        raise "cannot render template #{inspect template} without format. Use an atom if the " <>
              "template format is meant to be set dynamically based on the request format"
    end
  end

  def render(conn, template, format, assigns) do
    assigns = to_map(assigns)
    content_type = Plug.MIME.type(format)
    conn = prepare_assigns(conn, assigns, format)
    view = Map.get(conn.private, :phoenix_view) ||
            raise "a view module was not specified, set one with put_view/2"
    data = Phoenix.View.render_to_iodata(view, template,
                                         Map.put(conn.assigns, :conn, conn))
    send_resp(conn, conn.status || 200, content_type, data)
  end

  @doc """
  Scrubs the parameters from the request.

  This process is two-fold:

    * Checks to see if the `required_key` is present
    * Changes empty parameters of `required_key` (recursively) to nils

  This function is useful to remove empty strings sent
  via HTML forms. If you are providing an API, there
  is likely no need to invoke `scrub_params/2`.

  If the `required_key` is not present, it will
  raise `Phoenix.MissingParamError`.

  ## Examples

      iex> scrub_params(conn, "user")

  """
  @spec scrub_params(Plug.Conn.t, [String.t]) :: Plug.Conn.t
  def scrub_params(conn, required_key) when is_binary(required_key) do
    param = Map.get(conn.params, required_key) |> scrub_param()

    unless param do
      raise Phoenix.MissingParamError, key: required_key
    end

    params = Map.put(conn.params, required_key, param)
    %{conn | params: params}
  end

  defp scrub_param(%{__struct__: mod} = struct) when is_atom(mod) do
    struct
  end
  defp scrub_param(%{} = param) do
    Enum.reduce(param, %{}, fn({k, v}, acc) ->
      Map.put(acc, k, scrub_param(v))
    end)
  end
  defp scrub_param(param) when is_list(param) do
    Enum.map(param, &scrub_param/1)
  end
  defp scrub_param(param) do
    if scrub?(param), do: nil, else: param
  end

  defp scrub?(" " <> rest), do: scrub?(rest)
  defp scrub?(""), do: true
  defp scrub?(_), do: false

  defp prepare_assigns(conn, assigns, format) do
    layout =
      case layout(conn, assigns, format) do
        {mod, layout} -> {mod, template_name(layout, format)}
        false -> false
      end

    update_in conn.assigns,
              & &1 |> Map.merge(assigns) |> Map.put(:layout, layout)
  end

  defp layout(conn, assigns, format) do
    if format in layout_formats(conn) do
      case Map.fetch(assigns, :layout) do
        {:ok, layout} -> layout
        :error -> layout(conn)
      end
    else
      false
    end
  end

  defp to_map(assigns) when is_map(assigns), do: assigns
  defp to_map(assigns) when is_list(assigns), do: :maps.from_list(assigns)
  defp to_map(assigns), do: Dict.merge(%{}, assigns)

  defp template_name(name, format) when is_atom(name), do:
    Atom.to_string(name) <> "." <> format
  defp template_name(name, _format) when is_binary(name), do:
    name

  defp send_resp(conn, default_status, default_content_type, body) do
    conn
    |> ensure_resp_content_type(default_content_type)
    |> send_resp(conn.status || default_status, body)
  end

  defp ensure_resp_content_type(%{resp_headers: resp_headers} = conn, content_type) do
    if List.keyfind(resp_headers, "content-type", 0) do
      conn
    else
      content_type = content_type <> "; charset=utf-8"
      %{conn | resp_headers: [{"content-type", content_type}|resp_headers]}
    end
  end

  @doc """
  Performs content negotiation based on the available formats.

  It receives a connection, a list of formats that the server
  is capable of rendering and then proceeds to perform content
  negotiation based on the request information. If the client
  accepts any of the given formats, the request proceeds.

  If the request contains a "format" parameter, it is
  considered to be the format desired by the client. If no
  "format" parameter is available, this function will parse
  the "accept" header and find a matching format accordingly.

  It is important to notice that browsers have historically
  sent bad accept headers. For this reason, this function will
  default to "html" format whenever:

    * the accepted list of arguments contains the "html" format

    * the accept header specified more than one media type preceeded
      or followed by the wildcard media type "*/*"

  This function raises `Phoenix.NotAcceptableError`, which is rendered
  with status 406, whenever the server cannot serve a response in any
  of the formats expected by the client.

  ## Examples

  `accepts/2` can be invoked as a function:

      iex> accepts(conn, ["html", "json"])

  or used as a plug:

      plug :accepts, ["html", "json"]
      plug :accepts, ~w(html json)

  """
  @spec accepts(Plug.Conn.t, [binary]) :: Plug.Conn.t | no_return
  def accepts(conn, [_|_] = accepted) do
    case Map.fetch conn.params, "format" do
      {:ok, format} ->
        handle_params_accept(conn, format, accepted)
      :error ->
        handle_header_accept(conn, get_req_header(conn, "accept"), accepted)
    end
  end

  @doc """
  Enables CSRF protection.

  Currently used as a wrapper function for `Plug.CSRFProtection`
  and mainly serves as a function plug in `YourApp.Router`.

  Check `get_csrf_token/0` and `delete_csrf_token/0` for
  retrieving and deleting CSRF tokens.
  """
  def protect_from_forgery(conn, opts \\ []) do
    Plug.CSRFProtection.call(conn, opts)
  end

  @doc """
  Gets the CSRF token.
  """
  defdelegate get_csrf_token(), to: Plug.CSRFProtection

  @doc """
  Deletes any CSRF token set.
  """
  defdelegate delete_csrf_token(), to: Plug.CSRFProtection

  defp handle_params_accept(conn, format, accepted) do
    if format in accepted do
      conn
    else
      raise Phoenix.NotAcceptableError,
        message: "unknown format #{inspect format}, expected one of #{inspect accepted}"
    end
  end

  # In case there is no accept header or the header is */*
  # we use the first format specified in the accepts list.
  defp handle_header_accept(conn, header, [first|_]) when header == [] or header == ["*/*"] do
    accept(conn, first)
  end

  # In case there is a header, we need to parse it.
  # But before we check for */* because if one exists and we serve html,
  # we unfortunately need to assume it is a browser sending us a request.
  defp handle_header_accept(conn, [header|_], accepted) do
    if header =~ "*/*" and "html" in accepted do
      accept(conn, "html")
    else
      parse_header_accept(conn, String.split(header, ","), [], accepted)
    end
  end

  defp parse_header_accept(conn, [h|t], acc, accepted) do
    case Plug.Conn.Utils.media_type(h) do
      {:ok, type, subtype, args} ->
        exts = parse_exts(type <> "/" <> subtype)
        q    = parse_q(args)

        if q === 1.0 && (format = find_format(exts, accepted)) do
          accept(conn, format)
        else
          parse_header_accept(conn, t, [{-q, exts}|acc], accepted)
        end
      :error ->
        parse_header_accept(conn, t, acc, accepted)
    end
  end

  defp parse_header_accept(conn, [], acc, accepted) do
    acc
    |> Enum.sort()
    |> Enum.find_value(&parse_header_accept(conn, &1, accepted))
    |> Kernel.||(refuse(conn, accepted))
  end

  defp parse_header_accept(conn, {_, exts}, accepted) do
    if format = find_format(exts, accepted) do
      accept(conn, format)
    end
  end

  defp parse_q(args) do
    case Map.fetch(args, "q") do
      {:ok, float} ->
        case Float.parse(float) do
          {float, _} -> float
          :error -> 1.0
        end
      :error ->
        1.0
    end
  end

  defp parse_exts("*/*" = type), do: type
  defp parse_exts(type),         do: Plug.MIME.extensions(type)

  defp find_format("*/*", accepted), do: Enum.fetch!(accepted, 0)
  defp find_format(exts, accepted),  do: Enum.find(exts, &(&1 in accepted))

  defp accept(conn, format) do
    put_in conn.params["format"], format
  end

  defp refuse(_conn, accepted) do
    raise Phoenix.NotAcceptableError,
      message: "no supported media type in accept header, expected one of #{inspect accepted}"
  end

  @doc """
  Fetches the flash so it can be used during the request.
  """
  def fetch_flash(conn, _opts \\ []) do
    flash = get_session(conn, "phoenix_flash") || %{}
    conn  = persist_flash(conn, flash)

    register_before_send conn, fn conn ->
      flash = conn.private.phoenix_flash

      cond do
        map_size(flash) == 0 ->
          conn
        conn.status in 300..308 ->
          put_session(conn, "phoenix_flash", flash)
        true ->
          delete_session(conn, "phoenix_flash")
      end
    end
  end

  @doc """
  Persists a value in flash.

  Returns the updated connection.

  ## Examples

      iex> conn = put_flash(conn, :notice, "Welcome Back!")
      iex> get_flash(conn, :notice)
      "Welcome Back!"

  """
  def put_flash(conn, key, message) do
    persist_flash(conn, Map.put(get_flash(conn), flash_key(key), message))
  end

  @doc """
  Returns a previously set flash message or nil.

  ## Examples

      iex> conn = put_flash(conn, :notice, "Welcome Back!")
      iex> get_flash(conn)
      %{"notice" => "Welcome Back!"}

  """
  def get_flash(conn) do
    Map.get(conn.private, :phoenix_flash) ||
      raise ArgumentError, message: "flash not fetched, call fetch_flash/2"
  end

  @doc """
  Returns a message from flash by key

  ## Examples

      iex> conn = put_flash(conn, :notice, "Welcome Back!")
      iex> get_flash(conn, :notice)
      "Welcome Back!"

  """
  def get_flash(conn, key) do
    get_flash(conn)[flash_key(key)]
  end

  @doc """
  Clears all flash messages.
  """
  def clear_flash(conn) do
    persist_flash(conn, %{})
  end

  defp flash_key(binary) when is_binary(binary), do: binary
  defp flash_key(atom) when is_atom(atom), do: Atom.to_string(atom)

  defp persist_flash(conn, value) do
    put_private(conn, :phoenix_flash, value)
  end

  @doc false
  def __view__(controller_module) do
    controller_module
    |> Phoenix.Naming.unsuffix("Controller")
    |> Kernel.<>("View")
    |> Module.concat(nil)
  end

  @doc false
  def __layout__(controller_module) do
    Phoenix.Naming.base_concat(controller_module, "LayoutView")
  end
end

defmodule Phoenix.Controller do
  import Plug.Conn

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

  For example, the route:

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

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

      defmodule UserController do
        use Phoenix.Controller

        plug :action

        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 routers, controllers also have their own plug pipeline. However,
  different from routers, controllers have a single pipeline:

      defmodule UserController do
        use Phoenix.Controller

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

        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

  The `:action` plug must always be invoked and it represents the action
  to be dispatched to.

  Check `Phoenix.Controller.Pipeline` for more information on `plug/2`
  and how to customize the plug pipeline.
  """
  defmacro __using__(_options) do
    quote do
      import Plug.Conn
      import Phoenix.Controller

      use Phoenix.Controller.Pipeline

      plug Phoenix.Controller.Logger
      plug :put_new_layout, {Phoenix.Controller.__layout__(__MODULE__), :application}
      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 """
  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 =
      Application.get_env(:phoenix, :format_encoders)
      |> Keyword.get(:json, Poison)

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

  @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, "..."

  """
  @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)
    {:safe, html} = Phoenix.HTML.html_escape(url)
    body = "You are being redirected."

    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
          "/" <> _ -> to
          _        -> raise ArgumentError, "the :to option in redirect expects a path"
        end
      external = opts[:external] ->
        external
      true ->
        raise ArgumentError, "expected :to or :external option in redirect/2"
    end
  end

  @doc """
  Stores the view for rendering.
  """
  @spec put_view(Plug.Conn.t, atom) :: Plug.Conn.t
  def put_view(conn, module) do
    put_private(conn, :phoenix_view, module)
  end

  @doc """
  Stores the view for rendering if one was not stored yet.
  """
  @spec put_new_view(Plug.Conn.t, atom) :: Plug.Conn.t
  def put_new_view(conn, module) do
    update_in conn.private, &Map.put_new(&1, :phoenix_view, module)
  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`.

  ## Examples

      iex> layout(conn)
      false

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

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

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

  """
  @spec put_layout(Plug.Conn.t, {atom, binary} | binary | false) :: Plug.Conn.t
  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.
  """
  @spec put_new_layout(Plug.Conn.t, {atom, binary} | false) :: Plug.Conn.t
  def put_new_layout(conn, layout)
      when tuple_size(layout) == 2
      when layout == false do
    update_in conn.private, &Map.put_new(&1, :phoenix_layout, layout)
  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"]

  """
  @spec put_layout_formats(Plug.Conn.t, [String.t]) :: Plug.Conn.t
  def put_layout_formats(conn, formats) when is_list(formats) do
    put_private(conn, :phoenix_layout_formats, formats)
  end

  @doc """
  Retrieves current layout formats.
  """
  @spec layout_formats(Plug.Conn.t) :: [String.t]
  def layout_formats(conn) do
    Map.get(conn.private, :phoenix_layout_formats, ~w(html))
  end

  @doc """
  Retrieves the current layout.
  """
  @spec layout(Plug.Conn.t) :: {atom, String.t} | false
  def layout(conn), do: conn.private |> Map.get(:phoenix_layout, false)

  @doc """
  Render the given template or the default template
  specified by the current action with the given assigns.

  See `render/3` for more information.
  """
  @spec render(Plug.Conn.t, Dict.t | binary | atom) :: Plug.Conn.t
  def render(conn, template_or_assigns \\ [])

  def render(conn, template) when is_binary(template) or is_atom(template) do
    render(conn, template, [])
  end

  def render(conn, assigns) do
    render(conn, action_name(conn), assigns)
  end

  @doc """
  Renders the given `template` and `assigns` based on the `conn` information.

  Once the template is rendered, the template format is set as the response
  content type (for example, an HTML template will set "text/html" as response
  content type) and the data is sent to the client with default status of 200.

  ## Arguments

    * `conn` - the `Plug.Conn` struct

    * `template` - which may be an atom or a string. If an atom, like `:index`,
      it will render a template with the same format as the one found in
      `conn.params["format"]`. For example, for an HTML request, it will render
      the "index.html" template. If the template is a string, it must contain
      the extension too, like "index.json"

    * `assigns` - a dictionary with the assigns to be used in the view. Those
      assigns are merged and have higher precedence than the connection assigns
      (`conn.assigns`)

  ## Examples

      defmodule MyApp.UserController do
        use Phoenix.Controller

        plug :action

        def show(conn) do
          render conn, "show.html", message: "Hello"
        end
      end

  The example above renders a template "show.html" from the `MyApp.UserView`
  and sets the response content type to "text/html".

  In many cases, you may want the template format to be set dynamically based
  on the request. To do so, you can pass the template name as an atom (without
  the extension):

      def show(conn) do
        render conn, :show, message: "Hello"
      end

  In order for the example above to work, we need to do content negotiation with
  the accepts plug before rendering. You can do so by adding the following to your
  pipeline (in the router):

      plug :accepts, ["html"]

  ## Views

  By default, Controllers render templates in a view with a similar name to the
  controller. For example, `MyApp.UserController` will render templates inside
  the `MyApp.UserView`. This information can be changed any time by using the
  `put_view/2` function:

      def show(conn) do
        conn
        |> put_view(MyApp.SpecialView)
        |> render(:show, message: "Hello")
      end
.
  `put_view/2` can also be used as a plug:

      defmodule MyApp.UserController do
        use Phoenix.Controller

        plug :put_view, MyApp.SpecialView
        plug :action

        def show(conn) do
          render conn, :show, message: "Hello"
        end
      end

  ## Layouts

  Templates are often rendered inside layouts. By default, Phoenix
  will render layouts for html requests. For example:

      defmodule MyApp.UserController do
        use Phoenix.Controller

        plug :action

        def show(conn) do
          render conn, "show.html", message: "Hello"
        end
      end

  will render the  "show.html" template inside an "application.html"
  template specified in `MyApp.LayoutView`. `put_layout/2` can be used
  to change the layout, similar to how `put_view/2` can be used to change
  the view.

  `layout_formats/2` and `put_layout_formats/2` can be used to configure
  which formats support/require layout rendering (defaults to "html" only).
  """
  @spec render(Plug.Conn.t, binary | atom, Dict.t) :: Plug.Conn.t
  def render(conn, template, assigns) when is_atom(template) do
    format =
      conn.params["format"] ||
      raise "cannot render template #{inspect template} because conn.params[\"format\"] is not set. " <>
            "Please set `plug :accepts, %w(html json ...)` in your pipeline."
    render(conn, template_name(template, format), format, assigns)
  end

  def render(conn, template, assigns) when is_binary(template) do
    case Path.extname(template) do
      "." <> format ->
        render(conn, template, format, assigns)
      "" ->
        raise "cannot render template #{inspect template} without format. Use an atom if the " <>
              "template format is meant to be set dynamically based on the request format"
    end
  end

  def render(conn, template, format, assigns) do
    assigns = to_map(assigns)
    content_type = Plug.MIME.type(format)
    conn = prepare_assigns(conn, assigns, format)
    view = Map.get(conn.private, :phoenix_view) ||
            raise "a view module was not specified, set one with put_view/2"
    data = Phoenix.View.render_to_iodata(view, template,
                                         Map.put(conn.assigns, :conn, conn))
    send_resp(conn, conn.status || 200, content_type, data)
  end

  @doc """
  Scrubs the parameters from the request.

  This process is two-fold:

    * Checks to see if the `required_key` is present
    * Changes empty parameters of `required_key` (recursively) to nils

  This function is useful to remove empty strings sent
  via HTML forms. If you are providing an API, there
  is likely no need to invoke `scrub_params/2`.

  If the `required_key` is not present, it will
  raise `Phoenix.MissingParamError`.

  ## Examples

      iex> scrub_params(conn, "user")

  """
  @spec scrub_params(Plug.Conn.t, [String.t]) :: Plug.Conn.t
  def scrub_params(conn, required_key) when is_binary(required_key) do
    param = Map.get(conn.params, required_key) |> scrub_param()

    unless param do
      raise Phoenix.MissingParamError, key: required_key
    end

    params = Map.put(conn.params, required_key, param)
    %{conn | params: params}
  end

  defp scrub_param(%{__struct__: mod} = struct) when is_atom(mod) do
    struct
  end
  defp scrub_param(%{} = param) do
    Enum.reduce(param, %{}, fn({k, v}, acc) ->
      Map.put(acc, k, scrub_param(v))
    end)
  end
  defp scrub_param(param) when is_list(param) do
    Enum.map(param, &scrub_param/1)
  end
  defp scrub_param(param) do
    if scrub?(param), do: nil, else: param
  end

  defp scrub?(" " <> rest), do: scrub?(rest)
  defp scrub?(""), do: true
  defp scrub?(_), do: false

  defp prepare_assigns(conn, assigns, format) do
    layout =
      case layout(conn, assigns, format) do
        {mod, layout} -> {mod, template_name(layout, format)}
        false -> false
      end

    update_in conn.assigns,
              & &1 |> Map.merge(assigns) |> Map.put(:layout, layout)
  end

  defp layout(conn, assigns, format) do
    if format in layout_formats(conn) do
      case Map.fetch(assigns, :layout) do
        {:ok, layout} -> layout
        :error -> layout(conn)
      end
    else
      false
    end
  end

  defp to_map(assigns) when is_map(assigns), do: assigns
  defp to_map(assigns) when is_list(assigns), do: :maps.from_list(assigns)
  defp to_map(assigns), do: Dict.merge(%{}, assigns)

  defp template_name(name, format) when is_atom(name), do:
    Atom.to_string(name) <> "." <> format
  defp template_name(name, _format) when is_binary(name), do:
    name

  defp send_resp(conn, default_status, default_content_type, body) do
    case get_resp_header(conn, "content-type") do
      [] -> put_resp_content_type(conn, default_content_type)
      _  -> conn
    end
    |> send_resp(conn.status || default_status, body)
  end

  @doc """
  Performs content negotiation based on the available formats.

  It receives a connection, a list of formats that the server
  is capable of rendering and then proceeds to perform content
  negotiation based on the request information. If the client
  accepts any of the given formats, the request proceeds.

  If the request contains a "format" parameter, it is
  considered to be the format desired by the client. If no
  "format" parameter is available, this function will parse
  the "accept" header and find a matching format accordingly.

  It is important to notice that browsers have historically
  sent bad accept headers. For this reason, this function will
  default to "html" format whenever:

    * the accepted list of arguments contains the "html" format

    * the accept header specified more than one media type preceeded
      or followed by the wildcard media type "*/*"

  This function raises `Phoenix.NotAcceptableError`, which is rendered
  with status 406, whenever the server cannot serve a response in any
  of the formats expected by the client.

  ## Examples

  `accepts/2` can be invoked as a function:

      iex> accepts(conn, ["html", "json"])

  or used as a plug:

      plug :accepts, ["html", "json"]
      plug :accepts, ~w(html json)

  """
  @spec accepts(Plug.Conn.t, [binary]) :: Plug.Conn.t | no_return
  def accepts(conn, [_|_] = accepted) do
    case Map.fetch conn.params, "format" do
      {:ok, format} ->
        handle_params_accept(conn, format, accepted)
      :error ->
        handle_header_accept(conn, get_req_header(conn, "accept"), accepted)
    end
  end

  @doc """
  Enables CSRF protection.

  Currently used as a wrapper function for `Plug.CSRFProtection`
  and mainly serves as a function plug in `YourApp.Router`.

  Check `get_csrf_token/0` and `delete_csrf_token/0` for
  retrieving and deleting CSRF tokens.
  """
  def protect_from_forgery(conn, opts \\ []) do
    Plug.CSRFProtection.call(conn, opts)
  end

  @doc """
  Gets the CSRF token.
  """
  defdelegate get_csrf_token(), to: Plug.CSRFProtection

  @doc """
  Deletes any CSRF token set.
  """
  defdelegate delete_csrf_token(), to: Plug.CSRFProtection

  defp handle_params_accept(conn, format, accepted) do
    if format in accepted do
      conn
    else
      raise Phoenix.NotAcceptableError,
        message: "unknown format #{inspect format}, expected one of #{inspect accepted}"
    end
  end

  # In case there is no accept header or the header is */*
  # we use the first format specified in the accepts list.
  defp handle_header_accept(conn, header, [first|_]) when header == [] or header == ["*/*"] do
    accept(conn, first)
  end

  # In case there is a header, we need to parse it.
  # But before we check for */* because if one exists and we serve html,
  # we unfortunately need to assume it is a browser sending us a request.
  defp handle_header_accept(conn, [header|_], accepted) do
    if header =~ "*/*" and "html" in accepted do
      accept(conn, "html")
    else
      parse_header_accept(conn, String.split(header, ","), [], accepted)
    end
  end

  defp parse_header_accept(conn, [h|t], acc, accepted) do
    case Plug.Conn.Utils.media_type(h) do
      {:ok, type, subtype, args} ->
        exts = parse_exts(type <> "/" <> subtype)
        q    = parse_q(args)

        if q === 1.0 && (format = find_format(exts, accepted)) do
          accept(conn, format)
        else
          parse_header_accept(conn, t, [{-q, exts}|acc], accepted)
        end
      :error ->
        parse_header_accept(conn, t, acc, accepted)
    end
  end

  defp parse_header_accept(conn, [], acc, accepted) do
    acc
    |> Enum.sort()
    |> Enum.find_value(&parse_header_accept(conn, &1, accepted))
    |> Kernel.||(refuse(conn, accepted))
  end

  defp parse_header_accept(conn, {_, exts}, accepted) do
    if format = find_format(exts, accepted) do
      accept(conn, format)
    end
  end

  defp parse_q(args) do
    case Map.fetch(args, "q") do
      {:ok, float} ->
        case Float.parse(float) do
          {float, _} -> float
          :error -> 1.0
        end
      :error ->
        1.0
    end
  end

  defp parse_exts("*/*" = type), do: type
  defp parse_exts(type),         do: Plug.MIME.extensions(type)

  defp find_format("*/*", accepted), do: Enum.fetch!(accepted, 0)
  defp find_format(exts, accepted),  do: Enum.find(exts, &(&1 in accepted))

  defp accept(conn, format) do
    put_in conn.params["format"], format
  end

  defp refuse(_conn, accepted) do
    raise Phoenix.NotAcceptableError,
      message: "no supported media type in accept header, expected one of #{inspect accepted}"
  end

  @doc """
  Fetches the flash so it can be used during the request.
  """
  def fetch_flash(conn, _opts \\ []) do
    flash = get_session(conn, "phoenix_flash") || %{}
    conn  = persist_flash(conn, flash)

    register_before_send conn, fn conn ->
      flash = conn.private.phoenix_flash

      cond do
        map_size(flash) == 0 ->
          conn
        conn.status in 300..308 ->
          put_session(conn, "phoenix_flash", flash)
        true ->
          delete_session(conn, "phoenix_flash")
      end
    end
  end

  @doc """
  Persists a value in flash.

  Returns the updated connection.

  ## Examples

      iex> conn = put_flash(conn, :notice, "Welcome Back!")
      iex> get_flash(conn, :notice)
      "Welcome Back!"

  """
  def put_flash(conn, key, message) do
    persist_flash(conn, Map.put(get_flash(conn), flash_key(key), message))
  end

  @doc """
  Returns a previously set flash message or nil.

  ## Examples

      iex> conn = put_flash(conn, :notice, "Welcome Back!")
      iex> get_flash(conn)
      %{"notice" => "Welcome Back!"}

  """
  def get_flash(conn) do
    Map.get(conn.private, :phoenix_flash) ||
      raise ArgumentError, message: "flash not fetched, call fetch_flash/2"
  end

  @doc """
  Returns a message from flash by key

  ## Examples

      iex> conn = put_flash(conn, :notice, "Welcome Back!")
      iex> get_flash(conn, :notice)
      "Welcome Back!"

  """
  def get_flash(conn, key) do
    get_flash(conn)[flash_key(key)]
  end

  @doc """
  Clears all flash messages.
  """
  def clear_flash(conn) do
    persist_flash(conn, %{})
  end

  defp flash_key(binary) when is_binary(binary), do: binary
  defp flash_key(atom) when is_atom(atom), do: Atom.to_string(atom)

  defp persist_flash(conn, value) do
    put_private(conn, :phoenix_flash, value)
  end

  @doc false
  def __view__(controller_module) do
    controller_module
    |> Phoenix.Naming.unsuffix("Controller")
    |> Kernel.<>("View")
    |> Module.concat(nil)
  end

  @doc false
  def __layout__(controller_module) do
    Phoenix.Naming.base_concat(controller_module, "LayoutView")
  end
end


adds docs and type specs to Discovery.put_belongs_to_many_categories

defmodule Ello.Core.Discovery do
  import Ecto.Query
  alias Ello.Core.{Repo, Network, Discovery}
  alias Discovery.{Category, Promotional}
  alias Network.User

  @moduledoc """
  Responsible for retreiving and loading categories and related data.

  Handles database queryies, preloading relations, and fetching cached values.
  """

  @doc "Find a single category by slug or id - including promotionals"
  @spec category(String.t | integer, current_user :: User.t | nil) :: Category.t
  def category(id_or_slug, current_user \\ nil)
  def category(slug, current_user) when is_binary(slug) do
    Category
    |> Repo.get_by!(slug: slug)
    |> include_promotionals(current_user)
    |> load_images
  end
  def category(id, current_user) when is_number(id) do
    Category
    |> Repo.get!(id)
    |> include_promotionals(current_user)
    |> load_images
  end

  def categories_by_ids([]), do: []
  def categories_by_ids(ids) when is_list(ids) do
    Category
    |> where([c], c.id in ^ids)
    |> include_inactive_categories(false)
    |> include_meta_categories(false)
    |> Repo.all
    |> load_images
  end

  @type categorizable :: User.t | Post.t | [User.t | Post.t]

  @doc """
  Fetches the categories for a user or post

  Given a user or post struct (or list of users or posts), this function will
  fetch all the categories and include them in the struct (or list of structs).
  """
  @spec put_belongs_to_many_categories(categorizables :: categorizable | nil) :: categorizable | nil
  def put_belongs_to_many_categories(nil), do: nil
  def put_belongs_to_many_categories([]), do: []
  def put_belongs_to_many_categories(%{} = categorizable),
    do: hd(put_belongs_to_many_categories([categorizable]))
  def put_belongs_to_many_categories(categorizables) do
    categories = categorizables
                 |> Enum.flat_map(&(&1.category_ids))
                 |> Discovery.categories_by_ids
                 |> Enum.group_by(&(&1.id))
    Enum.map categorizables, fn
      %{category_ids: []} = categorizable -> categorizable
      categorizable ->
        categorizable_categories = categories
                          |> Map.take(categorizable.category_ids)
                          |> Map.values
                          |> List.flatten
        Map.put(categorizable, :categories, categorizable_categories)
    end
  end

  @doc """
  Return all Categories with related promotionals their user.

  By default neither "meta" categories nor "inactive" categories are included
  in the results. Pass `meta: true` or `inactive: true` as opts to include them.
  """
  @spec categories(current_user :: User.t, opts :: Keyword.t) :: [Category.t]
  def categories(current_user \\ nil, opts \\ []) do
    Category
    |> include_inactive_categories(opts[:inactive])
    |> include_meta_categories(opts[:meta])
    |> priority_order
    |> Repo.all
    |> include_promotionals(current_user)
    |> load_images
  end

  # Category Scopes
  defp priority_order(q),
    do: order_by(q, [:level, :order])

  defp include_inactive_categories(q, true), do: q
  defp include_inactive_categories(q, _),
    do: where(q, [c], not is_nil(c.level))

  defp include_meta_categories(q, true), do: q
  defp include_meta_categories(q, _),
    do: where(q, [c], c.level != "meta" or is_nil(c.level))

  defp include_promotionals(categories, current_user) do
    Repo.preload(categories, promotionals: [user: &Network.users(&1, current_user)])
  end

  defp load_images(categories) when is_list(categories) do
    Enum.map(categories, &load_images/1)
  end
  defp load_images(%Category{promotionals: promos} = category) when is_list(promos) do
    category
    |> Category.load_images
    |> Map.put(:promotionals, Enum.map(promos, &Promotional.load_images/1))
  end
  defp load_images(%Category{} = category) do
    Category.load_images(category)
  end
end

defmodule Ello.Core.Discovery do
  import Ecto.Query
  alias Ello.Core.{Repo, Network, Discovery}
  alias Discovery.{Category, Promotional}
  alias Network.User

  @moduledoc """
  Responsible for retreiving and loading categories and related data.

  Handles database queryies, preloading relations, and fetching cached values.
  """

  @doc "Find a single category by slug or id - including promotionals"
  @spec category(String.t | integer, current_user :: User.t | nil) :: Category.t
  def category(id_or_slug, current_user \\ nil)
  def category(slug, current_user) when is_binary(slug) do
    Category
    |> Repo.get_by!(slug: slug)
    |> include_promotionals(current_user)
    |> load_images
  end
  def category(id, current_user) when is_number(id) do
    Category
    |> Repo.get!(id)
    |> include_promotionals(current_user)
    |> load_images
  end

  def categories_by_ids([]), do: []
  def categories_by_ids(ids) when is_list(ids) do
    Category
    |> where([c], c.id in ^ids)
    |> include_inactive_categories(false)
    |> include_meta_categories(false)
    |> Repo.all
    |> load_images
  end

  def put_belongs_to_many_categories(nil), do: nil
  def put_belongs_to_many_categories([]), do: []
  def put_belongs_to_many_categories(%{} = categorizable),
    do: hd(put_belongs_to_many_categories([categorizable]))
  def put_belongs_to_many_categories(categorizables) do
    categories = categorizables
                 |> Enum.flat_map(&(&1.category_ids))
                 |> Discovery.categories_by_ids
                 |> Enum.group_by(&(&1.id))
    Enum.map categorizables, fn
      %{category_ids: []} = categorizable -> categorizable
      categorizable ->
        categorizable_categories = categories
                          |> Map.take(categorizable.category_ids)
                          |> Map.values
                          |> List.flatten
        Map.put(categorizable, :categories, categorizable_categories)
    end
  end

  @doc """
  Return all Categories with related promotionals their user.

  By default neither "meta" categories nor "inactive" categories are included
  in the results. Pass `meta: true` or `inactive: true` as opts to include them.
  """
  @spec categories(current_user :: User.t, opts :: Keyword.t) :: [Category.t]
  def categories(current_user \\ nil, opts \\ []) do
    Category
    |> include_inactive_categories(opts[:inactive])
    |> include_meta_categories(opts[:meta])
    |> priority_order
    |> Repo.all
    |> include_promotionals(current_user)
    |> load_images
  end

  # Category Scopes
  defp priority_order(q),
    do: order_by(q, [:level, :order])

  defp include_inactive_categories(q, true), do: q
  defp include_inactive_categories(q, _),
    do: where(q, [c], not is_nil(c.level))

  defp include_meta_categories(q, true), do: q
  defp include_meta_categories(q, _),
    do: where(q, [c], c.level != "meta" or is_nil(c.level))

  defp include_promotionals(categories, current_user) do
    Repo.preload(categories, promotionals: [user: &Network.users(&1, current_user)])
  end

  defp load_images(categories) when is_list(categories) do
    Enum.map(categories, &load_images/1)
  end
  defp load_images(%Category{promotionals: promos} = category) when is_list(promos) do
    category
    |> Category.load_images
    |> Map.put(:promotionals, Enum.map(promos, &Promotional.load_images/1))
  end
  defp load_images(%Category{} = category) do
    Category.load_images(category)
  end
end


Fix SysCalls.sync() tests

Fix SysCalls.sync() tests

defmodule FarmbotOS.SysCallsTest do
  use ExUnit.Case, async: true
  alias FarmbotOS.SysCalls
  alias FarmbotCore.Asset

  alias FarmbotCore.Asset.{
    Repo,
    Sequence,
    BoxLed
  }

  use Mimic
  setup :verify_on_exit!

  test "emergency_unlock" do
    expect(FarmbotFirmware, :command, fn {:command_emergency_unlock, []} ->
      :qqq
    end)

    assert :ok == SysCalls.emergency_unlock()
  end

  test "emergency_lock" do
    expect(FarmbotFirmware, :command, fn {:command_emergency_lock, []} ->
      :qqq
    end)

    assert :ok == SysCalls.emergency_lock()
  end

  test "wait()" do
    now = :os.system_time(:millisecond)
    SysCalls.wait(100)
    later = :os.system_time(:millisecond)
    assert later >= now + 100
  end

  test "named_pin()" do
    result1 = SysCalls.named_pin("x", 1)
    assert result1 == {:error, "unknown pin kind: x of id: 1"}

    result2 = SysCalls.named_pin("BoxLed23", 45)
    assert %BoxLed{id: 23} == result2

    expect(Asset, :get_sensor, fn id ->
      if id == 67 do
        %{id: id, is_mock: :yep}
      else
        nil
      end
    end)

    result3 = SysCalls.named_pin("Sensor", 67)
    assert %{id: 67, is_mock: :yep} == result3

    result4 = SysCalls.named_pin("Sensor", 89)
    assert {:error, "Could not find peripheral by id: 89"} == result4

    expect(Asset, :get_peripheral, fn [id: id] ->
      if id == 10 do
        %{id: id, is_mock: :yep}
      else
        nil
      end
    end)

    result5 = SysCalls.named_pin("Peripheral", 10)
    assert %{id: 10, is_mock: :yep} == result5

    result6 = SysCalls.named_pin("Peripheral", 11)
    assert {:error, "Could not find peripheral by id: 11"} == result6
  end

  test "sync() success" do
    # Expect 5 calls and an :ok response.
    expect(FarmbotExt.API.Reconciler, :sync_group, 5, fn changeset, _group ->
      changeset
    end)

    expect(FarmbotExt.API, :get_changeset, fn module ->
      {:ok, %{wut: module}}
    end)

    assert :ok == SysCalls.sync()
  end

  test "sync() failure" do
    # Expect 5 calls and an :ok response.
    expect(FarmbotExt.API, :get_changeset, fn FarmbotCore.Asset.Sync ->
      "this is a test"
    end)

    assert {:error, "\"this is a test\""} == SysCalls.sync()
  end

  test "get_sequence(id)" do
    _ = Repo.delete_all(Sequence)
    fake_id = 28
    fake_name = "X"

    fake_params = %{
      id: fake_id,
      name: fake_name,
      args: %{
        sequence_name: fake_name
      },
      kind: "sequence",
      body: []
    }

    assert SysCalls.get_sequence(fake_id) == {:error, "sequence not found"}

    %Sequence{id: id} =
      %Sequence{}
      |> Sequence.changeset(fake_params)
      |> Repo.insert!()

    assert id == fake_id
    result = SysCalls.get_sequence(fake_id)
    assert result.args == fake_params[:args]
    assert result.kind == :sequence
    assert result.body == fake_params[:body]
  end

  test "coordinate()" do
    expected = %{x: 1, y: 2, z: 3}
    actual = SysCalls.coordinate(1, 2, 3)
    assert expected == actual
  end

  test "nothing" do
    assert SysCalls.nothing() == nil
  end

  test "install_first_party_farmware()" do
    expected = {:error, "install_first_party_farmware not yet supported"}
    actual = SysCalls.install_first_party_farmware()
    assert expected == actual
  end
end

defmodule FarmbotOS.SysCallsTest do
  use ExUnit.Case, async: true
  alias FarmbotOS.SysCalls
  alias FarmbotCore.Asset

  alias FarmbotCore.Asset.{
    Repo,
    Sequence,
    BoxLed
  }

  use Mimic
  setup :verify_on_exit!

  test "emergency_unlock" do
    expect(FarmbotFirmware, :command, fn {:command_emergency_unlock, []} ->
      :qqq
    end)

    assert :ok == SysCalls.emergency_unlock()
  end

  test "emergency_lock" do
    expect(FarmbotFirmware, :command, fn {:command_emergency_lock, []} ->
      :qqq
    end)

    assert :ok == SysCalls.emergency_lock()
  end

  test "wait()" do
    now = :os.system_time(:millisecond)
    SysCalls.wait(100)
    later = :os.system_time(:millisecond)
    assert later >= now + 100
  end

  test "named_pin()" do
    result1 = SysCalls.named_pin("x", 1)
    assert result1 == {:error, "unknown pin kind: x of id: 1"}

    result2 = SysCalls.named_pin("BoxLed23", 45)
    assert %BoxLed{id: 23} == result2

    expect(Asset, :get_sensor, fn id ->
      if id == 67 do
        %{id: id, is_mock: :yep}
      else
        nil
      end
    end)

    result3 = SysCalls.named_pin("Sensor", 67)
    assert %{id: 67, is_mock: :yep} == result3

    result4 = SysCalls.named_pin("Sensor", 89)
    assert {:error, "Could not find peripheral by id: 89"} == result4

    expect(Asset, :get_peripheral, fn [id: id] ->
      if id == 10 do
        %{id: id, is_mock: :yep}
      else
        nil
      end
    end)

    result5 = SysCalls.named_pin("Peripheral", 10)
    assert %{id: 10, is_mock: :yep} == result5

    result6 = SysCalls.named_pin("Peripheral", 11)
    assert {:error, "Could not find peripheral by id: 11"} == result6
  end

  test "sync() success" do
    # Expect 5 calls and an :ok response.
    expect(FarmbotExt.API.Reconciler, :sync_group, 5, fn changeset, _group ->
      changeset
    end)

    assert :ok == SysCalls.sync()
  end

  test "sync() failure" do
    # Expect 5 calls and an :ok response.
    expect(FarmbotExt.API, :get_changeset, fn FarmbotCore.Asset.Sync ->
      "this is a test"
    end)

    assert {:error, "\"this is a test\""} == SysCalls.sync()
  end

  test "get_sequence(id)" do
    _ = Repo.delete_all(Sequence)
    fake_id = 28
    fake_name = "X"

    fake_params = %{
      id: fake_id,
      name: fake_name,
      args: %{
        sequence_name: fake_name
      },
      kind: "sequence",
      body: []
    }

    assert SysCalls.get_sequence(fake_id) == {:error, "sequence not found"}

    %Sequence{id: id} =
      %Sequence{}
      |> Sequence.changeset(fake_params)
      |> Repo.insert!()

    assert id == fake_id
    result = SysCalls.get_sequence(fake_id)
    assert result.args == fake_params[:args]
    assert result.kind == :sequence
    assert result.body == fake_params[:body]
  end

  test "coordinate()" do
    expected = %{x: 1, y: 2, z: 3}
    actual = SysCalls.coordinate(1, 2, 3)
    assert expected == actual
  end

  test "nothing" do
    assert SysCalls.nothing() == nil
  end

  test "install_first_party_farmware()" do
    expected = {:error, "install_first_party_farmware not yet supported"}
    actual = SysCalls.install_first_party_farmware()
    assert expected == actual
  end
end


Added media.evercam.io -> www.evercam.io redirect

defmodule Media.PageController do
  use Media.Web, :controller

  plug :action

  def index(conn, _params) do
    redirect conn, external: "http://www.evercam.io"
  end
end

defmodule Media.PageController do
  use Media.Web, :controller

  plug :action

  def index(conn, _params) do
    render conn, "index.html"
  end
end


Fix get_project()

Fix get_project()

defmodule Api.Documents.Index do
  require Logger
  alias Api.Documents.Mapping
  alias Api.Documents.Query
  alias Api.Documents.Filter
  alias Core.ProjectConfigLoader

  @max_geometries 10000
  @exists_geometries ["resource.geometry"]
  @fields_geometries ["resource.category", "resource.geometry", "resource.identifier", "resource.id", "project"]

  def get(id) do
    Query.init("_id:#{id}", 1)
    |> build_post_atomize()
    |> Mapping.map_single()
  end

  def search(q, size, from, filters, must_not, exists, readable_projects) do
    {filters, must_not, project_conf} = preprocess(filters, must_not)
    Query.init(q, size, from)
    |> Query.track_total
    |> Query.add_aggregations()
    |> Query.add_filters(filters)
    |> Query.add_must_not(must_not)
    |> Query.add_exists(exists)
    |> Query.set_readable_projects(readable_projects)
    |> build_post_atomize
    |> Mapping.map(project_conf)
  end

  def search_geometries(q, filters, must_not, exists, readable_projects) do
    {filters, must_not, project_conf} = preprocess(filters, must_not)
    Query.init(q, @max_geometries)
    |> Query.add_filters(filters)
    |> Query.add_must_not(must_not)
    |> Query.add_exists(exists)
    |> Query.add_exists(@exists_geometries)
    |> Query.only_fields(@fields_geometries)
    |> Query.set_readable_projects(readable_projects)
    |> build_post_atomize
    |> Mapping.map(project_conf)
  end

  defp preprocess(filters, must_not) do
    filters = Filter.parse(filters)
    project_conf = ProjectConfigLoader.get(get_project(filters))
    filters = Filter.expand(filters, project_conf)
    must_not = must_not |> Filter.parse |> Filter.expand(project_conf)
    {filters, must_not, project_conf}
  end

  defp get_project(nil), do: "default"
  defp get_project(filters) do
    case Enum.find(filters, fn {field, value} -> field == "project" end) do
      {"project", [project]} -> project
      _ -> "default"
    end
  end

  defp build_post_atomize(query) do
    query
    |> Query.build
    |> index_adapter().post_query
    |> Core.Utils.atomize_up_to(:_source)
  end

  defp index_adapter() do
    if Mix.env() == :test do
      Api.Documents.MockIndexAdapter
    else
      Api.Documents.ElasticsearchIndexAdapter
    end
  end
end

defmodule Api.Documents.Index do
  require Logger
  alias Api.Documents.Mapping
  alias Api.Documents.Query
  alias Api.Documents.Filter
  alias Core.ProjectConfigLoader

  @max_geometries 10000
  @exists_geometries ["resource.geometry"]
  @fields_geometries ["resource.category", "resource.geometry", "resource.identifier", "resource.id", "project"]

  def get(id) do
    Query.init("_id:#{id}", 1)
    |> build_post_atomize()
    |> Mapping.map_single()
  end

  def search(q, size, from, filters, must_not, exists, readable_projects) do
    {filters, must_not, project_conf} = preprocess(filters, must_not)
    Query.init(q, size, from)
    |> Query.track_total
    |> Query.add_aggregations()
    |> Query.add_filters(filters)
    |> Query.add_must_not(must_not)
    |> Query.add_exists(exists)
    |> Query.set_readable_projects(readable_projects)
    |> build_post_atomize
    |> Mapping.map(project_conf)
  end

  def search_geometries(q, filters, must_not, exists, readable_projects) do
    {filters, must_not, project_conf} = preprocess(filters, must_not)
    Query.init(q, @max_geometries)
    |> Query.add_filters(filters)
    |> Query.add_must_not(must_not)
    |> Query.add_exists(exists)
    |> Query.add_exists(@exists_geometries)
    |> Query.only_fields(@fields_geometries)
    |> Query.set_readable_projects(readable_projects)
    |> build_post_atomize
    |> Mapping.map(project_conf)
  end

  defp preprocess(filters, must_not) do
    filters = Filter.parse(filters)
    project_conf = ProjectConfigLoader.get(get_project(filters))
    filters = Filter.expand(filters, project_conf)
    must_not = must_not |> Filter.parse |> Filter.expand(project_conf)
    {filters, must_not, project_conf}
  end

  defp get_project(filters) do
    with [{"project", [project]}] <- filters, do: project, else: (_ -> "default")
  end

  defp build_post_atomize(query) do
    query
    |> Query.build
    |> index_adapter().post_query
    |> Core.Utils.atomize_up_to(:_source)
  end

  defp index_adapter() do
    if Mix.env() == :test do
      Api.Documents.MockIndexAdapter
    else
      Api.Documents.ElasticsearchIndexAdapter
    end
  end
end


Run code formatter on lib/elixir/test/elixir/kernel/binary_test.exs (#6833)

Code.require_file("../test_helper.exs", __DIR__)

defmodule Kernel.BinaryTest do
  use ExUnit.Case, async: true

  test "heredoc" do
    assert 7 == __ENV__.line

    assert "foo\nbar\n" == """
           foo
           bar
           """

    assert 14 == __ENV__.line

    assert "foo\nbar \"\"\"\n" == """
           foo
           bar \"""
           """
  end

  test "aligned heredoc" do
    assert "foo\nbar\n" == """
           foo
           bar
           """
  end

  test "heredoc with interpolation" do
    assert "31\n" == """
           #{__ENV__.line}
           """

    assert "\n36\n" == """

           #{__ENV__.line}
           """
  end

  test "heredoc in call" do
    assert "foo\nbar" ==
             Kernel.<>(
               """
               foo
               """,
               "bar"
             )
  end

  test "UTF-8" do
    assert byte_size(" ゆんゆん") == 13
  end

  test "UTF-8 char" do
    assert ?ゆ == 12422
    assert ?\ゆ == 12422
  end

  test "string concatenation as match" do
    "foo" <> x = "foobar"
    assert x == "bar"

    <<"foo">> <> x = "foobar"
    assert x == "bar"

    <<"f", "oo">> <> x = "foobar"
    assert x == "bar"

    <<x::binary-size(3)>> <> _ = "foobar"
    assert x == "foo"

    size = 3
    <<x::binary-size(size)>> <> _ = "foobar"
    assert x == "foo"

    size = 16
    <<x::size(size)>> <> _ = "foobar"
    assert x == 26223

    <<x::6*4-binary>> <> _ = "foobar"
    assert x == "foo"

    <<x::3-bytes>> <> _ = "foobar"
    assert x == "foo"

    <<x::24-bits>> <> _ = "foobar"
    assert x == "foo"

    assert_raise MatchError, fn ->
      Code.eval_string(~s{<<x::binary-size(3)-unit(4)>> <> _ = "foobar"})
    end

    assert_raise MatchError, fn ->
      Code.eval_string(~s{<<x::integer-size(4)>> <> _ = "foobar"})
    end
  end

  test "hex" do
    assert "\x76" == "v"
    assert "\u00FF" == "ÿ"
    assert "\u{A}" == "\n"
    assert "\u{E9}" == "é"
    assert "\u{10F}" == <<196, 143>>
    assert "\u{10FF}" == <<225, 131, 191>>
    assert "\u{10FFF}" == <<240, 144, 191, 191>>
    assert "\u{10FFFF}" == <<244, 143, 191, 191>>
  end

  test "match" do
    assert match?(<<?a, _::binary>>, "ab")
    refute match?(<<?a, _::binary>>, "cd")
    assert match?(<<_::utf8>> <> _, "éf")
  end

  test "interpolation" do
    res = "hello \\abc"
    assert "hello #{"\\abc"}" == res
    assert "hello #{"\\abc" <> ""}" == res
  end

  test "pattern match" do
    s = 16
    assert <<_a, _b::size(s)>> = "foo"
  end

  test "pattern match with splice" do
    assert <<1, <<2, 3, 4>>, 5>> = <<1, 2, 3, 4, 5>>
  end

  test "partial application" do
    assert (&<<&1, 2>>).(1) == <<1, 2>>
    assert (&<<&1, &2>>).(1, 2) == <<1, 2>>
    assert (&<<&2, &1>>).(2, 1) == <<1, 2>>
  end

  test "literal" do
    assert <<106, 111, 115, 195, 169>> == <<"josé">>
    assert <<106, 111, 115, 195, 169>> == <<"#{String.to_atom("josé")}">>
    assert <<106, 111, 115, 195, 169>> == <<"josé"::binary>>
    assert <<106, 111, 115, 195, 169>> == <<"josé"::bits>>
    assert <<106, 111, 115, 195, 169>> == <<"josé"::bitstring>>
    assert <<106, 111, 115, 195, 169>> == <<"josé"::bytes>>

    assert <<106, 111, 115, 195, 169>> == <<"josé"::utf8>>
    assert <<0, 106, 0, 111, 0, 115, 0, 233>> == <<"josé"::utf16>>
    assert <<106, 0, 111, 0, 115, 0, 233, 0>> == <<"josé"::little-utf16>>
    assert <<0, 0, 0, 106, 0, 0, 0, 111, 0, 0, 0, 115, 0, 0, 0, 233>> == <<"josé"::utf32>>
  end

  test "literal errors" do
    assert_raise CompileError, fn ->
      Code.eval_string(~s[<<"foo"::integer>>])
    end

    assert_raise CompileError, fn ->
      Code.eval_string(~s[<<"foo"::float>>])
    end

    assert_raise CompileError, fn ->
      Code.eval_string(~s[<<'foo'::binary>>])
    end

    assert_raise ArgumentError, fn ->
      Code.eval_string(~s[<<1::4>> <> "foo"])
    end
  end

  @bitstring <<"foo", 16::4>>

  test "bitstring attribute" do
    assert @bitstring == <<"foo", 16::4>>
  end

  @binary "new "

  test "bitsyntax expansion" do
    assert <<@binary, "world">> == "new world"
  end

  test "bitsyntax translation" do
    refb = "sample"
    sec_data = "another"

    <<
      byte_size(refb)::size(1)-big-signed-integer-unit(8),
      refb::binary,
      byte_size(sec_data)::1*16-big-signed-integer,
      sec_data::binary
    >>
  end

  test "bitsyntax size shortcut" do
    assert <<1::3>> == <<1::size(3)>>
    assert <<1::3*8>> == <<1::size(3)-unit(8)>>
  end

  test "bitsyntax variable size" do
    x = 8
    assert <<_, _::size(x)>> = <<?a, ?b>>
    assert (fn <<_, _::size(x)>> -> true end).(<<?a, ?b>>)
  end

  defmacrop signed_16 do
    quote do
      big - signed - integer - unit(16)
    end
  end

  defmacrop refb_spec do
    quote do
      1 * 8 - big - signed - integer
    end
  end

  test "bitsyntax macro" do
    refb = "sample"
    sec_data = "another"

    <<
      byte_size(refb)::refb_spec,
      refb::binary,
      byte_size(sec_data)::size(1)-signed_16,
      sec_data::binary
    >>
  end
end

Code.require_file "../test_helper.exs", __DIR__

defmodule Kernel.BinaryTest do
  use ExUnit.Case, async: true

  test "heredoc" do
    assert 7 == __ENV__.line
    assert "foo\nbar\n" == """
foo
bar
"""

    assert 13 == __ENV__.line
    assert "foo\nbar \"\"\"\n" == """
foo
bar \"""
"""
  end

  test "aligned heredoc" do
    assert "foo\nbar\n" == """
    foo
    bar
    """
  end

  test "heredoc with interpolation" do
    assert "29\n" == """
    #{__ENV__.line}
    """

    assert "\n34\n" == """

    #{__ENV__.line}
    """
  end

  test "heredoc in call" do
    assert "foo\nbar" == Kernel.<>("""
    foo
    """, "bar")
  end

  test "UTF-8" do
    assert byte_size(" ゆんゆん") == 13
  end

  test "UTF-8 char" do
    assert ?ゆ == 12422
    assert ?\ゆ == 12422
  end

  test "string concatenation as match" do
    "foo" <> x = "foobar"
    assert x == "bar"

    <<"foo">> <> x = "foobar"
    assert x == "bar"

    <<"f", "oo">> <> x = "foobar"
    assert x == "bar"

    <<x::binary-size(3)>> <> _ = "foobar"
    assert x == "foo"

    size = 3
    <<x::binary-size(size)>> <> _ = "foobar"
    assert x == "foo"

    size = 16
    <<x::size(size)>> <> _ = "foobar"
    assert x == 26223

    <<x::6*4-binary>> <> _ = "foobar"
    assert x == "foo"

    <<x::3-bytes>> <> _ = "foobar"
    assert x == "foo"

    <<x::24-bits>> <> _ = "foobar"
    assert x == "foo"

    assert_raise MatchError, fn ->
      Code.eval_string(~s{<<x::binary-size(3)-unit(4)>> <> _ = "foobar"})
    end

    assert_raise MatchError, fn ->
      Code.eval_string(~s{<<x::integer-size(4)>> <> _ = "foobar"})
    end
  end

  test "hex" do
    assert "\x76" == "v"
    assert "\u00FF" == "ÿ"
    assert "\u{A}"== "\n"
    assert "\u{E9}"== "é"
    assert "\u{10F}" == <<196, 143>>
    assert "\u{10FF}" == <<225, 131, 191>>
    assert "\u{10FFF}" == <<240, 144, 191, 191>>
    assert "\u{10FFFF}" == <<244, 143, 191, 191>>
  end

  test "match" do
    assert match?(<<?a, _::binary>>, "ab")
    refute match?(<<?a, _::binary>>, "cd")
    assert match?(<<_::utf8>> <> _, "éf")
  end

  test "interpolation" do
    res = "hello \\abc"
    assert "hello #{"\\abc"}" == res
    assert "hello #{"\\abc" <> ""}" == res
  end

  test "pattern match" do
    s = 16
    assert <<_a, _b :: size(s)>> = "foo"
  end

  test "pattern match with splice" do
    assert <<1, <<2, 3, 4>>, 5>> = <<1, 2, 3, 4, 5>>
  end

  test "partial application" do
    assert (&<<&1, 2>>).(1) == <<1, 2>>
    assert (&<<&1, &2>>).(1, 2) == <<1, 2>>
    assert (&<<&2, &1>>).(2, 1) == <<1, 2>>
  end

  test "literal" do
    assert <<106, 111, 115, 195, 169>> == <<"josé">>
    assert <<106, 111, 115, 195, 169>> == <<"#{:"josé"}">>
    assert <<106, 111, 115, 195, 169>> == <<"josé"::binary>>
    assert <<106, 111, 115, 195, 169>> == <<"josé"::bits>>
    assert <<106, 111, 115, 195, 169>> == <<"josé"::bitstring>>
    assert <<106, 111, 115, 195, 169>> == <<"josé"::bytes>>

    assert <<106, 111, 115, 195, 169>> == <<"josé"::utf8>>
    assert <<0, 106, 0, 111, 0, 115, 0, 233>> == <<"josé"::utf16>>
    assert <<106, 0, 111, 0, 115, 0, 233, 0>> == <<"josé"::little-utf16>>
    assert <<0, 0, 0, 106, 0, 0, 0, 111, 0, 0, 0, 115, 0, 0, 0, 233>> == <<"josé"::utf32>>
  end

  test "literal errors" do
    assert_raise CompileError, fn ->
      Code.eval_string(~s[<<"foo"::integer>>])
    end

    assert_raise CompileError, fn ->
      Code.eval_string(~s[<<"foo"::float>>])
    end

    assert_raise CompileError, fn ->
      Code.eval_string(~s[<<'foo'::binary>>])
    end

    assert_raise ArgumentError, fn ->
      Code.eval_string(~s[<<1::4>> <> "foo"])
    end
  end

  @bitstring <<"foo", 16::4>>

  test "bitstring attribute" do
    assert @bitstring == <<"foo", 16::4>>
  end

  @binary "new "

  test "bitsyntax expansion" do
    assert <<@binary, "world">> == "new world"
  end

  test "bitsyntax translation" do
    refb = "sample"
    sec_data = "another"
    <<byte_size(refb)::size(1)-big-signed-integer-unit(8),
      refb::binary,
      byte_size(sec_data)::1*16-big-signed-integer,
      sec_data::binary>>
  end

  test "bitsyntax size shortcut" do
    assert <<1::3>> == <<1::size(3)>>
    assert <<1::3*8>> == <<1::size(3)-unit(8)>>
  end

  test "bitsyntax variable size" do
    x = 8
    assert <<_, _::size(x)>> = <<?a, ?b>>
    assert (fn <<_, _::size(x)>> -> true end).(<<?a, ?b>>)
  end

  defmacrop signed_16 do
    quote do
      big-signed-integer-unit(16)
    end
  end

  defmacrop refb_spec do
    quote do
      1*8-big-signed-integer
    end
  end

  test "bitsyntax macro" do
    refb = "sample"
    sec_data = "another"
    <<byte_size(refb)::refb_spec,
      refb::binary,
      byte_size(sec_data)::size(1)-signed_16,
      sec_data::binary>>
  end
end


chore: let game ws crash if game not running

[ci skip]

defmodule Elixoids.Server.WebsocketGameHandler do
  @moduledoc """
  Websocket Handler for the graphics engine. Queries the game state at 30fps
  and publishes it to subscriber. Should the call from this process to the game
  process fail, the connection will close and the client is expected to reconnect.
  """

  alias Elixoids.Api.State
  alias Elixoids.Game.Server, as: Game

  @fps 30
  @ms_between_frames div(1000, @fps)
  @pause_ms 1000
  @opts %{idle_timeout: 60 * 60 * 1000, compress: false}
  @explosions_per_frame 7

  @behaviour :cowboy_handler

  def init(req = %{bindings: %{game: game}}, _state) do
    {:cowboy_websocket, req, game, @opts}
  end

  def websocket_init(game) do
    with {game_id, ""} <- Integer.parse(game),
         {:ok, _pid} <- Elixoids.News.subscribe(game_id),
         state <- %{game_id: game_id, explosions: []},
         _ref <- :erlang.start_timer(@pause_ms, self(), state) do
      {:ok, state}
    else
      :error -> {:stop, game}
    end
  end

  def terminate(_reason, _state), do: :ok

  def websocket_handle({:text, _ignore}, state), do: {:ok, state}

  def websocket_handle(_inframe, state), do: {:ok, state}

  def websocket_info({:timeout, _ref, _}, %{game_id: game_id, explosions: explosions}) do
    :erlang.start_timer(@ms_between_frames, self(), [])

    game_state =
      game_id
      |> Game.state()
      |> Map.put(:x, explosions)
      |> convert()

    case Jason.encode(game_state) do
      {:ok, message} -> {:reply, {:text, message}, %{game_id: game_id, explosions: []}}
      {:error, _} -> {:ok, %{game_id: game_id, explosions: []}}
    end
  end

  # Keep a bounded FIFO list of the recent explosion
  def websocket_info({:explosion, x}, state = %{explosions: []}) do
    {:ok, %{state | explosions: [x]}}
  end

  def websocket_info({:explosion, x}, state = %{explosions: explosions}) do
    {:ok, %{state | explosions: Enum.take([x | explosions], @explosions_per_frame)}}
  end

  def websocket_info(_info, state), do: {:ok, state}

  defp convert(game_state) do
    game_state
    |> Map.update(:a, [], &to_json/1)
    |> Map.update(:b, [], &to_json/1)
    |> Map.update(:s, [], &to_json/1)
    |> Map.update(:x, [], &to_json/1)
  end

  defp to_json(xs), do: Enum.map(xs, fn m -> State.WorldJSON.to_json_list(m) end)
end

defmodule Elixoids.Server.WebsocketGameHandler do
  @moduledoc """
  Websocket Handler for the graphics engine. Queries the game state at 30fps
  and publishes it to subscriber.

  1 hour idle timeout.
  """

  alias Elixoids.Api.State
  alias Elixoids.Game.Server, as: Game

  @fps 30
  @ms_between_frames div(1000, @fps)
  @pause_ms 1000
  @opts %{idle_timeout: 60 * 60 * 1000, compress: false}
  @explosions_per_frame 7

  @behaviour :cowboy_handler

  def init(req = %{bindings: %{game: game}}, _state) do
    {:cowboy_websocket, req, game, @opts}
  end

  def websocket_init(game) do
    with {game_id, ""} <- Integer.parse(game),
         {:ok, _pid} <- Elixoids.News.subscribe(game_id),
         state <- %{game_id: game_id, explosions: []},
         _ref <- :erlang.start_timer(@pause_ms, self(), state) do
      {:ok, state}
    else
      :error -> {:stop, game}
    end
  end

  def terminate(_reason, _state), do: :ok

  def websocket_handle({:text, _ignore}, state), do: {:ok, state}

  def websocket_handle(_inframe, state), do: {:ok, state}

  def websocket_info({:timeout, _ref, _}, %{game_id: game_id, explosions: explosions}) do
    :erlang.start_timer(@ms_between_frames, self(), [])

    game_state =
      game_id
      # TODO this call can fail with :noproc
      |> Game.state()
      |> Map.put(:x, explosions)
      |> convert()

    case Jason.encode(game_state) do
      {:ok, message} -> {:reply, {:text, message}, %{game_id: game_id, explosions: []}}
      {:error, _} -> {:ok, %{game_id: game_id, explosions: []}}
    end
  end

  # Keep a bounded FIFO list of the recent explosion
  def websocket_info({:explosion, x}, state = %{explosions: []}) do
    {:ok, %{state | explosions: [x] }}
  end

  def websocket_info({:explosion, x}, state = %{explosions: explosions}) do
    {:ok, %{state | explosions: Enum.take([x | explosions], @explosions_per_frame)}}
  end

  def websocket_info(_info, state), do: {:ok, state}

  defp convert(game_state) do
    game_state
    |> Map.update(:a, [], &to_json/1)
    |> Map.update(:b, [], &to_json/1)
    |> Map.update(:s, [], &to_json/1)
    |> Map.update(:x, [], &to_json/1)
  end

  defp to_json(xs), do: Enum.map(xs, fn m -> State.WorldJSON.to_json_list(m) end)
end