defmodule Wand.CLI.Commands.Add.Execute do @moduledoc false alias Wand.CLI.Commands.Add.Package alias WandCore.WandFile alias WandCore.WandFile.Dependency alias Wand.CLI.WandFileWithHelp alias Wand.CLI.Display alias Wand.CLI.Error def execute(packages) do with :ok <- Wand.CLI.CoreValidator.require_core(), {:ok, file} <- WandFileWithHelp.load(), {:ok, dependencies} <- get_dependencies(packages), {:ok, file} <- add_dependencies(file, dependencies), :ok <- WandFileWithHelp.save(file), :ok <- download(packages), :ok <- compile(packages) do message = Enum.map(dependencies, fn %Dependency{name: name, requirement: requirement} -> "Succesfully added #{name}: #{requirement}" end) |> Enum.join("\n") {:ok, message} else {:error, :wand_file, reason} -> WandFileWithHelp.handle_error(reason) {:error, :require_core, reason} -> Wand.CLI.CoreValidator.handle_error(reason) {:error, step, reason} -> handle_error(step, reason) end end 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, error} -> {:error, :dependency, error} 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, reason} -> {:halt, {:error, :add_dependency, reason}} 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 def 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 case Wand.CLI.Mix.update_deps() do :ok -> :ok {:error, reason} -> {:error, :download_failed, reason} end end defp compile([%Package{compile: compile} | _]) when not compile, do: :ok defp compile(_) do case Wand.CLI.Mix.compile() do :ok -> :ok {:error, reason} -> {:error, :compile_failed, reason} end end defp handle_error(:dependency, {:not_found, name}) do """ # Error Package does not exist in remote repository The remote server (hex.pm unless overridden), does not contain #{name} Please check the spelling and try again. """ |> Display.error() Error.get(:package_not_found) end defp handle_error(:dependency, {reason, _name}) when reason in [:no_connection, :bad_response] do """ # Error Error getting package version from the remote repository. Talking to the remote repository (hex.pm unless overridden) failed. Please check your network connection and try again. """ |> Display.error() Error.get(:hex_api_error) end defp handle_error(:add_dependency, {:already_exists, name}) do """ # Error Dependency already exists in wand.json Attempted to add #{name} to wand.json, but that package already exists. Did you mean to type wand upgrade #{name} instead? """ |> Display.error() Error.get(:package_already_exists) end defp handle_error(:download_failed, _reason) do """ # Partial Success Unable to run mix deps.get The wand.json file was successfully updated, however mix deps.get failed. """ |> Display.error() Error.get(:install_deps_error) end defp handle_error(:compile_failed, _reason) do """ # Partial Success Unable to run mix compile The wand.json file was successfully updated, however mix compile failed. """ |> Display.error() Error.get(:install_deps_error) end end