Packages
altworx_runbox
2.0.0
25.0.0
24.0.0
23.1.0
23.0.0
22.2.0
22.1.0
22.0.0
21.2.0
21.1.2
21.1.1
21.1.0
21.0.0
20.0.0
19.0.0
18.0.0
17.2.0
17.1.0
17.0.1
17.0.0
16.2.0
16.1.0
16.0.0
15.0.0
14.1.0
14.0.1
14.0.0
13.0.3
13.0.2
13.0.1
13.0.0
12.1.0
12.0.0
11.0.1
11.0.0
10.0.0
9.0.0
8.0.0
7.0.1
7.0.0
6.0.0
5.0.0
4.0.0
3.0.0
2.1.0
2.0.0
1.4.1
1.4.0
1.3.0
1.2.0
1.1.0
1.0.0
0.1.3
0.1.2
0.1.1
0.1.0
Runbox is a library for running Altworx scenarios.
Current section
Files
Jump to
Current section
Files
lib/runbox/scenario/notification.ex
defmodule Runbox.Scenario.Notification do
@moduledoc """
Module contains functions for working with scenario notifications.
"""
@spec_path Path.join(["spec.exs"])
@doc """
Tries to load the notification spec of the given scenario.
Returns
* `{:ok, notification_spec}` if the spec is found
* `{:error, :cannot_load, exception}` if the spec file cannot be found
* `{:error, :bad_syntax, exception}` if the spec file was found but contains syntactic errors
"""
@spec load_spec(String.t()) :: {:ok, any} | {:error, atom(), any}
def load_spec(scenario_id) do
spec_path = Path.join(scenario_notifications_path(scenario_id), @spec_path)
try do
{specs, _} = Code.eval_file(spec_path)
{:ok, specs}
rescue
e in Code.LoadError -> {:error, :cannot_load, e}
e in SyntaxError -> {:error, :bad_syntax, e}
end
end
@doc """
Lists all notification types for a scenario.
"""
@spec list_notification_types(String.t()) :: {:ok, [String.t()]} | {:error, File.posix()}
def list_notification_types(scenario_id) do
scenario_id
|> template_path()
|> ls()
end
@doc """
Lists all template types of the specified notification.
"""
@spec list_template_types(String.t(), String.t()) :: {:ok, [String.t()]} | {:error, File.posix()}
def list_template_types(scenario_id, notification_type) do
[template_path(scenario_id), to_string(notification_type)]
|> Path.join()
|> ls()
end
@doc """
Lists all channels with templates defined for scenario, notification type and template type.
"""
@spec list_channels(String.t(), String.t(), String.t()) ::
{:ok, [String.t()]} | {:error, File.posix()}
def list_channels(scenario_id, notification_type, template_type) do
[template_path(scenario_id), to_string(notification_type), to_string(template_type)]
|> Path.join()
|> ls()
end
@doc """
Lists all template languages defined for senario, notification type, template type and channel.
"""
@spec list_languages(String.t(), String.t(), String.t(), String.t()) ::
{:ok, [String.t()]} | {:error, File.posix()}
def list_languages(scenario_id, notification_type, template_type, channel) do
[
template_path(scenario_id),
to_string(notification_type),
to_string(template_type),
to_string(channel)
]
|> Path.join()
|> ls()
end
@doc """
Loads and evaluates templates specified by scenario, notification type, template type, channel
and language.
If language is not defined or does not exist, `fallback_language` is used.
"""
@spec load_and_eval_templates(
scenario_id :: String.t(),
notification_type :: String.t(),
template_type :: String.t(),
channel :: String.t(),
language :: String.t(),
data :: keyword(),
fallback_language :: String.t()
) :: {:ok, %{String.t() => String.t()}} | {:error, atom(), String.t()}
def load_and_eval_templates(
scenario,
notification_type,
template_type,
channel,
language,
data,
fallback_language
) do
with {:ok, templates} <-
load_templates(
scenario,
notification_type,
template_type,
channel,
language,
fallback_language
) do
{:ok, Enum.into(templates, %{}, fn {name, fun} -> {name, fun.(data)} end)}
end
end
defp load_templates(
scenario,
notification_type,
template_type,
channel,
language,
fallback_language
) do
folder =
Path.join([
template_path(scenario),
to_string(notification_type),
to_string(template_type),
to_string(channel),
to_string(language)
])
fallback_folder =
if fallback_language do
Path.join([
template_path(scenario),
to_string(notification_type),
to_string(template_type),
to_string(channel),
to_string(fallback_language)
])
end
case compile_templates(folder) do
{:ok, _template_map} = res ->
res
{:error, _, _} = error ->
if fallback_folder do
compile_templates(fallback_folder)
else
error
end
end
end
# finds all template files in the `folder` and compiles them.
# if any template failes then the whole compile fails, also fails if no templates are found
defp compile_templates(folder) do
{templates, errors} =
folder
|> ls_if_exists()
|> Enum.filter(&String.ends_with?(&1, ".eex"))
|> Enum.map(fn file ->
{template_name(file), compile_template(Path.join([folder, file]))}
end)
|> Enum.split_with(&match?({_, {:ok, _}}, &1))
case {templates, errors} do
{[], []} -> {:error, :no_templates_found, folder}
{_, []} -> {:ok, Enum.into(templates, %{}, fn {name, {:ok, fun}} -> {name, fun} end)}
{_, [{_name, error} | _rest]} -> error
end
end
defp compile_template(file) do
compiled = EEx.compile_file(file)
{:ok, fn bindings -> elem(Code.eval_quoted(compiled, bindings, file: file), 0) end}
rescue
_ in File.Error -> {:error, :not_found, file}
_ in EEx.SyntaxError -> {:error, :bad_syntax, file}
end
defp template_path(scenario_id) do
Path.join(scenario_notifications_path(scenario_id), "templates")
end
defp scenario_notifications_path(scenario_id) do
scenario_app = Application.fetch_env!(:runbox, :scenario_app)
Path.join([:code.priv_dir(scenario_app), "notifications", to_string(scenario_id)])
end
# lists all files and dirs in a path
defp ls(path) do
if File.dir?(path) do
File.ls(path)
else
{:ok, []}
end
end
defp ls_if_exists(path) do
case File.ls(path) do
{:ok, files} -> files
_ -> []
end
end
# extract template name from filename (i.e. "content.eex")
defp template_name(file) do
String.slice(file, 0..-5)
end
end