mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
feat: basic livebook generator and mix task (#420)
This commit is contained in:
parent
ce35d41ed1
commit
3f572c65a2
5 changed files with 267 additions and 5 deletions
114
lib/ash/api/info/livebook.ex
Normal file
114
lib/ash/api/info/livebook.ex
Normal file
|
@ -0,0 +1,114 @@
|
|||
defmodule Ash.Api.Info.Livebook do
|
||||
@moduledoc """
|
||||
Generate a Livebook from a specified API.
|
||||
"""
|
||||
|
||||
# Strip Elixir off front of module
|
||||
defp module_name(module) do
|
||||
module
|
||||
|> Module.split()
|
||||
|> Enum.join(".")
|
||||
end
|
||||
|
||||
# TODO: move to Ash.Resource.Info as it's also used in diagram
|
||||
defp resource_name(resource) do
|
||||
resource
|
||||
|> Ash.Resource.Info.short_name()
|
||||
|> to_string()
|
||||
|> Macro.camelize()
|
||||
end
|
||||
|
||||
defp short_module(module) do
|
||||
module
|
||||
|> Module.split()
|
||||
|> List.last()
|
||||
end
|
||||
|
||||
defp class_short_type({:array, t}), do: "#{short_module(t)}[]"
|
||||
defp class_short_type(t), do: short_module(t)
|
||||
|
||||
def overview(apis) do
|
||||
"""
|
||||
#{for api <- apis, do: api_section(api)}
|
||||
"""
|
||||
end
|
||||
|
||||
def api_section(api) do
|
||||
"""
|
||||
# API #{module_name(api)}
|
||||
|
||||
## Class Diagram
|
||||
|
||||
```mermaid
|
||||
#{Ash.Api.Info.Diagram.mermaid_class_diagram(api) |> String.trim()}
|
||||
```
|
||||
|
||||
## ER Diagram
|
||||
|
||||
```mermaid
|
||||
#{Ash.Api.Info.Diagram.mermaid_er_diagram(api) |> String.trim()}
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
#{for resource <- Ash.Api.Info.resources(api) do
|
||||
"""
|
||||
- [#{resource_name(resource)}](##{resource_name(resource) |> String.downcase()})
|
||||
"""
|
||||
end}
|
||||
#{for resource <- Ash.Api.Info.resources(api) do
|
||||
resource_section(resource)
|
||||
end |> Enum.join("\n")}
|
||||
"""
|
||||
end
|
||||
|
||||
def resource_section(resource) do
|
||||
"""
|
||||
## #{resource_name(resource)}
|
||||
|
||||
#{Ash.Resource.Info.description(resource)}
|
||||
|
||||
### Attributes
|
||||
|
||||
#{attr_header() |> String.trim()}
|
||||
#{for attr <- Ash.Resource.Info.attributes(resource) do
|
||||
attr_section(attr)
|
||||
end |> Enum.join("\n")}
|
||||
|
||||
### Actions
|
||||
|
||||
#{action_header() |> String.trim()}
|
||||
#{for action <- Ash.Resource.Info.actions(resource) do
|
||||
action_section(action)
|
||||
end |> Enum.join("\n")}
|
||||
"""
|
||||
end
|
||||
|
||||
def attr_header do
|
||||
"""
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
"""
|
||||
end
|
||||
|
||||
def attr_section(attr) do
|
||||
"| **#{attr.name}** | #{class_short_type(attr.type)} | #{attr.description} |"
|
||||
end
|
||||
|
||||
def action_header do
|
||||
"""
|
||||
| Name | Type | Description | Args |
|
||||
| ---- | ---- | ----------- | ---- |
|
||||
"""
|
||||
end
|
||||
|
||||
def action_section(action) do
|
||||
"| **#{action.name}** | _#{action.type}_ | #{action.description} | <ul>#{action_arg_section(action)}</ul> |"
|
||||
end
|
||||
|
||||
def action_arg_section(action) do
|
||||
for arg <- action.arguments do
|
||||
"<li><b>#{arg.name}</b> <i>#{class_short_type(arg.type)}</i> #{arg.description}</li>"
|
||||
end
|
||||
end
|
||||
end
|
25
lib/mix/tasks/generate_livebook.ex
Normal file
25
lib/mix/tasks/generate_livebook.ex
Normal file
|
@ -0,0 +1,25 @@
|
|||
defmodule Mix.Tasks.Ash.GenerateLivebook do
|
||||
@moduledoc """
|
||||
Generates a Livebook for each Ash API.
|
||||
|
||||
## Command line options
|
||||
|
||||
* `--only` - only generates the given API file
|
||||
|
||||
"""
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "Generates a Livebook for each Ash API"
|
||||
def run(_argv) do
|
||||
Mix.Task.run("compile")
|
||||
|
||||
File.write!("livebook.livemd", Ash.Api.Info.Livebook.overview(apis()))
|
||||
|
||||
Mix.shell().info("Generated Livebook")
|
||||
end
|
||||
|
||||
def apis do
|
||||
Mix.Project.config()[:app]
|
||||
|> Application.get_env(:ash_apis, [])
|
||||
end
|
||||
end
|
115
test/api/livebook_test.exs
Normal file
115
test/api/livebook_test.exs
Normal file
|
@ -0,0 +1,115 @@
|
|||
defmodule Ash.Test.Api.Info.LivebookTest do
|
||||
@moduledoc false
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test "generate a livebook API section from a given API" do
|
||||
assert Ash.Api.Info.Livebook.api_section(Ash.Test.Flow.Api) ==
|
||||
"""
|
||||
# API Ash.Test.Flow.Api
|
||||
|
||||
## Class Diagram
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class User {
|
||||
UUID id
|
||||
String first_name
|
||||
String last_name
|
||||
String email
|
||||
Org org
|
||||
destroy(UUID id, String first_name, String last_name, String email)
|
||||
read()
|
||||
for_org(UUID org)
|
||||
create(UUID org, UUID id, String first_name, String last_name, ...)
|
||||
update(UUID id, String first_name, String last_name, String email)
|
||||
approve()
|
||||
unapprove()
|
||||
}
|
||||
class Org {
|
||||
UUID id
|
||||
String name
|
||||
User[] users
|
||||
destroy(UUID id, String name)
|
||||
update(UUID id, String name)
|
||||
read()
|
||||
create(UUID id, String name)
|
||||
by_name(String name)
|
||||
}
|
||||
|
||||
Org -- User
|
||||
```
|
||||
|
||||
## ER Diagram
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
User {
|
||||
UUID id
|
||||
String first_name
|
||||
String last_name
|
||||
String email
|
||||
}
|
||||
Org {
|
||||
UUID id
|
||||
String name
|
||||
}
|
||||
|
||||
Org ||--|| User : ""
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [User](#user)
|
||||
- [Org](#org)
|
||||
|
||||
## User
|
||||
|
||||
User model
|
||||
|
||||
### Attributes
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| **id** | UUID | PK |
|
||||
| **first_name** | String | User's first name |
|
||||
| **last_name** | String | User's last name |
|
||||
| **email** | String | User's email address |
|
||||
| **approved** | Boolean | Is the user approved? |
|
||||
| **org_id** | UUID | |
|
||||
|
||||
### Actions
|
||||
|
||||
| Name | Type | Description | Args |
|
||||
| ---- | ---- | ----------- | ---- |
|
||||
| **destroy** | _destroy_ | | <ul></ul> |
|
||||
| **read** | _read_ | | <ul></ul> |
|
||||
| **for_org** | _read_ | | <ul><li><b>org</b> <i>UUID</i> </li></ul> |
|
||||
| **create** | _create_ | | <ul><li><b>org</b> <i>UUID</i> </li></ul> |
|
||||
| **update** | _update_ | | <ul></ul> |
|
||||
| **approve** | _update_ | | <ul></ul> |
|
||||
| **unapprove** | _update_ | | <ul></ul> |
|
||||
|
||||
## Org
|
||||
|
||||
Org model
|
||||
|
||||
### Attributes
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| **id** | UUID | |
|
||||
| **name** | String | |
|
||||
|
||||
### Actions
|
||||
|
||||
| Name | Type | Description | Args |
|
||||
| ---- | ---- | ----------- | ---- |
|
||||
| **destroy** | _destroy_ | | <ul></ul> |
|
||||
| **update** | _update_ | | <ul></ul> |
|
||||
| **read** | _read_ | | <ul></ul> |
|
||||
| **create** | _create_ | | <ul></ul> |
|
||||
| **by_name** | _read_ | | <ul><li><b>name</b> <i>String</i> </li></ul> |
|
||||
|
||||
"""
|
||||
end
|
||||
end
|
|
@ -2,6 +2,10 @@ defmodule Ash.Test.Flow.Org do
|
|||
@moduledoc false
|
||||
use Ash.Resource, data_layer: Ash.DataLayer.Mnesia
|
||||
|
||||
resource do
|
||||
description "Org model"
|
||||
end
|
||||
|
||||
identities do
|
||||
identity :unique_name, [:name], pre_check_with: Ash.Test.Flow.Api
|
||||
end
|
||||
|
|
|
@ -2,6 +2,10 @@ defmodule Ash.Test.Flow.User do
|
|||
@moduledoc false
|
||||
use Ash.Resource, data_layer: Ash.DataLayer.Mnesia
|
||||
|
||||
resource do
|
||||
description "User model"
|
||||
end
|
||||
|
||||
actions do
|
||||
defaults [:read, :destroy]
|
||||
|
||||
|
@ -32,13 +36,13 @@ defmodule Ash.Test.Flow.User do
|
|||
end
|
||||
|
||||
attributes do
|
||||
uuid_primary_key :id
|
||||
attribute :first_name, :string
|
||||
attribute :last_name, :string
|
||||
|
||||
attribute :email, :string
|
||||
uuid_primary_key :id, description: "PK"
|
||||
attribute :first_name, :string, description: "User's first name"
|
||||
attribute :last_name, :string, description: "User's last name"
|
||||
attribute :email, :string, description: "User's email address"
|
||||
|
||||
attribute :approved, :boolean do
|
||||
description "Is the user approved?"
|
||||
private? true
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue