Packages
spark
2.2.53
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
Current section
Files
lib/spark/formatter.ex
if Code.ensure_loaded?(Sourceror) do
defmodule Spark.Formatter do
@moduledoc """
Formats Spark modules.
Currently, it is very simple, and will only reorder the outermost sections according to some rules.
# Plugin
Include the plugin into your `.formatter.exs` like so `plugins: [Spark.Formatter]`.
If no configuration is provided, it will sort all top level DSL sections *alphabetically*.
# Section Order
To provide a custom section order, add configuration to your app, for example:
```elixir
config :spark, :formatter,
remove_parens?: true,
"Ash.Resource": [
section_order: [
:resource,
:postgres,
:attributes,
:relationships,
:aggregates,
:calculations
]
],
"MyApp.Resource": [
# Use this if you use a module that is not the spark DSL itself.
# For example, you might have a "base" that you use instead that sets some simple defaults.
# This tells us what the actual thing is so we know what extensions are included automatically.
type: Ash.Resource,
# Tell us what extensions might be added under the hood
extensions: [MyApp.ResourceExtension],
section_order: [...]
]
```
Any sections found that aren't in that list will be left in the order that they were in, the sections
in the list will be sorted "around" those sections. E.g the following list: `[:code_interface, :attributes]` can be interpreted as
"ensure that code_interface comes before attributes, and don't change the rest".
"""
@behaviour Mix.Tasks.Format
require Logger
def features(_opts) do
[extensions: [".ex", ".exs"]]
end
def format(contents, opts) do
config =
:spark
|> Application.get_env(:formatter, [])
|> Enum.map(fn
{key, value} when key in [:remove_parens?] ->
{key, value}
{key, value} ->
{Module.concat([key]), value}
end)
parse_result =
try do
{:ok, Sourceror.parse_string!(contents)}
rescue
_ ->
:error
end
case parse_result do
{:ok, parsed} ->
parsed
|> format_resources(opts, config)
|> then(fn patches ->
Sourceror.patch_string(contents, patches)
end)
|> Code.format_string!(opts_without_plugin(opts))
|> then(fn iodata ->
[iodata, ?\n]
end)
|> IO.iodata_to_binary()
:error ->
contents
end
end
defp format_resources(parsed, opts, config) do
{_, patches} =
Spark.CodeHelpers.prewalk(parsed, [], false, fn
{:defmodule, _, [_, [{{:__block__, _, [:do]}, {:__block__, _, body}}]]} = expr,
patches,
false ->
case get_extensions(body, config) do
{:ok, extensions, type, using} ->
replacement = format_resource(body, extensions, config, type, using)
patches =
body
|> Enum.zip(replacement)
|> Enum.reduce(patches, fn {body_section, replacement_section}, patches ->
if body_section == replacement_section do
patches
else
[
%{
range: Sourceror.get_range(body_section, include_comments: true),
change: Sourceror.to_string(replacement_section, opts)
}
| patches
]
end
end)
{expr, patches, true}
_ ->
{expr, patches, true}
end
expr, patches, branch_acc ->
{expr, patches, branch_acc}
end)
patches
end
defp format_resource(body, extensions, config, _type, using) do
sections =
extensions
|> Enum.flat_map(fn extension ->
Enum.map(extension.sections(), fn section ->
{extension, section}
end)
end)
|> sort_sections(config[using][:section_order])
section_names = Enum.map(sections, fn {_, section} -> section.name end)
{section_exprs, non_section_exprs} =
body
|> Enum.with_index()
|> Enum.split_with(fn {{name, _, _}, _index} ->
name in section_names
end)
new_sections =
if config[using][:section_order] && config[using][:section_order] != [] do
Enum.sort_by(section_exprs, fn {{name, _, _}, _} ->
Enum.find_index(section_names, &(&1 == name))
end)
else
section_exprs
end
new_section_indexes =
section_exprs
|> Enum.map(&elem(&1, 1))
|> Enum.sort()
new_sections =
Enum.zip_with(new_sections, new_section_indexes, fn {new_section, _}, index ->
{new_section, index}
end)
non_section_exprs
|> Enum.concat(new_sections)
|> Enum.sort_by(&elem(&1, 1))
|> Enum.map(&elem(&1, 0))
|> then(fn sections ->
if config[:remove_parens?] do
de_paren(sections, Enum.flat_map(extensions, & &1.sections()), extensions)
else
sections
end
end)
end
defp de_paren(actual_sections, dsl_sections, extensions) do
actual_sections
|> Enum.map(fn
{name, meta, body} ->
case Enum.find(dsl_sections, &(&1.name == name)) do
nil ->
{name, meta, body}
section ->
{name, meta, de_paren_section(body, section, extensions)}
end
other ->
other
end)
end
defp de_paren_section(body, section, extensions) do
builders = all_entity_builders([section], extensions)
Macro.prewalk(body, fn
{func, meta, body} = node when is_atom(func) ->
count = Enum.count(List.wrap(body))
builders = Keyword.get_values(builders, func)
if Enum.any?(builders, &(&1 in [count, count - 1])) &&
Keyword.keyword?(meta) &&
meta[:closing] do
{func, Keyword.delete(meta, :closing), body}
else
node
end
node ->
node
end)
end
defp sort_sections(sections, nil), do: sections
defp sort_sections(sections, []), do: sections
defp sort_sections(sections, section_order) do
{ordered, unordered} =
sections
|> Enum.with_index()
|> Enum.split_with(fn {{_, section}, _} ->
section.name in section_order
end)
reordered =
ordered
|> Enum.map(&elem(&1, 0))
|> Enum.sort_by(fn {_, section} ->
Enum.find_index(section_order, &(&1 == section.name))
end)
Enum.reduce(unordered, reordered, fn {{extension, section}, i}, acc ->
List.insert_at(acc, i, {extension, section})
end)
end
defp get_extensions(body, config) do
Enum.find_value(body, :error, fn
{:use, _, using} ->
[using, opts] =
case Spark.Dsl.Extension.expand_alias(using, __ENV__) do
[using] ->
[using, []]
[using, opts] ->
[using, opts]
end
if Keyword.has_key?(config, using) do
type = config[using][:type] || using
{:ok, parse_extensions(opts, config[using], type), type, using}
end
_ ->
nil
end)
end
defp parse_extensions(blocks, config, type) do
blocks
|> Enum.flat_map(fn {{:__block__, _, _}, extensions} ->
extensions
|> case do
{:__block__, _, [extensions]} ->
extensions
extension when is_atom(extension) ->
extension
_ ->
[]
end
|> List.wrap()
|> Enum.flat_map(fn extension ->
case is_atom(extension) and Code.ensure_compiled(extension) do
{:module, module} ->
if Spark.implements_behaviour?(module, Spark.Dsl.Extension) do
[module]
else
[]
end
_ ->
[]
end
end)
end)
|> Enum.concat(config[:extensions] || [])
|> Enum.concat(type.default_extensions() || [])
|> Enum.flat_map(fn extension ->
[extension | extension.add_extensions()]
end)
end
defp opts_without_plugin(opts) do
Keyword.update(opts, :plugins, [], &(&1 -- [__MODULE__]))
end
@doc false
def all_entity_builders(sections, extensions, path \\ []) do
Enum.flat_map(sections, fn section ->
Enum.concat([
all_entity_option_builders(section),
all_patch_entity_builders(extensions, section, path),
section_option_builders(section),
section_entity_builders(section, extensions, path)
])
end)
|> Enum.uniq()
|> Enum.sort()
end
defp all_patch_entity_builders(extensions, section, path) do
match_path = path ++ [section.name]
extensions
|> Enum.flat_map(& &1.dsl_patches())
|> tap(fn patches ->
Enum.map(patches, & &1.section_path)
end)
|> Enum.filter(fn
%Spark.Dsl.Patch.AddEntity{section_path: ^match_path} ->
true
_ ->
false
end)
|> Enum.map(& &1.entity)
|> Enum.flat_map(&entity_option_builders/1)
end
defp section_entity_builders(section, extensions, path) do
match_path = path ++ [section.name]
mixed_in_entities =
extensions
|> Enum.flat_map(& &1.dsl_patches())
|> Enum.filter(fn
%Spark.Dsl.Patch.AddEntity{section_path: ^match_path} ->
true
_ ->
false
end)
|> Enum.map(& &1.entity)
Enum.flat_map(section.entities ++ mixed_in_entities, fn entity ->
entity_builders(entity)
end) ++ all_entity_builders(section.sections, extensions, path ++ [section.name])
end
def entity_builders(entity) do
arg_count = Enum.count(entity.args)
non_optional_arg_count = Enum.count(entity.args, &is_atom/1)
non_optional_arg_count..arg_count
|> Enum.flat_map(&[{entity.name, &1}, {entity.name, &1 + 1}])
|> Enum.concat(flat_map_nested_entities(entity, &entity_builders/1))
end
defp all_entity_option_builders(section) do
Enum.flat_map(section.entities, fn entity ->
entity_option_builders(entity)
end)
end
@doc false
def entity_option_builders(entity) do
entity_args_to_drop = Spark.Dsl.Entity.required_arg_names(entity)
entity.schema
|> Keyword.drop(entity_args_to_drop)
|> Enum.map(fn {key, _schema} ->
{key, 1}
end)
|> Kernel.++(flat_map_nested_entities(entity, &entity_option_builders/1))
end
defp section_option_builders(section) do
Enum.map(section.schema, fn {key, _} ->
{key, 1}
end)
end
defp flat_map_nested_entities(entity, mapper) do
Enum.flat_map(entity.entities, fn {_, nested_entities} ->
nested_entities
|> List.wrap()
|> Enum.flat_map(fn nested_entity ->
mapper.(nested_entity)
end)
end)
end
end
else
defmodule Spark.Formatter do
@moduledoc """
Formats Spark modules.
"""
@behaviour Mix.Tasks.Format
require Logger
def features(_opts) do
[extensions: [".ex", ".exs"]]
end
def format(_content, _opts) do
raise """
#{inspect(__MODULE__)} requires sourceror to run. Please add it as a dev/test dependency
defp deps do
[
...,
{:sourceror, "~> 1.7", only: [:dev, :test]}
]
end
"""
end
end
end