fixes typo

fixes typo

defmodule Geolix.Adapter.MMDB2.Loader do
  @moduledoc """
  Loader module to load an MMDB2 database into Geolix.
  """

  alias Geolix.Adapter.MMDB2.Decoder
  alias Geolix.Adapter.MMDB2.Metadata
  alias Geolix.Adapter.MMDB2.Reader
  alias Geolix.Adapter.MMDB2.Storage

  @doc """
  Implementation of `Geolix.Adapter.MMDB2.load_database/1`.
  """
  @spec load_database(map) :: :ok
  def load_database(%{ id: id, source: source }) do
    source
    |> Reader.read_database()
    |> split_data()
    |> store_data(id)
  end

  defp split_data({ :error, _reason } = error), do: error
  defp split_data({ data, meta })               do
    meta           = Decoder.value(meta, 0)
    meta           = struct(%Metadata{}, meta)
    record_size    = Map.get(meta, :record_size)
    node_count     = Map.get(meta, :node_count)
    node_byte_size = div(record_size, 4)
    tree_size      = node_count * node_byte_size

    meta = %{ meta | node_byte_size: node_byte_size }
    meta = %{ meta | tree_size:      tree_size }

    tree      = data |> binary_part(0, tree_size)
    data_size = byte_size(data) - byte_size(tree) - 16
    data      = data |> binary_part(tree_size + 16, data_size)

    { tree, data, meta }
  end

  defp store_data({ :error, _reason } = error, _), do: error
  defp store_data({ tree, data, meta }, id)        do
    Storage.Data.set(id, data)
    Storage.Metadata.set(id, meta)
    Storage.Tree.set(id, tree)

    :ok
  end
end

defmodule Geolix.Adapter.MMDB2.Loader do
  @moduledoc """
  Loader module to load an MMDB2 database into Geolix.
  """

  alias Geolix.Adapter.MMDB2.Decoder
  alias Geolix.Adapter.MMDB2.Metadata
  alias Geolix.Adapter.MMDB2.Reader
  alias Geolix.Adapter.MMDB2.Storage

  @doc """
  Implementation of `Geolix.Adapter.MMDB2.load_database/2`.
  """
  @spec load_database(map) :: :ok
  def load_database(%{ id: id, source: source }) do
    source
    |> Reader.read_database()
    |> split_data()
    |> store_data(id)
  end

  defp split_data({ :error, _reason } = error), do: error
  defp split_data({ data, meta })               do
    meta           = Decoder.value(meta, 0)
    meta           = struct(%Metadata{}, meta)
    record_size    = Map.get(meta, :record_size)
    node_count     = Map.get(meta, :node_count)
    node_byte_size = div(record_size, 4)
    tree_size      = node_count * node_byte_size

    meta = %{ meta | node_byte_size: node_byte_size }
    meta = %{ meta | tree_size:      tree_size }

    tree      = data |> binary_part(0, tree_size)
    data_size = byte_size(data) - byte_size(tree) - 16
    data      = data |> binary_part(tree_size + 16, data_size)

    { tree, data, meta }
  end

  defp store_data({ :error, _reason } = error, _), do: error
  defp store_data({ tree, data, meta }, id)        do
    Storage.Data.set(id, data)
    Storage.Metadata.set(id, meta)
    Storage.Tree.set(id, tree)

    :ok
  end
end


