fix: better styling for docs

This commit is contained in:
2026-05-04 07:01:43 +02:00
parent cb46b45cda
commit 6b6c985187
7 changed files with 3602 additions and 1107 deletions

View File

@@ -1236,21 +1236,25 @@ defmodule BDS.Scripting.ApiDocs do
end
defp table_of_contents do
@methods
|> Enum.map(& &1.module)
|> Enum.uniq()
module_names()
|> Enum.map(fn module_name -> "- [#{module_name}](##{module_name})" end)
|> Kernel.++(["- [Data Structures](#data-structures)"])
end
defp render_modules do
@methods
|> Enum.group_by(& &1.module)
|> Enum.flat_map(fn {module_name, methods} ->
module_names()
|> Enum.flat_map(fn module_name ->
methods = module_methods(module_name)
[
"## #{module_name}",
"",
"**Module APIs**",
"",
Enum.map(methods, fn method -> "- [#{method.module}.#{method.name}](##{method.module}#{method.name})" end),
"",
Enum.map(methods, &render_method/1),
"[↑ Back to Table of contents](#table-of-contents)",
""
]
end)
@@ -1269,6 +1273,14 @@ defmodule BDS.Scripting.ApiDocs do
"**Response specification**",
"",
"- Return type: `#{method.returns}`",
render_nullability(method.returns),
render_data_structure_references(method.returns),
"",
"**Example response**",
"",
"```lua",
render_example_response(method.returns),
"```",
"",
"**Example call**",
"",
@@ -1277,6 +1289,7 @@ defmodule BDS.Scripting.ApiDocs do
"```",
""
]
|> Enum.reject(&is_nil/1)
end
defp render_params([]), do: ["- None"]
@@ -1289,18 +1302,191 @@ defmodule BDS.Scripting.ApiDocs do
end
defp example_call(method) do
args =
method.params
|> Enum.map(fn param -> example_value(param.type) end)
|> Enum.join(", ")
args = Enum.map_join(method.params, ", ", &example_argument/1)
"local result = bds.#{method.module}.#{method.name}(#{args})"
end
defp example_value("string"), do: "\"value\""
defp example_value("table"), do: "{}"
defp example_value("integer"), do: "1"
defp example_value(_type), do: "nil"
defp module_names do
@methods
|> Enum.map(& &1.module)
|> Enum.uniq()
end
defp module_methods(module_name) do
Enum.filter(@methods, &(&1.module == module_name))
end
defp render_nullability(returns) do
if nullable_return?(returns) do
"- Nullability: Returns `nil` when no matching value exists or the operation cannot produce a value."
end
end
defp render_data_structure_references(returns) do
case response_structure_names(returns) do
[] -> nil
names -> "- Data structures: `#{Enum.join(names, "`, `")}`"
end
end
defp render_example_response(returns) do
returns
|> example_response_value()
|> render_lua_value(0)
end
defp example_argument(%{name: name, type: type}) do
example_argument_value(name, type)
end
defp example_argument_value(name, "string") do
case name do
"id" -> "\"id-1\""
suffix when suffix in ["post_id", "media_id", "project_id", "tag_id", "target_tag_id"] -> "\"id-1\""
"source_tag_ids" -> "{\"id-1\", \"id-2\"}"
"language" -> "\"en\""
"status" -> "\"draft\""
"kind" -> "\"post\""
"slug" -> "\"example-slug\""
"title" -> "\"Example Title\""
"name" -> "\"Example Name\""
"query" -> "\"example query\""
"content" -> "\"Example content\""
"message" -> "\"Update content\""
"folder_path" -> "\"/Users/me/Sites/example\""
"source_path" -> "\"/Users/me/Pictures/example.jpg\""
"item_path" -> "\"/Users/me/Sites/example/output/index.html\""
"action" -> "\"save\""
_ -> "\"value\""
end
end
defp example_argument_value("limit", "integer"), do: "10"
defp example_argument_value(_name, "integer"), do: "1"
defp example_argument_value(_name, "number"), do: "1.0"
defp example_argument_value(name, "table") do
case name do
"data" -> "{title = \"Example Title\"}"
"filters" -> "{status = \"draft\"}"
"options" -> "{}"
"updates" -> "{name = \"Updated Blog\"}"
"prefs" -> "{provider = \"filesystem\"}"
"credentials" -> "{provider = \"sftp\"}"
"target_ids" -> "{\"id-2\", \"id-3\"}"
"exclude_tags" -> "{\"draft\"}"
_ -> "{}"
end
end
defp example_argument_value(_name, _type), do: "nil"
defp nullable_return?(returns), do: String.contains?(returns, "nil")
defp response_structure_names(returns) do
structure_names = MapSet.new(Enum.map(@data_structures, & &1.name))
returns
|> String.split(~r/\s*\|\s*/)
|> Enum.map(&String.replace(&1, "[]", ""))
|> Enum.reject(&(&1 in ["nil", "boolean", "string", "integer", "number", "table"]))
|> Enum.filter(&MapSet.member?(structure_names, &1))
|> Enum.uniq()
end
defp example_response_value(returns) do
cond do
returns == "nil" -> nil
nullable_return?(returns) -> {:nullable, example_response_value(non_nil_return(returns))}
String.ends_with?(returns, "[]") -> [example_value_for_type(String.trim_trailing(returns, "[]"))]
true -> example_value_for_type(returns)
end
end
defp non_nil_return(returns) do
returns
|> String.split(~r/\s*\|\s*/)
|> Enum.reject(&(&1 == "nil"))
|> List.first()
end
defp example_value_for_type("boolean"), do: true
defp example_value_for_type("string"), do: "value"
defp example_value_for_type("integer"), do: 1
defp example_value_for_type("number"), do: 1.0
defp example_value_for_type("nil"), do: nil
defp example_value_for_type("table"), do: [{"key", "value"}]
defp example_value_for_type(type) do
case Enum.find(@data_structures, &(&1.name == type)) do
nil -> [{"key", "value"}]
structure -> Enum.map(structure.fields, fn field -> {field.name, example_field_value(field.type)} end)
end
end
defp example_field_value(type) do
cond do
String.contains?(type, " | nil") -> nil
String.ends_with?(type, "[]") -> [example_value_for_type(String.trim_trailing(type, "[]"))]
true -> example_value_for_type(type)
end
end
defp render_lua_value({:nullable, value}, indent) do
["nil -- or", render_lua_value(value, indent)]
|> Enum.join("\n")
end
defp render_lua_value(true, _indent), do: "true"
defp render_lua_value(false, _indent), do: "false"
defp render_lua_value(nil, _indent), do: "nil"
defp render_lua_value(value, _indent) when is_integer(value), do: Integer.to_string(value)
defp render_lua_value(value, _indent) when is_float(value), do: :erlang.float_to_binary(value, [:compact])
defp render_lua_value(value, _indent) when is_binary(value), do: inspect(value)
defp render_lua_value([], _indent), do: "{}"
defp render_lua_value(list, indent) when is_list(list) do
if keyword_like_list?(list) do
render_lua_table(list, indent)
else
render_lua_array(list, indent)
end
end
defp keyword_like_list?(list) do
Enum.all?(list, fn
{key, _value} when is_binary(key) -> true
_ -> false
end)
end
defp render_lua_table(entries, indent) do
outer_indent = indent_spaces(indent)
inner_indent = indent_spaces(indent + 2)
rendered_entries =
Enum.map_join(entries, ",\n", fn {key, value} ->
"#{inner_indent}#{key} = #{render_lua_value(value, indent + 2)}"
end)
"{\n#{rendered_entries}\n#{outer_indent}}"
end
defp render_lua_array(values, indent) do
outer_indent = indent_spaces(indent)
inner_indent = indent_spaces(indent + 2)
rendered_values =
Enum.map_join(values, ",\n", fn value ->
"#{inner_indent}#{render_lua_value(value, indent + 2)}"
end)
"{\n#{rendered_values}\n#{outer_indent}}"
end
defp indent_spaces(indent), do: String.duplicate(" ", indent)
defp render_data_structures do
Enum.flat_map(@data_structures, fn structure ->