Packages
igniter
0.5.18
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, calling `updater` on the zipper at the value if the key is already present"
@spec put_in_keyword(
Zipper.t(),
list(atom()),
term(),
(Zipper.t() -> {:ok, Zipper.t()} | :error) | nil
) ::
{:ok, Zipper.t()} | :error
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))}
:error ->
:error
end
end
@spec set_keyword_key(
Zipper.t(),
atom(),
term(),
(Zipper.t() -> {:ok, Zipper.t()} | :error) | nil
) ::
{:ok, Zipper.t()} | :error
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]}}}
:error ->
:error
end
{:new, zipper} ->
{:ok, set_keyword_value!(zipper, value)}
:error ->
:error
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
if Igniter.Code.List.list?(zipper) do
Common.within(zipper, fn zipper ->
case get_key(zipper, key) do
{:ok, zipper} ->
{:ok, zipper |> Zipper.up() |> Zipper.remove()}
:error ->
{:ok, zipper}
end
end)
else
:error
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