defmodule Wand.CLI.Commands.Add.Execute do @moduledoc false alias Wand.CLI.Commands.Add.Package alias Wand.CLI.Executor.Result alias WandCore.WandFile alias WandCore.WandFile.Dependency alias Wand.CLI.DependencyDownloader def execute(packages, %{wand_file: file}) do with {:ok, dependencies} <- get_dependencies(packages), {:ok, file} <- add_dependencies(file, dependencies) do message = Enum.map(dependencies, fn %Dependency{name: name, requirement: requirement} -> "Succesfully added #{name}: #{requirement}" end) |> Enum.join("\n") {:ok, %Result{wand_file: file, message: message}} else error -> error end end def after_save(packages), do: download(packages) defp get_dependencies(packages) do dependencies = packages |> Enum.map(&Task.async(fn -> get_dependency(&1) end)) |> Enum.map(&Task.await/1) case Enum.find(dependencies, &(elem(&1, 0) == :error)) do nil -> {:ok, Enum.unzip(dependencies) |> elem(1)} {:error, {:not_found, name}} -> {:error, :package_not_found, name} {:error, {reason, _name}} -> {:error, :hex_api_error, reason} end end defp get_dependency(%Package{name: name, requirement: {:latest, mode}} = package) do case Wand.Hex.releases(name) do {:ok, [version | _]} -> requirement = Wand.Mode.get_requirement!(mode, version) opts = get_opts(package) {:ok, %Dependency{ name: name, opts: opts, requirement: requirement }} {:error, error} -> {:error, {error, name}} end end defp get_dependency(%Package{} = package) do {:ok, %Dependency{ name: package.name, opts: get_opts(package), requirement: package.requirement }} end defp add_dependencies(file, dependencies) do Enum.reduce_while(dependencies, {:ok, file}, fn dependency, {:ok, file} -> case WandFile.add(file, dependency) do {:ok, file} -> {:cont, {:ok, file}} {:error, {:already_exists, name}} -> {:halt, {:error, :package_already_exists, name}} end end) end defp get_opts(%Package{details: details} = package) do get_base_opts(package) |> Keyword.merge(get_detail_opts(details)) |> Enum.into(%{}) end defp get_base_opts(%Package{} = package) do [ :compile_env, :only, :optional, :override, :read_app_file, :runtime ] |> get_changed(package, %Package{}) end defp get_detail_opts(details) do default = Map.fetch!(details, :__struct__) |> struct() Map.keys(details) |> get_changed(details, default) end defp get_changed(keys, config, default) do defaults = Enum.map(keys, &Map.fetch!(config, &1)) Enum.zip(keys, defaults) |> Enum.filter(fn {key, value} -> value != Map.fetch!(default, key) end) end defp download([%Package{download: download} | _]) when not download, do: :ok defp download(_), do: DependencyDownloader.download() end