Fix read of app file located in archive .ez file (#10062)

defmodule Mix.Tasks.App.Start do
  use Mix.Task

  @shortdoc "Starts all registered apps"

  @moduledoc """
  Starts all registered apps.

  First this task guarantees that all dependencies are in place
  and that the current project has been compiled. Then the current
  application is started as a temporary application, unless
  `:start_permanent` is set to `true` in your project configuration
  or the `--permanent` option is given, then it's started as permanent,
  which guarantees the node will shut down if the application
  crashes permanently.

  ## Configuration

    * `:start_permanent` - the application and all of its children
      applications are started in permanent mode. Defaults to `false`.

    * `:consolidate_protocols` - when `true`, loads consolidated
      protocols before start. The default value is `true`.

    * `:elixir` - matches the current Elixir version against the
      given requirement

  ## Command line options

    * `--force` - forces compilation regardless of compilation times
    * `--temporary` - starts the application as temporary
    * `--permanent` - starts the application as permanent
    * `--preload-modules` - preloads all modules defined in applications
    * `--no-archives-check` - does not check archives
    * `--no-compile` - does not compile even if files require compilation
    * `--no-deps-check` - does not check dependencies
    * `--no-elixir-version-check` - does not check Elixir version
    * `--no-start` - does not actually start applications, only compiles and loads code
    * `--no-validate-compile-env` - does not validate the application compile environment

  """

  @compile {:no_warn_undefined, Logger.App}

  @switches [
    permanent: :boolean,
    temporary: :boolean,
    preload_modules: :boolean
  ]

  @impl true
  def run(args) do
    Mix.Project.get!()
    Mix.Task.run("compile", args)
    {opts, _, _} = OptionParser.parse(args, switches: @switches)

    if "--no-start" in args do
      Mix.Task.reenable("app.start")
    else
      # Stop Logger when starting the application as it is up to the
      # application to decide if it should be restarted or not.
      #
      # Mix should not depend directly on Logger so check that it's loaded.
      if Process.whereis(Logger) do
        Logger.App.stop()
      end

      config = Mix.Project.config()
      start(apps(config), type(config, opts), "--no-validate-compile-env" not in args)

      # If there is a build path, we will let the application
      # that owns the build path do the actual check
      unless config[:build_path] do
        loaded = loaded_applications(opts)
        check_configured(loaded)
      end
    end

    :ok
  end

  @doc false
  def start(apps, type, validate_compile_env? \\ true) do
    Enum.each(apps, &ensure_all_started(&1, type, validate_compile_env?))
    :ok
  end

  defp apps(config) do
    cond do
      Mix.Project.umbrella?(config) ->
        for %Mix.Dep{app: app} <- Mix.Dep.Umbrella.cached(), do: app

      app = config[:app] ->
        [app]

      true ->
        []
    end
  end

  defp ensure_all_started(app, type, validate_compile_env?) do
    case load_check_and_start(app, type, validate_compile_env?) do
      :ok ->
        :ok

      {:error, {:already_started, ^app}} ->
        :ok

      {:error, {:not_started, dep}} ->
        :ok = ensure_all_started(dep, type, validate_compile_env?)
        ensure_all_started(app, type, validate_compile_env?)

      {:error, reason} when type == :permanent ->
        # We need to stop immediately because application_controller is
        # shutting down all applications. Since any work we do here is prone
        # to race conditions as whatever process we call may no longer exist,
        # we print a quick message and then block by calling `System.stop/1`.
        Mix.shell().error(["** (Mix) ", could_not_start(app, reason)])
        System.stop(1)

      {:error, reason} ->
        Mix.raise(could_not_start(app, reason))
    end
  end

  defp could_not_start(app, reason) do
    "Could not start application #{app}: " <> Application.format_error(reason)
  end

  defp load_check_and_start(app, type, validate_compile_env?) do
    case :application.get_key(app, :vsn) do
      {:ok, _} ->
        :application.start(app, type)

      :undefined ->
        name = Atom.to_charlist(app) ++ '.app'

        case :code.where_is_file(name) do
          :non_existing ->
            {:error, {:file.format_error(:enoent), name}}

          path ->
            case consult_app_file_maybe_in_archive(path) do
              {:ok, {:application, _, properties} = application_data} ->
                with :ok <- :application.load(application_data) do
                  if compile_env = validate_compile_env? && properties[:compile_env] do
                    # Unfortunately we can only check the current app here,
                    # otherwise we would accidentally load upcoming apps
                    compile_env = for {^app, path, return} <- compile_env, do: {app, path, return}
                    Config.Provider.validate_compile_env(compile_env)
                  end

                  :application.start(app, type)
                end

              {:error, reason} ->
                {:error, {:file.format_error(reason), name}}
            end
        end
    end
  end

  defp consult_app_file_maybe_in_archive(path) do
    # The path could be located in .ez archive
    case :erl_prim_loader.get_file(path) do
      {:ok, bin, _full_name} ->
        with {:ok, tokens, _} <- :erl_scan.string(String.to_charlist(bin)) do
          :erl_parse.parse_term(tokens)
        end

      :error ->
        {:error, :enoent}
    end
  end

  @doc false
  def type(config, opts) do
    cond do
      opts[:temporary] -> :temporary
      opts[:permanent] -> :permanent
      config[:start_permanent] -> :permanent
      true -> :temporary
    end
  end

  defp loaded_applications(opts) do
    preload_modules? = opts[:preload_modules]

    for {app, _, _} <- Application.loaded_applications() do
      if modules = preload_modules? && Application.spec(app, :modules) do
        :code.ensure_modules_loaded(modules)
      end

      app
    end
  end

  defp check_configured(loaded) do
    configured = Mix.ProjectStack.config_apps()

    for app <- configured -- loaded, :code.lib_dir(app) == {:error, :bad_name} do
      Mix.shell().error("""
      You have configured application #{inspect(app)} in your configuration file,
      but the application is not available.

      This usually means one of:

        1. You have not added the application as a dependency in a mix.exs file.

        2. You are configuring an application that does not really exist.

      Please ensure #{inspect(app)} exists or remove the configuration.
      """)
    end

    :ok
  end
end

defmodule Mix.Tasks.App.Start do
  use Mix.Task

  @shortdoc "Starts all registered apps"

  @moduledoc """
  Starts all registered apps.

  First this task guarantees that all dependencies are in place
  and that the current project has been compiled. Then the current
  application is started as a temporary application, unless
  `:start_permanent` is set to `true` in your project configuration
  or the `--permanent` option is given, then it's started as permanent,
  which guarantees the node will shut down if the application
  crashes permanently.

  ## Configuration

    * `:start_permanent` - the application and all of its children
      applications are started in permanent mode. Defaults to `false`.

    * `:consolidate_protocols` - when `true`, loads consolidated
      protocols before start. The default value is `true`.

    * `:elixir` - matches the current Elixir version against the
      given requirement

  ## Command line options

    * `--force` - forces compilation regardless of compilation times
    * `--temporary` - starts the application as temporary
    * `--permanent` - starts the application as permanent
    * `--preload-modules` - preloads all modules defined in applications
    * `--no-archives-check` - does not check archives
    * `--no-compile` - does not compile even if files require compilation
    * `--no-deps-check` - does not check dependencies
    * `--no-elixir-version-check` - does not check Elixir version
    * `--no-start` - does not actually start applications, only compiles and loads code
    * `--no-validate-compile-env` - does not validate the application compile environment

  """

  @compile {:no_warn_undefined, Logger.App}

  @switches [
    permanent: :boolean,
    temporary: :boolean,
    preload_modules: :boolean
  ]

  @impl true
  def run(args) do
    Mix.Project.get!()
    Mix.Task.run("compile", args)
    {opts, _, _} = OptionParser.parse(args, switches: @switches)

    if "--no-start" in args do
      Mix.Task.reenable("app.start")
    else
      # Stop Logger when starting the application as it is up to the
      # application to decide if it should be restarted or not.
      #
      # Mix should not depend directly on Logger so check that it's loaded.
      if Process.whereis(Logger) do
        Logger.App.stop()
      end

      config = Mix.Project.config()
      start(apps(config), type(config, opts), "--no-validate-compile-env" not in args)

      # If there is a build path, we will let the application
      # that owns the build path do the actual check
      unless config[:build_path] do
        loaded = loaded_applications(opts)
        check_configured(loaded)
      end
    end

    :ok
  end

  @doc false
  def start(apps, type, validate_compile_env? \\ true) do
    Enum.each(apps, &ensure_all_started(&1, type, validate_compile_env?))
    :ok
  end

  defp apps(config) do
    cond do
      Mix.Project.umbrella?(config) ->
        for %Mix.Dep{app: app} <- Mix.Dep.Umbrella.cached(), do: app

      app = config[:app] ->
        [app]

      true ->
        []
    end
  end

  defp ensure_all_started(app, type, validate_compile_env?) do
    case load_check_and_start(app, type, validate_compile_env?) do
      :ok ->
        :ok

      {:error, {:already_started, ^app}} ->
        :ok

      {:error, {:not_started, dep}} ->
        :ok = ensure_all_started(dep, type, validate_compile_env?)
        ensure_all_started(app, type, validate_compile_env?)

      {:error, reason} when type == :permanent ->
        # We need to stop immediately because application_controller is
        # shutting down all applications. Since any work we do here is prone
        # to race conditions as whatever process we call may no longer exist,
        # we print a quick message and then block by calling `System.stop/1`.
        Mix.shell().error(["** (Mix) ", could_not_start(app, reason)])
        System.stop(1)

      {:error, reason} ->
        Mix.raise(could_not_start(app, reason))
    end
  end

  defp could_not_start(app, reason) do
    "Could not start application #{app}: " <> Application.format_error(reason)
  end

  defp load_check_and_start(app, type, validate_compile_env?) do
    case :application.get_key(app, :vsn) do
      {:ok, _} ->
        :application.start(app, type)

      :undefined ->
        name = Atom.to_charlist(app) ++ '.app'

        case :code.where_is_file(name) do
          :non_existing ->
            {:error, {:file.format_error(:enoent), name}}

          path ->
            case :file.consult(path) do
              {:ok, [{:application, _, properties} = application_data]} ->
                with :ok <- :application.load(application_data) do
                  if compile_env = validate_compile_env? && properties[:compile_env] do
                    # Unfortunately we can only check the current app here,
                    # otherwise we would accidentally load upcoming apps
                    compile_env = for {^app, path, return} <- compile_env, do: {app, path, return}
                    Config.Provider.validate_compile_env(compile_env)
                  end

                  :application.start(app, type)
                end

              {:error, reason} ->
                {:error, {:file.format_error(reason), name}}
            end
        end
    end
  end

  @doc false
  def type(config, opts) do
    cond do
      opts[:temporary] -> :temporary
      opts[:permanent] -> :permanent
      config[:start_permanent] -> :permanent
      true -> :temporary
    end
  end

  defp loaded_applications(opts) do
    preload_modules? = opts[:preload_modules]

    for {app, _, _} <- Application.loaded_applications() do
      if modules = preload_modules? && Application.spec(app, :modules) do
        :code.ensure_modules_loaded(modules)
      end

      app
    end
  end

  defp check_configured(loaded) do
    configured = Mix.ProjectStack.config_apps()

    for app <- configured -- loaded, :code.lib_dir(app) == {:error, :bad_name} do
      Mix.shell().error("""
      You have configured application #{inspect(app)} in your configuration file,
      but the application is not available.

      This usually means one of:

        1. You have not added the application as a dependency in a mix.exs file.

        2. You are configuring an application that does not really exist.

      Please ensure #{inspect(app)} exists or remove the configuration.
      """)
    end

    :ok
  end
end


Run the code formatter on Mix.Tasks.App.Start

defmodule Mix.Tasks.App.Start do
  use Mix.Task

  # Do not mark this task as recursive as it is
  # responsible for loading consolidated protocols.
  @shortdoc "Starts all registered apps"

  @moduledoc """
  Starts all registered apps.

  The application is started by default as temporary. In case
  `:start_permanent` is set to `true` in your project configuration
  or the `--permanent` flag is given, it is started as permanent,
  which guarantees the node will shutdown if the application
  crashes permanently.

  ## Configuration

    * `:start_permanent` - the application and all of its children
      applications are started in permanent mode. Defaults to `false`.

    * `:consolidate_protocols` - when `true`, loads consolidated
      protocols before start. The default value is `true`.

    * `:elixir` - matches the current Elixir version against the
      given requirement

  ## Command line options

    * `--force` - forces compilation regardless of compilation times
    * `--temporary` - starts the application as temporary
    * `--permanent` - starts the application as permanent
    * `--no-compile` - does not compile even if files require compilation
    * `--no-protocols` - does not load consolidated protocols
    * `--no-archives-check` - does not check archives
    * `--no-deps-check` - does not check dependencies
    * `--no-elixir-version-check` - does not check Elixir version
    * `--no-start` - does not start applications after compilation

  """
  def run(args) do
    Mix.Project.get!()
    config = Mix.Project.config()

    {opts, _, _} = OptionParser.parse(args, switches: [permanent: :boolean, temporary: :boolean])
    Mix.Task.run("loadpaths", args)

    unless "--no-compile" in args do
      Mix.Project.compile(args, config)
    end

    unless "--no-protocols" in args do
      path = Mix.Project.consolidation_path(config)

      if config[:consolidate_protocols] && File.dir?(path) do
        Code.prepend_path(path)
        Enum.each(File.ls!(path), &load_protocol/1)
      end
    end

    # Stop Logger when starting the application as it is
    # up to the application to decide if it should be restarted
    # or not.
    #
    # Mix should not depend directly on Logger so check that it's loaded.
    logger = Process.whereis(Logger)

    if logger do
      Logger.App.stop()
    end

    if "--no-start" in args do
      # Start Logger again if the application won't be starting it
      if logger do
        :ok = Logger.App.start()
      end
    else
      start(Mix.Project.config(), opts)
    end

    :ok
  end

  @doc false
  def start(config, opts) do
    apps =
      cond do
        Mix.Project.umbrella?(config) ->
          for %Mix.Dep{app: app} <- Mix.Dep.Umbrella.cached(), do: app

        app = config[:app] ->
          [app]

        true ->
          []
      end

    type = type(config, opts)
    Enum.each(apps, &ensure_all_started(&1, type))

    # If there is a build path, we will let the application
    # that owns the build path do the actual check
    unless config[:build_path] do
      check_configured()
    end

    :ok
  end

  defp ensure_all_started(app, type) do
    case Application.ensure_all_started(app, type) do
      {:ok, _} ->
        :ok

      {:error, {app, reason}} ->
        Mix.raise("Could not start application #{app}: " <> Application.format_error(reason))
    end
  end

  @doc false
  def type(config, opts) do
    cond do
      opts[:temporary] -> :temporary
      opts[:permanent] -> :permanent
      config[:start_permanent] -> :permanent
      true -> :temporary
    end
  end

  defp check_configured() do
    configured = Mix.ProjectStack.configured_applications()
    loaded = for {app, _, _} <- Application.loaded_applications(), do: app

    _ =
      for app <- configured -- loaded, :code.lib_dir(app) == {:error, :bad_name} do
        Mix.shell().error("""
        You have configured application #{inspect(app)} in your configuration
        file, but the application is not available.

        This usually means one of:

        1. You have not added the application as a dependency in a mix.exs file.

        2. You are configuring an application that does not really exist.

        Please ensure #{inspect(app)} exists or remove the configuration.
        """)
      end

    :ok
  end

  defp load_protocol(file) do
    case file do
      "Elixir." <> _ ->
        module = file |> Path.rootname() |> String.to_atom()
        :code.purge(module)
        :code.delete(module)

      _ ->
        :ok
    end
  end
end

defmodule Mix.Tasks.App.Start do
  use Mix.Task

  # Do not mark this task as recursive as it is
  # responsible for loading consolidated protocols.
  @shortdoc "Starts all registered apps"

  @moduledoc """
  Starts all registered apps.

  The application is started by default as temporary. In case
  `:start_permanent` is set to `true` in your project configuration
  or the `--permanent` flag is given, it is started as permanent,
  which guarantees the node will shutdown if the application
  crashes permanently.

  ## Configuration

    * `:start_permanent` - the application and all of its children
      applications are started in permanent mode. Defaults to `false`.

    * `:consolidate_protocols` - when `true`, loads consolidated
      protocols before start. The default value is `true`.

    * `:elixir` - matches the current Elixir version against the
      given requirement

  ## Command line options

    * `--force` - forces compilation regardless of compilation times
    * `--temporary` - starts the application as temporary
    * `--permanent` - starts the application as permanent
    * `--no-compile` - does not compile even if files require compilation
    * `--no-protocols` - does not load consolidated protocols
    * `--no-archives-check` - does not check archives
    * `--no-deps-check` - does not check dependencies
    * `--no-elixir-version-check` - does not check Elixir version
    * `--no-start` - does not start applications after compilation

  """
  def run(args) do
    Mix.Project.get!
    config = Mix.Project.config

    {opts, _, _} = OptionParser.parse args, switches: [permanent: :boolean, temporary: :boolean]
    Mix.Task.run "loadpaths", args

    unless "--no-compile" in args do
      Mix.Project.compile(args, config)
    end

    unless "--no-protocols" in args do
      path = Mix.Project.consolidation_path(config)

      if config[:consolidate_protocols] && File.dir?(path) do
        Code.prepend_path(path)
        Enum.each(File.ls!(path), &load_protocol/1)
      end
    end

    # Stop Logger when starting the application as it is
    # up to the application to decide if it should be restarted
    # or not.
    #
    # Mix should not depend directly on Logger so check that it's loaded.
    logger = Process.whereis(Logger)
    if logger do
      Logger.App.stop
    end

    if "--no-start" in args do
      # Start Logger again if the application won't be starting it
      if logger do
        :ok = Logger.App.start
      end
    else
      start(Mix.Project.config, opts)
    end

    :ok
  end

  @doc false
  def start(config, opts) do
    apps =
      cond do
        Mix.Project.umbrella?(config) ->
          for %Mix.Dep{app: app} <- Mix.Dep.Umbrella.cached, do: app
        app = config[:app] ->
          [app]
        true ->
          []
      end

    type = type(config, opts)
    Enum.each apps, &ensure_all_started(&1, type)

    # If there is a build path, we will let the application
    # that owns the build path do the actual check
    unless config[:build_path] do
      check_configured()
    end

    :ok
  end

  defp ensure_all_started(app, type) do
    case Application.ensure_all_started(app, type) do
      {:ok, _} -> :ok
      {:error, {app, reason}} ->
        Mix.raise "Could not start application #{app}: " <>
          Application.format_error(reason)
    end
  end

  @doc false
  def type(config, opts) do
    cond do
      opts[:temporary] -> :temporary
      opts[:permanent] -> :permanent
      config[:start_permanent] -> :permanent
      true -> :temporary
    end
  end

  defp check_configured() do
    configured = Mix.ProjectStack.configured_applications
    loaded = for {app, _, _} <- Application.loaded_applications(), do: app
    _ = for app <- configured -- loaded,
           :code.lib_dir(app) == {:error, :bad_name} do
      Mix.shell.error """
      You have configured application #{inspect app} in your configuration
      file, but the application is not available.

      This usually means one of:

      1. You have not added the application as a dependency in a mix.exs file.

      2. You are configuring an application that does not really exist.

      Please ensure #{inspect app} exists or remove the configuration.
      """
    end
    :ok
  end

  defp load_protocol(file) do
    case file do
      "Elixir." <> _ ->
        module = file |> Path.rootname |> String.to_atom
        :code.purge(module)
        :code.delete(module)
      _ ->
        :ok
    end
  end
end


Fix various typos and improve docs (#9922)

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

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

  For example:

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

        use Mix.Task

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

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

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

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

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

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

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

      @requirements ["compile"]

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

      @requirements ["app.start"]

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

  ## Attributes

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

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

  ## Documentation

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

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

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

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

      @behaviour Mix.Task
    end
  end

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

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

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

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

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

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

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

      _ ->
        nil
    end
  end

  @doc """
  Returns all loaded task modules.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    requirements
  end

  @doc """
  Returns the task name for the given `module`.

  ## Examples

      iex> Mix.Task.task_name(Mix.Tasks.Test)
      "test"

  """
  @spec task_name(task_module) :: task_name
  def task_name(module) when is_atom(module) do
    Mix.Utils.module_name_to_command(module, 2)
  end

  @doc """
  Checks if the given `task` name is an alias.

  Returns false if the given name is not an alias or if it is not a task.

  For more information about task aliasing, take a look at the
  ["Aliasing"](https://hexdocs.pm/mix/Mix.html#module-aliases) section in the
  docs for `Mix`.
  """
  @spec alias?(task_name) :: boolean
  def alias?(task) when is_binary(task) do
    alias?(String.to_atom(task))
  end

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

  @doc """
  Receives a task name and returns the corresponding task module if one exists.

  Returns `nil` if the module cannot be found, if it is an alias, or if it is
  not a valid `Mix.Task`.
  """
  @spec get(task_name) :: task_module | nil
  def get(task) do
    case fetch(task) do
      {:ok, module} -> module
      {:error, _} -> nil
    end
  end

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

  ## Exceptions

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

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

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

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

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

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

  @doc """
  Conditionally runs the task (or alias) with the given `args`.

  If there exists a task matching the given task name and it has not yet been
  invoked, this will run the task with the given `args` and return the result.

  If there is an [alias](https://hexdocs.pm/mix/Mix.html#module-aliases) defined
  for the given task name, the alias will be invoked instead of the original
  task.

  If the task or alias has already been invoked, subsequent calls to `run/2`
  will _abort_ without executing and return `:noop`.

  Remember: by default, tasks will only run _once_, even when called repeatedly!
  If you need to run a task multiple times, you need to re-enable it via
  `reenable/1` or call it using `rerun/2`.

  `run/2` raises an exception if an alias or a task cannot be found or if the
  task is invalid. See `get!/1` for more information.
  """
  @spec run(task_name, [any]) :: any
  def run(task, args \\ [])

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

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

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

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

      true ->
        :noop
    end
  end

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

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

    run_requirements(module)

    recursive = recursive(module)

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

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

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

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

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

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

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

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

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

      proj ->
        fun.()
        nil

      true ->
        nil
    end
  end

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

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

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

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

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

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

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

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

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

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

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

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

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

      true ->
        :ok
    end

    :ok
  end

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

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

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

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

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

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

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

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

  For example:

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

        use Mix.Task

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

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

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

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

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

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

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

      @requirements ["compile"]

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

      @requirements ["app.start"]

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

  ## Attributes

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

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

  ## Documentation

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

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

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

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

      @behaviour Mix.Task
    end
  end

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

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

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

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

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

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

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

      _ ->
        nil
    end
  end

  @doc """
  Returns all loaded task modules.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    requirements
  end

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

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

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

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

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

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

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

  ## Exceptions

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      true ->
        :noop
    end
  end

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

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

    run_requirements(module)

    recursive = recursive(module)

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

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

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

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

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

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

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

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

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

      proj ->
        fun.()
        nil

      true ->
        nil
    end
  end

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

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

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

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

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

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

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

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

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

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

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

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

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

      true ->
        :ok
    end

    :ok
  end

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

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

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

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

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

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


Better error when NERVES_SYSTEM/TOOLCHAIN missing

defmodule Mix.Nerves.Utils do
  @fwup_semver "~> 0.15 or ~> 1.0.0-dev or ~> 1.0"

  def shell(cmd, args, opts \\ []) do
    stream = opts[:stream] || IO.binstream(:standard_io, :line)
    std_err = opts[:stderr_to_stdout] || true
    opts = Keyword.drop(opts, [:into, :stderr_to_stdout, :stream])
    System.cmd(cmd, args, [into: stream, stderr_to_stdout: std_err] ++ opts)
  end

  def preflight do
    check_requirements()
    Mix.Task.run "nerves.loadpaths"
  end

  def check_requirements do
    {_, type} = :os.type

    which_or_where = case type do
                        :nt -> "where"
                        _ -> "which"
                     end

    case System.cmd(which_or_where, ["mksquashfs"]) do
      {_, 0} -> nil
      _ -> Mix.raise """
      Squash FS Tools are required to be installed on your system.
      Please see https://hexdocs.pm/nerves/installation.html#host-specific-tools
      for installation instructions
      """
    end

    with {_, 0} <- System.cmd(which_or_where, ["fwup"]),
         {vsn, 0} <- System.cmd("fwup", ["--version"]),
         vsn = String.trim(vsn),
         {:ok, req} = Version.parse_requirement(@fwup_semver),
         true <- Version.match?(vsn, req) do
    else
      false ->
        {vsn, 0} = System.cmd("fwup", ["--version"])
        Mix.raise """
        fwup #{@fwup_semver} is required for Nerves.
        You are running #{vsn}.
        Please see https://hexdocs.pm/nerves/installation.html#fwup
        for installation instructions
        """
      {_, _} ->
        Mix.raise """
        fwup is required to create and burn firmware.
        Please see https://hexdocs.pm/nerves/installation.html#fwup
        for installation instructions
        """
    end

    check_host_requirements(type)
  end

  def check_host_requirements(:darwin) do
    case System.cmd("which", ["gstat"]) do
      {_, 0} -> nil
      _ -> Mix.raise """
      gstat is required to create and burn firmware.
      Please see https://hexdocs.pm/nerves/installation.html#host-specific-tools
      for installation instructions
      """
    end
  end
  def check_host_requirements(_), do: nil

  def debug_info(msg) do
    if System.get_env("NERVES_DEBUG") == "1" do
      Mix.shell.info(msg)
    end
  end

  def check_nerves_system_is_set! do
    var_name = "NERVES_SYSTEM"
    System.get_env(var_name) || raise_env_var_missing(var_name)
  end

  def check_nerves_toolchain_is_set! do
    var_name = "NERVES_TOOLCHAIN"
    System.get_env(var_name) || raise_env_var_missing(var_name)
  end

  defp raise_env_var_missing(name) do
    Mix.raise """
    Environment variable $#{name} is not set.

    This variable is usually set for you by Nerves when you specify the
    $MIX_TARGET. For examples please see
    https://hexdocs.pm/nerves/getting-started.html#create-the-firmware-bundle
    """
  end
end

defmodule Mix.Nerves.Utils do
  @fwup_semver "~> 0.15 or ~> 1.0.0-dev or ~> 1.0"

  def shell(cmd, args, opts \\ []) do
    stream = opts[:stream] || IO.binstream(:standard_io, :line)
    std_err = opts[:stderr_to_stdout] || true
    opts = Keyword.drop(opts, [:into, :stderr_to_stdout, :stream])
    System.cmd(cmd, args, [into: stream, stderr_to_stdout: std_err] ++ opts)
  end

  def preflight do
    check_requirements()
    Mix.Task.run "nerves.loadpaths"
  end

  def check_requirements do
    {_, type} = :os.type

    which_or_where = case type do
                        :nt -> "where"
                        _ -> "which"
                     end

    case System.cmd(which_or_where, ["mksquashfs"]) do
      {_, 0} -> nil
      _ -> Mix.raise """
      Squash FS Tools are required to be installed on your system.
      Please see https://hexdocs.pm/nerves/installation.html#host-specific-tools
      for installation instructions
      """
    end

    with {_, 0} <- System.cmd(which_or_where, ["fwup"]),
         {vsn, 0} <- System.cmd("fwup", ["--version"]),
         vsn = String.trim(vsn),
         {:ok, req} = Version.parse_requirement(@fwup_semver),
         true <- Version.match?(vsn, req) do
    else
      false ->
        {vsn, 0} = System.cmd("fwup", ["--version"])
        Mix.raise """
        fwup #{@fwup_semver} is required for Nerves.
        You are running #{vsn}.
        Please see https://hexdocs.pm/nerves/installation.html#fwup
        for installation instructions
        """
      {_, _} ->
        Mix.raise """
        fwup is required to create and burn firmware.
        Please see https://hexdocs.pm/nerves/installation.html#fwup
        for installation instructions
        """
    end

    check_host_requirements(type)
  end

  def check_host_requirements(:darwin) do
    case System.cmd("which", ["gstat"]) do
      {_, 0} -> nil
      _ -> Mix.raise """
      gstat is required to create and burn firmware.
      Please see https://hexdocs.pm/nerves/installation.html#host-specific-tools
      for installation instructions
      """
    end
  end
  def check_host_requirements(_), do: nil

  def debug_info(msg) do
    if System.get_env("NERVES_DEBUG") == "1" do
      Mix.shell.info(msg)
    end
  end

  def check_nerves_system_is_set! do
    System.get_env("NERVES_SYSTEM") || Mix.raise """
      Environment variable $NERVES_SYSTEM is not set
    """

  end

  def check_nerves_toolchain_is_set! do
    System.get_env("NERVES_TOOLCHAIN") || Mix.raise """
      Environment variable $NERVES_TOOLCHAIN is not set
    """
  end
end


Remove the trivia message about mix and hex.pm

defmodule TriviaHook do
  def run(_sender, text) do
    tokens = tokenize(text)
    result = cond do
      find_at_least(tokens, [{["mix", "project", "application", "app"], 1}, {["shell", "iex", "repl"], 1}, {["?"], 1}]) ->
        "To start an interactive shell with your mix project loaded in it, run `iex -S mix`"

      find_at_least(tokens, [{["records"], 1}, {["remove", "removed"], 1}]) or find_at_least(tokens, [{["records"], 1}, {["replace", "replaced"], 1}, {["maps", "structs"], 1}]) ->
        "In Elixir v0.13 structs have replaced records. See this proposal https://gist.github.com/josevalim/b30c881df36801611d13. Private records remain unchanged."

      find_at_least(tokens, [{["mix"], 1}, {["hex"], 1}, {["replace", "replaces"], 1}, {["?"], 1}]) ->
        "hex does not replace mix, it augments it"

      #find_at_least(tokens, [{["mix"], 1}, {["hex", "hex.pm"], 1}, {["?"], 1}]) ->
        #"hex.pm hosts packages to be used as dependencies. hex is a command-line tool that interacts with hex.pm and resolves versioning of dependencies. Also see https://groups.google.com/d/msg/elixir-lang-talk/VmSacLsDSXk/fmAxXVn3jC4J"

      find_at_least(tokens, [{["elixir"], 1}, {["package", "packages", "npm", "gem", "gems", "bundler"], 1}, {["?"], 1}]) ->
        {:msg, msg} = LinkHook.run(_sender, "hex")
        msg

      find_at_least(tokens, [{["elixir"], 1}, {["conference", "conferences"], 1}, {["?"], 1}]) ->
        "ElixirConf is an upcoming conference (July 25-26, 2014 Austin, TX). See http://elixirconf.com/"

      find_at_least(tokens, [{["elixir"], 1}, {["rails"], 1}, {["want", "wants", "is", "why", "does"], 1}, {["?"], 1}]) ->
        "please don't make rails for Elixir please. https://twitter.com/thomasfuchs/status/457158363663843328"

      true -> nil
    end
    result && {:msg, result}
  end

  def tokenize(text) do
    Regex.split(~r"[[:space:]]|\b", String.downcase(text), trim: true)
  end

  def find_at_least(tokens, pairs) do
    count = process_pairs(pairs, tokens, 0)
    count == length(pairs)
  end

  defp process_pairs([], _, count), do: count

  defp process_pairs([{terms, tc}|t], tokens, count) do
    processed_count = process_terms(terms, tokens, tc)
    process_pairs(t, tokens, count + processed_count)
  end

  defp process_terms(_, _, 0), do: 1

  defp process_terms([], _, _), do: 0

  defp process_terms([word|t], tokens, count) do
    sub = if Enum.member?(tokens, word), do: 1, else: 0
    process_terms(t, tokens, count - sub)
  end
end

defmodule TriviaHook do
  def run(_sender, text) do
    tokens = tokenize(text)
    result = cond do
      find_at_least(tokens, [{["mix", "project", "application", "app"], 1}, {["shell", "iex", "repl"], 1}, {["?"], 1}]) ->
        "To start an interactive shell with your mix project loaded in it, run `iex -S mix`"

      find_at_least(tokens, [{["records"], 1}, {["remove", "removed"], 1}]) or find_at_least(tokens, [{["records"], 1}, {["replace", "replaced"], 1}, {["maps", "structs"], 1}]) ->
        "In Elixir v0.13 structs have replaced records. See this proposal https://gist.github.com/josevalim/b30c881df36801611d13. Private records remain unchanged."

      find_at_least(tokens, [{["mix"], 1}, {["hex"], 1}, {["replace", "replaces"], 1}, {["?"], 1}]) ->
        "hex does not replace mix, it augments it"

      find_at_least(tokens, [{["mix"], 1}, {["hex", "hex.pm"], 1}, {["?"], 1}]) ->
        "hex.pm hosts packages to be used as dependencies. hex is a command-line tool that interacts with hex.pm and resolves versioning of dependencies. Also see https://groups.google.com/d/msg/elixir-lang-talk/VmSacLsDSXk/fmAxXVn3jC4J"

      find_at_least(tokens, [{["elixir"], 1}, {["package", "packages", "npm", "gem", "gems", "bundler"], 1}, {["?"], 1}]) ->
        {:msg, msg} = LinkHook.run(_sender, "hex")
        msg

      find_at_least(tokens, [{["elixir"], 1}, {["conference", "conferences"], 1}, {["?"], 1}]) ->
        "ElixirConf is an upcoming conference (July 25-26, 2014 Austin, TX). See http://elixirconf.com/"

      find_at_least(tokens, [{["elixir"], 1}, {["rails"], 1}, {["want", "wants", "is", "why", "does"], 1}, {["?"], 1}]) ->
        "please don't make rails for Elixir please. https://twitter.com/thomasfuchs/status/457158363663843328"

      true -> nil
    end
    result && {:msg, result}
  end

  def tokenize(text) do
    Regex.split(~r"[[:space:]]|\b", String.downcase(text), trim: true)
  end

  def find_at_least(tokens, pairs) do
    count = process_pairs(pairs, tokens, 0)
    count == length(pairs)
  end

  defp process_pairs([], _, count), do: count

  defp process_pairs([{terms, tc}|t], tokens, count) do
    processed_count = process_terms(terms, tokens, tc)
    process_pairs(t, tokens, count + processed_count)
  end

  defp process_terms(_, _, 0), do: 1

  defp process_terms([], _, _), do: 0

  defp process_terms([word|t], tokens, count) do
    sub = if Enum.member?(tokens, word), do: 1, else: 0
    process_terms(t, tokens, count - sub)
  end
end


Allow http_opts to be over-written

Allow http_opts to be over-written

Github Enterprise seems to need `autoredirect: true` to download
large-ish packages

defmodule Nerves.Utils.HTTPClient do
  use GenServer

  @progress_steps 50
  @redirect_status_codes [301, 302, 303, 307, 308]

  def start_link() do
    {:ok, _} = Application.ensure_all_started(:nerves)
    start_httpc()
    GenServer.start_link(__MODULE__, [])
  end

  def stop(pid) do
    GenServer.stop(pid)
  end

  def get(_, _, _ \\ [])

  def get(_pid, %URI{host: nil, path: path}, _opts) do
    path
    |> Path.expand()
    |> File.read()
  end

  def get(pid, %URI{} = uri, opts) do
    url =
      uri
      |> URI.to_string()
      |> URI.encode()
      |> String.replace("+", "%2B")

    get(pid, url, opts)
  end

  def get(pid, url, opts), do: GenServer.call(pid, {:get, url, opts}, :infinity)

  def init([]) do
    {:ok,
     %{
       url: nil,
       content_length: 0,
       buffer: "",
       buffer_size: 0,
       filename: "",
       caller: nil,
       number_of_redirects: 0,
       progress?: true,
       opts: []
     }}
  end

  def handle_call({:get, _url, _opts}, _from, %{number_of_redirects: n} = s) when n > 5 do
    GenServer.reply(s.caller, {:error, :too_many_redirects})
    {:noreply, %{s | url: nil, number_of_redirects: 0, caller: nil}}
  end

  def handle_call({:get, url, opts}, from, s) do
    progress? = Keyword.get(opts, :progress?, true)

    headers =
      Keyword.get(opts, :headers, [])
      |> Enum.map(fn {k, v} ->
        {to_charlist(k), to_charlist(v)}
      end)

    headers = [
      {'User-Agent', 'Nerves HTTP Client #{Nerves.version()}'},
      {'Content-Type', 'application/octet-stream'} | headers
    ]

    http_opts =
      [timeout: :infinity, autoredirect: false]
      |> Keyword.merge(Nerves.Utils.Proxy.config(url))
      |> Keyword.merge(Keyword.get(opts, :http_opts, []))

    opts = [stream: :self, receiver: self(), sync: false]
    :httpc.request(:get, {String.to_charlist(url), headers}, http_opts, opts, :nerves)
    {:noreply, %{s | url: url, caller: from, opts: opts, progress?: progress?}}
  end

  def handle_info({:http, {_, :stream_start, headers}}, s) do
    content_length =
      case Enum.find(headers, fn {key, _} -> key == 'content-length' end) do
        nil ->
          0

        {_, content_length} ->
          {content_length, _} =
            content_length
            |> to_string()
            |> Integer.parse()

          content_length
      end

    filename =
      case Enum.find(headers, fn {key, _} -> key == 'content-disposition' end) do
        nil ->
          Path.basename(s.url)

        {_, filename} ->
          filename
          |> to_string
          |> String.split(";")
          |> List.last()
          |> String.trim()
          |> String.trim("filename=")
      end

    {:noreply, %{s | content_length: content_length, filename: filename}}
  end

  def handle_info({:http, {_, :stream, data}}, s) do
    size = byte_size(data) + s.buffer_size
    buffer = s.buffer <> data

    if progress?(s) do
      put_progress(size, s.content_length)
    end

    {:noreply, %{s | buffer_size: size, buffer: buffer}}
  end

  def handle_info({:http, {_, :stream_end, _headers}}, s) do
    if progress?(s) do
      IO.write(:stderr, "\n")
    end

    GenServer.reply(s.caller, {:ok, s.buffer})
    {:noreply, %{s | filename: "", content_length: 0, buffer: "", buffer_size: 0, url: nil}}
  end

  def handle_info({:http, {_ref, {{_, status_code, reason}, headers, _body}}}, s)
      when status_code in @redirect_status_codes do
    case Enum.find(headers, fn {key, _} -> key == 'location' end) do
      {'location', next_location} ->
        handle_call({:get, List.to_string(next_location), headers: headers}, s.caller, %{
          s
          | buffer: "",
            buffer_size: 0,
            number_of_redirects: s.number_of_redirects + 1
        })

      _ ->
        GenServer.reply(s.caller, {:error, format_error(status_code, reason)})
    end
  end

  def handle_info({:http, {_ref, {{_, status_code, reason}, _headers, _body}}}, s) do
    GenServer.reply(s.caller, {:error, format_error(status_code, reason)})
    {:noreply, s}
  end

  def put_progress(size, max) do
    fraction = size / max
    completed = trunc(fraction * @progress_steps)
    percent = trunc(fraction * 100)
    unfilled = @progress_steps - completed

    IO.write(
      :stderr,
      "\r|#{String.duplicate("=", completed)}#{String.duplicate(" ", unfilled)}| #{percent}% (#{
        bytes_to_mb(size)
      } / #{bytes_to_mb(max)}) MB"
    )
  end

  defp format_error(status_code, reason) do
    "Status #{to_string(status_code)} #{to_string(reason)}"
  end

  defp start_httpc() do
    :inets.start(:httpc, profile: :nerves)

    opts = [
      max_sessions: 8,
      max_keep_alive_length: 4,
      max_pipeline_length: 4,
      keep_alive_timeout: 120_000,
      pipeline_timeout: 60_000
    ]

    :httpc.set_options(opts, :nerves)
  end

  defp bytes_to_mb(bytes) do
    trunc(bytes / 1024 / 1024)
  end

  defp progress?(%{progress?: progress?}) do
    System.get_env("NERVES_LOG_DISABLE_PROGRESS_BAR") == nil and progress?
  end
end

defmodule Nerves.Utils.HTTPClient do
  use GenServer

  @progress_steps 50
  @redirect_status_codes [301, 302, 303, 307, 308]

  def start_link() do
    {:ok, _} = Application.ensure_all_started(:nerves)
    start_httpc()
    GenServer.start_link(__MODULE__, [])
  end

  def stop(pid) do
    GenServer.stop(pid)
  end

  def get(_, _, _ \\ [])

  def get(_pid, %URI{host: nil, path: path}, _opts) do
    path
    |> Path.expand()
    |> File.read()
  end

  def get(pid, %URI{} = uri, opts) do
    url =
      uri
      |> URI.to_string()
      |> URI.encode()
      |> String.replace("+", "%2B")

    get(pid, url, opts)
  end

  def get(pid, url, opts), do: GenServer.call(pid, {:get, url, opts}, :infinity)

  def init([]) do
    {:ok,
     %{
       url: nil,
       content_length: 0,
       buffer: "",
       buffer_size: 0,
       filename: "",
       caller: nil,
       number_of_redirects: 0,
       progress?: true,
       opts: []
     }}
  end

  def handle_call({:get, _url, _opts}, _from, %{number_of_redirects: n} = s) when n > 5 do
    GenServer.reply(s.caller, {:error, :too_many_redirects})
    {:noreply, %{s | url: nil, number_of_redirects: 0, caller: nil}}
  end

  def handle_call({:get, url, opts}, from, s) do
    progress? = Keyword.get(opts, :progress?, true)

    headers =
      Keyword.get(opts, :headers, [])
      |> Enum.map(fn {k, v} ->
        {to_charlist(k), to_charlist(v)}
      end)

    headers = [
      {'User-Agent', 'Nerves HTTP Client #{Nerves.version()}'},
      {'Content-Type', 'application/octet-stream'} | headers
    ]

    http_opts =
      [timeout: :infinity, autoredirect: false] ++
        Nerves.Utils.Proxy.config(url) ++ Keyword.get(opts, :http_opts, [])

    opts = [stream: :self, receiver: self(), sync: false]
    :httpc.request(:get, {String.to_charlist(url), headers}, http_opts, opts, :nerves)
    {:noreply, %{s | url: url, caller: from, opts: opts, progress?: progress?}}
  end

  def handle_info({:http, {_, :stream_start, headers}}, s) do
    content_length =
      case Enum.find(headers, fn {key, _} -> key == 'content-length' end) do
        nil ->
          0

        {_, content_length} ->
          {content_length, _} =
            content_length
            |> to_string()
            |> Integer.parse()

          content_length
      end

    filename =
      case Enum.find(headers, fn {key, _} -> key == 'content-disposition' end) do
        nil ->
          Path.basename(s.url)

        {_, filename} ->
          filename
          |> to_string
          |> String.split(";")
          |> List.last()
          |> String.trim()
          |> String.trim("filename=")
      end

    {:noreply, %{s | content_length: content_length, filename: filename}}
  end

  def handle_info({:http, {_, :stream, data}}, s) do
    size = byte_size(data) + s.buffer_size
    buffer = s.buffer <> data

    if progress?(s) do
      put_progress(size, s.content_length)
    end

    {:noreply, %{s | buffer_size: size, buffer: buffer}}
  end

  def handle_info({:http, {_, :stream_end, _headers}}, s) do
    if progress?(s) do
      IO.write(:stderr, "\n")
    end

    GenServer.reply(s.caller, {:ok, s.buffer})
    {:noreply, %{s | filename: "", content_length: 0, buffer: "", buffer_size: 0, url: nil}}
  end

  def handle_info({:http, {_ref, {{_, status_code, reason}, headers, _body}}}, s)
      when status_code in @redirect_status_codes do
    case Enum.find(headers, fn {key, _} -> key == 'location' end) do
      {'location', next_location} ->
        handle_call({:get, List.to_string(next_location), headers: headers}, s.caller, %{
          s
          | buffer: "",
            buffer_size: 0,
            number_of_redirects: s.number_of_redirects + 1
        })

      _ ->
        GenServer.reply(s.caller, {:error, format_error(status_code, reason)})
    end
  end

  def handle_info({:http, {_ref, {{_, status_code, reason}, _headers, _body}}}, s) do
    GenServer.reply(s.caller, {:error, format_error(status_code, reason)})
    {:noreply, s}
  end

  def put_progress(size, max) do
    fraction = size / max
    completed = trunc(fraction * @progress_steps)
    percent = trunc(fraction * 100)
    unfilled = @progress_steps - completed

    IO.write(
      :stderr,
      "\r|#{String.duplicate("=", completed)}#{String.duplicate(" ", unfilled)}| #{percent}% (#{
        bytes_to_mb(size)
      } / #{bytes_to_mb(max)}) MB"
    )
  end

  defp format_error(status_code, reason) do
    "Status #{to_string(status_code)} #{to_string(reason)}"
  end

  defp start_httpc() do
    :inets.start(:httpc, profile: :nerves)

    opts = [
      max_sessions: 8,
      max_keep_alive_length: 4,
      max_pipeline_length: 4,
      keep_alive_timeout: 120_000,
      pipeline_timeout: 60_000
    ]

    :httpc.set_options(opts, :nerves)
  end

  defp bytes_to_mb(bytes) do
    trunc(bytes / 1024 / 1024)
  end

  defp progress?(%{progress?: progress?}) do
    System.get_env("NERVES_LOG_DISABLE_PROGRESS_BAR") == nil and progress?
  end
end


Use a proper name for the static url configuration

defmodule Phoenix.Endpoint.Adapter do
  # This module contains the logic used by most functions
  # in Phoenix.Endpoint as well the supervisor for starting
  # the adapters/handlers.
  @moduledoc false

  require Logger
  import Supervisor.Spec

  @doc """
  Starts the endpoint supervision tree.
  """
  def start_link(otp_app, mod) do
    conf = config(otp_app, mod)

    children =
      config_children(mod, conf) ++
      pubsub_children(mod, conf) ++
      server_children(mod, conf) ++
      watcher_children(mod, conf) ++
      code_reloader_children(mod, conf)

    case Supervisor.start_link(children, strategy: :one_for_one, name: mod) do
      {:ok, pid} ->
        warmup(mod)
        {:ok, pid}
      {:error, reason} ->
        {:error, reason}
    end
  end

  defp config_children(mod, conf) do
    app  = conf[:otp_app]
    args = [app, mod, defaults(app, mod), [name: Module.concat(mod, Config)]]
    [worker(Phoenix.Config, args)]
  end

  defp pubsub_children(mod, conf) do
    pub_conf = conf[:pubsub]

    if adapter = pub_conf[:adapter] do
      [supervisor(adapter, [mod.__pubsub_server__(), pub_conf])]
    else
      []
    end
  end

  defp server_children(mod, conf) do
    args = [conf[:otp_app], mod, [name: Module.concat(mod, Server)]]
    [supervisor(Phoenix.Endpoint.Server, args)]
  end

  defp watcher_children(_mod, conf) do
    if conf[:server] do
      Enum.map(conf[:watchers], fn {cmd, args} ->
        worker(Phoenix.Endpoint.Watcher, [root!(conf), cmd, args],
               id: {cmd, args}, restart: :transient)
      end)
    else
      []
    end
  end

  defp code_reloader_children(mod, conf) do
    if conf[:code_reloader] do
      args = [conf[:otp_app], root!(conf), conf[:reloadable_paths],
              [name: Module.concat(mod, CodeReloader)]]
      [worker(Phoenix.CodeReloader.Server, args)]
    else
      []
    end
  end

  defp root!(conf) do
    conf[:root] ||
      raise "please set root: Path.expand(\"..\", __DIR__) in your endpoint " <>
            "inside config/config.exs in order to use code reloading or watchers"
  end

  @doc """
  The endpoint configuration used at compile time.
  """
  def config(otp_app, endpoint) do
    Phoenix.Config.from_env(otp_app, endpoint, defaults(otp_app, endpoint))
  end

  defp defaults(otp_app, module) do
    [otp_app: otp_app,

     # Compile-time config
     code_reloader: false,
     debug_errors: false,
     render_errors: [view: render_errors(module), format: "html"],

     # Transports
     transports: [
       longpoller_window_ms: 10_000,
       longpoller_pubsub_timeout_ms: 1000,
       longpoller_crypto: [iterations: 1000,
                           length: 32,
                           digest: :sha256,
                           cache: Plug.Keys],

       websocket_serializer: Phoenix.Transports.JSONSerializer,
       websocket_timeout: :infinity
     ],

     # Runtime config
     cache_static_lookup: true,
     cache_static_manifest: nil,
     http: false,
     https: false,
     reloadable_paths: ["web"],
     secret_key_base: nil,
     server: Application.get_env(:phoenix, :serve_endpoints, false),
     static_url: nil,
     url: [host: "localhost", path: "/"],

     # Supervisor config
     pubsub: [],
     watchers: []]
  end

  defp render_errors(module) do
    module
    |> Module.split
    |> Enum.at(0)
    |> Module.concat("ErrorView")
  end

  @doc """
  Callback that changes the configuration from the app callback.
  """
  def config_change(endpoint, changed, removed) do
    res = Phoenix.Config.config_change(endpoint, changed, removed)
    warmup(endpoint)
    res
  end

  @doc """
  Builds the endpoint url from its configuration.

  The result is wrapped in a `{:cache, value}` tuple so
  the Phoenix.Config layer knows how to cache it.
  """
  def url(endpoint) do
    {:cache, calculate_url(endpoint, endpoint.config(:url))}
  end

  @doc """
  Builds the endpoint static url from its configuration based on the multiple
  static hosts configured

  The result is wrapped in a `{:cache, value}` tuple so
  the Phoenix.Config layer knows how to cache it.
  """
  def static_url(endpoint) do
    {:cache, calculate_url(endpoint, endpoint.config(:static_url))}
  end

  defp calculate_url(endpoint, url) do
    {scheme, port} =
      cond do
        config = endpoint.config(:https) ->
          {"https", config[:port]}
        config = endpoint.config(:http) ->
          {"http", config[:port]}
        true ->
          {"http", "80"}
      end

    scheme = url[:scheme] || scheme
    host   = url[:host]
    port   = port_to_string(url[:port] || port)

    case {scheme, port} do
      {"https", "443"} -> "https://" <> host
      {"http", "80"}   -> "http://" <> host
      {_, _}           -> scheme <> "://" <> host <> ":" <> port
    end
  end

  @doc """
  Returns the static path of a file in the static root directory.

  When the file exists, it includes a timestamp. When it doesn't exist,
  just the static path is returned.

  The result is wrapped in a `{:cache | :stale, value}` tuple so
  the Phoenix.Config layer knows how to cache it.
  """
  def static_path(endpoint, "/" <> _ = path) do
    file = Application.app_dir(endpoint.config(:otp_app), Path.join("priv/static", path))

    case File.stat(file) do
      {:ok, %File.Stat{type: :regular, mtime: mtime, size: size}} ->
        key = if endpoint.config(:cache_static_lookup), do: :cache, else: :stale
        vsn = {size, mtime} |> :erlang.phash2() |> Integer.to_string(16)
        {key, endpoint.path(path <> "?vsn=" <> vsn)}
      _ ->
        {:stale, endpoint.path(path)}
    end
  end

  def static_path(_endpoint, path) when is_binary(path) do
    raise ArgumentError, "static_path/2 expects a path starting with / as argument"
  end

  defp port_to_string({:system, env_var}), do: System.get_env(env_var)
  defp port_to_string(port) when is_binary(port), do: port
  defp port_to_string(port) when is_integer(port), do: Integer.to_string(port)

  @doc """
  Invoked to warm up caches on start and config change.
  """
  def warmup(endpoint) do
    warmup_url(endpoint)
    warmup_static(endpoint)
    :ok
  rescue
    _ -> :ok
  end

  defp warmup_url(endpoint) do
    endpoint.url
  end

  defp warmup_static(endpoint) do
    for {key, value} <- cache_static_manifest(endpoint) do
      # This should be in sync with the endpoint lookup.
      Phoenix.Config.cache(endpoint,
        {:__phoenix_static__, "/" <> key},
         fn _ -> {:cache, endpoint.path("/" <> value <> "?vsn=d")} end)
    end
  end

  defp cache_static_manifest(endpoint) do
    if endpoint.config(:cache_static_lookup) &&
       (inner = endpoint.config(:cache_static_manifest)) do
      outer = Application.app_dir(endpoint.config(:otp_app), inner)

      if File.exists?(outer) do
        Poison.decode!(File.read!(outer))
      else
        Logger.error "Could not find static manifest at #{inspect outer}. " <>
                     "Run mix phoenix.digest after building your static files " <>
                     "or remove the configuration from config/prod.exs."
      end
    else
      %{}
    end
  end
end

defmodule Phoenix.Endpoint.Adapter do
  # This module contains the logic used by most functions
  # in Phoenix.Endpoint as well the supervisor for starting
  # the adapters/handlers.
  @moduledoc false

  require Logger
  import Supervisor.Spec

  @doc """
  Starts the endpoint supervision tree.
  """
  def start_link(otp_app, mod) do
    conf = config(otp_app, mod)

    children =
      config_children(mod, conf) ++
      pubsub_children(mod, conf) ++
      server_children(mod, conf) ++
      watcher_children(mod, conf) ++
      code_reloader_children(mod, conf)

    case Supervisor.start_link(children, strategy: :one_for_one, name: mod) do
      {:ok, pid} ->
        warmup(mod)
        {:ok, pid}
      {:error, reason} ->
        {:error, reason}
    end
  end

  defp config_children(mod, conf) do
    app  = conf[:otp_app]
    args = [app, mod, defaults(app, mod), [name: Module.concat(mod, Config)]]
    [worker(Phoenix.Config, args)]
  end

  defp pubsub_children(mod, conf) do
    pub_conf = conf[:pubsub]

    if adapter = pub_conf[:adapter] do
      [supervisor(adapter, [mod.__pubsub_server__(), pub_conf])]
    else
      []
    end
  end

  defp server_children(mod, conf) do
    args = [conf[:otp_app], mod, [name: Module.concat(mod, Server)]]
    [supervisor(Phoenix.Endpoint.Server, args)]
  end

  defp watcher_children(_mod, conf) do
    if conf[:server] do
      Enum.map(conf[:watchers], fn {cmd, args} ->
        worker(Phoenix.Endpoint.Watcher, [root!(conf), cmd, args],
               id: {cmd, args}, restart: :transient)
      end)
    else
      []
    end
  end

  defp code_reloader_children(mod, conf) do
    if conf[:code_reloader] do
      args = [conf[:otp_app], root!(conf), conf[:reloadable_paths],
              [name: Module.concat(mod, CodeReloader)]]
      [worker(Phoenix.CodeReloader.Server, args)]
    else
      []
    end
  end

  defp root!(conf) do
    conf[:root] ||
      raise "please set root: Path.expand(\"..\", __DIR__) in your endpoint " <>
            "inside config/config.exs in order to use code reloading or watchers"
  end

  @doc """
  The endpoint configuration used at compile time.
  """
  def config(otp_app, endpoint) do
    Phoenix.Config.from_env(otp_app, endpoint, defaults(otp_app, endpoint))
  end

  defp defaults(otp_app, module) do
    [otp_app: otp_app,

     # Compile-time config
     code_reloader: false,
     debug_errors: false,
     render_errors: [view: render_errors(module), format: "html"],

     # Transports
     transports: [
       longpoller_window_ms: 10_000,
       longpoller_pubsub_timeout_ms: 1000,
       longpoller_crypto: [iterations: 1000,
                           length: 32,
                           digest: :sha256,
                           cache: Plug.Keys],

       websocket_serializer: Phoenix.Transports.JSONSerializer,
       websocket_timeout: :infinity
     ],

     # Runtime config
     cache_static_lookup: true,
     cache_static_manifest: nil,
     http: false,
     https: false,
     reloadable_paths: ["web"],
     secret_key_base: nil,
     server: Application.get_env(:phoenix, :serve_endpoints, false),
     static_host: nil,
     url: [host: "localhost", path: "/"],

     # Supervisor config
     pubsub: [],
     watchers: []]
  end

  defp render_errors(module) do
    module
    |> Module.split
    |> Enum.at(0)
    |> Module.concat("ErrorView")
  end

  @doc """
  Callback that changes the configuration from the app callback.
  """
  def config_change(endpoint, changed, removed) do
    res = Phoenix.Config.config_change(endpoint, changed, removed)
    warmup(endpoint)
    res
  end

  @doc """
  Builds the endpoint url from its configuration.

  The result is wrapped in a `{:cache, value}` tuple so
  the Phoenix.Config layer knows how to cache it.
  """
  def url(endpoint) do
    {:cache, calculate_url(endpoint, endpoint.config(:url))}
  end

  @doc """
  Builds the endpoint static url from its configuration based on the multiple
  static hosts configured

  The result is wrapped in a `{:cache, value}` tuple so
  the Phoenix.Config layer knows how to cache it.
  """
  def static_url(endpoint) do
    url  = endpoint.config(:static_host)
    {:cache, calculate_url(endpoint, url) }
  end

  defp calculate_url(endpoint, url) do
    {scheme, port} =
      cond do
        config = endpoint.config(:https) ->
          {"https", config[:port]}
        config = endpoint.config(:http) ->
          {"http", config[:port]}
        true ->
          {"http", "80"}
      end

    scheme = url[:scheme] || scheme
    host   = url[:host]
    port   = port_to_string(url[:port] || port)

    case {scheme, port} do
      {"https", "443"} -> "https://" <> host
      {"http", "80"}   -> "http://" <> host
      {_, _}           -> scheme <> "://" <> host <> ":" <> port
    end
  end

  @doc """
  Returns the static path of a file in the static root directory.

  When the file exists, it includes a timestamp. When it doesn't exist,
  just the static path is returned.

  The result is wrapped in a `{:cache | :stale, value}` tuple so
  the Phoenix.Config layer knows how to cache it.
  """
  def static_path(endpoint, "/" <> _ = path) do
    file = Application.app_dir(endpoint.config(:otp_app), Path.join("priv/static", path))

    case File.stat(file) do
      {:ok, %File.Stat{type: :regular, mtime: mtime, size: size}} ->
        key = if endpoint.config(:cache_static_lookup), do: :cache, else: :stale
        vsn = {size, mtime} |> :erlang.phash2() |> Integer.to_string(16)
        {key, endpoint.path(path <> "?vsn=" <> vsn)}
      _ ->
        {:stale, endpoint.path(path)}
    end
  end

  def static_path(_endpoint, path) when is_binary(path) do
    raise ArgumentError, "static_path/2 expects a path starting with / as argument"
  end

  defp port_to_string({:system, env_var}), do: System.get_env(env_var)
  defp port_to_string(port) when is_binary(port), do: port
  defp port_to_string(port) when is_integer(port), do: Integer.to_string(port)

  @doc """
  Invoked to warm up caches on start and config change.
  """
  def warmup(endpoint) do
    warmup_url(endpoint)
    warmup_static(endpoint)
    :ok
  rescue
    _ -> :ok
  end

  defp warmup_url(endpoint) do
    endpoint.url
  end

  defp warmup_static(endpoint) do
    for {key, value} <- cache_static_manifest(endpoint) do
      # This should be in sync with the endpoint lookup.
      Phoenix.Config.cache(endpoint,
        {:__phoenix_static__, "/" <> key},
         fn _ -> {:cache, endpoint.path("/" <> value <> "?vsn=d")} end)
    end
  end

  defp cache_static_manifest(endpoint) do
    if endpoint.config(:cache_static_lookup) &&
       (inner = endpoint.config(:cache_static_manifest)) do
      outer = Application.app_dir(endpoint.config(:otp_app), inner)

      if File.exists?(outer) do
        Poison.decode!(File.read!(outer))
      else
        Logger.error "Could not find static manifest at #{inspect outer}. " <>
                     "Run mix phoenix.digest after building your static files " <>
                     "or remove the configuration from config/prod.exs."
      end
    else
      %{}
    end
  end
end