lib/mix/tasks/tablerone.download.ex

defmodule Mix.Tasks.Tablerone.Download do
  use Mix.Task

  @shortdoc "Downloads a tabler icon to the local priv directory"
  @moduledoc """
  Downloads a tabler icon to the local priv dir.

      mix tablerone.download cactus
      mix tablerone.download cactus cactus-off
  """

  @impl Mix.Task
  def run(args) do
    Mix.Project.get!()
    Mix.Task.run("app.config", args)

    config = Mix.Project.config()
    app = config[:app]
    start([app], :temporary, :serial)
    {:ok, tablerone_dir} = ensure_priv_dir!(app)

    {_opts, icon_names, _} = OptionParser.parse(args, switches: [])

    for icon_name <- icon_names do
      case __MODULE__.Downloader.get(icon_name) do
        {:ok, icon_contents} ->
          Path.join(tablerone_dir, "#{icon_name}.svg")
          |> File.write!(String.trim(icon_contents))

          Mix.shell().info("Downloaded icon: #{icon_name}.svg")

        {:error, error} ->
          Mix.raise(error_to_string("Could not download tabler icon #{icon_name}", error))
      end
    end
  end

  # # #

  defp error_to_string(msg, error),
    do: msg <> "\n\nError: " <> inspect(error)

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

  defp ensure_priv_dir!(app) do
    priv_dir = :code.priv_dir(app) |> Path.join("tablerone")

    case File.mkdir_p(priv_dir) do
      :ok -> {:ok, priv_dir}
      {:error, error} -> Mix.raise("Unable to create tablerone icon directory: #{priv_dir}, error: #{inspect(error)}")
    end
  end

  # copied from https://github.com/elixir-lang/elixir/blob/main/lib/mix/lib/mix/tasks/app.start.ex
  defp start(apps, type, mode) do
    Mix.ensure_application!(:public_key)
    Mix.ensure_application!(:ssl)
    Mix.ensure_application!(:inets)

    case Application.ensure_all_started(apps, type: type, mode: mode) do
      {:ok, _} -> :ok
      {:error, {app, reason}} -> Mix.raise(could_not_start(app, reason))
    end

    :ok
  end

  # # #

  defmodule Downloader do
    def get(name) do
      headers = [
        {~c"accept", ~c"*/*"},
        {~c"host", ~c"tabler-icons.io"},
        {~c"user-agent", String.to_charlist("erlang-httpc/OTP#{:erlang.system_info(:otp_release)} hex/tablerone")}
      ]

      case :httpc.request(:get, {icon_url(name), headers}, [], body_format: :binary) do
        {:ok, {{_, 200, _}, _resp_headers, body}} -> {:ok, to_string(body)}
        {:ok, {{_, 404, _}, _resp_headers, _body}} -> {:error, :not_found}
        {:ok, {{_, status_code, _}, _resp_headers, _body}} -> {:error, {:http_error, status_code}}
        {:error, error} -> {:error, error}
      end
    end

    defp icon_url(name),
      do: "https://tabler-icons.io/static/tabler-icons/icons/#{name}.svg"
  end
end