Packages

spark

2.2.51
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 cheat_sheet.ex
Raw

lib/spark/cheat_sheet.ex

defmodule Spark.CheatSheet do
@moduledoc "Tools to generate cheat sheets for spark DSLs"
@doc """
Generate a cheat sheet for a given DSL
"""
def cheat_sheet(extension) do
patches =
Enum.map_join(
extension.dsl_patches(),
"\n\n",
fn %Spark.Dsl.Patch.AddEntity{
section_path: section_path,
entity: entity
} ->
entity_cheat_sheet(entity, section_path)
end
)
body =
extension.sections()
|> Enum.map_join("\n\n", &section_cheat_sheet(&1))
|> Kernel.<>("\n")
|> Kernel.<>(patches)
extension_name = inspect(extension)
title =
if String.ends_with?(extension_name, ".Dsl") || String.ends_with?(extension_name, ".DSL") do
String.slice(extension_name, 0..-5//1)
else
extension_name
end
"""
<!--
This file was generated by Spark. Do not edit it by hand.
-->
# #{title}
#{module_docs(extension)}
#{body}
<style type="text/css">.spark-required::after { content: "*"; color: red !important; }</style>
"""
end
def section_cheat_sheet(section, path \\ []) do
examples =
case section.examples do
[] ->
""
examples ->
"""
### Examples
#{doc_examples(examples)}
"""
end
doc_index =
if !Enum.empty?(section.sections ++ section.entities) do
"""
### Nested DSLs
#{doc_index(section.sections ++ section.entities, 0, Enum.join(path ++ [section.name], "-"))}
"""
end
options =
Enum.reject(section.schema, fn {_key, opts} ->
opts[:hide]
end)
if section.schema && section.schema != [] do
"""
###{extra_header(path)} #{Enum.join(path ++ [section.name], ".")}
#{describe(section.describe)}
#{doc_index}
#{examples}
#{options_table(options, path ++ [section.name])}
#{Enum.map_join(section.sections, &section_cheat_sheet(&1, path ++ [section.name]))}
#{Enum.map_join(section.entities, &entity_cheat_sheet(&1, path ++ [section.name]))}
"""
else
"""
###{extra_header(path)} #{Enum.join(path ++ [section.name], ".")}
#{describe(section.describe)}
#{doc_index}
#{examples}
#{Enum.map_join(section.sections, &section_cheat_sheet(&1, path ++ [section.name]))}
#{Enum.map_join(section.entities, &entity_cheat_sheet(&1, path ++ [section.name]))}
"""
end
end
defp entity_cheat_sheet(entity, path) do
nested_entities =
entity.entities
|> List.wrap()
|> Enum.flat_map(&elem(&1, 1))
nested_entity_docs =
Enum.map_join(nested_entities, &entity_cheat_sheet(&1, path ++ [entity.name]))
options =
entity.schema
|> Keyword.drop(List.wrap(entity.hide))
|> Enum.reject(fn {_key, opts} ->
opts[:hide]
end)
|> Keyword.drop(Keyword.keys(entity.auto_set_fields))
arg_keys = arg_keys(entity)
reference =
if Enum.empty?(nested_entities) && Enum.empty?(options) do
""
else
"""
#{options_table(options, path ++ [entity.name], arg_keys)}
#{nested_entity_docs}
"""
end
args_example =
if Enum.empty?(entity.args) do
""
else
"""
```elixir
#{entity.name}#{entity_args(entity)}
```
"""
end
examples =
case entity.examples do
[] ->
""
examples ->
"""
### Examples
#{doc_examples(examples)}
"""
end
doc_index =
if !Enum.empty?(nested_entities) do
"""
### Nested DSLs
#{doc_index(nested_entities, 0, Enum.join(path ++ [entity.name], "-"))}
"""
end
"""
###{extra_header(path)} #{Enum.join(path ++ [entity.name], ".")}
#{args_example}
#{describe(entity.describe)}
#{doc_index}
#{examples}
#{reference}
#{entity_properties(entity)}
"""
end
defp describe(entity) do
entity
|> String.split("\n")
|> Enum.map(&String.trim_leading/1)
|> Enum.map_join("\n", fn
"####" <> rest ->
"######" <> rest
"###" <> rest ->
"#####" <> rest
"##" <> rest ->
"####" <> rest
"#" <> rest ->
"###" <> rest
other ->
other
end)
end
defp entity_args(%{args: []}), do: ""
defp entity_args(entity) do
" " <>
Enum.map_join(entity.args, ", ", fn
key when is_atom(key) ->
to_string(key)
{:optional, key} ->
"#{key} \\\\ nil"
{:optional, key, default} ->
"#{key} \\\\ #{inspect(default)}"
end)
end
defp arg_keys(entity) do
entity.args
|> Enum.map(fn
key when is_atom(key) ->
key
{:optional, key} ->
key
{:optional, key, _} ->
key
end)
end
defp entity_properties(entity) do
docs =
case Code.fetch_docs(entity.target) do
{:docs_v1, _, _, _, docs, _, _} -> docs
{:error, _} -> :hidden
end
if docs == :hidden do
""
else
"""
### Introspection
Target: `#{inspect(entity.target)}`
"""
end
end
defp options_table(options, path, positional_args \\ [])
defp options_table([], _path, _), do: nil
defp options_table(options, path, positional_args) do
{required, optional} =
Enum.split_with(options, fn {key, _config} ->
key in positional_args
end)
optional =
Enum.sort_by(optional, fn {_key, config} ->
!config[:required]
end)
required =
Enum.sort_by(required, fn {key, _} ->
Enum.find_index(positional_args, &(&1 == key))
end)
args = do_options_table(required, path, "Arguments")
opts = do_options_table(optional, path, "Options")
table =
String.trim(args) <> "\n" <> String.trim(opts)
"""
#{table}
"""
end
defp extra_header([]), do: ""
defp extra_header(_), do: "#"
defp do_options_table([], _path, _header), do: ""
defp do_options_table(options, path, header) do
rows =
Enum.map_join(options, "", fn {key, value} ->
required_class =
if value[:required] && is_nil(value[:default]) do
".spark-required"
else
""
end
anchor = Enum.join(path ++ [key], "-")
"""
| [`#{key}`](##{anchor}){: ##{anchor} #{required_class}} | `#{escape_pipes(Spark.Options.Docs.dsl_docs_type(value[:type]))}` | #{inspect_if(value[:default])} | #{escape_pipes(String.trim(value[:doc] || ""))} |
"""
end)
"""
### #{header}
| Name | Type | Default | Docs |
|------|------|---------|------|
#{rows}
"""
end
defp inspect_if(nil), do: ""
defp inspect_if(value), do: "`#{inspect(value)}`"
defp escape_pipes(string) do
string
|> String.trim()
|> String.replace("|", "\\|")
|> tap(fn thing ->
if String.contains?(thing, "\n") do
IO.warn("""
Multi-line DSL option doc detected. Please move contextual information into the
doc of a section, entity or module.
#{thing}
""")
end
end)
|> String.replace("\n\n", "\n")
|> String.replace("\n", " ")
end
@doc """
Generate a markdown bullet list documentation for a list of sections
"""
def doc(sections, depth \\ 1) do
Enum.map_join(sections, "\n\n", fn section ->
String.duplicate("#", depth + 1) <>
" " <>
to_string(section.name) <> "\n\n" <> doc_section(section, depth)
end)
end
@doc false
def doc_section(section, depth \\ 1) do
sections_and_entities = List.wrap(section.entities) ++ List.wrap(section.sections)
table_of_contents =
case sections_and_entities do
[] ->
""
sections_and_entities ->
doc_index(sections_and_entities, 0)
end
options = Spark.Options.docs(section.schema)
examples =
case section.examples do
[] ->
""
examples ->
"Examples:\n" <> Enum.map_join(examples, &doc_example/1)
end
entities =
Enum.map_join(section.entities, "\n\n", fn entity ->
String.duplicate("#", depth + 2) <>
" " <>
to_string(entity.name) <>
"\n\n" <>
doc_entity(entity, depth + 2)
end)
sections =
Enum.map_join(section.sections, "\n\n", fn section ->
String.duplicate("#", depth + 2) <>
" " <>
to_string(section.name) <>
"\n\n" <>
doc_section(section, depth + 1)
end)
imports =
case section.imports do
[] ->
""
mods ->
"Imports:\n\n" <>
Enum.map_join(mods, "\n", fn mod ->
"* `#{inspect(mod)}`"
end)
end
"""
#{section.describe}
#{table_of_contents}
#{examples}
#{imports}
---
#{options}
#{entities}
#{sections}
"""
end
@doc false
def doc_entity(entity, depth \\ 1) do
options = Spark.Options.docs(Keyword.drop(entity.schema, entity.hide))
examples =
case entity.examples do
[] ->
""
examples ->
"Examples:\n" <> Enum.map_join(examples, &doc_example/1)
end
entities =
Enum.flat_map(entity.entities, fn
{_, entities} ->
entities
other ->
[other]
end)
entities_doc =
Enum.map_join(entities, "\n\n", fn entity ->
String.duplicate("#", depth + 2) <>
" " <>
to_string(entity.name) <>
"\n\n" <>
doc_entity(entity, depth + 1)
end)
table_of_contents =
case entities do
[] ->
""
entities ->
doc_index(entities, depth)
end
"""
#{entity.describe}
#{table_of_contents}
#{examples}
#{options}
#{entities_doc}
"""
end
defp module_docs(module) do
{:docs_v1, _, :elixir, _, %{"en" => docs}, _, _} = Code.fetch_docs(module)
if docs != "" do
docs
else
""
end
rescue
_ -> ""
end
@doc """
Generate a table of contents for a list of sections
"""
def doc_index(sections, depth \\ 0, prefix \\ "module") do
sections
|> Enum.flat_map(fn
{_, entities} ->
entities
other ->
[other]
end)
|> Enum.map_join("\n", fn
section ->
docs =
if depth == 0 do
String.duplicate(" ", depth + 1) <>
"* [#{section.name}](##{prefix}-#{section.name})"
else
String.duplicate(" ", depth + 1) <> "* #{section.name}"
end
case List.wrap(section.entities) ++ List.wrap(Map.get(section, :sections)) do
[] ->
docs
sections_and_entities ->
new_prefix =
if prefix == "module" do
prefix
else
prefix <> "-#{section.name}"
end
docs <>
"\n" <> doc_index(sections_and_entities, depth + 2, new_prefix)
end
end)
end
defp doc_examples(examples) do
examples
|> List.wrap()
|> Enum.map_join("\n", &doc_example/1)
end
defp doc_example({description, example}) when is_binary(description) and is_binary(example) do
"""
#{description}
```
#{example}
```
"""
end
defp doc_example(example) when is_binary(example) do
"""
```
#{example}
```
"""
end
end