Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,8 @@ config :lightning, :gdpr_banner, false
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{config_env()}.exs"

# config docout
config :docout,
app_name: :lightning,
formatters: [LightningWeb.DocoutFormatter]
1 change: 1 addition & 0 deletions lib/lightning_web/controllers/api/credential_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ defmodule LightningWeb.API.CredentialController do
POST /api/credentials
DELETE /api/credentials/a1b2c3d4-...
"""
@moduledoc docout: true
use LightningWeb, :controller

alias Lightning.Credentials
Expand Down
1 change: 1 addition & 0 deletions lib/lightning_web/controllers/api/job_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ defmodule LightningWeb.API.JobController do
GET /api/jobs?project_id=a1b2c3d4-...&page=1&page_size=20
GET /api/jobs/a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
"""
@moduledoc docout: true
use LightningWeb, :controller

alias Lightning.Jobs
Expand Down
2 changes: 2 additions & 0 deletions lib/lightning_web/controllers/api/workflows_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ defmodule LightningWeb.API.WorkflowsController do
POST /api/workflows
PATCH /api/workflows/a1b2c3d4-...
"""
@moduledoc docout: true

use LightningWeb, :controller

import Lightning.Workflows.WorkflowUsageLimiter,
Expand Down
177 changes: 177 additions & 0 deletions lib/lightning_web/docout_formatter.ex
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is partly AI generated (80% ish). I am not sure what most of these codes mean but it does the job.
This is how i extract @moduledoc and @doc comments from API Controllers like workflows_controller.ex

The goal is to link these docs as description in postman collection requests

Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
defmodule LightningWeb.DocoutFormatter do
@moduledoc """
Docout formatter that extracts module and function docs to JSON.

Processes documentation from modules tagged with `@moduledoc docout: true`
and converts them into a structured JSON format suitable for API
documentation generation.

## Output Format

The formatter generates JSON with the following structure:

[
{
"module": "Elixir.MyModule",
"moduledoc": "Module documentation string",
"functions": [
{
"name": "my_function",
"arity": 2,
"doc": "Function documentation",
"metadata": {}
}
]
}
]

## Usage

This formatter is automatically invoked by the Docout library when
running documentation generation tasks:

mix docs.generate

The output is written to `priv/static/docs.json`.
"""

use Docout, output_path: "priv/static/docs.json"

@type doc_entry :: {module(), moduledoc(), metadata(), functions()}
@type moduledoc :: map() | binary() | :none | :hidden | nil
@type metadata :: map()
@type functions :: [function_entry()]
@type function_entry ::
{{:function, atom(), non_neg_integer()}, doc_content(), metadata()}
@type doc_content :: map() | binary() | :none | :hidden | nil

@doc """
Formats the documentation list into JSON.

Takes a list of documentation entries from Docout and converts them
into a pretty-printed JSON string.

## Parameters

- doc_list: List of documentation tuples from Docout

## Returns

Pretty-printed JSON string of all documentation

## Examples

iex> format([{MyModule, %{}, %{}, []}])
~s([{\\n "module": "Elixir.MyModule",\\n ...}])

