improvement: handle dsl patches in docs (sort of)

improvement: write article about macros
This commit is contained in:
Zach Daniel 2023-05-11 16:00:36 -04:00
parent e2c039f861
commit b76841a214
11 changed files with 630 additions and 106 deletions

View file

@ -77,7 +77,7 @@ defmodule AshHq.Blog.Post do
relationships do
has_many :tags, AshHq.Blog.Tag do
manual fn posts, %{query: query} ->
all_tags = Enum.flat_map(posts, & &1.tag_names)
all_tags = Enum.flat_map(posts, &(&1.tag_names || []))
tags =
query
@ -104,11 +104,12 @@ defmodule AshHq.Blog.Post do
Ash.Changeset.after_action(changeset, fn _, %{tag_names: tag_names} = record ->
all_post_tags =
__MODULE__.published!()
|> Enum.flat_map(& &1.tag_names)
|> Enum.flat_map(&(&1.tag_names || []))
|> IO.inspect()
notifications =
Enum.flat_map(
tag_names,
tag_names || [],
&elem(AshHq.Blog.Tag.upsert!(&1, return_notifications?: true), 1)
)

View file

@ -47,17 +47,20 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.PostProcessors.Highlighter do
end
end)
case lexer do
{lang, lexer, opts} ->
{:keep, render_code(lang, lexer, opts, body)}
code =
case lexer do
{lang, lexer, opts} ->
render_code(lang, lexer, opts, body)
nil ->
if find_value_class(attrs, &(&1 == "inline")) do
{:keep, maybe_highlight_module(body, libraries, current_module)}
else
{:keep, ~s(<code class="text-black dark:text-white">#{body}</code>)}
end
end
nil ->
if find_value_class(attrs, &(&1 == "inline")) do
maybe_highlight_module(body, libraries, current_module)
else
~s(<code class="text-black dark:text-white">#{body}</code>)
end
end
{:keep, code}
other ->
other
@ -423,7 +426,7 @@ defmodule AshHq.Docs.Extensions.RenderMarkdown.PostProcessors.Highlighter do
formatter_options: [highlight_tag: "span"]
)
~s(<code class="makeup #{lang} highlight">#{highlighted}</code>)
~s(<code class="not-prose makeup #{lang} highlight">#{highlighted}</code>)
end
entities = [{"&amp;", ?&}, {"&lt;", ?<}, {"&gt;", ?>}, {"&quot;", ?"}, {"&#39;", ?'}]

View file

@ -60,6 +60,8 @@ defmodule AshHq.Docs.Dsl do
attribute :name, :string do
allow_nil? false
end
attribute :requires_extension, :string
attribute :doc, :string do
allow_nil? false

View file

@ -6,13 +6,13 @@ defmodule AshHqWeb.Pages.Blog do
alias AshHqWeb.Components.Blog.Tag
prop params, :map, default: %{}
prop(params, :map, default: %{})
data post, :any, default: nil
data posts, :any, default: []
data tag, :string
data tags, :any, default: []
data slug, :string
data(post, :any, default: nil)
data(posts, :any, default: [])
data(tag, :string)
data(tags, :any, default: [])
data(slug, :string)
def render(assigns) do
~F"""
@ -28,7 +28,7 @@ defmodule AshHqWeb.Pages.Blog do
<div class="border-b">
<h1 class="mt-6 text-3xl font-semibold mb-4">{@post.title}</h1>
<div class="flex flex-row space-x-2 mb-4">
{#for tag <- @post.tag_names}
{#for tag <- @post.tag_names || []}
<Tag prefix="/blog" tag={tag} />
{/for}
</div>
@ -69,7 +69,7 @@ defmodule AshHqWeb.Pages.Blog do
{post.published_at |> DateTime.to_date()}
</div>
<div class="flex space-x-2">
{#for tag <- post.tag_names}
{#for tag <- post.tag_names || []}
<Tag prefix="/blog" tag={tag} />
{/for}
</div>

View file

@ -13,32 +13,32 @@ defmodule AshHqWeb.Pages.Docs do
require Logger
require Ash.Query
prop change_versions, :event, required: true
prop selected_versions, :map, required: true
prop libraries, :list, default: []
prop uri, :string
prop remove_version, :event
prop add_version, :event
prop change_version, :event
prop params, :map, required: true
prop show_catalogue_call_to_action, :boolean
prop(change_versions, :event, required: true)
prop(selected_versions, :map, required: true)
prop(libraries, :list, default: [])
prop(uri, :string)
prop(remove_version, :event)
prop(add_version, :event)
prop(change_version, :event)
prop(params, :map, required: true)
prop(show_catalogue_call_to_action, :boolean)
data library, :any
data docs, :any
data library_version, :any
data guide, :any
data doc_path, :list, default: []
data dsls, :list, default: []
data dsl, :any
data module, :any
data mix_task, :any
data positional_options, :list
data description, :string
data title, :string
data sidebar_data, :any
data not_found, :boolean, default: false
data dsl_target_extensions, :list
data dsl_target, :string
data(library, :any)
data(docs, :any)
data(library_version, :any)
data(guide, :any)
data(doc_path, :list, default: [])
data(dsls, :list, default: [])
data(dsl, :any)
data(module, :any)
data(mix_task, :any)
data(positional_options, :list)
data(description, :string)
data(title, :string)
data(sidebar_data, :any)
data(not_found, :boolean, default: false)
data(dsl_target_extensions, :list)
data(dsl_target, :string)
@spec render(any) :: Phoenix.LiveView.Rendered.t()
def render(assigns) do
@ -256,6 +256,11 @@ defmodule AshHqWeb.Pages.Docs do
</div>
</div>
</div>
{#if dsl.requires_extension}
<div class="w-full pt-4">
Requires Extension: <span class="text-primary-light-600">{dsl.requires_extension}</span>
</div>
{/if}
<div class="prose dark:prose-invert my-4">
{raw(dsl.doc_html)}

View file

@ -1,11 +1,11 @@
%{
"absinthe": {:hex, :absinthe, "1.7.0", "36819e7b1fd5046c9c734f27fe7e564aed3bda59f0354c37cd2df88fd32dd014", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "566a5b5519afc9b29c4d367f0c6768162de3ec03e9bf9916f9dc2bcbe7c09643"},
"absinthe_plug": {:hex, :absinthe_plug, "1.5.8", "38d230641ba9dca8f72f1fed2dfc8abd53b3907d1996363da32434ab6ee5d6ab", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bbb04176647b735828861e7b2705465e53e2cf54ccf5a73ddd1ebd855f996e5a"},
"ash": {:git, "https://github.com/ash-project/ash.git", "3d8f988e130a65dddbb4cbb397cc4750cc3502a0", []},
"ash": {:git, "https://github.com/ash-project/ash.git", "ccacfd78fcc51523603b2799d864931f916f28b4", []},
"ash_admin": {:git, "https://github.com/ash-project/ash_admin.git", "3002af9ec69dc475582ef5f445064e4594bf45ac", []},
"ash_authentication": {:hex, :ash_authentication, "3.10.5", "8a5e9b4b6887c8f6ddd44763dd1ce11fd6db1376e11cfa90dbbc24a72ee2ab2b", [:mix], [{:ash, ">= 2.5.11 and < 3.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:assent, "~> 0.2", [hex: :assent, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.5", [hex: :joken, repo: "hexpm", optional: false]}, {:mint, "~> 1.4", [hex: :mint, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}, {:spark, ">= 0.4.1 and < 1.0.0-0 or ~> 1.0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "6a7d5a64ce8afed4d13231b3964e79870c3de5d1d33b7311eb5173d9a64ceef0"},
"ash_authentication_phoenix": {:hex, :ash_authentication_phoenix, "1.7.0", "2dad9b819ea91893297417762be7cf61204520caaa8597fdfe49e5002070c76c", [:mix], [{:ash, "~> 2.2", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_authentication, "~> 3.10", [hex: :ash_authentication, repo: "hexpm", optional: false]}, {:ash_phoenix, "~> 1.1", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:slugify, "~> 1.3", [hex: :slugify, repo: "hexpm", optional: false]}], "hexpm", "bcb30999d214e12aeaceb31fe71bda0d12ec398c6195ccdd85589d11829bacca"},
"ash_blog": {:git, "https://github.com/ash-project/ash_blog.git", "9254773dfedabfc7987af6326a62885c24c3655b", []},
"ash_blog": {:git, "https://github.com/ash-project/ash_blog.git", "5d185eccd1ffad084642aa80c25af3d895a70be9", []},
"ash_csv": {:git, "https://github.com/ash-project/ash_csv.git", "7f47b820077619ccb7340fbede0c4fd1b5313a17", []},
"ash_graphql": {:git, "https://github.com/ash-project/ash_graphql.git", "2a9baf81646f242c1c17f2d28a381e4e02bea21e", []},
"ash_json_api": {:git, "https://github.com/ash-project/ash_json_api.git", "9663ddc68ec2384cc3412a997eacdb6e3f4c29d8", []},

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1 @@
name
ash
release
elixir
community
name

1 name
ash
release
elixir
community

View file

@ -0,0 +1,21 @@
defmodule AshHq.Repo.Migrations.MigrateResources52 do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
alter table(:dsls) do
add :requires_extension, :text
end
end
def down do
alter table(:dsls) do
remove :requires_extension
end
end
end

View file

@ -0,0 +1,301 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "sanitized_path",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"uuid_generate_v4()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "requires_extension",
"type": "text"
},
{
"allow_nil?": false,
"default": "\"\"",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "doc",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "doc_html",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "imports",
"type": [
"array",
"text"
]
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "examples",
"type": [
"array",
"text"
]
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "args",
"type": [
"array",
"text"
]
},
{
"allow_nil?": true,
"default": "[]",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "optional_args",
"type": [
"array",
"text"
]
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "arg_defaults",
"type": "map"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "path",
"type": [
"array",
"text"
]
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "recursive_as",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "order",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "type",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"now()\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"now()\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "dsls_library_version_id_fkey",
"on_delete": "delete",
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "library_versions"
},
"size": null,
"source": "library_version_id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "dsls_extension_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "extensions"
},
"size": null,
"source": "extension_id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "dsls_dsl_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "dsls"
},
"size": null,
"source": "dsl_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [
{
"concurrently": false,
"fields": [
"searchable"
],
"include": null,
"message": null,
"name": null,
"prefix": null,
"table": null,
"unique": false,
"using": "GIN",
"where": null
}
],
"custom_statements": [
{
"code?": false,
"down": "ALTER TABLE dsls\n DROP COLUMN searchable\n",
"name": "search_column",
"up": "ALTER TABLE dsls\n ADD COLUMN searchable tsvector\n GENERATED ALWAYS AS (\n setweight(to_tsvector('english', name), 'A') ||\n setweight(to_tsvector('english', doc), 'D')\n ) STORED;\n"
}
],
"has_create_action": true,
"hash": "7518001C6D3973E39FF61BD6EB92201BE1EE57714307E7CDB31593535C82382E",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.AshHq.Repo",
"schema": null,
"table": "dsls"
}

View file

@ -105,7 +105,7 @@ defmodule Utils do
default
end
def build(extension, order) do
def build(extension, all_extensions, order) do
%{
name: extension.name,
target: extension[:target],
@ -114,18 +114,37 @@ defmodule Utils do
type: extension[:type],
order: order,
doc: module_docs(extension.module) || "No documentation",
dsls: build_sections(extension.module.sections())
dsls: build_sections(extension.module.sections(), extension.module, all_extensions)
}
end
defp build_sections(sections, path \\ []) do
defp build_sections(sections, this_extension_module, all_extensions, path \\ []) do
sections
|> Enum.with_index()
|> Enum.flat_map(fn {section, index} ->
section_path = path ++ [section.name]
entities =
all_extensions
|> Enum.flat_map(fn extension ->
extension.module.dsl_patches()
|> Enum.filter(&match?(%Spark.Dsl.Patch.AddEntity{section_path: ^section_path}, &1))
|> Enum.map(fn %{entity: entity} ->
if extension.module == this_extension_module do
entity
else
Map.put(entity, :__requires_extension__, inspect(extension.module))
end
end)
end)
|> then(fn entities ->
Enum.concat(section.entities, entities)
end)
[
%{
name: section.name,
options: schema(section.schema, path ++ [section.name]),
options: schema(section.schema, section_path),
links: Map.new(section.links || []),
imports: Enum.map(section.imports, &inspect/1),
type: :section,
@ -134,8 +153,8 @@ defmodule Utils do
path: path
}
] ++
build_entities(section.entities, path ++ [section.name]) ++
build_sections(section.sections, path ++ [section.name])
build_entities(entities, section_path) ++
build_sections(section.sections, this_extension_module, all_extensions, section_path)
end)
end
@ -171,56 +190,67 @@ defmodule Utils do
entities
|> Enum.with_index()
|> Enum.flat_map(fn {entity, index} ->
keys_to_remove = Enum.map(entity.auto_set_fields || [], &elem(&1, 0))
[build_entity(entity, path, index)] ++
build_entities(List.flatten(Keyword.values(entity.entities)), path ++ [entity.name])
end)
end
option_schema = Keyword.drop(entity.schema || [], keys_to_remove)
defp build_entity(entity, path, index) do
keys_to_remove = Enum.map(entity.auto_set_fields || [], &elem(&1, 0))
option_schema = Keyword.drop(entity.schema || [], keys_to_remove)
[
%{
name: entity.name,
recursive_as: Map.get(entity, :recursive_as),
order: index,
doc: docs_with_examples(entity.describe || "", examples(entity.examples)),
imports: [],
links: Map.new(entity.links || []),
args:
Enum.map(entity.args, fn
{:optional, name, _} ->
name
%{
name: entity.name,
recursive_as: Map.get(entity, :recursive_as),
doc: docs_with_examples(entity.describe || "", examples(entity.examples)),
order: index,
imports: [],
links: Map.new(entity.links || []),
args:
Enum.map(entity.args, fn
{:optional, name, _} ->
name
{:optional, name} ->
name
{:optional, name} ->
name
name ->
name
end),
optional_args:
Enum.flat_map(entity.args, fn
{:optional, name, _} ->
[name]
name ->
name
end),
optional_args:
Enum.flat_map(entity.args, fn
{:optional, name, _} ->
[name]
{:optional, name} ->
[name]
{:optional, name} ->
[name]
_ ->
[]
end),
arg_defaults:
Enum.reduce(entity.args, %{}, fn
{:optional, name, default}, acc ->
Map.put(acc, name, inspect(default))
_ ->
[]
end),
arg_defaults:
Enum.reduce(entity.args, %{}, fn
{:optional, name, default}, acc ->
Map.put(acc, name, inspect(default))
_, acc ->
acc
end),
type: :entity,
path: path,
options:
option_schema
|> schema(path ++ [entity.name])
|> add_argument_indices(entity.args)
}
] ++ build_entities(List.flatten(Keyword.values(entity.entities)), path ++ [entity.name])
_, acc ->
acc
end),
type: :entity,
path: path,
options:
option_schema
|> schema(path ++ [entity.name])
|> add_argument_indices(entity.args)
}
|> then(fn config ->
case Map.fetch(entity, :__requires_extension__) do
{:ok, value} ->
Map.put(config, :requires_extension, value)
:error ->
config
end
end)
end
@ -271,7 +301,7 @@ defmodule Utils do
path: path,
order: index,
links: Map.new(value[:links] || []),
type: value[:type_name] || type(value[:type]),
type: value[:type_name] || type(value[:type] |> IO.inspect()),
doc: add_default(value[:doc] || "No documentation", value[:default]),
required: value[:required] || false,
default: inspect(value[:default])
@ -511,6 +541,8 @@ defmodule Utils do
defp type(:pid), do: "Pid"
defp type(:mfa), do: "MFA"
defp type(:mod_arg), do: "Module and Arguments"
defp type(:map), do: "Map"
defp type(nil), do: "nil"
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)
@ -752,8 +784,8 @@ data =
|> Enum.reduce(acc, fn {extension, i}, acc ->
acc
|> Map.put_new(:extensions, [])
|> Map.update!(:extensions, fn extensions ->
[Utils.build(extension, i) | extensions]
|> Map.update!(:extensions, fn acc ->
[Utils.build(extension, extensions, i) | acc]
end)
end)