Packages

spark

0.2.15
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 dsl.ex
Raw

lib/spark/dsl.ex

defmodule Spark.Dsl do
@using_schema [
single_extension_kinds: [
type: {:list, :atom},
default: [],
doc:
"The extension kinds that are allowed to have a single value. For example: `[:data_layer]`"
],
many_extension_kinds: [
type: {:list, :atom},
default: [],
doc:
"The extension kinds that can have multiple values. e.g `[notifiers: [Notifier1, Notifier2]]`"
],
untyped_extensions?: [
type: :boolean,
default: true,
doc: "Whether or not to support an `extensions` key which contains untyped extensions"
],
default_extensions: [
type: :keyword_list,
default: [],
doc: """
The extensions that are included by default. e.g `[data_layer: Default, notifiers: [Notifier1]]`
Default values for single extension kinds are overwritten if specified by the implementor, while many extension
kinds are appended to if specified by the implementor.
"""
]
]
@type entity :: %Spark.Dsl.Entity{}
@type section :: %Spark.Dsl.Section{}
@moduledoc """
The primary entry point for adding a DSL to a module.
To add a DSL to a module, add `use Spark.Dsl, ...options`. The options supported with `use Spark.Dsl` are:
#{Spark.OptionsHelpers.docs(@using_schema)}
See the callbacks defined in this module to augment the behavior/compilation of the module getting a Dsl.
## Schemas/Data Types
Spark DSLs use a superset of `NimbleOptions` for the `schema` that makes up sections/entities of the DSL.
For more information, see `Spark.OptionsHelpers`.
"""
@type opts :: Keyword.t()
@type t :: map()
@doc """
Validate/add options. Those options will be passed to `handle_opts` and `handle_before_compile`
"""
@callback init(opts) :: {:ok, opts} | {:error, String.t() | term}
@doc """
Handle options in the context of the module. Must return a `quote` block.
If you want to persist anything in the DSL persistence layer,
use `@persist {:key, value}`. It can be called multiple times to
persist multiple times.
"""
@callback handle_opts(Keyword.t()) :: Macro.t()
@doc """
Handle options in the context of the module, after all extensions have been processed. Must return a `quote` block.
"""
@callback handle_before_compile(Keyword.t()) :: Macro.t()
defmacro __using__(opts) do
opts = Spark.OptionsHelpers.validate!(opts, @using_schema)
their_opt_schema =
Enum.map(opts[:single_extension_kinds], fn extension_kind ->
{extension_kind, type: :atom, default: opts[:default_extensions][extension_kind]}
end) ++
Enum.map(opts[:many_extension_kinds], fn extension_kind ->
{extension_kind, type: {:list, :atom}, default: []}
end)
their_opt_schema =
if opts[:untyped_extensions?] do
Keyword.put(their_opt_schema, :extensions, type: {:list, :atom})
else
their_opt_schema
end
their_opt_schema = Keyword.put(their_opt_schema, :otp_app, type: :atom)
quote bind_quoted: [
their_opt_schema: their_opt_schema,
parent_opts: opts,
parent: __CALLER__.module
],
generated: true do
require Spark.Dsl.Extension
@dialyzer {:nowarn_function, handle_opts: 1, handle_before_compile: 1}
Module.register_attribute(__MODULE__, :spark_dsl, persist: true)
Module.register_attribute(__MODULE__, :spark_default_extensions, persist: true)
Module.register_attribute(__MODULE__, :spark_extension_kinds, persist: true)
@spark_dsl true
@spark_default_extensions parent_opts[:default_extensions]
|> Keyword.values()
|> List.flatten()
@spark_extension_kinds List.wrap(parent_opts[:many_extension_kinds]) ++
List.wrap(parent_opts[:single_extension_kinds])
def init(opts), do: {:ok, opts}
def default_extensions, do: @spark_default_extensions
def handle_opts(opts) do
quote do
end
end
def handle_before_compile(opts) do
quote do
end
end
defoverridable init: 1, handle_opts: 1, handle_before_compile: 1
defmacro __using__(opts) do
parent = unquote(parent)
parent_opts = unquote(parent_opts)
their_opt_schema = unquote(their_opt_schema)
require Spark.Dsl.Extension
{opts, extensions} =
parent_opts[:default_extensions]
|> Enum.reduce(opts, fn {key, defaults}, opts ->
Keyword.update(opts, key, defaults, fn current_value ->
cond do
key in parent_opts[:single_extension_kinds] ->
current_value || defaults
key in parent_opts[:many_extension_kinds] || key == :extensions ->
List.wrap(current_value) ++ List.wrap(defaults)
true ->
current_value
end
end)
end)
|> Spark.Dsl.expand_modules(parent_opts, __CALLER__)
opts =
opts
|> Spark.OptionsHelpers.validate!(their_opt_schema)
|> init()
|> Spark.Dsl.unwrap()
body =
quote generated: true do
parent = unquote(parent)
opts = unquote(opts)
parent_opts = unquote(parent_opts)
their_opt_schema = unquote(their_opt_schema)
@opts opts
@before_compile Spark.Dsl
@after_compile __MODULE__
@spark_is parent
@spark_parent parent
def spark_is, do: @spark_is
defmacro __after_compile__(_, _) do
quote do
transformers_to_run =
@extensions
|> Enum.flat_map(& &1.transformers())
|> Spark.Dsl.Transformer.sort()
|> Enum.filter(& &1.after_compile?())
__MODULE__
|> Spark.Dsl.Extension.run_transformers(
transformers_to_run,
@spark_dsl_config,
__ENV__
)
end
end
Module.register_attribute(__MODULE__, :persist, accumulate: true)
opts
|> @spark_parent.handle_opts()
|> Code.eval_quoted([], __ENV__)
if opts[:otp_app] do
@persist {:otp_app, opts[:otp_app]}
end
@persist {:module, __MODULE__}
for single_extension_kind <- parent_opts[:single_extension_kinds] do
@persist {single_extension_kind, opts[single_extension_kind]}
Module.put_attribute(__MODULE__, single_extension_kind, opts[single_extension_kind])
end
for many_extension_kind <- parent_opts[:many_extension_kinds] do
@persist {many_extension_kind, opts[many_extension_kind] || []}
Module.put_attribute(
__MODULE__,
many_extension_kind,
opts[many_extension_kind] || []
)
end
end
preparations = Spark.Dsl.Extension.prepare(extensions)
[body | preparations]
end
end
end
@doc false
def unwrap({:ok, value}), do: value
def unwrap({:error, error}), do: raise(error)
@doc false
def expand_modules(opts, their_opt_schema, env) do
Enum.reduce(opts, {[], []}, fn {key, value}, {opts, extensions} ->
cond do
key in their_opt_schema[:single_extension_kinds] ->
mod = Macro.expand(value, env)
extensions =
if Spark.implements_behaviour?(mod, Spark.Dsl.Extension) do
[mod | extensions]
else
extensions
end
{Keyword.put(opts, key, mod), extensions}
key in their_opt_schema[:many_extension_kinds] || key == :extensions ->
mods =
value
|> List.wrap()
|> Enum.map(&Macro.expand(&1, env))
extensions =
extensions ++
Enum.filter(mods, &Spark.implements_behaviour?(&1, Spark.Dsl.Extension))
{Keyword.put(opts, key, mods), extensions}
true ->
{Keyword.put(opts, key, value), extensions}
end
end)
end
defmacro __before_compile__(env) do
parent = Module.get_attribute(env.module, :spark_parent)
opts = Module.get_attribute(env.module, :opts)
parent_code = parent.handle_before_compile(opts)
code =
quote generated: true, bind_quoted: [dsl: __MODULE__] do
require Spark.Dsl.Extension
Module.register_attribute(__MODULE__, :spark_is, persist: true)
Module.put_attribute(__MODULE__, :spark_is, @spark_is)
Spark.Dsl.Extension.set_state(@persist)
for {block, bindings} <- @spark_dsl_config[:eval] || [] do
Code.eval_quoted(block, bindings, __ENV__)
end
def __spark_placeholder__, do: nil
def spark_dsl_config do
@spark_dsl_config
end
if @moduledoc do
@moduledoc """
#{@moduledoc}
#{Spark.Dsl.Extension.explain(@extensions, @spark_dsl_config)}
"""
else
@moduledoc Spark.Dsl.Extension.explain(@extensions, @spark_dsl_config)
end
end
[code, parent_code]
end
def is?(module, type) when is_atom(module) do
module.spark_is() == type
rescue
_ -> false
end
def is?(_module, _type), do: false
end