defmodule Toolbox.Keyword do @moduledoc """ Functions closely related to `Keyword` that didn't make it or didn't yet make it to the standard library. """ @doc """ Check if the given value is a keyword list. This function is **slow** because it iterates over every element in the list to assert that it is indeed a key-value tuple. """ def is_keyword_list(val) do is_list(val) and Toolbox.Enum.andmap(val, fn(el) -> is_tuple(el) and tuple_size(el) == 2 end) end end defmodule Toolbox.Enum do @moduledoc """ Functions closely related to `Enum` that didn't make it or didn't yet make it to the standard library. """ @doc """ Performs a classical ormap on the given enumerable. """ def andmap(enum, callback) do cond do length(enum) == 0 -> true not callback.(Enum.first(enum)) -> false true -> andmap(Enum.rest(enum),callback) end end @doc """ Performs a classical ormap on the given enumerable. """ def ormap(enum, callback) do cond do length(enum) == 0 -> false callback.(Enum.first(enum)) -> true true -> ormap(Enum.rest(enum),callback) end end @doc """ Transforms the keys of the map-like structure according to the given procedure in the second argument. """ def map_keys(maplike, callback) do Enum.map(maplike, fn({key,value}) -> {callback.(key),value} end) end @doc """ Transforms the values of the map-like structure according to the given procedure in the second argument. """ def map_values(maplike, callback) do Enum.map(maplike, fn({key,value}) -> {key,callback.(value)} end) end @doc """ Iterates over all own keys and nested keys and transforms them according to the given procedure. """ def map_keys_deep(maplike, callback) do Enum.map(maplike, fn({key,val}) -> if is_map(val) do {callback.(key), val |> map_keys_deep(callback)} else {callback.(key), val} end end) end @doc """ Iterates over all own values and nested values and transforms them according to the given procedure. """ def map_values_deep(maplike, callback) do Enum.map(maplike, fn({key,val}) -> if is_map(val) do {key, val |> map_values_deep(callback)} else {key,callback.(val)} end end) end @doc """ Deeply merges two maplike structures into one. As a matter of convenience, the second argument may be nil, which will cause the function to return the first argument unmodified. """ def merge_deep(a, b) do if is_nil(b) do a else Enum.reduce(b, a, fn({key,val},maplike) -> cond do is_map(val) -> {key,merge_deep(val,a[key])} Toolbox.Keyword.is_keyword_list(val) -> {key,merge_deep(val,a[key])} end end) end end end