lib/webp.ex

defmodule Webp do
  @moduledoc """
  Webp is a installer and runner for [Webp].

  ## Profiles

  You can define multiple configuration profiles. By default, there is a
  profile called `:default` which you can configure its args, current
  directory and environment:

      config :webp,
        default: [
          location: Path.expand("../priv/static/images/", __DIR__),
          destination: Path.expand("../priv/static/images/", __DIR__)
        ]

  ## Webp configuration

  There are two global configurations for the `webp` application:

    * `:path` - the path to the webp executable.

  Once you find the location of the executable, you can store it in a
  `MIX_WEBP_PATH` environment variable, which you can then read in
  your configuration file:

      config :webp, path: System.get_env("MIX_WEBP_PATH")

  """

  use Application
  require Logger

  @doc false
  def start(_, _) do
    Supervisor.start_link([], strategy: :one_for_one)
  end

  @doc """
  Returns the configuration for the given profile.

  Returns nil if the profile does not exist.
  """
  def config_for!(profile) when is_atom(profile) do
    Application.get_env(:webp, profile) ||
      raise ArgumentError, """
      unknown webp profile. Make sure the profile named #{inspect(profile)} is defined in your config files, such as:

          config :webp,
            #{profile}: [
              location: Path.expand("../assets", __DIR__),
              destination: Path.expand("../priv/static/images/", __DIR__)
            ]
      """
  end

  @doc false
  def script_path() do
    Path.join(:code.priv_dir(:webp), "webp.bash")
  end

  defp cmd(path, args) do
    cmd(path, args, [])
  end

  defp cmd([command | args], extra_args, opts) do
    System.cmd(command, args ++ extra_args, opts)
  end

  @doc """
  Runs the given command with `args`.

  The given args will be appended to the configured args.
  The task output will be streamed directly to stdio. It
  returns the status of the underlying call.
  """
  def run(profile, extra_args) when is_atom(profile) and is_list(extra_args) do
    config = config_for!(profile)
    location = config[:location] || []
    destination = config[:destination] || nil
    quality = config[:quality] || 80

    if location == [] and extra_args == [] do
      raise "no arguments passed to webp"
    end


    opts = [
      cd: config[:location] || File.cwd!(),
      env: config[:env] || %{},
      into: IO.stream(:stdio, :line),
      stderr_to_stdout: true
    ]

    args = [location] ++ extra_args

    files = File.glob(location)

    for source <- files do
      path = Application.get_env(:webp, :path, "/usr/bin/cwebp")

      extname = Path.extname(source)

      destination = destination || Path.basename(source, extname)

      filename = Path.basename(source, extname)

      params = "-q #{quality}} #{source} -o #{destination}/#{filename}.webp"

      command = path <> params

      path =
        if "--watch" in args do
          [script_path() | command]
        else
          command
        end

      path
      |> cmd(args, opts)
      |> elem(1)
    end
  end

  @doc """
  Returns the path to the executable.

  The executable may not be available if it was not yet installed.
  """
  def bin_path do
    Application.get_env(:cwebp, :path)
  end

  defp path_exists?(path) do
    Enum.all?(path, &File.exists?/1)
  end

  @doc """
  Convert Source to Destination `args`.

  """
  def convert(source, params \\ []) do
    opts = [
      cd: params[:location] || File.cwd!(),
      env: params[:env] || %{},
      into: IO.stream(:stdio, :line),
      stderr_to_stdout: true
    ]

    quality = params[:quality] || 80

    path = Application.get_env(:webp, :path, "/usr/bin/cwebp")

    extname = Path.extname(source)

    filename = Path.basename(source, extname)

    destination = params[:destination] || Path.basename(source, extname)

    params = ["-q #{quality}}", "#{source} -o #{destination}/#{filename}.webp"]

    path
    |> cmd(params, opts)
    |> elem(1)
  end

  @doc """
  Convert Source to Destination if cwebp exists.

  """
  def install_and_run(profile, args) do
    unless File.exists?(bin_path()) do
      raise "#{} Not Found please set path to the cwebp location"
    end

    run(profile, args)
  end
end