parent
5f81d8dfe4
commit
046c4d47a9
@ -0,0 +1,73 @@
|
||||
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
|
@ -0,0 +1,4 @@
|
||||
defmodule Frenzy.KeypathTest do
|
||||
use ExUnit.Case
|
||||
doctest Frenzy.Keypath
|
||||
end
|
Loading…
Reference in new issue