"""
@spec format([doc_entry()]) :: String.t()
def format(doc_list) do
doc_list
|> Enum.map(&format_module/1)
|> Jason.encode!(pretty: true)
end

# Formats a module documentation tuple into a structured map.
# Structure: {module, moduledoc_map, metadata_map, function_list}
@spec format_module(doc_entry()) :: map()
defp format_module({module, moduledoc, _metadata, functions}) do
%{
module: inspect(module),
moduledoc: extract_moduledoc(moduledoc),
functions:
functions
|> Enum.map(&format_function/1)
|> Enum.reject(&is_nil/1)
}
end

# Formats a function documentation entry into a map.
# Returns nil for hidden functions or non-function entries.
@spec format_function(function_entry() | any()) :: map() | nil
defp format_function({{:function, _name, _arity}, :hidden, _metadata}) do
nil
end

defp format_function({{:function, name, arity}, doc, metadata}) do
%{
name: to_string(name),
arity: arity,
doc: extract_doc(doc),
metadata: sanitize_metadata(metadata || %{})
}
end

# Skip non-function entries (like :macro, :type, etc)
defp format_function(_), do: nil

# Converts metadata to JSON-safe format
@spec sanitize_metadata(map() | any()) :: map()
defp sanitize_metadata(metadata) when is_map(metadata) do
metadata
|> Enum.map(fn {key, value} -> {key, sanitize_value(value)} end)
|> Enum.into(%{})
end

defp sanitize_metadata(_), do: %{}

# Converts various Elixir types to JSON-safe values
@spec sanitize_value(any()) :: any()
defp sanitize_value(value) when is_binary(value), do: value
defp sanitize_value(value) when is_number(value), do: value
defp sanitize_value(value) when is_boolean(value), do: value
defp sanitize_value(value) when is_atom(value), do: to_string(value)

defp sanitize_value(value) when is_list(value) do
# Check if it's a charlist or regular list
if charlist?(value) do
to_string(value)
else
Enum.map(value, &sanitize_value/1)
end
end

defp sanitize_value(value) when is_tuple(value) do
value
|> Tuple.to_list()
|> Enum.map(&sanitize_value/1)
end

defp sanitize_value(value) when is_map(value) do
value
|> Enum.map(fn {k, v} ->
{sanitize_value(k), sanitize_value(v)}
end)
|> Enum.into(%{})
end

defp sanitize_value(_), do: nil

# Checks if a list is a charlist (printable ASCII)
@spec charlist?(list()) :: boolean()
defp charlist?(value) do
Enum.all?(value, &is_integer/1) and List.ascii_printable?(value)
end

# Extracts moduledoc content from various formats
@spec extract_moduledoc(moduledoc()) :: String.t() | nil
defp extract_moduledoc({:docs_v1, _, _, _, %{"en" => doc}, _, _}) do
doc
end

defp extract_moduledoc(%{"en" => doc}), do: doc
defp extract_moduledoc(doc) when is_binary(doc), do: doc
defp extract_moduledoc(nil), do: nil
defp extract_moduledoc(:none), do: nil
defp extract_moduledoc(:hidden), do: nil
defp extract_moduledoc(_), do: nil

# Extracts function doc content from various formats
@spec extract_doc(doc_content()) :: String.t() | nil
defp extract_doc(%{"en" => doc}), do: doc
defp extract_doc(doc) when is_binary(doc), do: doc
defp extract_doc(nil), do: nil
defp extract_doc(:none), do: nil
defp extract_doc(:hidden), do: nil
defp extract_doc(_), do: nil
end
7 changes: 5 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ defmodule Lightning.MixProject do
aliases: aliases(),
deps: deps(),
dialyzer: [
plt_add_apps: [:mix],
plt_add_apps: [:mix, :docout],
plt_local_path: "priv/plts/",
plt_core_path: "priv/plts/core.plt"
],
Expand Down Expand Up @@ -153,7 +153,10 @@ defmodule Lightning.MixProject do
{:benchee, "~> 1.5.0", only: :dev},
{:statistics, "~> 0.6", only: :dev},
{:y_ex, "~> 0.8.0"},
{:chameleon, "~> 2.5"}
{:chameleon, "~> 2.5"},
{:bureaucrat, "~> 0.2.10"},
{:poison, "~> 3.0"},
{:docout, github: "tfwright/docout", branch: "main", runtime: false}
]
end

Expand Down
5 changes: 4 additions & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"broadway_kafka": {:hex, :broadway_kafka, "0.4.4", "ebcaa4b2495c672f459bd8ea12b81ae64dfcfd12ec0a77ef65779e35bffd48e0", [:mix], [{:broadway, "~> 1.0", [hex: :broadway, repo: "hexpm", optional: false]}, {:brod, "~> 3.16 or ~> 4.0", [hex: :brod, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c595b6d6b6d1eac6bb291b151ad92f7a1a101ec8af173f282a0d4fd8fa88b253"},
"brod": {:hex, :brod, "4.4.6", "790fec8ad8d894d2ed34933a4d941e4846280ad9dc89bacb8312a8711cbf05fb", [:cmake, :rebar3], [{:kafka_protocol, "4.2.8", [hex: :kafka_protocol, repo: "hexpm", optional: false]}], "hexpm", "a47757f985c52681d5ab25d41a52cafe7c188208f6d2be2864f0a11ec8a19568"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"bureaucrat": {:hex, :bureaucrat, "0.2.10", "b0de157dad540e40007b663b683f716ced21f85ff0591093aadb209ad0d967e1", [:mix], [{:inflex, ">= 1.10.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.2.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, ">= 1.0.0", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "bc7e5162b911c29c8ebefee87a2c16fbf13821a58f448a8fd024eb6c17fae15c"},
"bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"},
"cachex": {:hex, :cachex, "4.1.1", "574c5cd28473db313a0a76aac8c945fe44191659538ca6a1e8946ec300b1a19f", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:ex_hash_ring, "~> 6.0", [hex: :ex_hash_ring, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "d6b7449ff98d6bb92dda58bd4fc3189cae9f99e7042054d669596f56dc503cd8"},
"castore": {:hex, :castore, "1.0.16", "8a4f9a7c8b81cda88231a08fe69e3254f16833053b23fa63274b05cbc61d2a1e", [:mix], [], "hexpm", "33689203a0eaaf02fcd0e86eadfbcf1bd636100455350592e7e2628564022aaf"},
Expand All @@ -28,6 +29,7 @@
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
"dialyxir": {:hex, :dialyxir, "1.4.6", "7cca478334bf8307e968664343cbdb432ee95b4b68a9cba95bdabb0ad5bdfd9a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "8cf5615c5cd4c2da6c501faae642839c8405b49f8aa057ad4ae401cb808ef64d"},
"docout": {:git, "https://github.com/tfwright/docout.git", "8b807d5ebcbf19beb04a3d2b56ef333178f0d5d1", [branch: "main"]},
"dotenvy": {:hex, :dotenvy, "1.1.0", "316aee89c11a4ec8be3d74a69d17d17ea2e21e633e0cac9f155cf420e237ccb4", [:mix], [], "hexpm", "0519bda67fdfa1c22279c2654b2f292485f0caae7360fe29205f74f28a93df18"},
"earmark": {:hex, :earmark, "1.4.48", "5f41e579d85ef812351211842b6e005f6e0cef111216dea7d4b9d58af4608434", [:mix], [], "hexpm", "a461a0ddfdc5432381c876af1c86c411fd78a25790c75023c7a4c035fdc858f9"},
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
Expand Down Expand Up @@ -67,6 +69,7 @@
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
"httpoison": {:hex, :httpoison, "2.2.3", "a599d4b34004cc60678999445da53b5e653630651d4da3d14675fedc9dd34bd6", [:mix], [{:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "fa0f2e3646d3762fdc73edb532104c8619c7636a6997d20af4003da6cfc53e53"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"},
"jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},
Expand Down Expand Up @@ -118,7 +121,7 @@
"plug": {:hex, :plug, "1.18.1", "5067f26f7745b7e31bc3368bc1a2b818b9779faa959b49c934c17730efc911cf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "57a57db70df2b422b564437d2d33cf8d33cd16339c1edb190cd11b1a3a546cc2"},
"plug_cowboy": {:hex, :plug_cowboy, "2.7.5", "261f21b67aea8162239b2d6d3b4c31efde4daa22a20d80b19c2c0f21b34b270e", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "20884bf58a90ff5a5663420f5d2c368e9e15ed1ad5e911daf0916ea3c57f77ac"},
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
"poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"postgrex": {:hex, :postgrex, "0.21.1", "2c5cc830ec11e7a0067dd4d623c049b3ef807e9507a424985b8dcf921224cd88", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "27d8d21c103c3cc68851b533ff99eef353e6a0ff98dc444ea751de43eb48bdac"},
"prom_ex": {:hex, :prom_ex, "1.11.0", "1f6d67f2dead92224cb4f59beb3e4d319257c5728d9638b4a5e8ceb51a4f9c7e", [:mix], [{:absinthe, ">= 1.7.0", [hex: :absinthe, repo: "hexpm", optional: true]}, {:broadway, ">= 1.1.0", [hex: :broadway, repo: "hexpm", optional: true]}, {:ecto, ">= 3.11.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:finch, "~> 0.18", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, ">= 2.10.0", [hex: :oban, repo: "hexpm", optional: true]}, {:octo_fetch, "~> 0.4", [hex: :octo_fetch, repo: "hexpm", optional: false]}, {:peep, "~> 3.0", [hex: :peep, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.7.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, ">= 0.20.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, ">= 1.16.0", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 2.6.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, ">= 1.0.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.2", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 1.1", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "76b074bc3730f0802978a7eb5c7091a65473eaaf07e99ec9e933138dcc327805"},
Expand Down
Loading