defmodule StepFlow.Step.Helpers do
@moduledoc """
The Helper Step context.
"""
@doc """
Retrieves a value on an Objecta and filtered by the key.
"""
def get_value_in_parameters(object, key) do
StepFlow.Map.get_by_key_or_atom(object, :parameters, [])
|> Enum.filter(fn param ->
StepFlow.Map.get_by_key_or_atom(param, :id) == key
end)
|> Enum.map(fn param ->
StepFlow.Map.get_by_key_or_atom(
param,
:value,
StepFlow.Map.get_by_key_or_atom(param, :default)
)
end)
end
@doc """
Retrieves a value on an Object and filtered by the key and type.
"""
def get_value_in_parameters_with_type(object, key, type) do
StepFlow.Map.get_by_key_or_atom(object, :parameters, [])
|> Enum.filter(fn param ->
StepFlow.Map.get_by_key_or_atom(param, :id) == key &&
StepFlow.Map.get_by_key_or_atom(param, :type) == type
end)
|> Enum.map(fn param ->
StepFlow.Map.get_by_key_or_atom(
param,
:value,
StepFlow.Map.get_by_key_or_atom(param, :default)
)
end)
end
def get_string_or_processed_template_value(
workflow,
step,
dates,
source_paths,
key,
default \\ ""
) do
get_value_in_parameters_with_type(step, key, "string")
|> List.first()
|> case do
nil ->
get_value_in_parameters_with_type(step, key, "template")
|> List.first()
|> case do
nil ->
default
template ->
template
|> template_process(workflow, step, dates, source_paths)
end
strng_value ->
strng_value
end
end
def get_jobs_destination_paths(jobs) do
jobs
|> Enum.map(fn job ->
get_job_destination_paths(job)
end)
|> List.flatten()
|> Enum.uniq()
|> Enum.filter(fn path -> !is_nil(path) end)
end
def get_job_destination_paths(job) do
destination_path = get_value_in_parameters(job, "destination_path")
destination_paths = get_value_in_parameters(job, "destination_paths")
destination_path ++ destination_paths
end
@doc """
Filter a list of paths.
## Examples
iex> StepFlow.Step.Helpers.filter_path_list(["path_1.ext1", "path2.ext2"], [%{"ends_with" => ".ext2"}])
["path2.ext2"]
iex> StepFlow.Step.Helpers.filter_path_list(["path_1.ext1", "path2.ext2"], [%{ends_with: ".ext2"}])
["path2.ext2"]
"""
def filter_path_list(source_paths, []), do: source_paths
def filter_path_list(source_paths, [filter | filters]) do
new_source_paths =
case filter do
%{ends_with: ends_with} ->
Enum.filter(source_paths, fn path -> String.ends_with?(path, ends_with) end)
%{"ends_with" => ends_with} ->
Enum.filter(source_paths, fn path -> String.ends_with?(path, ends_with) end)
end
filter_path_list(new_source_paths, filters)
end
def filter_empty(source_paths) do
source_paths
|> Enum.filter(fn path -> !blank?(path) end)
end
def filter_empty_templated(defined_parameters) do
defined_parameters
|> Enum.filter(fn {_, value} -> value != "nil" and !blank?(value) end)
end
def filter_untemplated(parameters) do
parameters
|> Enum.filter(fn value -> !String.contains?(value, ["{", "}"]) end)
end
def get_step_requirements(jobs, step) do
%{paths: get_required_paths(jobs, step)}
end
def get_required_paths(jobs, step) do
# required does not refer to anything for now
required_ids = StepFlow.Map.get_by_key_or_atom(step, :required, [])
jobs
|> Enum.filter(fn job -> job.step_id in required_ids end)
|> get_jobs_destination_paths
end
def add_required_paths(requirements, paths) when is_list(paths) do
Map.update(requirements, :paths, paths, fn cur_paths ->
Enum.concat(cur_paths, paths)
|> Enum.uniq()
end)
end
def add_required_paths(requirements, path) do
paths =
Map.get(requirements, :paths, [])
|> List.insert_at(-1, path)
add_required_paths(requirements, paths)
end
def get_dates do
now = Timex.now()
%{
date_time: Timex.format!(now, "%Y_%m_%d__%H_%M_%S", :strftime),
date: Timex.format!(now, "%Y_%m_%d", :strftime),
epoch: Timex.epoch()
}
end
def get_work_directory(step) do
StepFlow.Map.get_by_key_or_atom(step, :work_dir) ||
StepFlow.Configuration.get_var_value(StepFlow, :workers_work_directory) ||
""
end
def get_base_directory(workflow, step) do
get_work_directory(step) <> "/" <> Integer.to_string(workflow.id) <> "/"
end
def templates_process(_templates, _workflow, _step, _dates, result \\ [])
def templates_process([], _workflow, _step, _dates, result), do: result
def templates_process([template | templates], workflow, step, dates, result) do
processed = intern_template_process(template, workflow, step, dates, [])
result = List.insert_at(result, -1, processed)
templates_process(templates, workflow, step, dates, result)
end
def templates_process(template, workflow, step, dates, _result) do
intern_template_process(template, workflow, step, dates, [])
end
def template_process(template, workflow, step, dates, nil) do
intern_template_process(template, workflow, step, dates, [])
end
def template_process(template, workflow, step, dates, source_path)
when is_binary(source_path) do
filename = Path.basename(source_path)
extension = Path.extname(source_path)
name = Path.basename(source_path, extension)
source_keywords =
Keyword.new()
|> Keyword.put(:source_path, source_path)
|> Keyword.put(:filename, filename)
|> Keyword.put(:extension, extension)
|> Keyword.put(:name, name)
intern_template_process(template, workflow, step, dates, source_keywords)
end
def template_process(template, workflow, step, dates, source_paths)
when is_list(source_paths) do
source_keywords =
Keyword.new()
|> Keyword.put(:source_paths, source_paths)
intern_template_process(template, workflow, step, dates, source_keywords)
end
defp intern_template_process(template, workflow, step, dates, source_keywords) do
step_parameters =
StepFlow.Map.get_by_key_or_atom(step, :parameters, [])
|> Enum.filter(fn item ->
StepFlow.Map.get_by_key_or_atom(item, :type) in [
"string",
"integer"
]
end)
|> Enum.map(fn item ->
identifier =
StepFlow.Map.get_by_key_or_atom(item, :id)
|> String.to_atom()
value =
StepFlow.Map.get_by_key_or_atom(
item,
:value,
StepFlow.Map.get_by_key_or_atom(item, :default)
)
{identifier, value}
end)
defined_parameters =
workflow.parameters
|> Enum.filter(fn item ->
StepFlow.Map.get_by_key_or_atom(item, :type) in [
"string",
"array_of_strings",
"integer",
"array_of_integers"
]
end)
|> Enum.map(fn item ->
identifier =
StepFlow.Map.get_by_key_or_atom(item, :id)
|> String.to_atom()
value =
StepFlow.Map.get_by_key_or_atom(
item,
:value,
StepFlow.Map.get_by_key_or_atom(item, :default)
)
|> convert_to_string()
{identifier, value}
end)
|> Keyword.put(:workflow_id, workflow.id)
|> Keyword.put(:workflow_reference, workflow.reference)
|> Keyword.put(:step_name, StepFlow.Map.get_by_key_or_atom(step, :name))
|> Keyword.put(:work_directory, get_work_directory(step))
|> Keyword.put(:date_time, dates.date_time)
|> Keyword.put(:date, dates.date)
|> Keyword.merge(source_keywords)
|> Keyword.merge(step_parameters)
defined_parameters
|> filter_empty_templated()
|> Keyword.keys()
|> replace(template)
|> EEx.eval_string(defined_parameters)
|> unstringify_array()
end
defp replace([], template), do: template
defp replace([key | keys], template) do
template =
String.replace(
template,
"{" <> Atom.to_string(key) <> "}",
"<%= " <> Atom.to_string(key) <> "%>"
)
replace(keys, template)
end
defp unstringify_array(template) do
# "[131e508b-d9a0-4811-be34-1e8c9ec11af6" will crash and probably not much to do
if String.starts_with?(template, "[") do
case JSON.decode(template) do
{:ok, array_template} ->
if is_list(array_template) do
array_template
else
template
end
{_, _} ->
template
end
else
template
end
end
defp convert_to_string(value) when is_bitstring(value), do: value
defp convert_to_string(value), do: "#{inspect(value)}"
# https://stackoverflow.com/questions/52482825/how-do-you-pattern-match-for-nil-or-a-blank-string-in-elixir
defp blank?(path),
do: "" == path |> to_string() |> String.trim()
end