lib/say.ex

defmodule Say do
  @moduledoc """
  Lets the System say a given text via text-to-speech.

  ## Configuration (How to say something)

  You can configure the way how to say something...

  via an elixir function:

      config :say,
         func: &IO.inspect/1

  via shell command:

      config :say,
         exec: "say"

      config :say,
         exec: "say"
         exec_args: "-v somevoice"

  via shell command over an SSH tunnel:

      config :say,
         exec: "say",
         ssh_args: ~w(-p 2222 localhost)

      config :say,
         exec: "say",
         exec_args: "-v somevoice"
         ssh_args: ~w(-p 2222 localhost)

  ## OS specialities

    * On the Mac the executable `say` can be used directly.

    * On Linux I haven't tried, but these should work [https://linuxhint.com/command-line-text-speech-apps-linux/](https://linuxhint.com/command-line-text-speech-apps-linux/)

  """

  @doc """
  Lets the system say the given text.

  ## Examples

      iex> Say.say("hello")

  """
  def say(text) do
    do_say(text,
      Application.get_env(:say, :func),
      Application.get_env(:say, :exec),
      Application.get_env(:say, :exec_args),
      Application.get_env(:say, :ssh_args)
    )
  end

  defp do_say(text, func, exec, _exec_args, ssh_args) when func != nil and exec == nil and ssh_args == nil do
    func.(text)
  end

  defp do_say(text, func, exec, exec_args, ssh_args) when func == nil and exec != nil and exec_args != nil and ssh_args == nil do
    cmd = exec
    args = exec_args ++ [text] |> Enum.reject(&is_nil/1)
    do_system_cmd(cmd, args)
  end

  defp do_say(text, func, exec, exec_args, ssh_args) when func == nil and exec != nil and exec_args == nil and ssh_args == nil do
    cmd = exec
    args = [text]
    do_system_cmd(cmd, args)
  end

  defp do_say(text, func, exec, exec_args, ssh_args) when func == nil and exec != nil and ssh_args != nil do
    cmd = "ssh"
    exec_args = if exec_args != nil, do: exec_args, else: []
    args = ssh_args ++ [exec] ++ exec_args ++ [text] |> Enum.reject(&is_nil/1)
    do_system_cmd(cmd, args)
  end

  defp do_say(_text, _func, _exec, _exec_args, _ssh_args) do
    raise RuntimeError, message: "see config samples"
  end

  defp do_system_cmd(cmd, args) do
    if Application.get_env(:say, :test) do
      {cmd, args}
    else
      System.cmd(cmd, args)
    end
  end

end