defmodule Runbox.Utils.Traversal do @moduledoc group: :utilities @doc """ Performs a depth-first pre-order traversal on the given `value`. Transforms each subterm via the given `fun`, before descending into its descendants. ## Example iex> Traversal.prewalk( ...> [1, 2, :pi, 4, %{5 => [6, 7]}], ...> fn ...> x when is_integer(x) -> Integer.to_string(x) ...> x when is_list(x) -> Enum.reverse(x) ...> x -> x ...> end ...> ) [%{"5" => ["7", "6"]}, "4", :pi, "2", "1"] """ def prewalk(value, fun) do value |> fun.() |> prewalk_children(fun) end defp prewalk_children(list, fun) when is_list(list) do Enum.map(list, &prewalk(&1, fun)) end defp prewalk_children(tuple, fun) when is_tuple(tuple) do tuple |> Tuple.to_list() |> Enum.map(&prewalk(&1, fun)) |> List.to_tuple() end defp prewalk_children(%module{} = struct, fun) do struct |> Map.from_struct() |> Map.new(fn {key, value} -> {prewalk(key, fun), prewalk(value, fun)} end) |> Map.put(:__struct__, module) end defp prewalk_children(map, fun) when is_map(map) do Map.new(map, fn {key, value} -> {prewalk(key, fun), prewalk(value, fun)} end) end defp prewalk_children(scalar, _fun) do scalar end end