defmodule Populator.Helpers do @moduledoc """ require Populator.Helpers, as: H # the cool way """ @doc """ Convenience to get environment bits. Avoid all that repetitive `Application.get_env( :myapp, :blah, :blah)` noise. """ def env(key, default \\ nil), do: env(Mix.Project.get!.project[:app], key, default) def env(app, key, default), do: Application.get_env(app, key, default) @doc """ Spit to stdout any passed variable, with location information. """ defmacro spit(obj \\ "", inspect_opts \\ []) do quote do %{file: file, line: line} = __ENV__ name = Process.info(self)[:registered_name] chain = [ :bright, :red, "\n\n#{file}:#{line}", :normal, "\n #{inspect self}", :green," #{name}"] msg = inspect(unquote(obj),unquote(inspect_opts)) if String.length(msg) > 2, do: chain = chain ++ [:red, "\n\n#{msg}"] # chain = chain ++ [:yellow, "\n\n#{inspect Process.info(self)}"] (chain ++ ["\n\n", :reset]) |> IO.ANSI.format(true) |> IO.puts unquote(obj) end end @doc """ Print to stdout a _TODO_ message, with location information. """ defmacro todo(msg \\ "") do quote do %{file: file, line: line} = __ENV__ [ :yellow, "\nTODO: #{file}:#{line} #{unquote(msg)}\n", :reset] |> IO.ANSI.format(true) |> IO.puts :todo end end @doc """ Wait for given function to return true. Optional `msecs` and `step`. Be aware that exceptions raised and thrown messages by given `func` will be discarded. Optional `:timeout` giving the return value for a timeout event. Special `:raise` value (default) will raise en exception. """ def wait_for(opts \\ [], func) do opts = [msecs: 5_000, step: 100, timeout: :raise] |> Keyword.merge(opts) res = try do func.() rescue _ -> nil catch :exit, _ -> nil end if res do :ok else if opts[:msecs] <= 0 do case opts[:timeout] do :raise -> raise "Timeout!" x -> x end else # sleep and loop :timer.sleep opts[:step] wait_for Keyword.merge(opts, [msecs: opts[:msecs] - opts[:step]]), func end end end @doc """ Gets names for children in given supervisor. Children with no registered name are not returned. List is sorted. Options al passed to `children_data/2`. """ def children_names(supervisor) do supervisor |> get_linked_ids |> Enum.map( &( get_name(&1) ) ) |> Enum.filter( &(&1) ) # remove nils |> Enum.sort end @doc """ Get linked pids list, useful to guess supervisor children without blocking it. """ def get_linked_ids(name) do res = name |> Process.whereis |> Process.info |> Keyword.get(:links) res end defp get_name(pid) do (Process.info(pid) || [])|> Keyword.get(:registered_name, nil) end @doc """ Kill all of given supervisor's children """ def kill_children(supervisor) do children = supervisor |> Supervisor.which_children for {_,pid,_,_} <- children, do: true = Process.exit(pid, :kill) :ok end @doc """ Start a children with given spec in the given supervisor if not already started. Returns `{:ok, child}` if the child is successfully started or it was already started. `{:error, reason}` otherwise. """ def start_child(spec, supervisor) do res = Supervisor.start_child supervisor, spec case child_is_started_ok(res) do # Something went wrong so return the gotten result false -> res # The spec is there but the process isn't so restart it :already_present -> child_id = elem(spec, 0) Supervisor.restart_child(supervisor, child_id) # There's a running process so it's returned child -> {:ok, child} end end defp child_is_started_ok({:ok,child}), do: child defp child_is_started_ok({:ok,child,_}), do: child defp child_is_started_ok({:error, {:already_started, child}}), do: child defp child_is_started_ok({:error, :already_present}), do: :already_present defp child_is_started_ok(_), do: false @doc """ Raise an error if any given key is not in the given Keyword. Returns given Keyword, so it can be chained using pipes. """ def requires(args, required) do keys = args |> Keyword.keys for r <- required do if not r in keys do raise ArgumentError, message: "Required argument '#{r}' was not present in #{inspect(args)}" end end args # chainable end @doc """ Apply given defaults to given Keyword. Returns merged Keyword. """ def defaults(args, defs) do defs |> Keyword.merge(args) end end