74 lines
2.2 KiB
Elixir
74 lines
2.2 KiB
Elixir
defmodule Frenzy.Keypath do
|
|
@moduledoc """
|
|
Utilities for accessing or updating values using keypaths.
|
|
A keypath is a list of map keys or list indices representing the path through nested maps/lists.
|
|
"""
|
|
|
|
@type container() :: map() | list()
|
|
@type t() :: [Map.key() | non_neg_integer()]
|
|
|
|
@doc """
|
|
Gets the value in the given container at the given keypath. Raises on index out of bounds/missing map key.
|
|
|
|
## Examples
|
|
|
|
iex> Frenzy.Keypath.get(%{foo: %{bar: "baz"}}, [:foo, :bar])
|
|
"baz"
|
|
|
|
iex> Frenzy.Keypath.get([%{"foo" => "bar"}, %{"foo" => "baz"}], [1, "foo"])
|
|
"baz"
|
|
"""
|
|
@spec get(container(), t()) :: any()
|
|
|
|
def get(value, []), do: value
|
|
|
|
def get(map, [key | rest]) when is_map(map) do
|
|
get(Map.fetch!(map, key), rest)
|
|
end
|
|
|
|
def get(list, [index | rest]) when is_list(list) and is_integer(index) and index >= 0 do
|
|
if index >= length(list) do
|
|
raise KeyError, "Index #{index} out of bounds (>= #{length(list)})"
|
|
else
|
|
get(Enum.at(list, index), rest)
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Sets the vlaue in the given container at the given keypath. Raises on index out of bounds/missing map key.
|
|
|
|
## Examples
|
|
|
|
iex> Frenzy.Keypath.set(%{foo: %{bar: "baz"}}, [:foo, :bar], "blah")
|
|
%{foo: %{bar: "blah"}}
|
|
|
|
iex> Frenzy.Keypath.set([%{"foo" => "bar"}, %{"list" => ["a", "b"]}], [1, "list", 0], "c")
|
|
[%{"foo" => "bar"}, %{"list" => ["c", "b"]}]
|
|
"""
|
|
@spec set(container(), t(), any()) :: map()
|
|
|
|
def set(map, [key], value) when is_map(map) do
|
|
Map.put(map, key, value)
|
|
end
|
|
|
|
def set(list, [index], value) when is_list(list) and is_integer(index) and index >= 0 do
|
|
if index >= length(list) do
|
|
raise KeyError, "Index #{index} out of bounds (>= #{length(list)})"
|
|
else
|
|
List.replace_at(list, index, value)
|
|
end
|
|
end
|
|
|
|
def set(map, [key | rest], value) when is_map(map) do
|
|
Map.put(map, key, set(Map.fetch!(map, key), rest, value))
|
|
end
|
|
|
|
def set(list, [index | rest], value) when is_list(list) and is_integer(index) and index >= 0 do
|
|
if index >= length(list) do
|
|
raise KeyError, "Index #{index} out of bounds (>= #{length(list)})"
|
|
else
|
|
List.replace_at(list, index, set(Enum.at(list, index), rest, value))
|
|
end
|
|
end
|
|
end
|