Remove the reverse on the dependency order (see: bitwalker/distillery#638) (#689)

defmodule Distillery.Releases.Release do
  @moduledoc """
  Represents metadata about a release
  """
  alias Distillery.Releases.App
  alias Distillery.Releases.Profile
  alias Distillery.Releases.Overlays
  alias Distillery.Releases.Config
  alias Distillery.Releases.Utils
  alias Distillery.Releases.Environment
  alias Distillery.Releases.Shell

  defstruct name: nil,
            version: "0.1.0",
            applications: [
              # required for elixir apps
              :elixir,
              # included so the elixir shell works
              :iex,
              # required for Mix config provider
              :mix,
              # required for upgrades
              :sasl,
              # required for some command tooling
              :runtime_tools,
              # needed for config provider API
              :distillery
              # can also use `app_name: type`, as in `some_dep: load`,
              # to only load the application, not start it
            ],
            is_upgrade: false,
            upgrade_from: :latest,
            resolved_overlays: [],
            profile: %Profile{
              erl_opts: "",
              run_erl_env: "",
              executable: [enabled: false, transient: false],
              dev_mode: false,
              include_erts: true,
              include_src: false,
              include_system_libs: true,
              included_configs: [],
              config_providers: [],
              appup_transforms: [],
              strip_debug_info: false,
              plugins: [],
              overlay_vars: [],
              overlays: [],
              commands: [],
              overrides: []
            },
            env: nil

  @type t :: %__MODULE__{
          name: atom,
          version: String.t(),
          applications: list(atom | {atom, App.start_type()} | App.t()),
          is_upgrade: boolean,
          upgrade_from: nil | String.t() | :latest,
          resolved_overlays: [Overlays.overlay()],
          profile: Profile.t(),
          env: atom
        }

  @type app_resource ::
          {atom, app_version :: charlist}
          | {atom, app_version :: charlist, App.start_type()}
  @type resource ::
          {:release, {name :: charlist, version :: charlist}, {:erts, erts_version :: charlist},
           [app_resource]}

  @doc """
  Creates a new Release with the given name, version, and applications.
  """
  @spec new(atom, String.t()) :: t
  @spec new(atom, String.t(), [atom]) :: t
  def new(name, version, apps \\ []) do
    build_path = Mix.Project.build_path()
    output_dir = Path.relative_to_cwd(Path.join([build_path, "rel", "#{name}"]))
    definition = %__MODULE__{name: name, version: version}

    %__MODULE__{
      definition
      | applications: definition.applications ++ apps,
        profile: %Profile{definition.profile | output_dir: output_dir}
    }
  end

  @doc """
  Load a fully configured Release object given a release name and environment name.
  """
  @spec get(atom) :: {:ok, t} | {:error, term}
  @spec get(atom, atom) :: {:ok, t} | {:error, term}
  @spec get(atom, atom, Keyword.t()) :: {:ok, t} | {:error, term}
  def get(name, env \\ :default, opts \\ [])

  def get(name, env, opts) when is_atom(name) and is_atom(env) do
    # load release configuration
    default_opts = [
      selected_environment: env,
      selected_release: name,
      is_upgrade: Keyword.get(opts, :is_upgrade, false),
      upgrade_from: Keyword.get(opts, :upgrade_from, false)
    ]

    case Config.get(Keyword.merge(default_opts, opts)) do
      {:error, _} = err ->
        err

      {:ok, config} ->
        with {:ok, env} <- select_environment(config),
             {:ok, rel} <- select_release(config),
             rel <- apply_environment(rel, env),
             do: apply_configuration(rel, config, false)
    end
  end

  @doc """
  Converts a Release struct to a release resource structure.

  The format of release resources is documented [in the Erlang manual](http://erlang.org/doc/design_principles/release_structure.html#res_file)
  """
  @spec to_resource(t) :: resource
  def to_resource(
        %__MODULE__{applications: apps, profile: %Profile{erts_version: erts}} = release
      ) do
    rel_name = Atom.to_charlist(release.name)
    rel_version = String.to_charlist(release.version)
    erts = String.to_charlist(erts)

    {
      :release,
      {rel_name, rel_version},
      {:erts, erts},
      for %App{name: name, vsn: vsn, start_type: start_type} <- apps do
        if is_nil(start_type) do
          {name, '#{vsn}'}
        else
          {name, '#{vsn}', start_type}
        end
      end
    }
  end

  @doc """
  Returns true if the release is executable
  """
  @spec executable?(t) :: boolean()
  def executable?(%__MODULE__{profile: %Profile{executable: false}}),
    do: false

  def executable?(%__MODULE__{profile: %Profile{executable: true}}),
    do: true

  def executable?(%__MODULE__{profile: %Profile{executable: e}}),
    do: Keyword.get(e, :enabled, false)

  @doc """
  Get the path to which release binaries will be output
  """
  @spec bin_path(t) :: String.t()
  def bin_path(%__MODULE__{profile: %Profile{output_dir: output_dir}}) do
    [output_dir, "bin"]
    |> Path.join()
    |> Path.expand()
  end

  @doc """
  Get the path to which versioned release data will be output
  """
  @spec version_path(t) :: String.t()
  def version_path(%__MODULE__{profile: %Profile{output_dir: output_dir}} = r) do
    [output_dir, "releases", "#{r.version}"]
    |> Path.join()
    |> Path.expand()
  end

  @doc """
  Get the path to which compiled applications will be output
  """
  @spec lib_path(t) :: String.t()
  def lib_path(%__MODULE__{profile: %Profile{output_dir: output_dir}}) do
    [output_dir, "lib"]
    |> Path.join()
    |> Path.expand()
  end

  @doc """
  Get the path to which the release tarball will be output
  """
  @spec archive_path(t) :: String.t()
  def archive_path(%__MODULE__{profile: %Profile{executable: e} = p} = r) when is_list(e) do
    if Keyword.get(e, :enabled, false) do
      Path.join([bin_path(r), "#{r.name}.run"])
    else
      archive_path(%__MODULE__{r | profile: %{p | executable: false}})
    end
  end

  def archive_path(%__MODULE__{profile: %Profile{executable: false}} = r) do
    Path.join([version_path(r), "#{r.name}.tar.gz"])
  end

  # Returns the environment that the provided Config has selected
  @doc false
  @spec select_environment(Config.t()) :: {:ok, Environment.t()} | {:error, :missing_environment}
  def select_environment(
        %Config{selected_environment: :default, default_environment: :default} = c
      ) do
    case Map.get(c.environments, :default) do
      nil ->
        {:error, :missing_environment}

      env ->
        {:ok, env}
    end
  end

  def select_environment(%Config{selected_environment: :default, default_environment: name} = c),
    do: select_environment(%Config{c | selected_environment: name})

  def select_environment(%{selected_environment: name} = c) do
    case Map.get(c.environments, name) do
      nil ->
        {:error, :missing_environment}

      env ->
        {:ok, env}
    end
  end

  # Returns the release that the provided Config has selected
  @doc false
  @spec select_release(Config.t()) :: {:ok, t} | {:error, :missing_release}
  def select_release(%Config{selected_release: :default, default_release: :default} = c),
    do: {:ok, List.first(Map.values(c.releases))}

  def select_release(%Config{selected_release: :default, default_release: name} = c),
    do: select_release(%Config{c | selected_release: name})

  def select_release(%Config{selected_release: name} = c) do
    case Map.get(c.releases, name) do
      nil ->
        {:error, :missing_release}

      release ->
        {:ok, release}
    end
  end

  # Applies the environment settings to a release
  @doc false
  @spec apply_environment(t, Environment.t()) :: t
  def apply_environment(%__MODULE__{profile: rel_profile} = r, %Environment{name: env_name} = env) do
    env_profile = Map.from_struct(env.profile)

    profile =
      Enum.reduce(env_profile, rel_profile, fn
        {:plugins, ps}, acc when ps not in [nil, []] ->
          # Merge plugins
          rel_plugins = Map.get(acc, :plugins, [])
          Map.put(acc, :plugins, rel_plugins ++ ps)
        {k, v}, acc ->
          case v do
            ignore when ignore in [nil, []] ->
              acc
            _ ->
              Map.put(acc, k, v)
          end
      end)

    %{r | :env => env_name, :profile => profile}
  end

  @doc false
  defdelegate validate(release), to: Distillery.Releases.Checks, as: :run

  # Applies global configuration options to the release profile
  @doc false
  @spec apply_configuration(t, Config.t()) :: {:ok, t} | {:error, term}
  @spec apply_configuration(t, Config.t(), log? :: boolean) :: {:ok, t} | {:error, term}
  def apply_configuration(%__MODULE__{} = release, %Config{} = config, log? \\ false) do
    profile = release.profile

    profile =
      case profile.config do
        p when is_binary(p) ->
          %{profile | config: p}

        _ ->
          %{profile | config: Keyword.get(Mix.Project.config(), :config_path)}
      end

    profile =
      case profile.include_erts do
        p when is_binary(p) ->
          case Utils.detect_erts_version(p) do
            {:error, _} = err ->
              throw(err)

            {:ok, vsn} ->
              %{profile | erts_version: vsn, include_system_libs: true}
          end

        true ->
          %{profile | erts_version: Utils.erts_version(), include_system_libs: true}

        _ ->
          %{profile | erts_version: Utils.erts_version(), include_system_libs: false}
      end

    profile =
      case profile.cookie do
        nil ->
          profile

        c when is_atom(c) ->
          profile

        c when is_binary(c) ->
          %{profile | cookie: String.to_atom(c)}

        c ->
          throw({:error, {:assembler, {:invalid_cookie, c}}})
      end

    release = %{release | profile: profile}

    release =
      case apps(release) do
        {:error, _} = err ->
          throw(err)

        apps ->
          %{release | applications: apps}
      end

    if config.is_upgrade do
      apply_upgrade_configuration(release, config, log?)
    else
      {:ok, release}
    end
  catch
    :throw, {:error, _} = err ->
      err
  end

  defp apply_upgrade_configuration(%__MODULE__{} = release, %Config{upgrade_from: :latest}, log?) do
    current_version = release.version

    upfrom =
      case Utils.get_release_versions(release.profile.output_dir) do
        [] ->
          :no_upfrom

        [^current_version, v | _] ->
          v

        [v | _] ->
          v
      end

    case upfrom do
      :no_upfrom ->
        if log? do
          Shell.warn(
            "An upgrade was requested, but there are no " <>
              "releases to upgrade from, no upgrade will be performed."
          )
        end

        {:ok, %{release | :is_upgrade => false, :upgrade_from => nil}}

      v ->
        {:ok, %{release | :is_upgrade => true, :upgrade_from => v}}
    end
  end

  defp apply_upgrade_configuration(%__MODULE__{version: v}, %Config{upgrade_from: v}, _log?) do
    {:error, {:assembler, {:bad_upgrade_spec, :upfrom_is_current, v}}}
  end

  defp apply_upgrade_configuration(
         %__MODULE__{name: name} = release,
         %Config{upgrade_from: v},
         log?
       ) do
    current_version = release.version

    if log?,
      do: Shell.debug("Upgrading #{name} from #{v} to #{current_version}")

    upfrom_path = Path.join([release.profile.output_dir, "releases", v])

    if File.exists?(upfrom_path) do
      {:ok, %{release | :is_upgrade => true, :upgrade_from => v}}
    else
      {:error, {:assembler, {:bad_upgrade_spec, :doesnt_exist, v, upfrom_path}}}
    end
  end

  @doc """
  Returns a list of all code_paths of all applications included in the release
  """
  @spec get_code_paths(t) :: [charlist]
  def get_code_paths(%__MODULE__{profile: %Profile{output_dir: output_dir}} = release) do
    release.applications
    |> Enum.flat_map(fn %App{name: name, vsn: version, path: path} ->
      lib_dir = Path.join([output_dir, "lib", "#{name}-#{version}", "ebin"])
      [String.to_charlist(lib_dir), String.to_charlist(Path.join(path, "ebin"))]
    end)
  end

  @doc """
  Gets a list of {app, vsn} tuples for the current release.

  An optional second parameter enables/disables debug logging of discovered apps.
  """
  @spec apps(t) :: [App.t()] | {:error, term}
  def apps(%__MODULE__{name: name, applications: apps} = release) do
    # The list of applications which have been _manually_ specified
    # to be part of this release - it is not required to be exhaustive
    apps =
      if Enum.member?(apps, name) do
        apps
      else
        cond do
          Mix.Project.umbrella?() ->
            # Nothing to do
            apps

          Mix.Project.config()[:app] == name ->
            # This is a non-umbrella project, with a release named the same as the app
            # Make sure the app is part of the release, or it makes no sense
            apps ++ [name]

          :else ->
            # The release is named something different, nothing to do
            apps
        end
      end

    # Validate listed apps
    for app <- apps do
      app_name =
        case app do
          {name, start_type} ->
            if App.valid_start_type?(start_type) do
              name
            else
              throw({:invalid_start_type, name, start_type})
            end

          name ->
            name
        end

      case validate_app(app_name) do
        :ok ->
          :ok

        {:error, reason} ->
          throw({:invalid_app, app_name, reason})
      end
    end

    # A graph of relationships between applications
    dg = :digraph.new([:acyclic, :protected])
    as = :ets.new(name, [:set, :protected])

    try do
      # Add app relationships to the graph
      add_apps(dg, as, apps)

      # Perform topological sort
      result =
        case :digraph_utils.topsort(dg) do
          false ->
            raise "Unable to topologically sort the dependency graph!"

          sorted ->
            sorted
            |> Enum.map(fn a -> elem(hd(:ets.lookup(as, a)), 1) end)
            |> Enum.map(&correct_app_path_and_vsn(&1, release))
        end

      print_discovered_apps(result)

      result
    after
      :ets.delete(as)
      :digraph.delete(dg)
    end
  catch
    :throw, err ->
      {:error, {:apps, err}}
  end

  defp add_apps(_dg, _as, []),
    do: :ok

  defp add_apps(dg, as, [app | apps]) do
    add_app(dg, as, nil, app)
    add_apps(dg, as, apps)
  end

  defp add_app(dg, as, parent, {name, start_type}) do
    case :digraph.vertex(dg, name) do
      false ->
        case validate_app(name) do
          :ok ->
            # Haven't seen this app yet, and it is not excluded
            do_add_app(dg, as, parent, App.new(name, start_type))
          error ->
            error
        end
      _ ->
        # Already visited
        :ok
    end
  end
  defp add_app(dg, as, parent, name) do
    add_app(dg, as, parent, {name, nil})
  end

  defp do_add_app(dg, as, nil, app) do
    :digraph.add_vertex(dg, app.name)
    :ets.insert(as, {app.name, app})
    do_add_children(dg, as, app.name, app.applications ++ app.included_applications)
  end
  defp do_add_app(dg, as, parent, app) do
    :digraph.add_vertex(dg, app.name)
    :ets.insert(as, {app.name, app})
    case :digraph.add_edge(dg, parent, app.name) do
      {:error, reason} ->
        raise "edge from #{parent} to #{app.name} would result in cycle: #{inspect(reason)}"

      _ ->
        do_add_children(dg, as, app.name, app.applications ++ app.included_applications)
    end
  end

  defp do_add_children(_dg, _as, _parent, []),
    do: :ok

  defp do_add_children(dg, as, parent, [app | apps]) do
    add_app(dg, as, parent, app)
    do_add_children(dg, as, parent, apps)
  end

  defp correct_app_path_and_vsn(%App{} = app, %__MODULE__{profile: %Profile{include_erts: ie}})
       when ie in [true, false] do
    app
  end

  defp correct_app_path_and_vsn(%App{} = app, %__MODULE__{profile: %Profile{include_erts: p}}) do
    # Correct any ERTS libs which should be pulled from the correct
    # ERTS directory, not from the current environment.
    lib_dir = Path.expand(Path.join(p, "lib"))

    if Utils.is_erts_lib?(app.path) do
      case Path.wildcard(Path.join(lib_dir, "#{app.name}-*")) do
        [corrected_app_path | _] ->
          [_, corrected_app_vsn] =
            String.split(Path.basename(corrected_app_path), "-", trim: true)

          %App{app | vsn: corrected_app_vsn, path: corrected_app_path}

        _ ->
          throw({:apps, {:missing_required_lib, app.name, lib_dir}})
      end
    else
      app
    end
  end

  defp print_discovered_apps(apps) do
    Shell.debug("Discovered applications:")
    do_print_discovered_apps(apps)
  end

  defp do_print_discovered_apps([]), do: :ok

  defp do_print_discovered_apps([app | apps]) do
    where = Path.relative_to_cwd(app.path)
    Shell.debugf("  > #{Shell.colorf("#{app.name}-#{app.vsn}", :white)}")
    Shell.debugf("\n  |\n  |  from: #{where}\n")

    case app.applications do
      [] ->
        Shell.debugf("  |  applications: none\n")

      apps ->
        display_apps =
          apps
          |> Enum.map(&inspect/1)
          |> Enum.join("\n  |      ")

        Shell.debugf("  |  applications:\n  |      #{display_apps}\n")
    end

    case app.included_applications do
      [] ->
        Shell.debugf("  |  includes: none\n")

      included_apps ->
        display_apps =
          included_apps
          |> Enum.map(&inspect/1)
          |> Enum.join("\n  |  ")

        Shell.debugf("  |  includes:\n  |      #{display_apps}")
    end

    Shell.debugf("  |_____\n\n")
    do_print_discovered_apps(apps)
  end

  defp validate_app(app_name) do
    case Application.load(app_name) do
      :ok ->
        :ok

      {:error, {:already_loaded, _}} ->
        :ok

      error ->
        error
    end
  end
end

defmodule Distillery.Releases.Release do
  @moduledoc """
  Represents metadata about a release
  """
  alias Distillery.Releases.App
  alias Distillery.Releases.Profile
  alias Distillery.Releases.Overlays
  alias Distillery.Releases.Config
  alias Distillery.Releases.Utils
  alias Distillery.Releases.Environment
  alias Distillery.Releases.Shell

  defstruct name: nil,
            version: "0.1.0",
            applications: [
              # required for elixir apps
              :elixir,
              # included so the elixir shell works
              :iex,
              # required for Mix config provider
              :mix,
              # required for upgrades
              :sasl,
              # required for some command tooling
              :runtime_tools,
              # needed for config provider API
              :distillery
              # can also use `app_name: type`, as in `some_dep: load`,
              # to only load the application, not start it
            ],
            is_upgrade: false,
            upgrade_from: :latest,
            resolved_overlays: [],
            profile: %Profile{
              erl_opts: "",
              run_erl_env: "",
              executable: [enabled: false, transient: false],
              dev_mode: false,
              include_erts: true,
              include_src: false,
              include_system_libs: true,
              included_configs: [],
              config_providers: [],
              appup_transforms: [],
              strip_debug_info: false,
              plugins: [],
              overlay_vars: [],
              overlays: [],
              commands: [],
              overrides: []
            },
            env: nil

  @type t :: %__MODULE__{
          name: atom,
          version: String.t(),
          applications: list(atom | {atom, App.start_type()} | App.t()),
          is_upgrade: boolean,
          upgrade_from: nil | String.t() | :latest,
          resolved_overlays: [Overlays.overlay()],
          profile: Profile.t(),
          env: atom
        }

  @type app_resource ::
          {atom, app_version :: charlist}
          | {atom, app_version :: charlist, App.start_type()}
  @type resource ::
          {:release, {name :: charlist, version :: charlist}, {:erts, erts_version :: charlist},
           [app_resource]}

  @doc """
  Creates a new Release with the given name, version, and applications.
  """
  @spec new(atom, String.t()) :: t
  @spec new(atom, String.t(), [atom]) :: t
  def new(name, version, apps \\ []) do
    build_path = Mix.Project.build_path()
    output_dir = Path.relative_to_cwd(Path.join([build_path, "rel", "#{name}"]))
    definition = %__MODULE__{name: name, version: version}

    %__MODULE__{
      definition
      | applications: definition.applications ++ apps,
        profile: %Profile{definition.profile | output_dir: output_dir}
    }
  end

  @doc """
  Load a fully configured Release object given a release name and environment name.
  """
  @spec get(atom) :: {:ok, t} | {:error, term}
  @spec get(atom, atom) :: {:ok, t} | {:error, term}
  @spec get(atom, atom, Keyword.t()) :: {:ok, t} | {:error, term}
  def get(name, env \\ :default, opts \\ [])

  def get(name, env, opts) when is_atom(name) and is_atom(env) do
    # load release configuration
    default_opts = [
      selected_environment: env,
      selected_release: name,
      is_upgrade: Keyword.get(opts, :is_upgrade, false),
      upgrade_from: Keyword.get(opts, :upgrade_from, false)
    ]

    case Config.get(Keyword.merge(default_opts, opts)) do
      {:error, _} = err ->
        err

      {:ok, config} ->
        with {:ok, env} <- select_environment(config),
             {:ok, rel} <- select_release(config),
             rel <- apply_environment(rel, env),
             do: apply_configuration(rel, config, false)
    end
  end

  @doc """
  Converts a Release struct to a release resource structure.

  The format of release resources is documented [in the Erlang manual](http://erlang.org/doc/design_principles/release_structure.html#res_file)
  """
  @spec to_resource(t) :: resource
  def to_resource(
        %__MODULE__{applications: apps, profile: %Profile{erts_version: erts}} = release
      ) do
    rel_name = Atom.to_charlist(release.name)
    rel_version = String.to_charlist(release.version)
    erts = String.to_charlist(erts)

    {
      :release,
      {rel_name, rel_version},
      {:erts, erts},
      for %App{name: name, vsn: vsn, start_type: start_type} <- apps do
        if is_nil(start_type) do
          {name, '#{vsn}'}
        else
          {name, '#{vsn}', start_type}
        end
      end
    }
  end

  @doc """
  Returns true if the release is executable
  """
  @spec executable?(t) :: boolean()
  def executable?(%__MODULE__{profile: %Profile{executable: false}}),
    do: false

  def executable?(%__MODULE__{profile: %Profile{executable: true}}),
    do: true

  def executable?(%__MODULE__{profile: %Profile{executable: e}}),
    do: Keyword.get(e, :enabled, false)

  @doc """
  Get the path to which release binaries will be output
  """
  @spec bin_path(t) :: String.t()
  def bin_path(%__MODULE__{profile: %Profile{output_dir: output_dir}}) do
    [output_dir, "bin"]
    |> Path.join()
    |> Path.expand()
  end

  @doc """
  Get the path to which versioned release data will be output
  """
  @spec version_path(t) :: String.t()
  def version_path(%__MODULE__{profile: %Profile{output_dir: output_dir}} = r) do
    [output_dir, "releases", "#{r.version}"]
    |> Path.join()
    |> Path.expand()
  end

  @doc """
  Get the path to which compiled applications will be output
  """
  @spec lib_path(t) :: String.t()
  def lib_path(%__MODULE__{profile: %Profile{output_dir: output_dir}}) do
    [output_dir, "lib"]
    |> Path.join()
    |> Path.expand()
  end

  @doc """
  Get the path to which the release tarball will be output
  """
  @spec archive_path(t) :: String.t()
  def archive_path(%__MODULE__{profile: %Profile{executable: e} = p} = r) when is_list(e) do
    if Keyword.get(e, :enabled, false) do
      Path.join([bin_path(r), "#{r.name}.run"])
    else
      archive_path(%__MODULE__{r | profile: %{p | executable: false}})
    end
  end

  def archive_path(%__MODULE__{profile: %Profile{executable: false}} = r) do
    Path.join([version_path(r), "#{r.name}.tar.gz"])
  end

  # Returns the environment that the provided Config has selected
  @doc false
  @spec select_environment(Config.t()) :: {:ok, Environment.t()} | {:error, :missing_environment}
  def select_environment(
        %Config{selected_environment: :default, default_environment: :default} = c
      ) do
    case Map.get(c.environments, :default) do
      nil ->
        {:error, :missing_environment}

      env ->
        {:ok, env}
    end
  end

  def select_environment(%Config{selected_environment: :default, default_environment: name} = c),
    do: select_environment(%Config{c | selected_environment: name})

  def select_environment(%{selected_environment: name} = c) do
    case Map.get(c.environments, name) do
      nil ->
        {:error, :missing_environment}

      env ->
        {:ok, env}
    end
  end

  # Returns the release that the provided Config has selected
  @doc false
  @spec select_release(Config.t()) :: {:ok, t} | {:error, :missing_release}
  def select_release(%Config{selected_release: :default, default_release: :default} = c),
    do: {:ok, List.first(Map.values(c.releases))}

  def select_release(%Config{selected_release: :default, default_release: name} = c),
    do: select_release(%Config{c | selected_release: name})

  def select_release(%Config{selected_release: name} = c) do
    case Map.get(c.releases, name) do
      nil ->
        {:error, :missing_release}

      release ->
        {:ok, release}
    end
  end

  # Applies the environment settings to a release
  @doc false
  @spec apply_environment(t, Environment.t()) :: t
  def apply_environment(%__MODULE__{profile: rel_profile} = r, %Environment{name: env_name} = env) do
    env_profile = Map.from_struct(env.profile)

    profile =
      Enum.reduce(env_profile, rel_profile, fn
        {:plugins, ps}, acc when ps not in [nil, []] ->
          # Merge plugins
          rel_plugins = Map.get(acc, :plugins, [])
          Map.put(acc, :plugins, rel_plugins ++ ps)
        {k, v}, acc ->
          case v do
            ignore when ignore in [nil, []] ->
              acc
            _ ->
              Map.put(acc, k, v)
          end
      end)

    %{r | :env => env_name, :profile => profile}
  end

  @doc false
  defdelegate validate(release), to: Distillery.Releases.Checks, as: :run

  # Applies global configuration options to the release profile
  @doc false
  @spec apply_configuration(t, Config.t()) :: {:ok, t} | {:error, term}
  @spec apply_configuration(t, Config.t(), log? :: boolean) :: {:ok, t} | {:error, term}
  def apply_configuration(%__MODULE__{} = release, %Config{} = config, log? \\ false) do
    profile = release.profile

    profile =
      case profile.config do
        p when is_binary(p) ->
          %{profile | config: p}

        _ ->
          %{profile | config: Keyword.get(Mix.Project.config(), :config_path)}
      end

    profile =
      case profile.include_erts do
        p when is_binary(p) ->
          case Utils.detect_erts_version(p) do
            {:error, _} = err ->
              throw(err)

            {:ok, vsn} ->
              %{profile | erts_version: vsn, include_system_libs: true}
          end

        true ->
          %{profile | erts_version: Utils.erts_version(), include_system_libs: true}

        _ ->
          %{profile | erts_version: Utils.erts_version(), include_system_libs: false}
      end

    profile =
      case profile.cookie do
        nil ->
          profile

        c when is_atom(c) ->
          profile

        c when is_binary(c) ->
          %{profile | cookie: String.to_atom(c)}

        c ->
          throw({:error, {:assembler, {:invalid_cookie, c}}})
      end

    release = %{release | profile: profile}

    release =
      case apps(release) do
        {:error, _} = err ->
          throw(err)

        apps ->
          %{release | applications: apps}
      end

    if config.is_upgrade do
      apply_upgrade_configuration(release, config, log?)
    else
      {:ok, release}
    end
  catch
    :throw, {:error, _} = err ->
      err
  end

  defp apply_upgrade_configuration(%__MODULE__{} = release, %Config{upgrade_from: :latest}, log?) do
    current_version = release.version

    upfrom =
      case Utils.get_release_versions(release.profile.output_dir) do
        [] ->
          :no_upfrom

        [^current_version, v | _] ->
          v

        [v | _] ->
          v
      end

    case upfrom do
      :no_upfrom ->
        if log? do
          Shell.warn(
            "An upgrade was requested, but there are no " <>
              "releases to upgrade from, no upgrade will be performed."
          )
        end

        {:ok, %{release | :is_upgrade => false, :upgrade_from => nil}}

      v ->
        {:ok, %{release | :is_upgrade => true, :upgrade_from => v}}
    end
  end

  defp apply_upgrade_configuration(%__MODULE__{version: v}, %Config{upgrade_from: v}, _log?) do
    {:error, {:assembler, {:bad_upgrade_spec, :upfrom_is_current, v}}}
  end

  defp apply_upgrade_configuration(
         %__MODULE__{name: name} = release,
         %Config{upgrade_from: v},
         log?
       ) do
    current_version = release.version

    if log?,
      do: Shell.debug("Upgrading #{name} from #{v} to #{current_version}")

    upfrom_path = Path.join([release.profile.output_dir, "releases", v])

    if File.exists?(upfrom_path) do
      {:ok, %{release | :is_upgrade => true, :upgrade_from => v}}
    else
      {:error, {:assembler, {:bad_upgrade_spec, :doesnt_exist, v, upfrom_path}}}
    end
  end

  @doc """
  Returns a list of all code_paths of all applications included in the release
  """
  @spec get_code_paths(t) :: [charlist]
  def get_code_paths(%__MODULE__{profile: %Profile{output_dir: output_dir}} = release) do
    release.applications
    |> Enum.flat_map(fn %App{name: name, vsn: version, path: path} ->
      lib_dir = Path.join([output_dir, "lib", "#{name}-#{version}", "ebin"])
      [String.to_charlist(lib_dir), String.to_charlist(Path.join(path, "ebin"))]
    end)
  end

  @doc """
  Gets a list of {app, vsn} tuples for the current release.

  An optional second parameter enables/disables debug logging of discovered apps.
  """
  @spec apps(t) :: [App.t()] | {:error, term}
  def apps(%__MODULE__{name: name, applications: apps} = release) do
    # The list of applications which have been _manually_ specified
    # to be part of this release - it is not required to be exhaustive
    apps =
      if Enum.member?(apps, name) do
        apps
      else
        cond do
          Mix.Project.umbrella?() ->
            # Nothing to do
            apps

          Mix.Project.config()[:app] == name ->
            # This is a non-umbrella project, with a release named the same as the app
            # Make sure the app is part of the release, or it makes no sense
            apps ++ [name]

          :else ->
            # The release is named something different, nothing to do
            apps
        end
      end

    # Validate listed apps
    for app <- apps do
      app_name =
        case app do
          {name, start_type} ->
            if App.valid_start_type?(start_type) do
              name
            else
              throw({:invalid_start_type, name, start_type})
            end

          name ->
            name
        end

      case validate_app(app_name) do
        :ok ->
          :ok

        {:error, reason} ->
          throw({:invalid_app, app_name, reason})
      end
    end

    # A graph of relationships between applications
    dg = :digraph.new([:acyclic, :protected])
    as = :ets.new(name, [:set, :protected])

    try do
      # Add app relationships to the graph
      add_apps(dg, as, apps)

      # Perform topological sort
      result =
        case :digraph_utils.topsort(dg) do
          false ->
            raise "Unable to topologically sort the dependency graph!"

          sorted ->
            sorted
            |> Enum.reverse()
            |> Enum.map(fn a -> elem(hd(:ets.lookup(as, a)), 1) end)
            |> Enum.map(&correct_app_path_and_vsn(&1, release))
        end

      print_discovered_apps(result)

      result
    after
      :ets.delete(as)
      :digraph.delete(dg)
    end
  catch
    :throw, err ->
      {:error, {:apps, err}}
  end

  defp add_apps(_dg, _as, []),
    do: :ok

  defp add_apps(dg, as, [app | apps]) do
    add_app(dg, as, nil, app)
    add_apps(dg, as, apps)
  end

  defp add_app(dg, as, parent, {name, start_type}) do
    case :digraph.vertex(dg, name) do
      false ->
        case validate_app(name) do
          :ok ->
            # Haven't seen this app yet, and it is not excluded
            do_add_app(dg, as, parent, App.new(name, start_type))
          error ->
            error
        end
      _ ->
        # Already visited
        :ok
    end
  end
  defp add_app(dg, as, parent, name) do
    add_app(dg, as, parent, {name, nil})
  end

  defp do_add_app(dg, as, nil, app) do
    :digraph.add_vertex(dg, app.name)
    :ets.insert(as, {app.name, app})
    do_add_children(dg, as, app.name, app.applications ++ app.included_applications)
  end
  defp do_add_app(dg, as, parent, app) do
    :digraph.add_vertex(dg, app.name)
    :ets.insert(as, {app.name, app})
    case :digraph.add_edge(dg, parent, app.name) do
      {:error, reason} ->
        raise "edge from #{parent} to #{app.name} would result in cycle: #{inspect(reason)}"

      _ ->
        do_add_children(dg, as, app.name, app.applications ++ app.included_applications)
    end
  end

  defp do_add_children(_dg, _as, _parent, []),
    do: :ok

  defp do_add_children(dg, as, parent, [app | apps]) do
    add_app(dg, as, parent, app)
    do_add_children(dg, as, parent, apps)
  end

  defp correct_app_path_and_vsn(%App{} = app, %__MODULE__{profile: %Profile{include_erts: ie}})
       when ie in [true, false] do
    app
  end

  defp correct_app_path_and_vsn(%App{} = app, %__MODULE__{profile: %Profile{include_erts: p}}) do
    # Correct any ERTS libs which should be pulled from the correct
    # ERTS directory, not from the current environment.
    lib_dir = Path.expand(Path.join(p, "lib"))

    if Utils.is_erts_lib?(app.path) do
      case Path.wildcard(Path.join(lib_dir, "#{app.name}-*")) do
        [corrected_app_path | _] ->
          [_, corrected_app_vsn] =
            String.split(Path.basename(corrected_app_path), "-", trim: true)

          %App{app | vsn: corrected_app_vsn, path: corrected_app_path}

        _ ->
          throw({:apps, {:missing_required_lib, app.name, lib_dir}})
      end
    else
      app
    end
  end

  defp print_discovered_apps(apps) do
    Shell.debug("Discovered applications:")
    do_print_discovered_apps(apps)
  end

  defp do_print_discovered_apps([]), do: :ok

  defp do_print_discovered_apps([app | apps]) do
    where = Path.relative_to_cwd(app.path)
    Shell.debugf("  > #{Shell.colorf("#{app.name}-#{app.vsn}", :white)}")
    Shell.debugf("\n  |\n  |  from: #{where}\n")

    case app.applications do
      [] ->
        Shell.debugf("  |  applications: none\n")

      apps ->
        display_apps =
          apps
          |> Enum.map(&inspect/1)
          |> Enum.join("\n  |      ")

        Shell.debugf("  |  applications:\n  |      #{display_apps}\n")
    end

    case app.included_applications do
      [] ->
        Shell.debugf("  |  includes: none\n")

      included_apps ->
        display_apps =
          included_apps
          |> Enum.map(&inspect/1)
          |> Enum.join("\n  |  ")

        Shell.debugf("  |  includes:\n  |      #{display_apps}")
    end

    Shell.debugf("  |_____\n\n")
    do_print_discovered_apps(apps)
  end

  defp validate_app(app_name) do
    case Application.load(app_name) do
      :ok ->
        :ok

      {:error, {:already_loaded, _}} ->
        :ok

      error ->
        error
    end
  end
end


Check if curl is installed before calling it

Closes #223

It can happen that curl is not installed on the system. If this is the
case, print an error that the agent could not be installed.

This to improve the normal error message which doesn't help the user
figure out what's going wrong.

```
** (ErlangError) erlang error: :enoent
    (elixir) lib/system.ex:552: System.cmd("curl", {options...})
    mix_helpers.exs:80: Mix.Appsignal.Helper.download_file/2
    {stacktrace...}
```

defmodule Mix.Appsignal.Helper do
  @moduledoc """
  Helper functions for downloading and compiling the AppSignal agent library.
  """

  require Logger

  @max_retries 5

  def verify_system_architecture() do
    input_arch = :erlang.system_info(:system_architecture)
    case map_arch(input_arch) do
      :unsupported ->
        {:error, {:unsupported, input_arch}}
      arch when is_binary(arch) ->
        {:ok, arch}
    end
  end

  def ensure_downloaded(arch) do
    arch_config = Appsignal.Agent.triples[arch]

    System.put_env("LIB_DIR", priv_dir())

    if has_local_release_files?() do
      IO.puts "AppSignal: Using local agent release."
      File.rm_rf!(priv_dir())
      File.mkdir_p!(priv_dir())
      Enum.each(["appsignal.h", "appsignal-agent", "appsignal.version", "libappsignal.a"], fn(file) ->
        File.cp(project_ext_path(file), priv_path(file))
      end)
    else
      unless has_files?() and has_correct_agent_version?() do
        version = Appsignal.Agent.version
        File.rm_rf!(priv_dir())
        File.mkdir_p!(priv_dir())
        try do
          download_and_extract(arch_config[:download_url], version, arch_config[:checksum])
        catch
          {:checksum_mismatch, filename, _, _} ->
            File.rm!(filename)
            try do
              download_and_extract(arch_config[:download_url], version, arch_config[:checksum])
            catch
              {:checksum_mismatch, filename, calculated, expected} ->
                raise Mix.Error, message: """
                Checksum verification of #{filename} failed!
                Calculated: #{calculated}
                Expected: #{expected}
                """
            end
        end
      else
        :ok
      end
    end
  end

  defp download_and_extract(url, version, checksum) do
    download_file(url, version)
    |> verify_checksum(checksum)
    |> extract
  end

  defp download_file(url, version) do
    filename = "/tmp/appsignal-agent-#{version}.tar.gz"
    case File.exists?(filename) do
      true ->
        filename
      false ->
        Logger.info "Downloading agent release from #{url}"
        case System.find_executable("curl") do
          nil ->
            raise Mix.Error, message: """
            Could not find the curl executable. Please make sure curl is
            installed on this system as it is needed to download the AppSignal
            extension and agent.
            """
          _ ->
            case System.cmd("curl", ["-s", "-S", "--retry", Integer.to_string(@max_retries), "-f", "-o", filename, url], stderr_to_stdout: true) do
              {_, 0} ->
                filename
              {result, exitcode} ->
                IO.binwrite(result)
                raise Mix.Error, message: """
                Download failed with code #{exitcode}
                """
            end
        end
    end
  end

  defp verify_checksum(filename, expected) do
    data = File.read!(filename)
    calculated = :crypto.hash(:sha256, data) |> Base.encode16(case: :lower)
    if calculated != expected do
      throw {:checksum_mismatch, filename, calculated, expected}
    end
    filename
  end

  defp extract(filename) do
    case System.cmd("tar", ["zxf", filename, "--no-same-owner"], stderr_to_stdout: true, cd: priv_dir()) do
      {_, 0} ->
        :ok
      {result, _exitcode} ->
        IO.binwrite(result)
        raise Mix.Error, message: """
        Extracting of #{filename} failed!
        """
    end
  end

  def compile do
    {result, error_code} = System.cmd("make", make_args(to_string(Mix.env)))
    IO.binwrite(result)

    if error_code != 0 do
      raise Mix.Error, message: """
      Could not run `make`. Please check if `make` and either `clang` or `gcc` are installed
      """
    end
    :ok
  end

  defp make_args("test" <> _), do: ["-e", "CFLAGS_ADD=-DTEST"]
  defp make_args(_), do: []

  if Mix.env != :test_no_nif do
    defp map_arch('i686-alpine-linux' ++ _), do: "i686-linux"
    defp map_arch('i686-pc-linux-gnu' ++ _), do: "i686-linux"
    defp map_arch('i686-redhat-linux-gnu' ++ _), do: "i686-linux"
    defp map_arch('i686-unknown-linux' ++ _), do: "i686-linux"
    defp map_arch('x86_64-alpine-linux' ++ _), do: "x86_64-linux"
    defp map_arch('x86_64-pc-linux-gnu' ++ _), do: "x86_64-linux"
    defp map_arch('x86_64-redhat-linux-gnu' ++ _), do: "x86_64-linux"
    defp map_arch('x86_64-unknown-linux' ++ _), do: "x86_64-linux"
    defp map_arch('x86_64-apple-darwin' ++ _), do: "x86_64-darwin"
  end
  defp map_arch(_), do: :unsupported

  defp priv_dir() do
    path = case :code.priv_dir(:appsignal) do
             {:error, :bad_name} ->
               # this happens on initial compilation
               :filename.join(:filename.dirname(String.to_char_list(List.first(Mix.Tasks.Compile.Erlang.manifests))), 'priv')
             path ->
               path
           end
    List.to_string(path)
  end

  defp priv_path(filename) do
    Path.join(priv_dir(), filename)
  end

  defp project_ext_path(filename) do
    Path.join([__DIR__, "c_src", filename])
  end

  defp has_file(filename) do
    filename |> priv_path |> File.exists?
  end

  defp has_local_ext_file(filename) do
    filename |> project_ext_path |> File.exists?
  end

  defp has_files? do
    has_file("appsignal-agent") and
    has_file("appsignal.h") and
    has_file("appsignal_extension.so")
  end

  defp has_local_release_files? do
    has_local_ext_file("appsignal-agent") and
    has_local_ext_file("appsignal.h") and
    has_local_ext_file("libappsignal.a")
  end

  defp has_correct_agent_version? do
    path = priv_path("appsignal.version")
    File.read(path) == {:ok, "#{Appsignal.Agent.version}\n"}
  end
end

defmodule Mix.Appsignal.Helper do
  @moduledoc """
  Helper functions for downloading and compiling the AppSignal agent library.
  """

  require Logger

  @max_retries 5

  def verify_system_architecture() do
    input_arch = :erlang.system_info(:system_architecture)
    case map_arch(input_arch) do
      :unsupported ->
        {:error, {:unsupported, input_arch}}
      arch when is_binary(arch) ->
        {:ok, arch}
    end
  end

  def ensure_downloaded(arch) do
    arch_config = Appsignal.Agent.triples[arch]

    System.put_env("LIB_DIR", priv_dir())

    if has_local_release_files?() do
      IO.puts "AppSignal: Using local agent release."
      File.rm_rf!(priv_dir())
      File.mkdir_p!(priv_dir())
      Enum.each(["appsignal.h", "appsignal-agent", "appsignal.version", "libappsignal.a"], fn(file) ->
        File.cp(project_ext_path(file), priv_path(file))
      end)
    else
      unless has_files?() and has_correct_agent_version?() do
        version = Appsignal.Agent.version
        File.rm_rf!(priv_dir())
        File.mkdir_p!(priv_dir())
        try do
          download_and_extract(arch_config[:download_url], version, arch_config[:checksum])
        catch
          {:checksum_mismatch, filename, _, _} ->
            File.rm!(filename)
            try do
              download_and_extract(arch_config[:download_url], version, arch_config[:checksum])
            catch
              {:checksum_mismatch, filename, calculated, expected} ->
                raise Mix.Error, message: """
                Checksum verification of #{filename} failed!
                Calculated: #{calculated}
                Expected: #{expected}
                """
            end
        end
      else
        :ok
      end
    end
  end

  defp download_and_extract(url, version, checksum) do
    download_file(url, version)
    |> verify_checksum(checksum)
    |> extract
  end

  defp download_file(url, version) do
    filename = "/tmp/appsignal-agent-#{version}.tar.gz"
    case File.exists?(filename) do
      true ->
        filename
      false ->
        Logger.info "Downloading agent release from #{url}"
        case System.cmd("curl", ["-s", "-S", "--retry", Integer.to_string(@max_retries), "-f", "-o", filename, url], stderr_to_stdout: true) do
          {_, 0} ->
            filename
          {result, exitcode} ->
            IO.binwrite(result)
            raise Mix.Error, message: """
            Download failed with code #{exitcode}
            """
        end
    end
  end

  defp verify_checksum(filename, expected) do
    data = File.read!(filename)
    calculated = :crypto.hash(:sha256, data) |> Base.encode16(case: :lower)
    if calculated != expected do
      throw {:checksum_mismatch, filename, calculated, expected}
    end
    filename
  end

  defp extract(filename) do
    case System.cmd("tar", ["zxf", filename, "--no-same-owner"], stderr_to_stdout: true, cd: priv_dir()) do
      {_, 0} ->
        :ok
      {result, _exitcode} ->
        IO.binwrite(result)
        raise Mix.Error, message: """
        Extracting of #{filename} failed!
        """
    end
  end

  def compile do
    {result, error_code} = System.cmd("make", make_args(to_string(Mix.env)))
    IO.binwrite(result)

    if error_code != 0 do
      raise Mix.Error, message: """
      Could not run `make`. Please check if `make` and either `clang` or `gcc` are installed
      """
    end
    :ok
  end

  defp make_args("test" <> _), do: ["-e", "CFLAGS_ADD=-DTEST"]
  defp make_args(_), do: []

  if Mix.env != :test_no_nif do
    defp map_arch('i686-alpine-linux' ++ _), do: "i686-linux"
    defp map_arch('i686-pc-linux-gnu' ++ _), do: "i686-linux"
    defp map_arch('i686-redhat-linux-gnu' ++ _), do: "i686-linux"
    defp map_arch('i686-unknown-linux' ++ _), do: "i686-linux"
    defp map_arch('x86_64-alpine-linux' ++ _), do: "x86_64-linux"
    defp map_arch('x86_64-pc-linux-gnu' ++ _), do: "x86_64-linux"
    defp map_arch('x86_64-redhat-linux-gnu' ++ _), do: "x86_64-linux"
    defp map_arch('x86_64-unknown-linux' ++ _), do: "x86_64-linux"
    defp map_arch('x86_64-apple-darwin' ++ _), do: "x86_64-darwin"
  end
  defp map_arch(_), do: :unsupported

  defp priv_dir() do
    path = case :code.priv_dir(:appsignal) do
             {:error, :bad_name} ->
               # this happens on initial compilation
               :filename.join(:filename.dirname(String.to_char_list(List.first(Mix.Tasks.Compile.Erlang.manifests))), 'priv')
             path ->
               path
           end
    List.to_string(path)
  end

  defp priv_path(filename) do
    Path.join(priv_dir(), filename)
  end

  defp project_ext_path(filename) do
    Path.join([__DIR__, "c_src", filename])
  end

  defp has_file(filename) do
    filename |> priv_path |> File.exists?
  end

  defp has_local_ext_file(filename) do
    filename |> project_ext_path |> File.exists?
  end

  defp has_files? do
    has_file("appsignal-agent") and
    has_file("appsignal.h") and
    has_file("appsignal_extension.so")
  end

  defp has_local_release_files? do
    has_local_ext_file("appsignal-agent") and
    has_local_ext_file("appsignal.h") and
    has_local_ext_file("libappsignal.a")
  end

  defp has_correct_agent_version? do
    path = priv_path("appsignal.version")
    File.read(path) == {:ok, "#{Appsignal.Agent.version}\n"}
  end
end


Add style block skiped in text

Add style block skiped in text

Add opts `style` for skip text in <style> block.  (By default is true, for backward compatibility.)

defmodule Floki do
  alias Floki.{Finder, HTMLParser, FilterOut, HTMLTree}

  @moduledoc """
  Floki is a simple HTML parser that enables search for nodes using CSS selectors.

  ## Example

  Assuming that you have the following HTML:

  ```html
  <!doctype html>
  <html>
  <body>
    <section id="content">
      <p class="headline">Floki</p>
      <a href="http://github.com/philss/floki">Github page</a>
      <span data-model="user">philss</span>
    </section>
  </body>
  </html>
  ```

  Examples of queries that you can perform:

    * Floki.find(html, "#content")
    * Floki.find(html, ".headline")
    * Floki.find(html, "a")
    * Floki.find(html, "[data-model=user]")
    * Floki.find(html, "#content a")
    * Floki.find(html, ".headline, a")

  Each HTML node is represented by a tuple like:

      {tag_name, attributes, children_nodes}

  Example of node:

      {"p", [{"class", "headline"}], ["Floki"]}

  So even if the only child node is the element text, it is represented
  inside a list.

  You can write a simple HTML crawler (with support of [HTTPoison](https://github.com/edgurgel/httpoison)) with a few lines of code:

      html
      |> Floki.find(".pages a")
      |> Floki.attribute("href")
      |> Enum.map(fn(url) -> HTTPoison.get!(url) end)

  It is simple as that!
  """

  @type html_tree :: tuple | list

  @doc """
  Parses a HTML string.

  ## Examples

      iex> Floki.parse("
hello world
")
      {"div", [{"class", "js-action"}], ["hello world"]}

      iex> Floki.parse("
first

second
")
      [{"div", [], ["first"]}, {"div", [], ["second"]}]

  """

  @spec parse(binary) :: html_tree | String.t()

  def parse(html) do
    HTMLParser.parse(html)
  end

  @doc """
  Converts HTML tree to raw HTML.
  Note that the resultant HTML may be different from the original one.
  Spaces after tags and doctypes are ignored.

  Floki.raw_html/2 accepts a keyword list of options. Currently, the
  only supported option is `:encode`, which can be set to `true` or `false`.

  You can also control the encoding behaviour at the application level via
  `config :floki, :encode_raw_html, true | false`

  ## Examples

      iex> Floki.parse(~s(
my content
)) |> Floki.raw_html
      ~s(
my content
)

      iex> Floki.parse(~s(
10 > 5
)) |> Floki.raw_html(encode: true)
      ~s(
10 > 5
)

      iex> Floki.parse(~s(
10 > 5
)) |> Floki.raw_html(encode: false)
      ~s(
10 > 5
)
  """

  @spec raw_html(html_tree | binary, keyword) :: binary

  defdelegate raw_html(html_tree, options \\ []), to: Floki.RawHTML

  @doc """
  Find elements inside a HTML tree or string.

  ## Examples

      iex> Floki.find("
hello
", ".hint")
      [{"span", [{"class", "hint"}], ["hello"]}]

      iex> Floki.find("

Content

", "#important")
      [{"div", [{"id", "important"}], [{"div", [], ["Content"]}]}]

      iex> Floki.find("
Google
", "a")
      [{"a", [{"href", "https://google.com"}], ["Google"]}]

      iex> Floki.find([{ "div", [], [{"a", [{"href", "https://google.com"}], ["Google"]}]}], "div a")
      [{"a", [{"href", "https://google.com"}], ["Google"]}]

  """

  @spec find(binary | html_tree, binary) :: html_tree

  def find(html, selector) when is_binary(html) do
    html_as_tuple = parse(html)

    {tree, results} = Finder.find(html_as_tuple, selector)

    Enum.map(results, fn html_node -> HTMLTree.to_tuple(tree, html_node) end)
  end

  def find(html_tree_as_tuple, selector) do
    {tree, results} = Finder.find(html_tree_as_tuple, selector)

    Enum.map(results, fn html_node -> HTMLTree.to_tuple(tree, html_node) end)
  end

  @doc """
  Changes the attribute values of the elements matched by `selector`
  with the function `mutation` and returns the whole element tree

  ## Examples

      iex> Floki.attr("

", "#a", "id", fn(id) -> String.replace(id, "a", "b") end)
      [{"div", [{"id", "b"}], []}]

      iex> Floki.attr("

", "div", "id", fn _ -> "b" end)
      [{"div", [{"id", "b"}, {"class", "class_name"}], []}]

  """
  @spec attr(binary | html_tree, binary, binary, (binary -> binary)) :: html_tree

  def attr(html_elem_tuple, selector, attribute_name, mutation) when is_tuple(html_elem_tuple) do
    attr([html_elem_tuple], selector, attribute_name, mutation)
  end

  def attr(html_str, selector, attribute_name, mutation) when is_binary(html_str) do
    attr(parse(html_str), selector, attribute_name, mutation)
  end

  def attr(html_tree_list, selector, attribute_name, mutation) when is_list(html_tree_list) do
    {tree, results} = Finder.find(html_tree_list, selector)
    mutate_attrs(html_tree_list, tree, results, attribute_name, mutation)
  end

  defp add_nodes_to_tree(tree, [html_node]) do
    nodes = Map.put(tree.nodes, html_node.node_id, html_node)
    Map.put(tree, :nodes, nodes)
  end

  defp add_nodes_to_tree(tree, [html_node | tail]) do
    nodes = Map.put(tree.nodes, html_node.node_id, html_node)

    tree
    |> Map.put(:nodes, nodes)
    |> add_nodes_to_tree(tail)
  end

  defp mutate_attrs(html_tree_list, _, [], _, _), do: html_tree_list

  defp mutate_attrs(_, tree, results, attribute_name, mutation_fn) do
    mutated_nodes =
      Enum.map(results, fn result ->
        mutated_attributes =
          if Enum.any?(result.attributes, &match?({^attribute_name, _}, &1)) do
            Enum.map(result.attributes, fn attribute ->
              with {^attribute_name, attribute_value} <- attribute do
                {attribute_name, mutation_fn.(attribute_value)}
              end
            end)
          else
            [{attribute_name, mutation_fn.(nil)} | result.attributes]
          end

        Map.put(result, :attributes, mutated_attributes)
      end)

    tree = add_nodes_to_tree(tree, mutated_nodes)

    tree.root_nodes_ids
    |> Enum.reverse()
    |> Enum.map(fn id -> Map.get(tree.nodes, id) end)
    |> Enum.map(fn html_node -> HTMLTree.to_tuple(tree, html_node) end)
  end

  @doc """
  It receives a HTML tree structure as tuple and maps
  through all nodes with a given function that receives
  a tuple with {name, attributes}.

  It returns that structure transformed by the function.

  ## Examples

      iex> html = {"div", [{"class", "foo"}], ["text"]}
      iex> Floki.map(html, fn({name, attrs}) -> {name, [{"data-name", "bar"} | attrs]} end)
      {"div", [{"data-name", "bar"}, {"class", "foo"}], ["text"]}

  """
  def map(html_tree_list, fun) when is_list(html_tree_list) do
    Enum.map(html_tree_list, &Finder.map(&1, fun))
  end

  def map(html_tree, fun), do: Finder.map(html_tree, fun)

  @doc """
  Returns the text nodes from a HTML tree.
  By default, it will perform a deep search through the HTML tree.
  You can disable deep search with the option `deep` assigned to false.
  You can include content of script tags with the option `js` assigned to true.
  You can specify a separator between nodes content.

  ## Examples

      iex> Floki.text("
hello world
")
      "hello world"

      iex> Floki.text("
hello world
", deep: false)
      " world"

      iex> Floki.text("
 world
")
      " world"

      iex> Floki.text("
 world
", js: true)
      "hello world"

      iex> Floki.text("

hello

world

", sep: " ")
      "hello world"

      iex> Floki.text([{"div", [], ["hello world"]}])
      "hello world"

      iex> Floki.text([{"p", [], ["1"]},{"p", [], ["2"]}])
      "12"

      iex> Floki.text("
 world
")
      "hello world"

      iex> Floki.text("
 world
", style: false)
      " world"
  """

  @spec text(html_tree | binary) :: binary

  def text(html, opts \\ [deep: true, js: false, style: true, sep: ""]) do
    html_tree =
      if is_binary(html) do
        parse(html)
      else
        html
      end

    cleaned_html_tree =
      case opts[:js] do
        true -> html_tree
        _ -> filter_out(html_tree, "script")
      end

    cleaned_html_tree =
      case opts[:style] do
        true -> html_tree
        _ -> filter_out(html_tree, "style")
      end

    search_strategy =
      case opts[:deep] do
        false -> Floki.FlatText
        _ -> Floki.DeepText
      end

    case opts[:sep] do
      nil -> search_strategy.get(cleaned_html_tree)
      sep -> search_strategy.get(cleaned_html_tree, sep)
    end
  end

  @doc """
  Returns a list with attribute values for a given selector.

  ## Examples

      iex> Floki.attribute("Google", "a", "href")
      ["https://google.com"]

      iex> Floki.attribute([{"a", [{"href", "https://google.com"}], ["Google"]}], "a", "href")
      ["https://google.com"]

  """

  @spec attribute(binary | html_tree, binary, binary) :: list

  def attribute(html, selector, attribute_name) do
    html
    |> find(selector)
    |> attribute_values(attribute_name)
  end

  @doc """
  Returns a list with attribute values from elements.

  ## Examples

      iex> Floki.attribute("Google", "href")
      ["https://google.com"]

      iex> Floki.attribute([{"a", [{"href", "https://google.com"}], ["Google"]}], "href")
      ["https://google.com"]

  """

  @spec attribute(binary | html_tree, binary) :: list

  def attribute(html_tree, attribute_name) when is_binary(html_tree) do
    html_tree
    |> parse
    |> attribute_values(attribute_name)
  end

  def attribute(elements, attribute_name) do
    attribute_values(elements, attribute_name)
  end

  defp attribute_values(element, attr_name) when is_tuple(element) do
    attribute_values([element], attr_name)
  end

  defp attribute_values(elements, attr_name) do
    values =
      Enum.reduce(elements, [], fn {_, attributes, _}, acc ->
        case attribute_match?(attributes, attr_name) do
          {_attr_name, value} ->
            [value | acc]

          _ ->
            acc
        end
      end)

    Enum.reverse(values)
  end

  defp attribute_match?(attributes, attribute_name) do
    Enum.find(attributes, fn {attr_name, _} ->
      attr_name == attribute_name
    end)
  end

  @doc """
  Returns the nodes from a HTML tree that don't match the filter selector.

  ## Examples

      iex> Floki.filter_out("
 world
", "script")
      {"div", [], [" world"]}

      iex> Floki.filter_out([{"body", [], [{"script", [], []},{"div", [], []}]}], "script")
      [{"body", [], [{"div", [], []}]}]

      iex> Floki.filter_out("
 text
", :comment)
      {"div", [], [" text"]}

  """

  @spec filter_out(binary | html_tree, binary) :: list

  def filter_out(html_tree, selector) when is_binary(html_tree) do
    html_tree
    |> parse
    |> FilterOut.filter_out(selector)
  end

  def filter_out(elements, selector) do
    FilterOut.filter_out(elements, selector)
  end
end

defmodule Floki do
  alias Floki.{Finder, HTMLParser, FilterOut, HTMLTree}

  @moduledoc """
  Floki is a simple HTML parser that enables search for nodes using CSS selectors.

  ## Example

  Assuming that you have the following HTML:

  ```html
  <!doctype html>
  <html>
  <body>
    <section id="content">
      <p class="headline">Floki</p>
      <a href="http://github.com/philss/floki">Github page</a>
      <span data-model="user">philss</span>
    </section>
  </body>
  </html>
  ```

  Examples of queries that you can perform:

    * Floki.find(html, "#content")
    * Floki.find(html, ".headline")
    * Floki.find(html, "a")
    * Floki.find(html, "[data-model=user]")
    * Floki.find(html, "#content a")
    * Floki.find(html, ".headline, a")

  Each HTML node is represented by a tuple like:

      {tag_name, attributes, children_nodes}

  Example of node:

      {"p", [{"class", "headline"}], ["Floki"]}

  So even if the only child node is the element text, it is represented
  inside a list.

  You can write a simple HTML crawler (with support of [HTTPoison](https://github.com/edgurgel/httpoison)) with a few lines of code:

      html
      |> Floki.find(".pages a")
      |> Floki.attribute("href")
      |> Enum.map(fn(url) -> HTTPoison.get!(url) end)

  It is simple as that!
  """

  @type html_tree :: tuple | list

  @doc """
  Parses a HTML string.

  ## Examples

      iex> Floki.parse("
hello world
")
      {"div", [{"class", "js-action"}], ["hello world"]}

      iex> Floki.parse("
first

second
")
      [{"div", [], ["first"]}, {"div", [], ["second"]}]

  """

  @spec parse(binary) :: html_tree | String.t()

  def parse(html) do
    HTMLParser.parse(html)
  end

  @doc """
  Converts HTML tree to raw HTML.
  Note that the resultant HTML may be different from the original one.
  Spaces after tags and doctypes are ignored.

  Floki.raw_html/2 accepts a keyword list of options. Currently, the
  only supported option is `:encode`, which can be set to `true` or `false`.

  You can also control the encoding behaviour at the application level via
  `config :floki, :encode_raw_html, true | false`

  ## Examples

      iex> Floki.parse(~s(
my content
)) |> Floki.raw_html
      ~s(
my content
)

      iex> Floki.parse(~s(
10 > 5
)) |> Floki.raw_html(encode: true)
      ~s(
10 > 5
)

      iex> Floki.parse(~s(
10 > 5
)) |> Floki.raw_html(encode: false)
      ~s(
10 > 5
)
  """

  @spec raw_html(html_tree | binary, keyword) :: binary

  defdelegate raw_html(html_tree, options \\ []), to: Floki.RawHTML

  @doc """
  Find elements inside a HTML tree or string.

  ## Examples

      iex> Floki.find("
hello
", ".hint")
      [{"span", [{"class", "hint"}], ["hello"]}]

      iex> Floki.find("

Content

", "#important")
      [{"div", [{"id", "important"}], [{"div", [], ["Content"]}]}]

      iex> Floki.find("
Google
", "a")
      [{"a", [{"href", "https://google.com"}], ["Google"]}]

      iex> Floki.find([{ "div", [], [{"a", [{"href", "https://google.com"}], ["Google"]}]}], "div a")
      [{"a", [{"href", "https://google.com"}], ["Google"]}]

  """

  @spec find(binary | html_tree, binary) :: html_tree

  def find(html, selector) when is_binary(html) do
    html_as_tuple = parse(html)

    {tree, results} = Finder.find(html_as_tuple, selector)

    Enum.map(results, fn html_node -> HTMLTree.to_tuple(tree, html_node) end)
  end

  def find(html_tree_as_tuple, selector) do
    {tree, results} = Finder.find(html_tree_as_tuple, selector)

    Enum.map(results, fn html_node -> HTMLTree.to_tuple(tree, html_node) end)
  end

  @doc """
  Changes the attribute values of the elements matched by `selector`
  with the function `mutation` and returns the whole element tree

  ## Examples

      iex> Floki.attr("

", "#a", "id", fn(id) -> String.replace(id, "a", "b") end)
      [{"div", [{"id", "b"}], []}]

      iex> Floki.attr("

", "div", "id", fn _ -> "b" end)
      [{"div", [{"id", "b"}, {"class", "class_name"}], []}]

  """
  @spec attr(binary | html_tree, binary, binary, (binary -> binary)) :: html_tree

  def attr(html_elem_tuple, selector, attribute_name, mutation) when is_tuple(html_elem_tuple) do
    attr([html_elem_tuple], selector, attribute_name, mutation)
  end

  def attr(html_str, selector, attribute_name, mutation) when is_binary(html_str) do
    attr(parse(html_str), selector, attribute_name, mutation)
  end

  def attr(html_tree_list, selector, attribute_name, mutation) when is_list(html_tree_list) do
    {tree, results} = Finder.find(html_tree_list, selector)
    mutate_attrs(html_tree_list, tree, results, attribute_name, mutation)
  end

  defp add_nodes_to_tree(tree, [html_node]) do
    nodes = Map.put(tree.nodes, html_node.node_id, html_node)
    Map.put(tree, :nodes, nodes)
  end

  defp add_nodes_to_tree(tree, [html_node | tail]) do
    nodes = Map.put(tree.nodes, html_node.node_id, html_node)

    tree
    |> Map.put(:nodes, nodes)
    |> add_nodes_to_tree(tail)
  end

  defp mutate_attrs(html_tree_list, _, [], _, _), do: html_tree_list

  defp mutate_attrs(_, tree, results, attribute_name, mutation_fn) do
    mutated_nodes =
      Enum.map(results, fn result ->
        mutated_attributes =
          if Enum.any?(result.attributes, &match?({^attribute_name, _}, &1)) do
            Enum.map(result.attributes, fn attribute ->
              with {^attribute_name, attribute_value} <- attribute do
                {attribute_name, mutation_fn.(attribute_value)}
              end
            end)
          else
            [{attribute_name, mutation_fn.(nil)} | result.attributes]
          end

        Map.put(result, :attributes, mutated_attributes)
      end)

    tree = add_nodes_to_tree(tree, mutated_nodes)

    tree.root_nodes_ids
    |> Enum.reverse()
    |> Enum.map(fn id -> Map.get(tree.nodes, id) end)
    |> Enum.map(fn html_node -> HTMLTree.to_tuple(tree, html_node) end)
  end

  @doc """
  It receives a HTML tree structure as tuple and maps
  through all nodes with a given function that receives
  a tuple with {name, attributes}.

  It returns that structure transformed by the function.

  ## Examples

      iex> html = {"div", [{"class", "foo"}], ["text"]}
      iex> Floki.map(html, fn({name, attrs}) -> {name, [{"data-name", "bar"} | attrs]} end)
      {"div", [{"data-name", "bar"}, {"class", "foo"}], ["text"]}

  """
  def map(html_tree_list, fun) when is_list(html_tree_list) do
    Enum.map(html_tree_list, &Finder.map(&1, fun))
  end

  def map(html_tree, fun), do: Finder.map(html_tree, fun)

  @doc """
  Returns the text nodes from a HTML tree.
  By default, it will perform a deep search through the HTML tree.
  You can disable deep search with the option `deep` assigned to false.
  You can include content of script tags with the option `js` assigned to true.
  You can specify a separator between nodes content.

  ## Examples

      iex> Floki.text("
hello world
")
      "hello world"

      iex> Floki.text("
hello world
", deep: false)
      " world"

      iex> Floki.text("
 world
")
      " world"

      iex> Floki.text("
 world
", js: true)
      "hello world"

      iex> Floki.text("

hello

world

", sep: " ")
      "hello world"

      iex> Floki.text([{"div", [], ["hello world"]}])
      "hello world"

      iex> Floki.text([{"p", [], ["1"]},{"p", [], ["2"]}])
      "12"

  """

  @spec text(html_tree | binary) :: binary

  def text(html, opts \\ [deep: true, js: false, sep: ""]) do
    html_tree =
      if is_binary(html) do
        parse(html)
      else
        html
      end

    cleaned_html_tree =
      case opts[:js] do
        true -> html_tree
        _ -> filter_out(html_tree, "script")
      end

    search_strategy =
      case opts[:deep] do
        false -> Floki.FlatText
        _ -> Floki.DeepText
      end

    case opts[:sep] do
      nil -> search_strategy.get(cleaned_html_tree)
      sep -> search_strategy.get(cleaned_html_tree, sep)
    end
  end

  @doc """
  Returns a list with attribute values for a given selector.

  ## Examples

      iex> Floki.attribute("Google", "a", "href")
      ["https://google.com"]

      iex> Floki.attribute([{"a", [{"href", "https://google.com"}], ["Google"]}], "a", "href")
      ["https://google.com"]

  """

  @spec attribute(binary | html_tree, binary, binary) :: list

  def attribute(html, selector, attribute_name) do
    html
    |> find(selector)
    |> attribute_values(attribute_name)
  end

  @doc """
  Returns a list with attribute values from elements.

  ## Examples

      iex> Floki.attribute("Google", "href")
      ["https://google.com"]

      iex> Floki.attribute([{"a", [{"href", "https://google.com"}], ["Google"]}], "href")
      ["https://google.com"]

  """

  @spec attribute(binary | html_tree, binary) :: list

  def attribute(html_tree, attribute_name) when is_binary(html_tree) do
    html_tree
    |> parse
    |> attribute_values(attribute_name)
  end

  def attribute(elements, attribute_name) do
    attribute_values(elements, attribute_name)
  end

  defp attribute_values(element, attr_name) when is_tuple(element) do
    attribute_values([element], attr_name)
  end

  defp attribute_values(elements, attr_name) do
    values =
      Enum.reduce(elements, [], fn {_, attributes, _}, acc ->
        case attribute_match?(attributes, attr_name) do
          {_attr_name, value} ->
            [value | acc]

          _ ->
            acc
        end
      end)

    Enum.reverse(values)
  end

  defp attribute_match?(attributes, attribute_name) do
    Enum.find(attributes, fn {attr_name, _} ->
      attr_name == attribute_name
    end)
  end

  @doc """
  Returns the nodes from a HTML tree that don't match the filter selector.

  ## Examples

      iex> Floki.filter_out("
 world
", "script")
      {"div", [], [" world"]}

      iex> Floki.filter_out([{"body", [], [{"script", [], []},{"div", [], []}]}], "script")
      [{"body", [], [{"div", [], []}]}]

      iex> Floki.filter_out("
 text
", :comment)
      {"div", [], [" text"]}

  """

  @spec filter_out(binary | html_tree, binary) :: list

  def filter_out(html_tree, selector) when is_binary(html_tree) do
    html_tree
    |> parse
    |> FilterOut.filter_out(selector)
  end

  def filter_out(elements, selector) do
    FilterOut.filter_out(elements, selector)
  end
end


Fix resource_controller macro compiler warning

When using this macro, you would get this warning:
> warning: this check/guard will always yield the same result

defmodule ExAdmin.ResourceController do
  @moduledoc false

  defmacro __using__(_opts) do
    quote do
      alias ExAdmin.Authorization
      import ExAdmin.Utils
      import ExAdmin.ParamsToAtoms

      def action(%{private: %{phoenix_action: action}} = conn, _options) do
        conn = conn |> assign(:xhr, get_req_header(conn, "x-requested-with") == ["XMLHttpRequest"])
        resource = Macro.expand(@resource, __ENV__) || conn.params["resource"]
        conn = scrub_params(conn, resource, action)
        defn = get_registered_by_controller_route!(conn, resource)
        params = filter_params(conn.params, defn.resource_model)

        if !restricted_action?(action, defn) do
          conn
          |> assign(:defn, defn)
          |> load_resource(action, defn, params[:id])
          |> authorize_action(action)
          |> handle_plugs(action, defn)
          |> handle_before_filter(action, defn, params)
          |> handle_custom_actions(action, defn, params)
        else
          render_403 conn
        end
      end

      defp do_form_view(conn, resource, params) do
        model = conn.assigns.defn.__struct__
        if function_exported? model, :form_view, 3 do
          apply(model, :form_view, [conn, resource, params])
        else
          ExAdmin.Form.default_form_view conn, resource, params
        end
      end

      defp handle_changeset_error(conn, defn, changeset, params) do
        conn = put_flash(conn, :inline_error, changeset.errors)
        |> Plug.Conn.assign(:changeset, changeset)
        |> Plug.Conn.assign(:ea_required, changeset.required)
        contents = do_form_view(conn, ExAdmin.Changeset.get_data(changeset), params)
        render(conn, "admin.html", html: contents, filters: nil)
      end

      defp render_403(conn) do
        conn
        |> put_layout(false)
        |> put_status(403)
        |> render(ExAdmin.ErrorView, "403.html")
        |> halt
      end

      defp restricted_action?(:destroy, defn), do: restricted_action?(:delete, defn)
      defp restricted_action?(:create, defn), do: restricted_action?(:new, defn)
      defp restricted_action?(:update, defn), do: restricted_action?(:edit, defn)
      defp restricted_action?(action, defn) do
        if action in [:show, :edit, :update, :new, :destroy, :delete] do
          not action in defn.actions
        else
          false
        end
      end

      def authorize_action(conn, action) do
        if ExAdmin.Authorization.authorize_action(conn.assigns[:resource], conn, action) do
          conn
        else
          render_403(conn)
        end
      end

      defp scrub_params(conn, required_key, action) when action in [:create, :update] do
        if conn.params[required_key] do
          Phoenix.Controller.scrub_params conn, required_key
        else
          conn
        end
      end
      defp scrub_params(conn, _required_key, _action), do: conn

      defp load_resource(conn, _action, defn, nil) do
        resource = defn.resource_model.__struct__
        assign(conn, :resource, resource)
      end
      defp load_resource(conn, action, defn, resource_id) do
        model = defn.__struct__
        query = model.run_query(repo(), defn, action, resource_id)
        resource =
        Authorization.authorize_query(defn.resource_model.__struct__, conn, query, action, resource_id)
        |> ExAdmin.Query.execute_query(repo(), action, resource_id)

        if resource == nil do
          raise Phoenix.Router.NoRouteError, conn: conn, router: __MODULE__
        end

        assign(conn, :resource, resource)
      end

      defp handle_custom_actions(%{halted: true} = conn, _, _, _), do: conn
      defp handle_custom_actions({conn, params}, action, defn, _) do
        handle_custom_actions(conn, action, defn, params)
      end
      defp handle_custom_actions(conn, :member, defn, params) do
        %{member_actions: member_actions} = defn
        action = String.to_atom params[:action]
        cond do
          member_action = Keyword.get(member_actions, action) ->
            member_action[:fun].(conn, params)
          true ->
            render_403 conn
        end
      end
      defp handle_custom_actions(conn, :collection, defn, params) do
        %{collection_actions: collection_actions} = defn
        action = String.to_atom params[:action]
        cond do
          collection_action = Keyword.get(collection_actions, action) ->
            collection_action[:fun].(conn, params)
          true ->
            render_403 conn
        end
      end

      defp handle_custom_actions(conn, action, defn, params) do
        apply(__MODULE__, action, [conn, defn, params])
      end

      defp handle_before_filter(%{halted: true} = conn, _, _, _), do: conn
      defp handle_before_filter(conn, action, defn, params) do
        _handle_before_filter(conn, action, defn, params, defn.controller_filters[:before_filter])
      end

      defp _handle_before_filter(conn, action, defn, params, [{name, opts} | t]) do
        filter = cond do
          opts[:only] ->
            if action in opts[:only], do: true, else: false
          opts[:except] ->
            if not action in opts[:except], do: true, else: false
          true -> true
        end
        if filter do
          apply(defn.__struct__, name, [conn, params])
        else
          conn
        end
        |> _handle_before_filter(action, defn, params, t)
      end
      defp _handle_before_filter(conn, _action, _defn, _params, _), do: conn

      defp handle_after_filter(conn, action, defn, params, resource) do
        _handle_after_filter({conn, params, resource}, action, defn, defn.controller_filters[:after_filter])
      end

      defp _handle_after_filter({conn, params, resource}, action, defn, [{name, opts} | t]) do
        filter = cond do
          opts[:only] ->
            if action in opts[:only], do: true, else: false
          opts[:except] ->
            if not action in opts[:except], do: true, else: false
          true -> true
        end
        if filter do
          case apply(defn.__struct__, name, [conn, params, resource, action]) do
            {_, _, _} = tuple -> tuple
            %Plug.Conn{} = conn -> {conn, params, resource}
            error ->
              raise ExAdmin.RuntimeError, message: (gettext "invalid after_filter return:") <> " #{inspect error}"
          end
        else
          {conn, params, resource}
        end
        |> _handle_after_filter(action, defn, t)
      end
      defp _handle_after_filter(args, _action, _defn, _), do: args

      defp handle_plugs(%{halted: true} = conn, _, _), do: conn
      defp handle_plugs(conn, :nested, _defn), do: conn
      defp handle_plugs(conn, _action, defn) do
        case Application.get_env(:ex_admin, :plug, []) do
          list when is_list(list) -> list
          item -> [{item, []}]
        end
        |> Keyword.merge(defn.plugs)
        |> Enum.reduce(conn, fn({name, opts}, conn) ->
          apply(name, :call, [conn, opts])
        end)
        |> authorized?
      end

      defp authorized?(%{assigns: %{authorized: true}} = conn), do: conn
      defp authorized?(%{assigns: %{authorized: false}}) do
        throw :unauthorized
      end
      defp authorized?(conn), do: conn

      defp repo, do: Application.get_env(:ex_admin, :repo)
    end
  end
end

defmodule ExAdmin.ResourceController do
  @moduledoc false

  defmacro __using__(_opts) do
    quote do
      alias ExAdmin.Authorization
      import ExAdmin.Utils
      import ExAdmin.ParamsToAtoms

      def action(%{private: %{phoenix_action: action}} = conn, _options) do
        conn = conn |> assign(:xhr, get_req_header(conn, "x-requested-with") == ["XMLHttpRequest"])
        resource = @resource || conn.params["resource"]
        conn = scrub_params(conn, resource, action)
        defn = get_registered_by_controller_route!(conn, resource)
        params = filter_params(conn.params, defn.resource_model)

        if !restricted_action?(action, defn) do
          conn
          |> assign(:defn, defn)
          |> load_resource(action, defn, params[:id])
          |> authorize_action(action)
          |> handle_plugs(action, defn)
          |> handle_before_filter(action, defn, params)
          |> handle_custom_actions(action, defn, params)
        else
          render_403 conn
        end
      end

      defp do_form_view(conn, resource, params) do
        model = conn.assigns.defn.__struct__
        if function_exported? model, :form_view, 3 do
          apply(model, :form_view, [conn, resource, params])
        else
          ExAdmin.Form.default_form_view conn, resource, params
        end
      end

      defp handle_changeset_error(conn, defn, changeset, params) do
        conn = put_flash(conn, :inline_error, changeset.errors)
        |> Plug.Conn.assign(:changeset, changeset)
        |> Plug.Conn.assign(:ea_required, changeset.required)
        contents = do_form_view(conn, ExAdmin.Changeset.get_data(changeset), params)
        render(conn, "admin.html", html: contents, filters: nil)
      end

      defp render_403(conn) do
        conn
        |> put_layout(false)
        |> put_status(403)
        |> render(ExAdmin.ErrorView, "403.html")
        |> halt
      end

      defp restricted_action?(:destroy, defn), do: restricted_action?(:delete, defn)
      defp restricted_action?(:create, defn), do: restricted_action?(:new, defn)
      defp restricted_action?(:update, defn), do: restricted_action?(:edit, defn)
      defp restricted_action?(action, defn) do
        if action in [:show, :edit, :update, :new, :destroy, :delete] do
          not action in defn.actions
        else
          false
        end
      end

      def authorize_action(conn, action) do
        if ExAdmin.Authorization.authorize_action(conn.assigns[:resource], conn, action) do
          conn
        else
          render_403(conn)
        end
      end

      defp scrub_params(conn, required_key, action) when action in [:create, :update] do
        if conn.params[required_key] do
          Phoenix.Controller.scrub_params conn, required_key
        else
          conn
        end
      end
      defp scrub_params(conn, _required_key, _action), do: conn

      defp load_resource(conn, _action, defn, nil) do
        resource = defn.resource_model.__struct__
        assign(conn, :resource, resource)
      end
      defp load_resource(conn, action, defn, resource_id) do
        model = defn.__struct__
        query = model.run_query(repo(), defn, action, resource_id)
        resource =
        Authorization.authorize_query(defn.resource_model.__struct__, conn, query, action, resource_id)
        |> ExAdmin.Query.execute_query(repo(), action, resource_id)

        if resource == nil do
          raise Phoenix.Router.NoRouteError, conn: conn, router: __MODULE__
        end

        assign(conn, :resource, resource)
      end

      defp handle_custom_actions(%{halted: true} = conn, _, _, _), do: conn
      defp handle_custom_actions({conn, params}, action, defn, _) do
        handle_custom_actions(conn, action, defn, params)
      end
      defp handle_custom_actions(conn, :member, defn, params) do
        %{member_actions: member_actions} = defn
        action = String.to_atom params[:action]
        cond do
          member_action = Keyword.get(member_actions, action) ->
            member_action[:fun].(conn, params)
          true ->
            render_403 conn
        end
      end
      defp handle_custom_actions(conn, :collection, defn, params) do
        %{collection_actions: collection_actions} = defn
        action = String.to_atom params[:action]
        cond do
          collection_action = Keyword.get(collection_actions, action) ->
            collection_action[:fun].(conn, params)
          true ->
            render_403 conn
        end
      end

      defp handle_custom_actions(conn, action, defn, params) do
        apply(__MODULE__, action, [conn, defn, params])
      end

      defp handle_before_filter(%{halted: true} = conn, _, _, _), do: conn
      defp handle_before_filter(conn, action, defn, params) do
        _handle_before_filter(conn, action, defn, params, defn.controller_filters[:before_filter])
      end

      defp _handle_before_filter(conn, action, defn, params, [{name, opts} | t]) do
        filter = cond do
          opts[:only] ->
            if action in opts[:only], do: true, else: false
          opts[:except] ->
            if not action in opts[:except], do: true, else: false
          true -> true
        end
        if filter do
          apply(defn.__struct__, name, [conn, params])
        else
          conn
        end
        |> _handle_before_filter(action, defn, params, t)
      end
      defp _handle_before_filter(conn, _action, _defn, _params, _), do: conn

      defp handle_after_filter(conn, action, defn, params, resource) do
        _handle_after_filter({conn, params, resource}, action, defn, defn.controller_filters[:after_filter])
      end

      defp _handle_after_filter({conn, params, resource}, action, defn, [{name, opts} | t]) do
        filter = cond do
          opts[:only] ->
            if action in opts[:only], do: true, else: false
          opts[:except] ->
            if not action in opts[:except], do: true, else: false
          true -> true
        end
        if filter do
          case apply(defn.__struct__, name, [conn, params, resource, action]) do
            {_, _, _} = tuple -> tuple
            %Plug.Conn{} = conn -> {conn, params, resource}
            error ->
              raise ExAdmin.RuntimeError, message: (gettext "invalid after_filter return:") <> " #{inspect error}"
          end
        else
          {conn, params, resource}
        end
        |> _handle_after_filter(action, defn, t)
      end
      defp _handle_after_filter(args, _action, _defn, _), do: args

      defp handle_plugs(%{halted: true} = conn, _, _), do: conn
      defp handle_plugs(conn, :nested, _defn), do: conn
      defp handle_plugs(conn, _action, defn) do
        case Application.get_env(:ex_admin, :plug, []) do
          list when is_list(list) -> list
          item -> [{item, []}]
        end
        |> Keyword.merge(defn.plugs)
        |> Enum.reduce(conn, fn({name, opts}, conn) ->
          apply(name, :call, [conn, opts])
        end)
        |> authorized?
      end

      defp authorized?(%{assigns: %{authorized: true}} = conn), do: conn
      defp authorized?(%{assigns: %{authorized: false}}) do
        throw :unauthorized
      end
      defp authorized?(conn), do: conn

      defp repo, do: Application.get_env(:ex_admin, :repo)
    end
  end
end


test login token email

test login token email

defmodule Scripture.LoginTokenControllerTest do
  use Scripture.ConnCase, async: true

  import Swoosh.TestAssertions

  alias Scripture.User

  test "renders login form", %{conn: conn} do
    conn = get conn, login_token_path(conn, :new)
    assert html_response(conn, 200) =~ "Login"
  end

  test "creates login token, sends email and redirects when user exists", %{conn: conn} do
    user = persist_fixture(:user)

    conn = post conn, login_token_path(conn, :create), login_token_create: %{email: user.email}
    assert redirected_to(conn) == login_token_path(conn, :success)

    conn = get conn, redirected_to(conn)
    assert html_response(conn, 200) =~ "Login link sent to #{user.email}"

    changed_user = Repo.get!(User, user.id)
    assert changed_user.login_token != user.login_token
    assert_email_sent Scripture.UserEmail.login_token(changed_user)
  end

  test "does not create login token and renders error when user not found", %{conn: conn} do
    email = "not-existing@example.com"

    conn = post conn, login_token_path(conn, :create), login_token_create: %{email: email}
    assert html_response(conn, 200) =~ "No user found with that email"
  end
end

defmodule Scripture.LoginTokenControllerTest do
  use Scripture.ConnCase

  alias Scripture.User

  test "renders login form", %{conn: conn} do
    conn = get conn, login_token_path(conn, :new)
    assert html_response(conn, 200) =~ "Login"
  end

  test "creates login token and redirects when user exists", %{conn: conn} do
    user = persist_fixture(:user)

    conn = post conn, login_token_path(conn, :create), login_token_create: %{email: user.email}
    assert redirected_to(conn) == login_token_path(conn, :success)

    conn = get conn, redirected_to(conn)
    assert html_response(conn, 200) =~ "Login link sent to #{user.email}"

    assert Repo.get!(User, user.id).login_token
  end

  test "does not create login token and renders error when user not found", %{conn: conn} do
    email = "not-existing@example.com"

    conn = post conn, login_token_path(conn, :create), login_token_create: %{email: email}
    assert html_response(conn, 200) =~ "No user found with that email"
  end
end


Use byte_size() for determining state string length.

defmodule Shippex.Util do
  @states [
    ["Alabama", "AL"],
    ["Alaska", "AK"],
    ["Arizona", "AZ"],
    ["Arkansas", "AR"],
    ["California", "CA"],
    ["Colorado", "CO"],
    ["Connecticut", "CT"],
    ["Delaware", "DE"],
    ["District of Columbia", "DC"],
    ["Florida", "FL"],
    ["Georgia", "GA"],
    ["Hawaii", "HI"],
    ["Idaho", "ID"],
    ["Illinois", "IL"],
    ["Indiana", "IN"],
    ["Iowa", "IA"],
    ["Kansas", "KS"],
    ["Kentucky", "KY"],
    ["Louisiana", "LA"],
    ["Maine", "ME"],
    ["Maryland", "MD"],
    ["Massachusetts", "MA"],
    ["Michigan", "MI"],
    ["Minnesota", "MN"],
    ["Mississippi", "MS"],
    ["Missouri", "MO"],
    ["Montana", "MT"],
    ["Nebraska", "NE"],
    ["Nevada", "NV"],
    ["New Hampshire", "NH"],
    ["New Jersey", "NJ"],
    ["New Mexico", "NM"],
    ["New York", "NY"],
    ["North Carolina", "NC"],
    ["North Dakota", "ND"],
    ["Ohio", "OH"],
    ["Oklahoma", "OK"],
    ["Oregon", "OR"],
    ["Pennsylvania", "PA"],
    ["Rhode Island", "RI"],
    ["South Carolina", "SC"],
    ["South Dakota", "SD"],
    ["Tennessee", "TN"],
    ["Texas", "TX"],
    ["Utah", "UT"],
    ["Vermont", "VT"],
    ["Virginia", "VA"],
    ["Washington", "WA"],
    ["West Virginia", "WV"],
    ["Wisconsin", "WI"],
    ["Wyoming", "WY"]
  ]

  def full_state_to_abbreviation(state) when is_bitstring(state) and byte_size(state) == 2 do
    state
  end

  def full_state_to_abbreviation(state) when is_bitstring(state) do
    state = String.downcase(state)

    result = Enum.find @states, fn (s) ->
      [full, abbr] = s

      if state == String.downcase(full) do
        abbr
      else
        false
      end
    end

    case result do
      [_full, abbr] -> abbr
      _ -> nil
    end
  end
end

defmodule Shippex.Util do
    @states [
      ["Alabama", "AL"],
      ["Alaska", "AK"],
      ["Arizona", "AZ"],
      ["Arkansas", "AR"],
      ["California", "CA"],
      ["Colorado", "CO"],
      ["Connecticut", "CT"],
      ["Delaware", "DE"],
      ["District of Columbia", "DC"],
      ["Florida", "FL"],
      ["Georgia", "GA"],
      ["Hawaii", "HI"],
      ["Idaho", "ID"],
      ["Illinois", "IL"],
      ["Indiana", "IN"],
      ["Iowa", "IA"],
      ["Kansas", "KS"],
      ["Kentucky", "KY"],
      ["Louisiana", "LA"],
      ["Maine", "ME"],
      ["Maryland", "MD"],
      ["Massachusetts", "MA"],
      ["Michigan", "MI"],
      ["Minnesota", "MN"],
      ["Mississippi", "MS"],
      ["Missouri", "MO"],
      ["Montana", "MT"],
      ["Nebraska", "NE"],
      ["Nevada", "NV"],
      ["New Hampshire", "NH"],
      ["New Jersey", "NJ"],
      ["New Mexico", "NM"],
      ["New York", "NY"],
      ["North Carolina", "NC"],
      ["North Dakota", "ND"],
      ["Ohio", "OH"],
      ["Oklahoma", "OK"],
      ["Oregon", "OR"],
      ["Pennsylvania", "PA"],
      ["Rhode Island", "RI"],
      ["South Carolina", "SC"],
      ["South Dakota", "SD"],
      ["Tennessee", "TN"],
      ["Texas", "TX"],
      ["Utah", "UT"],
      ["Vermont", "VT"],
      ["Virginia", "VA"],
      ["Washington", "WA"],
      ["West Virginia", "WV"],
      ["Wisconsin", "WI"],
      ["Wyoming", "WY"]
    ]

    def full_state_to_abbreviation(state) when is_bitstring(state) and length(state) == 2 do
      state
    end

    def full_state_to_abbreviation(state) when is_bitstring(state) do
      result = Enum.find @states, fn (s) ->
        [full, abbr] = s

        if String.downcase(full) == String.downcase(state) do
          abbr
        else
          false
        end
      end

      case result do
        [_full, abbr] -> abbr
        _ -> nil
      end
    end
end


fix compiler error compllaining about unused variable

defmodule TougouBot.Util.Parsers do
  @moduledoc """
  A separate module to store the parsers that we use in for different commands
  to support code re-use.
  """

  #function returns all arguments concatinated with a +
  def plus_parser(args) do
    args = String.split(args)
    args = rebuild_string(args, "+")
    List.wrap(args)
  end

  #function returns all arguments concatinated by a space.
  def space_parser(args) do
    args = String.split(args)
    args = rebuild_string(args, " ")
    List.wrap(args)
  end

  #parser used by the tags module to extract the first word as one argument
  # and then the rest all as one space separated string, resulting in two args.
  def tags_parser(args) do
    args = String.split(args)
    arg0 = Enum.at(args, 0)
    args = Enum.drop(args, 1)
    args = rebuild_string(args, " ")
    List.wrap(arg0) ++ List.wrap(args)
  end

  #helper functions to re-build our list as a string with a given concatinator
  def rebuild_string([], _concatenator) do
    ""
  end
  def rebuild_string([head | []], _concatenator) do
    head
  end
  def rebuild_string([head | tail], concatenator) do
    head<>concatenator<>rebuild_string(tail, concatenator)
  end
end

defmodule TougouBot.Util.Parsers do
  @moduledoc """
  A separate module to store the parsers that we use in for different commands
  to support code re-use.
  """

  #function returns all arguments concatinated with a +
  def plus_parser(args) do
    args = String.split(args)
    args = rebuild_string(args, "+")
    List.wrap(args)
  end

  #function returns all arguments concatinated by a space.
  def space_parser(args) do
    args = String.split(args)
    args = rebuild_string(args, " ")
    List.wrap(args)
  end

  #parser used by the tags module to extract the first word as one argument
  # and then the rest all as one space separated string, resulting in two args.
  def tags_parser(args) do
    args = String.split(args)
    arg0 = Enum.at(args, 0)
    args = Enum.drop(args, 1)
    args = rebuild_string(args, " ")
    List.wrap(arg0) ++ List.wrap(args)
  end

  #helper functions to re-build our list as a string with a given concatinator
  def rebuild_string([], _concatenator) do
    ""
  end
  def rebuild_string([head | []], concatenator) do
    head
  end
  def rebuild_string([head | tail], concatenator) do
    head<>concatenator<>rebuild_string(tail, concatenator)
  end
end


Ensure link: false is completely covered

defmodule Phoenix.PubSub.PubSubTest do
  # TODO: Should be async
  use ExUnit.Case
  alias Phoenix.PubSub

  @adapters [
    {Phoenix.PubSub.Redis, :redis_pub},
    {Phoenix.PubSub.PG2,   :pg2_pub}
  ]

  def spawn_pid do
    spawn fn -> :timer.sleep(:infinity) end
  end

  for {adapter, name} <- @adapters do
    @adapter adapter
    @server name
    setup_all do
      @adapter.start_link(name: @server)
      :ok
    end

    test "#{inspect @adapter} #subscribers, #subscribe, #unsubscribe" do
      pid = spawn_pid
      assert Enum.empty?(PubSub.subscribers(@server, "topic4"))
      assert PubSub.subscribe(@server, pid, "topic4")
      assert PubSub.subscribers(@server, "topic4") |> Enum.to_list == [pid]
      assert PubSub.unsubscribe(@server, pid, "topic4")
      assert Enum.empty?(PubSub.subscribers(@server, "topic4"))
      Process.exit pid, :kill
    end

    test "#{inspect @adapter} subscribe/3 with link does not down adapter" do
      server_name = Module.concat(@server, :link_pub)
      {:ok, _super_pid} = @adapter.start_link(name: server_name)
      server_pid = Process.whereis(server_name)
      assert Process.alive?(server_pid)
      pid = spawn_pid

      assert Enum.empty?(PubSub.subscribers(server_name, "topic4"))
      assert PubSub.subscribe(server_name, pid, "topic4", link: true)
      Process.exit(pid, :kill)
      refute Process.alive?(pid)
      assert Process.alive?(server_pid)
    end

    test "#{inspect @adapter} subscribe/3 with link downs subscriber" do
      server_name = Module.concat(@server, :link_pub2)
      {:ok, _super_pid} = @adapter.start_link(name: server_name)
      server_pid = Process.whereis(server_name)
      assert Process.alive?(server_pid)
      pid = spawn_pid
      non_linked_pid = spawn_pid
      non_linked_pid2 = spawn_pid

      assert PubSub.subscribe(server_name, pid, "topic4", link: true)
      assert PubSub.subscribe(server_name, non_linked_pid, "topic4")
      assert PubSub.subscribe(server_name, non_linked_pid2, "topic4", link: false)
      Process.exit(server_pid, :kill)
      refute Process.alive?(server_pid)
      refute Process.alive?(pid)
      assert Process.alive?(non_linked_pid)
      assert Process.alive?(non_linked_pid2)
    end

    test "#{inspect @adapter} #broadcast publishes message to each subscriber" do
      PubSub.subscribe(@server, self, "topic9")
      assert PubSub.subscribers(@server, "topic9") |> Enum.to_list == [self]
      PubSub.broadcast(@server, "topic9", :ping)
      assert_receive :ping
    end

    test "#{inspect @adapter} #broadcast does not publish message to other topic subscribers" do
      pids = Enum.map 0..10, fn _ -> spawn_pid end
      pids |> Enum.each(&PubSub.subscribe(@server, &1, "topic10"))
      PubSub.broadcast(@server, "topic10", :ping)
      refute_receive :ping
      pids |> Enum.each(&Process.exit &1, :kill)
    end

    test "#{inspect @adapter} #broadcast_from does not publish to broadcaster pid when provided" do
      PubSub.subscribe(@server, self, "topic11")
      PubSub.broadcast_from(@server, self, "topic11", :ping)
      refute_receive :ping
    end

    test "#{inspect @adapter} processes automatically removed from topic when killed" do
      pid = spawn_pid
      assert PubSub.subscribe(@server, pid, "topic12")
      assert PubSub.subscribers(@server, "topic12") |> Enum.to_list == [pid]
      Process.exit pid, :kill
      :timer.sleep 10 # wait until adapter removes dead pid
      assert PubSub.subscribers(@server, "topic12") |> Enum.to_list == []
    end
  end
end

defmodule Phoenix.PubSub.PubSubTest do
  # TODO: Should be async
  use ExUnit.Case
  alias Phoenix.PubSub

  @adapters [
    {Phoenix.PubSub.Redis, :redis_pub},
    {Phoenix.PubSub.PG2,   :pg2_pub}
  ]

  def spawn_pid do
    spawn fn -> :timer.sleep(:infinity) end
  end

  for {adapter, name} <- @adapters do
    @adapter adapter
    @server name
    setup_all do
      @adapter.start_link(name: @server)
      :ok
    end

    test "#{inspect @adapter} #subscribers, #subscribe, #unsubscribe" do
      pid = spawn_pid
      assert Enum.empty?(PubSub.subscribers(@server, "topic4"))
      assert PubSub.subscribe(@server, pid, "topic4")
      assert PubSub.subscribers(@server, "topic4") |> Enum.to_list == [pid]
      assert PubSub.unsubscribe(@server, pid, "topic4")
      assert Enum.empty?(PubSub.subscribers(@server, "topic4"))
      Process.exit pid, :kill
    end

    test "#{inspect @adapter} subscribe/3 with link does not down adapter" do
      server_name = Module.concat(@server, :link_pub)
      {:ok, _super_pid} = @adapter.start_link(name: server_name)
      server_pid = Process.whereis(server_name)
      assert Process.alive?(server_pid)
      pid = spawn_pid

      assert Enum.empty?(PubSub.subscribers(server_name, "topic4"))
      assert PubSub.subscribe(server_name, pid, "topic4", link: true)
      Process.exit(pid, :kill)
      refute Process.alive?(pid)
      assert Process.alive?(server_pid)
    end

    test "#{inspect @adapter} subscribe/3 with link downs subscriber" do
      server_name = Module.concat(@server, :link_pub2)
      {:ok, _super_pid} = @adapter.start_link(name: server_name)
      server_pid = Process.whereis(server_name)
      assert Process.alive?(server_pid)
      pid = spawn_pid
      non_linked_pid = spawn_pid

      assert PubSub.subscribe(server_name, pid, "topic4", link: true)
      assert PubSub.subscribe(server_name, non_linked_pid, "topic4")
      Process.exit(server_pid, :kill)
      refute Process.alive?(server_pid)
      refute Process.alive?(pid)
      assert Process.alive?(non_linked_pid)
    end

    test "#{inspect @adapter} #broadcast publishes message to each subscriber" do
      PubSub.subscribe(@server, self, "topic9")
      assert PubSub.subscribers(@server, "topic9") |> Enum.to_list == [self]
      PubSub.broadcast(@server, "topic9", :ping)
      assert_receive :ping
    end

    test "#{inspect @adapter} #broadcast does not publish message to other topic subscribers" do
      pids = Enum.map 0..10, fn _ -> spawn_pid end
      pids |> Enum.each(&PubSub.subscribe(@server, &1, "topic10"))
      PubSub.broadcast(@server, "topic10", :ping)
      refute_receive :ping
      pids |> Enum.each(&Process.exit &1, :kill)
    end

    test "#{inspect @adapter} #broadcast_from does not publish to broadcaster pid when provided" do
      PubSub.subscribe(@server, self, "topic11")
      PubSub.broadcast_from(@server, self, "topic11", :ping)
      refute_receive :ping
    end

    test "#{inspect @adapter} processes automatically removed from topic when killed" do
      pid = spawn_pid
      assert PubSub.subscribe(@server, pid, "topic12")
      assert PubSub.subscribers(@server, "topic12") |> Enum.to_list == [pid]
      Process.exit pid, :kill
      :timer.sleep 10 # wait until adapter removes dead pid
      assert PubSub.subscribers(@server, "topic12") |> Enum.to_list == []
    end
  end
end


clean up

clean up