2022-03-26 10:17:01 +13:00
|
|
|
require Logger
|
2023-01-27 04:07:32 +13:00
|
|
|
[name, version, file, mix_project, repo_org | rest] = System.argv()
|
|
|
|
|
|
|
|
github_sha =
|
|
|
|
case rest do
|
|
|
|
[github_sha] ->
|
|
|
|
github_sha
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
2023-01-18 15:33:49 +13:00
|
|
|
mix_project = Module.concat([mix_project])
|
2022-03-26 10:17:01 +13:00
|
|
|
|
2022-03-28 10:26:35 +13:00
|
|
|
Application.put_env(:dsl, :name, name)
|
2023-01-18 15:33:49 +13:00
|
|
|
Application.put_env(:ash, :use_all_identities_in_manage_relationship?, true)
|
2022-03-28 10:26:35 +13:00
|
|
|
|
2023-01-27 04:07:32 +13:00
|
|
|
if github_sha do
|
|
|
|
Mix.install(
|
|
|
|
[
|
|
|
|
{String.to_atom(name), github: "#{repo_org}/#{name}", sha: github_sha}
|
|
|
|
],
|
|
|
|
force: true,
|
|
|
|
system_env: [
|
|
|
|
{"MIX_QUIET", "true"}
|
|
|
|
]
|
|
|
|
)
|
|
|
|
else
|
|
|
|
Mix.install(
|
|
|
|
[
|
|
|
|
{String.to_atom(name), "== #{version}"}
|
|
|
|
],
|
|
|
|
force: true,
|
|
|
|
system_env: [
|
|
|
|
{"MIX_QUIET", "true"}
|
|
|
|
]
|
|
|
|
)
|
|
|
|
end
|
2022-09-16 10:35:33 +12:00
|
|
|
|
|
|
|
defmodule Types do
|
|
|
|
def for_module(module) do
|
|
|
|
{:ok, types} = Code.Typespec.fetch_types(module)
|
|
|
|
types
|
|
|
|
rescue
|
|
|
|
_ ->
|
|
|
|
[]
|
|
|
|
end
|
|
|
|
|
|
|
|
def callbacks_for_module(module) do
|
|
|
|
{:ok, callbacks} = Code.Typespec.fetch_callbacks(module)
|
|
|
|
callbacks
|
|
|
|
rescue
|
|
|
|
_ ->
|
|
|
|
[]
|
|
|
|
end
|
|
|
|
|
|
|
|
def specs_for_module(module) do
|
|
|
|
{:ok, specs} = Code.Typespec.fetch_specs(module)
|
|
|
|
specs
|
|
|
|
rescue
|
|
|
|
_ ->
|
|
|
|
[]
|
|
|
|
end
|
|
|
|
|
|
|
|
def additional_heads_for(specs, name, arity) do
|
|
|
|
specs
|
|
|
|
|> Enum.flat_map(fn
|
|
|
|
{{^name, ^arity}, contents} ->
|
|
|
|
contents
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
[]
|
|
|
|
end)
|
|
|
|
|> Enum.map(fn body ->
|
|
|
|
name
|
|
|
|
|> Code.Typespec.spec_to_quoted(body)
|
|
|
|
|> Macro.to_string()
|
|
|
|
end)
|
|
|
|
end
|
2022-08-23 11:44:21 +12:00
|
|
|
|
2022-09-16 10:35:33 +12:00
|
|
|
def code_for(types, name, arity) do
|
|
|
|
case Enum.find_value(types, fn
|
|
|
|
{:type, {^name, _, args} = type} ->
|
|
|
|
if Enum.count(args) == arity do
|
|
|
|
type
|
|
|
|
end
|
|
|
|
|
|
|
|
_other ->
|
|
|
|
false
|
|
|
|
end) do
|
|
|
|
nil ->
|
|
|
|
""
|
|
|
|
|
|
|
|
type ->
|
|
|
|
type |> Code.Typespec.type_to_quoted() |> Macro.to_string()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2022-03-26 10:17:01 +13:00
|
|
|
|
|
|
|
defmodule Utils do
|
|
|
|
def try_apply(func, default \\ nil) do
|
|
|
|
func.()
|
|
|
|
rescue
|
|
|
|
_ ->
|
|
|
|
default
|
|
|
|
end
|
|
|
|
|
2022-03-28 10:26:35 +13:00
|
|
|
def build(extension, order) do
|
2022-03-26 10:17:01 +13:00
|
|
|
%{
|
2023-02-07 06:35:52 +13:00
|
|
|
name: extension.name,
|
2022-03-26 10:17:01 +13:00
|
|
|
target: extension[:target],
|
2023-02-07 06:35:52 +13:00
|
|
|
module: inspect(extension.module),
|
2022-03-26 10:17:01 +13:00
|
|
|
default_for_target: extension[:default_for_target?] || false,
|
|
|
|
type: extension[:type],
|
2022-03-28 10:26:35 +13:00
|
|
|
order: order,
|
|
|
|
doc: module_docs(extension.module) || "No documentation",
|
2022-03-26 10:17:01 +13:00
|
|
|
dsls: build_sections(extension.module.sections())
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
defp build_sections(sections, path \\ []) do
|
2022-03-28 10:26:35 +13:00
|
|
|
sections
|
|
|
|
|> Enum.with_index()
|
|
|
|
|> Enum.flat_map(fn {section, index} ->
|
2022-09-16 10:35:33 +12:00
|
|
|
[
|
|
|
|
%{
|
|
|
|
name: section.name,
|
|
|
|
options: schema(section.schema, path ++ [section.name]),
|
|
|
|
links: Map.new(section.links || []),
|
|
|
|
imports: Enum.map(section.imports, &inspect/1),
|
|
|
|
type: :section,
|
|
|
|
order: index,
|
2022-10-10 18:19:49 +13:00
|
|
|
doc: docs_with_examples(section.describe || "", examples(section.examples)),
|
2022-09-16 10:35:33 +12:00
|
|
|
path: path
|
|
|
|
}
|
|
|
|
] ++
|
|
|
|
build_entities(section.entities, path ++ [section.name]) ++
|
|
|
|
build_sections(section.sections, path ++ [section.name])
|
2022-03-26 10:17:01 +13:00
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2022-10-10 18:19:49 +13:00
|
|
|
defp wrap_code(text) do
|
|
|
|
"""
|
|
|
|
```elixir
|
|
|
|
#{text}
|
|
|
|
```
|
|
|
|
"""
|
|
|
|
end
|
|
|
|
|
|
|
|
defp docs_with_examples(doc, examples) do
|
|
|
|
case doc do
|
|
|
|
nil ->
|
2023-02-22 02:26:31 +13:00
|
|
|
maybe_add_examples(examples)
|
2022-10-10 18:19:49 +13:00
|
|
|
|
|
|
|
doc ->
|
|
|
|
"""
|
|
|
|
#{doc}
|
2023-02-22 02:26:31 +13:00
|
|
|
|
|
|
|
#{maybe_add_examples(examples)}
|
2022-10-10 18:19:49 +13:00
|
|
|
"""
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-22 02:26:31 +13:00
|
|
|
defp maybe_add_examples([]), do: ""
|
|
|
|
|
|
|
|
defp maybe_add_examples(examples) do
|
|
|
|
"Examples:\n\n#{Enum.map_join(examples, "\n\n", &wrap_code/1)}"
|
|
|
|
end
|
|
|
|
|
2022-03-26 10:17:01 +13:00
|
|
|
defp build_entities(entities, path) do
|
2022-03-28 10:26:35 +13:00
|
|
|
entities
|
|
|
|
|> Enum.with_index()
|
|
|
|
|> Enum.flat_map(fn {entity, index} ->
|
2022-10-26 17:53:18 +13:00
|
|
|
keys_to_remove = Enum.map(entity.auto_set_fields || [], &elem(&1, 0))
|
2022-10-19 11:34:50 +13:00
|
|
|
|
|
|
|
option_schema = Keyword.drop(entity.schema || [], keys_to_remove)
|
2022-10-26 17:53:18 +13:00
|
|
|
|
2022-09-16 10:35:33 +12:00
|
|
|
[
|
|
|
|
%{
|
|
|
|
name: entity.name,
|
|
|
|
recursive_as: Map.get(entity, :recursive_as),
|
|
|
|
order: index,
|
2022-10-10 18:19:49 +13:00
|
|
|
doc: docs_with_examples(entity.describe || "", examples(entity.examples)),
|
2022-09-16 10:35:33 +12:00
|
|
|
imports: [],
|
|
|
|
links: Map.new(entity.links || []),
|
2023-01-15 19:48:24 +13:00
|
|
|
args:
|
|
|
|
Enum.map(entity.args, fn
|
|
|
|
{:optional, name, _} ->
|
|
|
|
name
|
|
|
|
|
|
|
|
{:optional, name} ->
|
|
|
|
name
|
|
|
|
|
|
|
|
name ->
|
|
|
|
name
|
|
|
|
end),
|
|
|
|
optional_args:
|
|
|
|
Enum.flat_map(entity.args, fn
|
|
|
|
{:optional, name, _} ->
|
|
|
|
[name]
|
|
|
|
|
|
|
|
{:optional, name} ->
|
|
|
|
[name]
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
[]
|
|
|
|
end),
|
|
|
|
arg_defaults:
|
2023-01-18 15:33:49 +13:00
|
|
|
Enum.reduce(entity.args, %{}, fn
|
|
|
|
{:optional, name, default}, acc ->
|
2023-01-15 19:48:24 +13:00
|
|
|
Map.put(acc, name, inspect(default))
|
|
|
|
|
|
|
|
_, acc ->
|
|
|
|
acc
|
|
|
|
end),
|
2022-09-16 10:35:33 +12:00
|
|
|
type: :entity,
|
|
|
|
path: path,
|
2023-02-21 18:52:45 +13:00
|
|
|
options:
|
|
|
|
option_schema
|
|
|
|
|> schema(path ++ [entity.name])
|
|
|
|
|> add_argument_indices(entity.args)
|
2022-09-16 10:35:33 +12:00
|
|
|
}
|
|
|
|
] ++ build_entities(List.flatten(Keyword.values(entity.entities)), path ++ [entity.name])
|
2022-03-26 10:17:01 +13:00
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2022-07-15 07:09:04 +12:00
|
|
|
defp add_argument_indices(values, []) do
|
|
|
|
values
|
|
|
|
end
|
|
|
|
|
|
|
|
defp add_argument_indices(values, arguments) do
|
|
|
|
Enum.map(values, fn value ->
|
2023-01-18 15:33:49 +13:00
|
|
|
case Enum.find_index(arguments, fn
|
|
|
|
{:optional, name, _} ->
|
|
|
|
name == value.name
|
2023-01-15 19:48:24 +13:00
|
|
|
|
2023-01-18 15:33:49 +13:00
|
|
|
{:optional, name} ->
|
|
|
|
name == value.name
|
2023-01-15 19:48:24 +13:00
|
|
|
|
2023-01-18 15:33:49 +13:00
|
|
|
name ->
|
|
|
|
name == value.name
|
|
|
|
end) do
|
2022-07-15 07:09:04 +12:00
|
|
|
nil ->
|
|
|
|
value
|
|
|
|
|
|
|
|
arg_index ->
|
|
|
|
Map.put(value, :argument_index, arg_index)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2022-03-26 10:17:01 +13:00
|
|
|
defp examples(examples) do
|
2022-09-16 10:35:33 +12:00
|
|
|
Enum.map(examples, fn
|
|
|
|
{title, example} ->
|
|
|
|
"#{title}<>\n<>#{example}"
|
2022-03-26 10:17:01 +13:00
|
|
|
|
|
|
|
example ->
|
|
|
|
example
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp schema(schema, path) do
|
2022-03-28 10:26:35 +13:00
|
|
|
schema
|
2023-02-22 02:49:56 +13:00
|
|
|
|> Enum.reject(fn {key, config} ->
|
|
|
|
config[:hide]
|
|
|
|
end)
|
2022-03-28 10:26:35 +13:00
|
|
|
|> Enum.with_index()
|
|
|
|
|> Enum.map(fn {{name, value}, index} ->
|
2022-03-26 10:17:01 +13:00
|
|
|
%{
|
|
|
|
name: name,
|
|
|
|
path: path,
|
2022-03-28 10:26:35 +13:00
|
|
|
order: index,
|
2022-07-26 13:24:43 +12:00
|
|
|
links: Map.new(value[:links] || []),
|
2022-03-26 10:17:01 +13:00
|
|
|
type: value[:type_name] || type(value[:type]),
|
2023-02-21 18:52:45 +13:00
|
|
|
doc: add_default(value[:doc] || "No documentation", value[:default]),
|
2022-03-26 10:17:01 +13:00
|
|
|
required: value[:required] || false,
|
|
|
|
default: inspect(value[:default])
|
|
|
|
}
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2023-02-21 18:52:45 +13:00
|
|
|
defp add_default(docs, nil), do: docs
|
|
|
|
|
|
|
|
defp add_default(docs, default) do
|
|
|
|
"#{docs} Defaults to `#{inspect(default)}`."
|
|
|
|
end
|
|
|
|
|
2022-03-28 10:26:35 +13:00
|
|
|
def module_docs(module) do
|
|
|
|
{:docs_v1, _, :elixir, _, %{"en" => docs}, _, _} = Code.fetch_docs(module)
|
|
|
|
docs
|
|
|
|
rescue
|
2022-09-16 10:35:33 +12:00
|
|
|
_ -> ""
|
2022-03-28 10:26:35 +13:00
|
|
|
end
|
|
|
|
|
2022-09-16 10:35:33 +12:00
|
|
|
def build_function({_, _, _, :hidden, _}, _, _, _, _, _), do: []
|
|
|
|
|
|
|
|
def build_function(
|
2023-02-17 00:58:48 +13:00
|
|
|
{{type, name, arity}, line, heads, docs, metadata},
|
2022-09-16 10:35:33 +12:00
|
|
|
file,
|
|
|
|
types,
|
|
|
|
callbacks,
|
|
|
|
specs,
|
|
|
|
order
|
|
|
|
)
|
|
|
|
when not is_nil(type) and not is_nil(name) and not is_nil(arity) do
|
|
|
|
docs =
|
|
|
|
case docs do
|
|
|
|
%{"en" => en} ->
|
|
|
|
en
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
""
|
|
|
|
end
|
|
|
|
|
|
|
|
heads = List.wrap(heads)
|
|
|
|
|
|
|
|
heads =
|
|
|
|
case type do
|
|
|
|
:type ->
|
|
|
|
case Types.code_for(types, name, arity) do
|
|
|
|
"" ->
|
|
|
|
heads
|
|
|
|
|
|
|
|
head ->
|
|
|
|
[head | heads]
|
|
|
|
end
|
|
|
|
|
|
|
|
:callback ->
|
|
|
|
heads ++ Types.additional_heads_for(callbacks, name, arity)
|
|
|
|
|
|
|
|
:function ->
|
|
|
|
heads ++ Types.additional_heads_for(specs, name, arity)
|
|
|
|
|
|
|
|
:macro ->
|
|
|
|
heads ++ Types.additional_heads_for(specs, name, arity)
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
heads
|
|
|
|
end
|
|
|
|
|
|
|
|
heads =
|
|
|
|
heads
|
|
|
|
|> Enum.map(fn head ->
|
|
|
|
head
|
|
|
|
|> Code.format_string!(line_length: 68)
|
|
|
|
|> IO.iodata_to_binary()
|
|
|
|
end)
|
|
|
|
|> case do
|
|
|
|
[] ->
|
|
|
|
["#{name}/#{arity}"]
|
|
|
|
|
|
|
|
heads ->
|
|
|
|
heads
|
|
|
|
end
|
|
|
|
|
|
|
|
docs = """
|
|
|
|
```elixir
|
|
|
|
#{Enum.join(heads, "\n")}
|
|
|
|
```
|
|
|
|
|
|
|
|
<!--- heads-end -->
|
|
|
|
|
|
|
|
#{docs}
|
|
|
|
"""
|
|
|
|
|
|
|
|
[
|
|
|
|
%{
|
|
|
|
name: to_string(name),
|
|
|
|
type: type,
|
|
|
|
file: file,
|
|
|
|
line: line,
|
|
|
|
arity: arity,
|
|
|
|
order: order,
|
2023-02-17 00:58:48 +13:00
|
|
|
doc: docs || "No documentation",
|
|
|
|
deprecated: Map.get(metadata, :deprecated)
|
2022-09-16 10:35:33 +12:00
|
|
|
}
|
|
|
|
]
|
2022-03-30 17:40:17 +13:00
|
|
|
end
|
|
|
|
|
2022-09-16 10:35:33 +12:00
|
|
|
def build_function(_, _, _, _, _, _), do: []
|
2022-03-30 17:40:17 +13:00
|
|
|
|
2022-08-29 00:42:24 +12:00
|
|
|
def build_module(module, category, order) do
|
2023-01-19 03:13:12 +13:00
|
|
|
case Code.fetch_docs(module) do
|
|
|
|
{:docs_v1, _, :elixir, _, docs, _, defs} ->
|
|
|
|
module_doc =
|
|
|
|
case docs do
|
|
|
|
%{"en" => en} ->
|
|
|
|
en
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
""
|
|
|
|
end
|
2022-07-15 07:09:04 +12:00
|
|
|
|
2023-01-19 03:13:12 +13:00
|
|
|
module_info =
|
|
|
|
try do
|
|
|
|
module.module_info(:compile)
|
|
|
|
rescue
|
|
|
|
_ ->
|
|
|
|
nil
|
|
|
|
end
|
2022-07-15 07:09:04 +12:00
|
|
|
|
2023-01-19 03:13:12 +13:00
|
|
|
file = file(module_info[:source])
|
|
|
|
|
|
|
|
types = Types.for_module(module)
|
|
|
|
callbacks = Types.callbacks_for_module(module)
|
|
|
|
typespecs = Types.specs_for_module(module)
|
|
|
|
|
2023-01-19 05:22:12 +13:00
|
|
|
{:ok,
|
|
|
|
%{
|
|
|
|
name: inspect(module),
|
|
|
|
doc: module_doc,
|
|
|
|
file: file,
|
|
|
|
order: order,
|
|
|
|
category: category,
|
|
|
|
functions:
|
|
|
|
defs
|
|
|
|
|> Enum.with_index()
|
|
|
|
|> Enum.flat_map(fn {definition, i} ->
|
|
|
|
build_function(definition, file, types, callbacks, typespecs, i)
|
|
|
|
end)
|
|
|
|
}}
|
|
|
|
|
2023-01-19 03:13:12 +13:00
|
|
|
_ ->
|
|
|
|
:error
|
|
|
|
end
|
2022-03-30 17:40:17 +13:00
|
|
|
end
|
|
|
|
|
2022-09-30 05:56:07 +13:00
|
|
|
def build_mix_task(mix_task, category, order) do
|
|
|
|
{:docs_v1, _, :elixir, _, docs, _, _} = Code.fetch_docs(mix_task)
|
|
|
|
|
|
|
|
module_doc =
|
|
|
|
case docs do
|
|
|
|
%{"en" => en} ->
|
|
|
|
en
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
""
|
|
|
|
end
|
|
|
|
|
|
|
|
module_info =
|
|
|
|
try do
|
|
|
|
mix_task.module_info(:compile)
|
|
|
|
rescue
|
|
|
|
_ ->
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
|
|
|
file = file(module_info[:source])
|
|
|
|
|
|
|
|
%{
|
|
|
|
name: Mix.Task.task_name(mix_task),
|
|
|
|
doc: module_doc,
|
|
|
|
file: file,
|
|
|
|
order: order,
|
|
|
|
category: category
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2022-07-15 07:09:04 +12:00
|
|
|
defp file(nil), do: nil
|
2022-09-16 10:35:33 +12:00
|
|
|
|
2022-07-15 07:09:04 +12:00
|
|
|
defp file(path) do
|
|
|
|
this_path = Path.split(__ENV__.file)
|
|
|
|
compile_path = Path.split(path)
|
|
|
|
|
|
|
|
this_path
|
|
|
|
|> remove_shared_root(compile_path)
|
|
|
|
|> Enum.drop_while(&(&1 != "deps"))
|
|
|
|
|> Enum.drop(2)
|
|
|
|
|> case do
|
|
|
|
[] ->
|
|
|
|
nil
|
|
|
|
|
|
|
|
other ->
|
|
|
|
Path.join(other)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defp remove_shared_root([left | left_rest], [left | right_rest]) do
|
|
|
|
remove_shared_root(left_rest, right_rest)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp remove_shared_root(_, remaining), do: remaining
|
|
|
|
|
2022-03-26 10:17:01 +13:00
|
|
|
defp type({:behaviour, mod}), do: Module.split(mod) |> List.last()
|
2022-08-20 04:35:07 +12:00
|
|
|
defp type({:spark, mod}), do: Module.split(mod) |> List.last()
|
|
|
|
defp type({:spark_behaviour, mod}), do: Module.split(mod) |> List.last()
|
|
|
|
defp type({:spark_behaviour, mod, _builtins}), do: Module.split(mod) |> List.last()
|
2022-12-15 21:28:03 +13:00
|
|
|
defp type(:module), do: "Module"
|
2022-11-01 03:34:24 +13:00
|
|
|
|
|
|
|
defp type({:spark_function_behaviour, mod, {_, arity}}) do
|
|
|
|
type({:or, [{:fun, arity}, {:spark_behaviour, mod}]})
|
|
|
|
end
|
|
|
|
|
|
|
|
defp type({:spark_function_behaviour, mod, _builtins, {_, arity}}) do
|
|
|
|
type({:or, [{:fun, arity}, {:spark_behaviour, mod}]})
|
|
|
|
end
|
|
|
|
|
2022-03-26 10:17:01 +13:00
|
|
|
defp type({:custom, _, _, _}), do: "any"
|
|
|
|
defp type(:any), do: "any"
|
|
|
|
defp type(:keyword_list), do: "Keyword List"
|
|
|
|
defp type({:keyword_list, _schema}), do: "Keyword List"
|
|
|
|
defp type(:non_empty_keyword_list), do: "Keyword List"
|
|
|
|
defp type(:atom), do: "Atom"
|
|
|
|
defp type(:string), do: "String"
|
|
|
|
defp type(:boolean), do: "Boolean"
|
|
|
|
defp type(:integer), do: "Integer"
|
|
|
|
defp type(:non_neg_integer), do: "Non Negative Integer"
|
|
|
|
defp type(:pos_integer), do: "Positive Integer"
|
|
|
|
defp type(:float), do: "Float"
|
|
|
|
defp type(:timeout), do: "Timeout"
|
|
|
|
defp type(:pid), do: "Pid"
|
|
|
|
defp type(:mfa), do: "MFA"
|
|
|
|
defp type(:mod_arg), do: "Module and Arguments"
|
|
|
|
defp type({:fun, arity}), do: "Function/#{arity}"
|
|
|
|
defp type({:one_of, choices}), do: type({:in, choices})
|
|
|
|
defp type({:in, choices}), do: Enum.map_join(choices, " | ", &inspect/1)
|
|
|
|
defp type({:or, subtypes}), do: Enum.map_join(subtypes, " | ", &type/1)
|
|
|
|
defp type({:list, subtype}), do: type(subtype) <> "[]"
|
2022-10-10 18:19:49 +13:00
|
|
|
defp type({:list_of, subtype}), do: type({:list, subtype})
|
2022-07-15 07:09:04 +12:00
|
|
|
defp type({:mfa_or_fun, arity}), do: "MFA | function/#{arity}"
|
|
|
|
defp type(:literal), do: "any literal"
|
|
|
|
defp type({:tagged_tuple, tag, type}), do: "{:#{tag}, #{type(type)}}"
|
2022-08-20 04:35:07 +12:00
|
|
|
defp type({:spark_type, type, _}), do: inspect(type)
|
|
|
|
defp type({:spark_type, type, _, _}), do: inspect(type)
|
2022-03-28 10:26:35 +13:00
|
|
|
|
2023-01-18 15:33:49 +13:00
|
|
|
def modules_for(all_modules, modules) do
|
|
|
|
case modules do
|
|
|
|
%Regex{} = regex ->
|
|
|
|
Enum.filter(all_modules, fn module ->
|
|
|
|
Regex.match?(regex, inspect(module)) || Regex.match?(regex, to_string(module))
|
|
|
|
end)
|
2022-03-26 10:17:01 +13:00
|
|
|
|
2023-01-18 15:33:49 +13:00
|
|
|
other ->
|
|
|
|
if is_list(modules) do
|
|
|
|
Enum.flat_map(modules, &modules_for(all_modules, &1))
|
|
|
|
else
|
2023-01-24 02:23:09 +13:00
|
|
|
other
|
|
|
|
|> List.wrap()
|
|
|
|
|> Enum.map(&Module.concat(List.wrap(&1)))
|
2023-01-18 15:33:49 +13:00
|
|
|
end
|
|
|
|
end
|
2022-03-26 10:17:01 +13:00
|
|
|
end
|
|
|
|
|
2023-01-18 15:33:49 +13:00
|
|
|
def guides(mix_project, name) do
|
|
|
|
root_dir = File.cwd!()
|
2023-01-19 05:22:12 +13:00
|
|
|
|
2023-01-18 15:33:49 +13:00
|
|
|
app_dir =
|
|
|
|
name
|
|
|
|
|> Application.app_dir()
|
|
|
|
|> Path.join("../../../../deps/#{name}")
|
|
|
|
|> Path.expand()
|
|
|
|
|
|
|
|
try do
|
|
|
|
File.cd!(app_dir)
|
|
|
|
|
|
|
|
extras =
|
2023-01-26 20:07:56 +13:00
|
|
|
mix_project.project[:docs][:extras]
|
|
|
|
|> Enum.reject(fn
|
|
|
|
{name, config} ->
|
2023-01-27 04:07:32 +13:00
|
|
|
config[:ash_hq?] == false || config[:ash_hq] == false
|
2023-01-26 20:07:56 +13:00
|
|
|
|
|
|
|
_ ->
|
|
|
|
false
|
|
|
|
end)
|
|
|
|
|> Enum.map(fn
|
2023-01-19 05:22:12 +13:00
|
|
|
{file, config} ->
|
|
|
|
file = to_string(file)
|
|
|
|
|
|
|
|
config =
|
|
|
|
if config[:title] do
|
|
|
|
Keyword.put(config, :name, config[:title])
|
|
|
|
else
|
|
|
|
title =
|
|
|
|
file
|
|
|
|
|> Path.basename(".md")
|
|
|
|
|> String.split(~r/[-_]/)
|
|
|
|
|> Enum.map(&String.capitalize/1)
|
|
|
|
|> Enum.join(" ")
|
|
|
|
|> case do
|
|
|
|
"F A Q" ->
|
|
|
|
"FAQ"
|
|
|
|
|
|
|
|
other ->
|
|
|
|
other
|
|
|
|
end
|
|
|
|
|
|
|
|
Keyword.put(config, :name, title)
|
|
|
|
end
|
2022-03-26 10:17:01 +13:00
|
|
|
|
2023-01-19 05:22:12 +13:00
|
|
|
{to_string(file), config}
|
2022-03-28 10:26:35 +13:00
|
|
|
|
2023-01-18 15:33:49 +13:00
|
|
|
file ->
|
2023-01-19 05:22:12 +13:00
|
|
|
file = to_string(file)
|
|
|
|
|
2023-01-18 15:33:49 +13:00
|
|
|
title =
|
|
|
|
file
|
|
|
|
|> Path.basename(".md")
|
|
|
|
|> String.split(~r/[-_]/)
|
|
|
|
|> Enum.map(&String.capitalize/1)
|
|
|
|
|> Enum.join(" ")
|
|
|
|
|> case do
|
|
|
|
"F A Q" ->
|
|
|
|
"FAQ"
|
|
|
|
|
|
|
|
other ->
|
|
|
|
other
|
|
|
|
end
|
2022-03-28 10:26:35 +13:00
|
|
|
|
2023-01-19 05:22:12 +13:00
|
|
|
{file, name: title}
|
2022-03-28 10:26:35 +13:00
|
|
|
end)
|
|
|
|
|
2023-01-18 15:33:49 +13:00
|
|
|
groups_for_extras =
|
|
|
|
mix_project.project[:docs][:groups_for_extras]
|
|
|
|
|> List.wrap()
|
|
|
|
|> Kernel.++([{"Miscellaneous", ~r/.*/}])
|
2022-12-15 21:28:03 +13:00
|
|
|
|
2023-01-18 15:33:49 +13:00
|
|
|
groups_for_extras
|
|
|
|
|> Enum.reduce({extras, []}, fn {group, matcher}, {remaining_extras, acc} ->
|
|
|
|
matches_for_group =
|
|
|
|
matcher
|
|
|
|
|> List.wrap()
|
|
|
|
|> Enum.flat_map(&take_matching(remaining_extras, &1))
|
2023-01-19 11:56:07 +13:00
|
|
|
# only reason I'm using uniq_by here is because the module docs for uniq_by says
|
|
|
|
# "the first unique element is kept", and I need those semantics.
|
|
|
|
# uniq might be the same way, but the docs don't say it.
|
|
|
|
|> Enum.uniq_by(& &1)
|
2022-12-15 21:28:03 +13:00
|
|
|
|
2023-01-18 15:33:49 +13:00
|
|
|
{remaining_extras -- matches_for_group, [{group, matches_for_group} | acc]}
|
2022-12-15 21:28:03 +13:00
|
|
|
end)
|
2023-01-18 15:33:49 +13:00
|
|
|
|> elem(1)
|
|
|
|
|> Enum.reverse()
|
|
|
|
|> Enum.flat_map(fn {category, matches} ->
|
|
|
|
matches
|
2022-06-06 06:03:45 +12:00
|
|
|
|> Enum.with_index()
|
2023-01-18 15:33:49 +13:00
|
|
|
|> Enum.map(fn {{path, config}, index} ->
|
|
|
|
route =
|
|
|
|
path
|
|
|
|
|> Path.absname()
|
|
|
|
|> String.trim_leading(app_dir)
|
|
|
|
|> String.trim_leading("/documentation/")
|
|
|
|
|> String.trim_trailing(".md")
|
|
|
|
|
|
|
|
config
|
|
|
|
|> Map.new()
|
|
|
|
|> Map.put(:order, index)
|
|
|
|
|> Map.put(:route, route)
|
|
|
|
|> Map.put(:category, category)
|
|
|
|
|> Map.put(:text, File.read!(path))
|
2022-03-30 17:40:17 +13:00
|
|
|
end)
|
|
|
|
end)
|
2023-01-18 15:33:49 +13:00
|
|
|
after
|
|
|
|
File.cd!(root_dir)
|
|
|
|
end
|
|
|
|
end
|
2022-03-30 17:40:17 +13:00
|
|
|
|
2023-01-18 15:33:49 +13:00
|
|
|
defp take_matching(extras, matcher) when is_binary(matcher) do
|
|
|
|
extras
|
|
|
|
|> Enum.filter(fn {name, _} ->
|
|
|
|
name == matcher
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp take_matching(extras, %Regex{} = matcher) do
|
|
|
|
Enum.filter(extras, fn {name, _} -> Regex.match?(matcher, name) end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
acc = %{
|
|
|
|
extensions: [],
|
|
|
|
guides: Utils.guides(mix_project, String.to_atom(name)),
|
|
|
|
modules: [],
|
2023-01-18 17:53:21 +13:00
|
|
|
mix_tasks: []
|
2023-01-18 15:33:49 +13:00
|
|
|
}
|
|
|
|
|
2023-01-19 05:22:12 +13:00
|
|
|
extensions =
|
|
|
|
mix_project.project[:docs][:spark][:extensions] || mix_project.project[:docs][:spark_extensions]
|
2023-01-18 15:33:49 +13:00
|
|
|
|
|
|
|
{:ok, all_modules} =
|
|
|
|
name
|
|
|
|
|> String.to_atom()
|
|
|
|
|> :application.get_key(:modules)
|
|
|
|
|
|
|
|
all_modules =
|
|
|
|
all_modules
|
|
|
|
|> Kernel.||([])
|
|
|
|
|> Enum.reject(fn module ->
|
2023-01-18 17:53:21 +13:00
|
|
|
Enum.find(extensions || [], &(&1.module == module))
|
2023-01-18 15:33:49 +13:00
|
|
|
end)
|
|
|
|
|
|
|
|
all_modules =
|
2023-01-19 16:03:23 +13:00
|
|
|
all_modules
|
|
|
|
|> Enum.filter(fn module ->
|
2023-01-18 15:33:49 +13:00
|
|
|
case Code.fetch_docs(module) do
|
2023-01-19 16:03:23 +13:00
|
|
|
{:docs_v1, _, _, _, :none, _, _} ->
|
|
|
|
false
|
|
|
|
|
2023-01-18 15:33:49 +13:00
|
|
|
{:docs_v1, _, _, _, type, _, _} when type != :hidden ->
|
|
|
|
true
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
|
|
acc =
|
|
|
|
mix_project.project[:docs][:groups_for_modules]
|
2023-01-19 05:22:12 +13:00
|
|
|
|> Enum.reject(fn
|
|
|
|
{"Internals", _} ->
|
|
|
|
true
|
|
|
|
|
2023-01-19 16:03:23 +13:00
|
|
|
{:Internals, _} ->
|
|
|
|
true
|
|
|
|
|
2023-01-18 18:03:28 +13:00
|
|
|
_ ->
|
|
|
|
false
|
|
|
|
end)
|
2023-01-18 15:33:49 +13:00
|
|
|
|> Enum.reduce(acc, fn {category, modules}, acc ->
|
2023-01-19 05:22:12 +13:00
|
|
|
modules = Utils.modules_for(all_modules, modules)
|
2023-01-18 15:33:49 +13:00
|
|
|
|
|
|
|
modules
|
|
|
|
|> Enum.with_index()
|
|
|
|
|> Enum.reduce(acc, fn {module, order}, acc ->
|
2023-01-19 03:13:12 +13:00
|
|
|
case Utils.build_module(module, category, order) do
|
|
|
|
{:ok, built} ->
|
|
|
|
Map.update!(acc, :modules, fn modules ->
|
|
|
|
[built | modules]
|
|
|
|
end)
|
2023-01-19 05:22:12 +13:00
|
|
|
|
2023-01-19 03:13:12 +13:00
|
|
|
_ ->
|
|
|
|
acc
|
|
|
|
end
|
2023-01-18 15:33:49 +13:00
|
|
|
end)
|
|
|
|
end)
|
2022-09-30 05:56:07 +13:00
|
|
|
|
2023-01-18 15:33:49 +13:00
|
|
|
acc =
|
|
|
|
mix_project.project[:docs][:spark][:mix_tasks]
|
|
|
|
|> List.wrap()
|
|
|
|
|> Enum.reduce(acc, fn {category, mix_tasks}, acc ->
|
|
|
|
mix_tasks
|
|
|
|
|> Enum.with_index()
|
|
|
|
|> Enum.reduce(acc, fn {mix_task, order}, acc ->
|
|
|
|
Map.update!(acc, :mix_tasks, fn mix_tasks ->
|
|
|
|
[Utils.build_mix_task(mix_task, category, order) | mix_tasks]
|
2022-03-28 10:26:35 +13:00
|
|
|
end)
|
2023-01-18 15:33:49 +13:00
|
|
|
end)
|
|
|
|
end)
|
|
|
|
|
|
|
|
data =
|
|
|
|
extensions
|
|
|
|
|> Kernel.||([])
|
|
|
|
|> Enum.with_index()
|
|
|
|
|> Enum.reduce(acc, fn {extension, i}, acc ->
|
|
|
|
acc
|
|
|
|
|> Map.put_new(:extensions, [])
|
|
|
|
|> Map.update!(:extensions, fn extensions ->
|
|
|
|
[Utils.build(extension, i) | extensions]
|
|
|
|
end)
|
|
|
|
end)
|
2022-03-28 10:26:35 +13:00
|
|
|
|
2023-01-18 15:33:49 +13:00
|
|
|
File.write!(file, Base.encode64(:erlang.term_to_binary(data)))
|