Packages

spark

2.2.4
2.7.2 2.7.1 2.7.0 2.6.1 2.6.0 2.5.0 2.4.1 2.4.0 2.3.14 2.3.13 2.3.12 2.3.11 2.3.10 2.3.9 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.69 2.2.68 2.2.67 2.2.66 2.2.65 2.2.64 2.2.63 2.2.62 2.2.61 2.2.60 2.2.59 2.2.58 2.2.57 2.2.56 2.2.55 2.2.54 2.2.53 2.2.52 2.2.51 2.2.50 2.2.49 2.2.48 2.2.47 2.2.46 2.2.45 2.2.44 2.2.43 2.2.42 2.2.41 2.2.40 2.2.39 2.2.38 2.2.37 2.2.36 2.2.35 2.2.34 2.2.33 2.2.32 2.2.31 2.2.30 2.2.29 2.2.28 2.2.27 2.2.26 2.2.25 2.2.24 2.2.23 2.2.22 2.2.21 2.2.20 2.2.19 2.2.18 2.2.17 2.2.16 2.2.15 2.2.14 2.2.13 2.2.12 2.2.11 2.2.10 2.2.9 2.2.8 2.2.7 2.2.6 2.2.5 2.2.4 2.2.3 2.2.2 2.2.1 2.2.0 2.1.24 2.1.23 2.1.22 2.1.21 2.1.20 2.1.19 2.1.18 2.1.17 2.1.16 2.1.15 2.1.14 2.1.13 2.1.12 2.1.11 2.1.10 2.1.9 2.1.8 2.1.7 2.1.6 2.1.5 2.1.4 2.1.3 2.1.2 2.1.1 2.1.0 2.0.1 2.0.0 1.1.55 1.1.54 1.1.53 1.1.52 1.1.51 1.1.50 1.1.49 1.1.48 1.1.47 1.1.46 1.1.45 1.1.44 1.1.43 1.1.42 1.1.41 1.1.40 1.1.39 1.1.38 1.1.37 1.1.36 1.1.35 1.1.34 1.1.32 1.1.31 1.1.30 1.1.29 1.1.28 1.1.27 1.1.26 1.1.25 1.1.24 1.1.22 1.1.21 1.1.20 1.1.19 1.1.18 1.1.17 1.1.16 1.1.15 1.1.14 retired 1.1.13 1.1.12 1.1.11 1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.9 1.0.8 1.0.7 1.0.6 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 1.0.0 0.4.12 0.4.11 0.4.10 0.4.9 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.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.18 0.2.17 0.2.16 0.2.15 0.2.14 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.29 0.1.28 0.1.27 0.1.26 0.1.25 0.1.24 0.1.23 0.1.22 0.1.21 0.1.20 0.1.19 0.1.18 0.1.17 0.1.16 0.1.15 0.1.14 0.1.13 0.1.12 0.1.11 0.1.10 0.1.9 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

Generic tooling for building DSLs

Current section

Files

Jump to
spark lib spark igniter.ex
Raw

lib/spark/igniter.ex

