Packages
igniter
0.6.14
0.8.2
0.8.1
0.8.0
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.30
0.6.29
0.6.28
0.6.27
0.6.26
0.6.25
0.6.24
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.52
0.5.51
0.5.50
0.5.49
0.5.48
0.5.47
0.5.46
0.5.45
0.5.44
0.5.43
0.5.42
0.5.41
0.5.40
0.5.39
0.5.38
0.5.37
0.5.36
0.5.35
0.5.34
0.5.33
0.5.32
0.5.31
0.5.30
0.5.29
0.5.28
0.5.27
0.5.26
0.5.25
0.5.24
0.5.23
0.5.22
0.5.21
0.5.20
0.5.19
0.5.18
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.78
0.3.77
0.3.76
0.3.75
0.3.74
0.3.73
0.3.72
0.3.71
0.3.70
0.3.69
0.3.68
0.3.67
0.3.66
0.3.65
0.3.64
0.3.63
0.3.62
0.3.61
0.3.60
0.3.59
0.3.58
0.3.57
0.3.56
0.3.55
0.3.54
0.3.53
0.3.52
0.3.51
0.3.50
0.3.49
0.3.48
0.3.47
0.3.46
0.3.45
0.3.44
0.3.43
0.3.42
0.3.41
0.3.40
0.3.39
0.3.38
0.3.37
0.3.36
0.3.35
0.3.34
0.3.33
0.3.32
0.3.31
0.3.30
0.3.29
0.3.28
0.3.27
0.3.26
0.3.25
0.3.24
0.3.23
0.3.22
0.3.21
0.3.20
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
A code generation and project patching framework
Current section
Files
Jump to
Current section
Files
lib/igniter/code/keyword.ex
defmodule Igniter.Code.Keyword do
@moduledoc """
Utilities for working with keyword.
"""
require Igniter.Code.Common
alias Igniter.Code.Common
alias Sourceror.Zipper
@doc "Returns true if the node is a nested keyword list containing a value at the given path."
@spec keyword_has_path?(Zipper.t(), [atom()]) :: boolean()
def keyword_has_path?(_zipper, []), do: true
def keyword_has_path?(zipper, [key | rest]) do
case get_key(zipper, key) do
{:ok, zipper} -> keyword_has_path?(zipper, rest)
:error -> false
end
end
@doc "Moves the zipper to the value of `key` in a keyword list."
@spec get_key(Zipper.t(), atom()) :: {:ok, Zipper.t()} | :error
def get_key(zipper, key) do
zipper = Common.maybe_move_to_single_child_block(zipper)
if Igniter.Code.List.list?(zipper) do
item =
Igniter.Code.List.move_to_list_item(zipper, fn item ->
if Igniter.Code.Tuple.tuple?(item) do
case Igniter.Code.Tuple.tuple_elem(item, 0) do
{:ok, first_elem} ->
Common.nodes_equal?(first_elem, key)
:error ->
false
end
end
end)
case item do
{:ok, zipper} -> Igniter.Code.Tuple.tuple_elem(zipper, 1)
:error -> :error
end
else
:error
end
end
@doc """
Puts a value at a path into a keyword list, calling `updater` on the zipper at the value if the key is already present.
Navigates through nested keyword lists following the given `path` (list of atoms).
If the full path doesn't exist, it creates the necessary nested structure.
If the path exists, it calls the `updater` function with the zipper at the existing value.
The `updater` function can return:
- `{:ok, zipper}` - Replace the existing value with the updated zipper
- anything else, which is returned untouched
This function preserves any errors or warnings returned by the updater function,
passing them through unchanged.
"""
@spec put_in_keyword(
Zipper.t(),
list(atom()),
term(),
(Zipper.t() ->
{:ok, Zipper.t()} | :error | {:error, String.t()} | {:warning, String.t()})
| nil
) ::
{:ok, Zipper.t()} | :error | {:error, String.t()} | {:warning, String.t()}
def put_in_keyword(zipper, path, value, updater \\ nil) do
updater = updater || fn zipper -> {:ok, Common.replace_code(zipper, value)} end
Common.within(zipper, fn zipper ->
do_put_in_keyword(zipper, path, value, updater)
end)
end
defp do_put_in_keyword(zipper, [key], value, updater) do
set_keyword_key(zipper, key, value, updater)
end
defp do_put_in_keyword(zipper, [key | rest], value, updater) do
case create_or_move_to_value_for_key(zipper, key) do
{:found, zipper} ->
do_put_in_keyword(zipper, rest, value, updater)
{:new, zipper} ->
{:ok, set_keyword_value!(zipper, keywordify(rest, value))}
other ->
other
end
end
@doc """
Sets a key in a keyword list to a value.
If the key already exists, calls the `updater` function with the zipper at the existing value.
If the key doesn't exist, sets it to the given `value`.
The `updater` function can return:
- `{:ok, zipper}` - Replace the existing value with the updated zipper
- anything else - which is returned untouched
This function preserves any errors or warnings returned by the updater function,
passing them through unchanged.
"""
@spec set_keyword_key(
Zipper.t(),
atom(),
term(),
(Zipper.t() -> {:ok, Zipper.t()} | term)
| nil
) ::
{:ok, Zipper.t()} | term
def set_keyword_key(zipper, key, value, updater \\ nil) do
updater = updater || (&{:ok, &1})
Common.within(zipper, fn zipper ->
case create_or_move_to_value_for_key(zipper, key) do
{:found, zipper} ->
case updater.(zipper) do
{:ok, zipper} ->
{:ok, %{zipper | node: {:__block__, [], [zipper.node]}}}
other ->
other
end
{:new, zipper} ->
{:ok, set_keyword_value!(zipper, value)}
other ->
other
end
end)
end
defp set_keyword_value!(zipper, value) do
value =
value
|> Sourceror.to_string()
|> Sourceror.parse_string!()
|> Common.use_aliases(zipper)
Zipper.replace(zipper, value)
end
@spec create_or_move_to_value_for_key(Zipper.t(), atom()) ::
{:found, Zipper.t()} | {:new, Zipper.t()} | :error
defp create_or_move_to_value_for_key(zipper, key) do
zipper = Common.maybe_move_to_single_child_block(zipper)
if Igniter.Code.List.list?(zipper) do
case get_key(zipper, key) do
{:ok, zipper} ->
{:found, zipper}
:error ->
to_append =
case zipper.node do
[{{:__block__, meta, _}, _} | _] ->
if meta[:format] do
{{:__block__, [format: meta[:format]], [key]}, {:__block__, [], [nil]}}
else
{{:__block__, [], [key]}, {:__block__, [], [nil]}}
end
[] ->
{{:__block__, [format: :keyword], [key]}, {:__block__, [], [nil]}}
_current_node ->
{{:__block__, [], [key]}, {:__block__, [], [nil]}}
end
{:ok, zipper} =
zipper
|> Zipper.append_child(to_append)
|> get_key(key)
{:new, zipper}
end
else
:error
end
end
@doc "Removes a key from a keyword list if present. Returns `:error` only if the node is not a list"
@spec remove_keyword_key(Zipper.t(), atom()) :: {:ok, Zipper.t()} | :error
def remove_keyword_key(zipper, key) do
Igniter.Code.List.remove_from_list(zipper, fn zipper ->
Igniter.Code.Tuple.elem_equals?(zipper, 0, key)
end)
end
@doc "Puts into nested keyword lists represented by `path`"
@spec keywordify(path :: [atom()], value :: any()) :: any()
def keywordify([], value) when is_integer(value) or is_float(value) do
{:__block__, [token: to_string(value)], [value]}
end
def keywordify([], value) do
{:__block__, [], [value]}
end
def keywordify([key | rest], value) do
[{{:__block__, [format: :keyword], [key]}, {:__block__, [], [keywordify(rest, value)]}}]
end
end