defmodule Say do
@moduledoc """
Lets the System say a given text via text-to-speech.
## Usage
Import Say
def foo() do
if func() do
say("success")
else
say("error in foo")
end
end
## 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: ~w(-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: ~w(-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`.
`text` is expected to be a binary.
Returns a tuple containing :ok or :error, the collected result
and the command exit status.
## Examples
iex> Say.say("hello")
"""
@spec say(binary) :: {:ok | :error, Collectable.t(), exit_status :: non_neg_integer}
def say(text) when is_binary(text) do
func = Application.get_env(:say, :func)
exec = Application.get_env(:say, :exec)
exec_args = Application.get_env(:say, :exec_args)
ssh_args = Application.get_env(:say, :ssh_args)
if exec && !is_binary(exec), do: raise ArgumentError, "exec must be a binary"
if exec_args do
if !is_list(exec_args), do: raise ArgumentError, "exec_args must be a list of binaries"
unless Enum.all?(exec_args, &is_binary/1), do: raise ArgumentError, "all exec_args must be binaries"
end
if ssh_args do
if !is_list(ssh_args), do: raise ArgumentError, "ssh_args must be a list of binaries"
unless Enum.all?(ssh_args, &is_binary/1), do: raise ArgumentError, "all ssh_args must be binaries"
end
do_say(text, func, exec, exec_args, ssh_args)
end
defp do_say(text, func, exec, _exec_args, ssh_args) when func != nil and exec == nil and ssh_args == nil do
result = func.(text)
{:ok, result, 0}
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 ArgumentError, "see config samples"
end
defp do_system_cmd(cmd, args) do
if Application.get_env(:say, :test) do
{cmd, args}
else
{result, exit_code} = System.cmd(cmd, args)
case exit_code do
0 -> {:ok, result, 0}
e -> {:error, result, e}
end
end
end
end