defmodule Spark.Igniter do
@moduledoc """
Helpers for patching Spark DSLs.
"""
require Igniter.Code.Common
alias Sourceror.Zipper
@doc "Sets an option at a given path within in a DSL."
@spec set_option(
Igniter.t(),
spark :: module,
Path.t(),
dsl_path :: list(atom),
value :: term(),
(Zipper.t() -> {:ok, Zipper.t()} | {:error, term | list(term)} | :error)
) :: Igniter.t()
def set_option(igniter, spark, file_path, path, value, updater \\ &{:ok, &1}) do
{body, [last]} = Enum.split(List.wrap(path), -1)
path = Enum.map(body, &{:section, &1}) ++ [{:option, last}]
update_dsl(igniter, spark, file_path, path, value, updater)
end
def update_dsl(igniter, spark, file_path, path, value, func) do
Igniter.update_elixir_file(igniter, file_path, fn zipper ->
case Igniter.Code.Module.move_to_module_using(zipper, spark) do
{:ok, zipper} ->
do_update_dsl(zipper, path, value, func)
_ ->
{:ok, zipper}
end
end)
end
defp do_update_dsl(zipper, [], value, fun) do
if Igniter.Code.Common.node_matches_pattern?(zipper, v when is_nil(v)) do
{:ok, Sourceror.Zipper.replace(zipper, value)}
else
fun.(zipper)
end
end
defp do_update_dsl(zipper, [{:section, name} | rest], value, fun) do
with {:ok, zipper} <-
Igniter.Code.Function.move_to_function_call_in_current_scope(zipper, name, 1),
{:ok, zipper} <- Igniter.Code.Common.move_to_do_block(zipper) do
do_update_dsl(zipper, rest, value, fun)
else
_ ->
zipper =
Igniter.Code.Common.add_code(zipper, """
#{name} do
end
""")
with {:ok, zipper} <-
Igniter.Code.Function.move_to_function_call_in_current_scope(zipper, name, 1),
{:ok, zipper} <- Igniter.Code.Common.move_to_do_block(zipper) do
do_update_dsl(zipper, rest, value, fun)
else
_ ->
{:ok, zipper}
end
end
end
defp do_update_dsl(zipper, [{:option, name}], value, fun) do
case Igniter.Code.Function.move_to_function_call_in_current_scope(zipper, name, 1) do
{:ok, zipper} ->
Igniter.Code.Function.update_nth_argument(zipper, 0, fun)
_ ->
added_zipper = Igniter.Code.Common.add_code(zipper, {name, [], [value]})
case Igniter.Code.Function.move_to_function_call_in_current_scope(added_zipper, name, 1) do
{:ok, zipper} ->
{:ok, zipper}
_ ->
{:ok, zipper}
end
end
end
defp do_update_dsl(_zipper, [{:option, name} | _], _value, _fun) do
raise ArgumentError,
"{:option, #{inspect(name)}} was found as a non-leaf node in a path to update. Options must be the last item in the list."
end
def remove_extension(igniter, path, type, key, extension, singleton? \\ false) do
Igniter.update_elixir_file(igniter, path, fn zipper ->
with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, type),
{:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, type) do
if Igniter.Code.Common.node_matches_pattern?(zipper, {_, _, [_]}) do
:error
else
if singleton? do
case Igniter.Code.Common.within(zipper, fn zipper ->
Igniter.Code.Keyword.remove_keyword_key(zipper, key)
end) do
{:ok, zipper} ->
{:ok, zipper}
_ ->
:error
end
else
Igniter.Code.Keyword.put_in_keyword(zipper, [key], [], fn zipper ->
Igniter.Code.List.remove_from_list(zipper, fn nested_zipper ->
Igniter.Code.Common.nodes_equal?(nested_zipper, extension)
end)
end)
end
end
end
|> case do
:error ->
{:ok, zipper}
{:ok, zipper} ->
do_remove_constructors(zipper, type, extension)
end
end)
end
defp do_remove_constructors(zipper, type, extension) do
constructors_to_remove = extension_constructors(extension)
zipper = Zipper.topmost(zipper)
{:ok, zipper} = Igniter.Code.Module.move_to_module_using(zipper, type)
remove_constructors(zipper, constructors_to_remove)
end
def add_extension(igniter, path, type, key, extension, singleton? \\ false) do
Igniter.update_elixir_file(igniter, path, fn zipper ->
with {:ok, zipper} <- Igniter.Code.Module.move_to_module_using(zipper, type),
{:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, type) do
if Igniter.Code.Common.node_matches_pattern?(zipper, {_, _, [_]}) do
value =
if singleton? do
extension
else
[extension]
end
Igniter.Code.Function.append_argument(zipper, [{key, value}])
else
remove_extension =
if singleton? do
with {:ok, arg_zipper} <- Igniter.Code.Function.move_to_nth_argument(zipper, 1),
{:ok, value_zipper} <- Igniter.Code.Keyword.get_key(arg_zipper, key),
true <- Igniter.Code.Module.module?(value_zipper),
module_zipper <- Igniter.Code.Common.expand_aliases(value_zipper),
{:__aliases__, _, parts} <- Zipper.node(module_zipper) do
{:ok, Module.concat(parts)}
end
else
:error
end
Igniter.Code.Function.update_nth_argument(zipper, 1, fn zipper ->
if singleton? do
case Igniter.Code.Common.within(zipper, fn zipper ->
Igniter.Code.Keyword.put_in_keyword(zipper, [key], extension, fn x ->
{:ok, Sourceror.Zipper.replace(x, extension)}
end)
end) do
{:ok, zipper} ->
{:ok, zipper}
_ ->
{:ok, zipper}
end
else
Igniter.Code.Keyword.put_in_keyword(zipper, [key], [extension], fn zipper ->
Igniter.Code.List.prepend_new_to_list(
zipper,
extension
)
end)
end
end)
|> case do
{:ok, zipper} ->
case remove_extension do
{:ok, module} ->
if Code.ensure_loaded?(module) &&
Spark.implements_behaviour?(module, Spark.Dsl.Extension) do
do_remove_constructors(zipper, type, module)
else
{:ok, zipper}
end
:error ->
{:ok, zipper}
end
:error ->
:error
end
end
else
_ ->
{:ok, zipper}
end
end)
end
defp remove_constructors(zipper, []), do: {:ok, zipper}
defp remove_constructors(zipper, [{name, arity} | rest] = all) do
Igniter.Code.Common.within(zipper, fn zipper ->
case Igniter.Code.Function.move_to_function_call_in_current_scope(zipper, name, arity) do
{:ok, zipper} ->
{:ok, Zipper.remove(zipper)}
:error ->
:error
end
end)
|> case do
:error ->
remove_constructors(zipper, rest)
{:ok, zipper} ->
remove_constructors(zipper, all)
end
end
defp extension_constructors(module) do
module.sections()
|> Enum.flat_map(fn section ->
if section.top_level? do
nested_entities =
Enum.flat_map(section.entities, fn entity ->
total_count = Enum.count(entity.args)
optional_count = Enum.count(entity.args, &(not is_atom(&1)))
required_count = total_count - optional_count
Enum.map(required_count..total_count, fn i ->
{entity.name, i}
end)
end)
nested_sections =
Enum.map(section.sections, fn section ->
{section.name, 1}
end)
nested_entities ++ nested_sections
else
[{section.name, 1}]
end
end)
end
end