ash_hq/build_docs/build_dsl_docs.exs

223 lines
6 KiB
Elixir
Raw Normal View History

2022-03-26 10:17:01 +13:00
require Logger
[name, version, file, branch] = System.argv()
2022-03-28 10:26:35 +13:00
Application.put_env(:dsl, :name, name)
2022-03-26 10:17:01 +13:00
if branch == "true" do
Mix.install([
{String.to_atom(name), github: "ash-project/#{name}", ref: version}
], force: true,
system_env: [
{"MIX_QUIET", "true"}
])
else
Mix.install([
{String.to_atom(name), "== #{version}"}
], system_env: [
{"MIX_QUIET", "true"}
])
end
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
extension_name = extension.name
%{
name: extension_name,
target: extension[:target],
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-03-26 10:17:01 +13:00
[%{
name: section.name,
options: schema(section.schema, path ++ [section.name]),
2022-03-29 08:47:43 +13:00
doc: section.describe || "No documentation",
2022-03-26 10:17:01 +13:00
type: :section,
2022-03-28 10:26:35 +13:00
order: index,
2022-03-26 10:17:01 +13:00
examples: examples(section.examples),
path: path
}] ++
build_entities(section.entities, path ++ [section.name]) ++
build_sections(section.sections, path ++ [section.name])
end)
end
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-03-26 10:17:01 +13:00
[%{
name: entity.name,
recursive_as: Map.get(entity, :recursive_as),
examples: examples(entity.examples),
2022-03-28 10:26:35 +13:00
order: index,
2022-03-29 08:47:43 +13:00
doc: entity.describe || "No documentation",
2022-03-26 10:17:01 +13:00
args: entity.args,
type: :entity,
path: path,
options: schema(entity.schema, path ++ [entity.name]),
}] ++ build_entities(List.flatten(Keyword.values(entity.entities)), path ++ [entity.name])
end)
end
defp examples(examples) do
Enum.map(examples, fn {title, example} ->
"#{title}<>\n<>#{example}"
example ->
example
end)
end
defp schema(schema, path) do
2022-03-28 10:26:35 +13:00
schema
|> 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-03-26 10:17:01 +13:00
type: value[:type_name] || type(value[:type]),
2022-03-29 08:47:43 +13:00
doc: value[:doc] || "No documentation",
2022-03-26 10:17:01 +13:00
required: value[:required] || false,
default: inspect(value[:default])
}
end)
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
_ -> "No Documentation"
end
2022-03-30 17:40:17 +13:00
def build_function({{type, name, arity}, _, heads, %{"en" => docs}, _}, order) do
[%{
name: to_string(name),
type: type,
arity: arity,
order: order,
heads: heads,
doc: docs || "No documentation"
}]
end
def build_function(_, _), do: []
def build_module(module, order) do
{:docs_v1, _, :elixir, _, %{
"en" => module_doc
}, _, defs} = Code.fetch_docs(module)
%{
name: inspect(module),
doc: module_doc,
order: order,
functions: defs |> Enum.with_index() |> Enum.flat_map(fn {definition, i} ->
build_function(definition, i)
end)
}
end
2022-03-26 10:17:01 +13:00
defp type({:behaviour, mod}), do: Module.split(mod) |> List.last()
defp type({:ash_behaviour, mod}), do: Module.split(mod) |> List.last()
defp type({:ash_behaviour, mod, _builtins}), do: Module.split(mod) |> List.last()
defp type({:custom, _, _, _}), do: "any"
defp type(:ash_type), do: "Type"
defp type(:ash_resource), do: "Resource"
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-03-28 10:26:35 +13:00
def doc_index?(module) do
Ash.Helpers.implements_behaviour?(module, Ash.DocIndex) && module.for_library() == Application.get_env(:dsl, :name)
end
2022-03-26 10:17:01 +13:00
end
dsls =
for [app] <- :ets.match(:ac_tab, {{:loaded, :"$1"}, :_}),
{:ok, modules} = :application.get_key(app, :modules),
2022-03-28 10:26:35 +13:00
module <- Enum.filter(modules, &Utils.doc_index?/1) do
2022-03-26 10:17:01 +13:00
module
end
2022-03-28 10:26:35 +13:00
case Enum.at(dsls, 0) do
nil ->
File.write!(file, Base.encode64(:erlang.term_to_binary(nil)))
2022-03-26 10:17:01 +13:00
2022-03-28 10:26:35 +13:00
dsl ->
extensions = dsl.extensions()
acc = %{
2022-03-29 08:47:43 +13:00
doc: Utils.module_docs(dsl),
2022-03-30 17:40:17 +13:00
guides: [],
modules: []
2022-03-28 10:26:35 +13:00
}
acc =
Utils.try_apply(fn -> dsl.guides() end, [])
|> Enum.with_index()
|> Enum.reduce(acc, fn {guide, order}, acc ->
2022-03-29 08:47:43 +13:00
Map.update!(acc, :guides, fn guides ->
2022-03-28 10:26:35 +13:00
[Map.put(guide, :order, order) | guides]
end)
end)
2022-03-30 17:40:17 +13:00
acc =
Utils.try_apply(fn -> dsl.code_modules() end, [])
|> Enum.with_index()
|> Enum.reduce(acc, fn {module, order}, acc ->
Map.update!(acc, :modules, fn modules ->
[Utils.build_module(module, order) | modules]
end)
end)
2022-03-28 10:26:35 +13:00
data =
extensions
|> 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)
File.write!(file, Base.encode64(:erlang.term_to_binary(data)))
end