lib/nested.ex

# Generated by erl2ex (http://github.com/dazuma/erl2ex)
# From Erlang source: /home/dgulino/Documents/workspace/nested/src/nested.erl
# At: 2022-06-25 17:24:15

defmodule Nested do
  @moduledoc """
  Library to work with nested maps
  Elixir reimplementation of Erlang library [nested](https://github.com/odo/nested)
  """

  @doc ~S"""
  Appends value to List at key path.

    iex> Nested.append(%{"test" => "rest", "rest" => [1]},["rest"],2)
    %{"rest" => [1, 2], "test" => "rest"}
  """
  @spec append(map :: map(), path :: [any()], value :: any()) :: map()
  def append(map, path, value) do
    appendFun = fn
      list when is_list(list) ->
        list ++ [value]
      _ ->
        :erlang.error(:no_list)
    end
    update!(map, path, appendFun)
  end

  @doc ~S"""
  Appends value to List at key path.
  If key not found at path, add key and append value to default List provided
  Similar to Python's defaultdict

    iex> Nested.append(%{"test" => "rest", "rest" => [1]},["xest"],2,[])
    %{"rest" => [1], "test" => "rest", "xest" => [2]}

    iex> Nested.append(%{"test" => "rest", "rest" => [1]},["rest"],2,[])
    %{"rest" => [1, 2], "test" => "rest"}
  """
  @spec append(map(), [any()], any(), list()) :: map()
  def append(map, path, value, default) do
    appendFun = fn
      list when is_list(list) ->
        list ++ [value]
      _ ->
        default ++ [value]
    end
    case has_key?(map,path) do
      true ->
        update!(map, path, appendFun)
      false ->
        put(map, path, default ++ [value])
    end
  end
  
  @spec delete(map :: map(), list()) :: map()
  def delete(map, []) do
    map
  end

  @spec delete(map :: map(), list()) :: map()
  def delete(map, [lastKey]) do
    Map.delete(map,lastKey)
  end

  @spec delete(map :: map(), list()) :: map()
  def delete(map, [key | pathRest]) do
    case(Map.has_key?(map,key)) do
      true ->
        Map.put(map, key, delete(Map.get(map, key), pathRest))
      false ->
        map
    end
  end

  @spec fetch!(map :: map(), [any()]) :: {:ok, map()}
  def fetch!(map, path) do
    case get(map, path) do
      :nil ->
        raise KeyError, "key path #{inspect(path)} not found"
      value ->
        {:ok, value}
    end
  end

  @spec fetch(map :: map(), [any()]) :: {:ok, map()}
  def fetch(map, path) do
    case get(map, path) do
      :nil ->
        :error
      value ->
        {:ok, value}
    end
  end
  
  @doc ~S"""

  ## Examples

      iex> Nested.get(%{test: :rest}, [:test] )
      :rest

      iex> %{test: :rest} |> Nested.get([:test])
      :rest

      iex> Nested.get(%{}, [:a])
      nil 

      iex> Nested.get(%{a: %{b: 1}}, [:a,:b])
      1
      
      iex> Nested.get(%{a: %{b: 1}}, [:a,:c])
      nil
      
      iex> Nested.get(%{a: %{b: 1}}, [:c], 3)
      3
  """
  
  @spec get(map :: map(), [any()]) :: any()
  def get(map,[key | pathRest]) do
    get(Map.get(map, key), pathRest)
  end

  @spec get(map :: map(), []) :: any()
  def get(value, []) do
    value
  end

  @spec get(map :: map(), [any()], any()) :: any()
  def get(map, [key | pathRest], default) do
    case(Map.get(map,key,{__MODULE__, default})) do
      {__MODULE__, ^default} ->
        default
      nestedMap ->
        get(nestedMap, pathRest, default)
    end
  end

  @spec get(map :: map(), [], any()) :: any()
  def get(value, [], _) do
    value
  end

  @spec has_key?(map :: map(), list()) :: boolean()
  def has_key?(map, [key]) do
    Map.has_key?(map, key)
  end

  @spec has_key?(map :: map(), list()) :: boolean()
  def has_key?(map,[key | pathRest]) do
    case(map) do
      %{^key => subMap} ->
        has_key?(subMap, pathRest)
      _ ->
        false
    end
  end

  @spec keys(map :: map(), [any()]) :: [any()] 
  def keys(map, [key | pathRest]) do
    keys(Map.get(map,key), pathRest)
  end

  @spec keys(map :: map(), []) :: [any()]
  def keys(map, []) do
    Map.keys(map)
  end

  @spec put(map :: map(), [any()], any()) :: map()
  def put(map, [key | pathRest], value) do
    subMap = case(Map.has_key?(map, key) and is_map(Map.get(map,key))) do
      true ->
        Map.get(map, key)
      false ->
        %{}
    end
    Map.put(map, key, put(subMap, pathRest, value) )
  end

  @spec put(map :: map(), [], any()) :: map()
  def put(_, [], value) do
    value
  end

  @spec update!(map(), [any()], any()) :: map()
  def update!(map, path, valueOrFun) do
    try do
     updatef_internal!(map, path, valueOrFun)
    catch
      :error, {:error, {:no_map, pathRest, element}} ->
        pathLength = length(path) - length(pathRest)
        pathToThrow = :lists.sublist(path, pathLength)
        :erlang.error({:no_map, pathToThrow, element})
    end
  end

  @spec updatef_internal!(map :: map(), [any()], function() | term()) :: map()
  defp updatef_internal!(map, [key | pathRest], valueOrFun) when is_map(map) do
    Map.put(map,key, updatef_internal!(Map.fetch!(map,key), pathRest, valueOrFun))
  end

  @spec updatef_internal!(map :: map(), [], function() ) :: map()
  defp updatef_internal!(oldValue, [], fun) when is_function(fun) do
    fun.(oldValue)
  end

  @spec updatef_internal!(any(), [], any() ) :: map()
  defp updatef_internal!(_, [], value) do
    value
  end

  @spec updatef_internal!(any(), [any()], any() ) :: map()
  defp updatef_internal!(element, path, _) do
    :erlang.error({:error, {:no_map, path, element}})
  end
  
  @spec update(map :: map(), path :: [any()], default :: any(), any() ) :: map()
  def update(map, path, default, valueOrFun) do
    try do
      updatef_internal(map, path, default, valueOrFun)
    catch
      :error, {:error, {:no_map, pathRest, element}} ->
        pathLength = length(path) - length(pathRest)
        pathToThrow = :lists.sublist(path, pathLength)
        :erlang.error({:no_map, pathToThrow, element})
    end
  end

  @spec updatef_internal(map :: map(), path :: [any()], default :: any(), any() ) :: map()
  defp updatef_internal(map, [key | pathRest], default, valueOrFun) when is_map(map) do
    Map.put(map,key, updatef_internal(Map.get(map,key,default), pathRest, valueOrFun))
  end

  @spec updatef_internal(map :: map(), path :: [], default :: any(), function() ) :: map()
  defp updatef_internal(oldValue, [], fun) when is_function(fun) do
    fun.(oldValue)
  end

  @spec updatef_internal(any(), [], any() ) :: map()
  defp updatef_internal(_, [], value) do
    value
  end

  @spec updatef_internal(any(), [any()], any() ) :: map()
  defp updatef_internal(element, path, _) do
    :erlang.error({:error, {:no_map, path, element}})
  end

end