defmodule Scenic.Utilities.Map do @moduledoc false # @default_uid_length 4 # ============================================================================ @doc """ Convenience wrapper around Kernel.pop_in that allows for use in a simple pipeline. See Kernel.pop_in Returns the map directly """ def delete_in(data, keys) def delete_in(data, keys) do # do_delete_in(map, path) {_, data} = pop_in(data, keys) data end # ============================================================================ # ============================================================================ # Generate a unique uid for an element. Currently uses uuid4, which in # turn uses psuedo-random bytes. Odds of a collision are very low # def generate_uid( map, uid_length \\ @default_uid_length ) # def generate_uid( map = %{}, uid_length ) when is_integer(uid_length) do # uid = Scenic.Utilities.rand_string( uid_length ) # case Map.get(map, uid) do # nil -> uid # unique # _ -> generate_uid(map, uid_length + 1) # collision # end # end # ============================================================================ # similar to List.meyers_difference, the below code compares two maps # and generates a list of actions that can be applied to the first map # to transform it into the second map. # if recurse_nested is true, the result is a flat list that includes differences into # any nested maps in the map # -------------------------------------------------------- def difference(map_1, map_2, recurse_nested \\ true) def difference(map_1, map_2, _) when map_1 == map_2, do: [] def difference(nil, map_2, recurse_nested), do: difference(%{}, map_2, recurse_nested) def difference(map_1, map_2, recurse) when is_map(map_1) and is_map(map_2) do # remove any keys from map_1 that are simply not present (at all) in map_2 diff_list = Enum.reduce(map_1, [], fn {k, _}, d -> case Map.has_key?(map_2, k) do false -> [{:del, k} | d] true -> d end end) # add any puts for keys that have changed between map_2 to map_1 Enum.reduce(map_2, diff_list, fn {k, v}, d -> case Map.has_key?(map_1, k) do true -> v1 = Map.get(map_1, k) # credo:disable-for-next-line Credo.Check.Refactor.Nesting case v1 == v do true -> d false -> add_difference(d, k, v1, v, recurse) end false -> add_difference(d, k, nil, v, recurse) end end) |> List.flatten() end # ---------------------------------------------- defp add_difference(diff_list, k1, v1, v2, recurse) defp add_difference(diff_list, k1, v1, v2, true) when not is_map(v1) do add_difference(diff_list, k1, %{}, v2, true) end defp add_difference(diff_list, k1, v1, v2, true) when is_map(v2) do diff = difference(v1, v2, true) |> Enum.map(fn delta -> case delta do {:put, k, v} -> {:put, {k1, k}, v} {:del, k} -> {:del, {k1, k}} end end) [diff | diff_list] end defp add_difference(diff_list, k1, _, v2, _) do [{:put, k1, v2} | diff_list] end # -------------------------------------------------------- # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity def apply_difference(map, diff_list, delete_empty \\ false) when is_map(map) and is_list(diff_list) do Enum.reduce(diff_list, map, fn diff, acc -> case diff do {:put, {k0, k1}, v} -> map = Map.get(acc, k0, %{}) if is_map(map) do map else %{} end |> apply_difference([{:put, k1, v}], delete_empty) |> (&Map.put(acc, k0, &1)).() {:put, k, v} -> Map.put(acc, k, v) {:del, {k0, k1}} -> map = Map.get(acc, k0, %{}) |> apply_difference([{:del, k1}], delete_empty) # credo:disable-for-next-line Credo.Check.Refactor.Nesting case delete_empty && map == %{} do true -> Map.delete(acc, k0) false -> Map.put(acc, k0, map) end {:del, k} -> Map.delete(acc, k) end end) end # -------------------------------------------------------- def merge_difference(diff_old, diff_new) when is_list(diff_old) and is_list(diff_new) do # enumerate through the new items, applying them to the old one at a time Enum.reduce(diff_new, diff_old, fn diff, acc -> case diff do {:del, key} -> acc = delete_diff_by_key(acc, key) [{:del, key} | acc] {:put, key, value} -> acc = delete_diff_by_key(acc, key) [{:put, key, value} | acc] end end) end defp delete_diff_by_key(diff_list, key) do Enum.reject(diff_list, fn diff -> case diff do {:del, diff_key} -> diff_key == key {:put, diff_key, _} -> diff_key == key end end) end # -------------------------------------------------------- def put_set(map, _, nil), do: map def put_set(map, key, value), do: Map.put(map, key, value) end