aboutsummaryrefslogtreecommitdiff
path: root/lib/pleroma/docs
diff options
context:
space:
mode:
authordcc <dcc@logografos.com>2023-09-02 00:52:52 -0700
committerdcc <dcc@logografos.com>2023-09-02 00:52:52 -0700
commit3a4773c3c2bd0bbef244eb519b07208da9108e49 (patch)
tree973567a6f3abb37bfb0f785b1cad14ed55840ef5 /lib/pleroma/docs
downloadanni-3a4773c3c2bd0bbef244eb519b07208da9108e49.tar.gz
anni-3a4773c3c2bd0bbef244eb519b07208da9108e49.tar.bz2
anni-3a4773c3c2bd0bbef244eb519b07208da9108e49.zip
First
Diffstat (limited to 'lib/pleroma/docs')
-rw-r--r--lib/pleroma/docs/generator.ex136
-rw-r--r--lib/pleroma/docs/json.ex38
-rw-r--r--lib/pleroma/docs/markdown.ex97
-rw-r--r--lib/pleroma/docs/translator.ex10
-rw-r--r--lib/pleroma/docs/translator/compiler.ex119
5 files changed, 400 insertions, 0 deletions
diff --git a/lib/pleroma/docs/generator.ex b/lib/pleroma/docs/generator.ex
new file mode 100644
index 0000000..6508f19
--- /dev/null
+++ b/lib/pleroma/docs/generator.ex
@@ -0,0 +1,136 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Docs.Generator do
+ @callback process(keyword()) :: {:ok, String.t()}
+
+ @spec process(module(), keyword()) :: {:ok, String.t()}
+ def process(implementation, descriptions) do
+ implementation.process(descriptions)
+ end
+
+ @spec list_behaviour_implementations(behaviour :: module()) :: [module()]
+ def list_behaviour_implementations(behaviour) do
+ :code.all_loaded()
+ |> Enum.filter(fn {module, _} ->
+ # This shouldn't be needed as all modules are expected to have module_info/1,
+ # but in test enviroments some transient modules `:elixir_compiler_XX`
+ # are loaded for some reason (where XX is a random integer).
+ if function_exported?(module, :module_info, 1) do
+ module.module_info(:attributes)
+ |> Keyword.get_values(:behaviour)
+ |> List.flatten()
+ |> Enum.member?(behaviour)
+ end
+ end)
+ |> Enum.map(fn {module, _} -> module end)
+ end
+
+ @doc """
+ Converts:
+ - atoms to strings with leading `:`
+ - module names to strings, without leading `Elixir.`
+ - add humanized labels to `keys` if label is not defined, e.g. `:instance` -> `Instance`
+ """
+ @spec convert_to_strings([map()]) :: [map()]
+ def convert_to_strings(descriptions) do
+ Enum.map(descriptions, &format_entity(&1))
+ end
+
+ defp format_entity(entity) do
+ entity
+ |> format_key()
+ |> Map.put(:group, atom_to_string(entity[:group]))
+ |> format_children()
+ end
+
+ defp format_key(%{key: key} = entity) do
+ entity
+ |> Map.put(:key, atom_to_string(key))
+ |> Map.put(:label, entity[:label] || humanize(key))
+ end
+
+ defp format_key(%{group: group} = entity) do
+ Map.put(entity, :label, entity[:label] || humanize(group))
+ end
+
+ defp format_key(entity), do: entity
+
+ defp format_children(%{children: children} = entity) do
+ Map.put(entity, :children, Enum.map(children, &format_child(&1)))
+ end
+
+ defp format_children(entity), do: entity
+
+ defp format_child(%{suggestions: suggestions} = entity) do
+ entity
+ |> Map.put(:suggestions, format_suggestions(suggestions))
+ |> format_key()
+ |> format_group()
+ |> format_children()
+ end
+
+ defp format_child(entity) do
+ entity
+ |> format_key()
+ |> format_group()
+ |> format_children()
+ end
+
+ defp format_group(%{group: group} = entity) do
+ Map.put(entity, :group, format_suggestion(group))
+ end
+
+ defp format_group(entity), do: entity
+
+ defp atom_to_string(entity) when is_binary(entity), do: entity
+
+ defp atom_to_string(entity) when is_atom(entity), do: inspect(entity)
+
+ defp humanize(entity) do
+ string = inspect(entity)
+
+ if String.starts_with?(string, ":"),
+ do: Phoenix.Naming.humanize(entity),
+ else: string
+ end
+
+ defp format_suggestions({:list_behaviour_implementations, behaviour}) do
+ behaviour
+ |> list_behaviour_implementations()
+ |> format_suggestions()
+ end
+
+ defp format_suggestions([]), do: []
+
+ defp format_suggestions([suggestion | tail]) do
+ [format_suggestion(suggestion) | format_suggestions(tail)]
+ end
+
+ defp format_suggestion(entity) when is_atom(entity) do
+ atom_to_string(entity)
+ end
+
+ defp format_suggestion([head | tail] = entity) when is_list(entity) do
+ [format_suggestion(head) | format_suggestions(tail)]
+ end
+
+ defp format_suggestion(entity) when is_tuple(entity) do
+ format_suggestions(Tuple.to_list(entity)) |> List.to_tuple()
+ end
+
+ defp format_suggestion(entity), do: entity
+end
+
+defimpl Jason.Encoder, for: Tuple do
+ def encode(tuple, opts), do: Jason.Encode.list(Tuple.to_list(tuple), opts)
+end
+
+defimpl Jason.Encoder, for: [Regex, Function] do
+ def encode(term, opts), do: Jason.Encode.string(inspect(term), opts)
+end
+
+defimpl String.Chars, for: Regex do
+ def to_string(term), do: inspect(term)
+end
diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex
new file mode 100644
index 0000000..05f46f3
--- /dev/null
+++ b/lib/pleroma/docs/json.ex
@@ -0,0 +1,38 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Docs.JSON do
+ @behaviour Pleroma.Docs.Generator
+ @external_resource "config/description.exs"
+ @raw_config Pleroma.Config.Loader.read("config/description.exs")
+ @raw_descriptions @raw_config[:pleroma][:config_description]
+ @term __MODULE__.Compiled
+
+ @spec compile :: :ok
+ def compile do
+ descriptions =
+ Pleroma.Web.ActivityPub.MRF.config_descriptions()
+ |> Enum.reduce(@raw_descriptions, fn description, acc -> [description | acc] end)
+
+ :persistent_term.put(@term, Pleroma.Docs.Generator.convert_to_strings(descriptions))
+ end
+
+ @spec compiled_descriptions :: Map.t()
+ def compiled_descriptions do
+ :persistent_term.get(@term)
+ end
+
+ @spec process(keyword()) :: {:ok, String.t()}
+ def process(descriptions) do
+ with path <- "docs/generated_config.json",
+ {:ok, file} <- File.open(path, [:write, :utf8]),
+ formatted_descriptions <-
+ Pleroma.Docs.Generator.convert_to_strings(descriptions),
+ json <- Jason.encode!(formatted_descriptions),
+ :ok <- IO.write(file, json),
+ :ok <- File.close(file) do
+ {:ok, path}
+ end
+ end
+end
diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex
new file mode 100644
index 0000000..949388e
--- /dev/null
+++ b/lib/pleroma/docs/markdown.ex
@@ -0,0 +1,97 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Docs.Markdown do
+ @behaviour Pleroma.Docs.Generator
+
+ @spec process(keyword()) :: {:ok, String.t()}
+ def process(descriptions) do
+ config_path = "docs/generated_config.md"
+ {:ok, file} = File.open(config_path, [:utf8, :write])
+ IO.write(file, "# Generated configuration\n")
+ IO.write(file, "Date of generation: #{Date.utc_today()}\n\n")
+
+ IO.write(
+ file,
+ "This file describe the configuration, it is recommended to edit the relevant `*.secret.exs` file instead of the others founds in the ``config`` directory.\n\n" <>
+ "If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherwise it is ``dev.secret.exs``.\n\n"
+ )
+
+ for group <- descriptions do
+ if is_nil(group[:key]) do
+ IO.write(file, "## #{inspect(group[:group])}\n")
+ else
+ IO.write(file, "## #{inspect(group[:key])}\n")
+ end
+
+ IO.write(file, "#{group[:description]}\n")
+
+ for child <- group[:children] || [] do
+ print_child_header(file, child)
+
+ print_suggestions(file, child[:suggestions])
+
+ if child[:children] do
+ for subchild <- child[:children] do
+ print_child_header(file, subchild)
+
+ print_suggestions(file, subchild[:suggestions])
+ end
+ end
+ end
+
+ IO.write(file, "\n")
+ end
+
+ :ok = File.close(file)
+ {:ok, config_path}
+ end
+
+ defp print_child_header(file, %{key: key, type: type, description: description} = _child) do
+ IO.write(
+ file,
+ "- `#{inspect(key)}` (`#{inspect(type)}`): #{description} \n"
+ )
+ end
+
+ defp print_child_header(file, %{key: key, type: type} = _child) do
+ IO.write(file, "- `#{inspect(key)}` (`#{inspect(type)}`) \n")
+ end
+
+ defp print_suggestion(file, suggestion) when is_list(suggestion) do
+ IO.write(file, " `#{inspect(suggestion)}`\n")
+ end
+
+ defp print_suggestion(file, suggestion) when is_function(suggestion) do
+ IO.write(file, " `#{inspect(suggestion.())}`\n")
+ end
+
+ defp print_suggestion(file, suggestion, as_list \\ false) do
+ list_mark = if as_list, do: "- ", else: ""
+ IO.write(file, " #{list_mark}`#{inspect(suggestion)}`\n")
+ end
+
+ defp print_suggestions(file, {:list_behaviour_implementations, behaviour}) do
+ suggestions = Pleroma.Docs.Generator.list_behaviour_implementations(behaviour)
+ print_suggestions(file, suggestions)
+ end
+
+ defp print_suggestions(_file, nil), do: nil
+
+ defp print_suggestions(_file, ""), do: nil
+
+ defp print_suggestions(file, suggestions) do
+ if length(suggestions) > 1 do
+ IO.write(file, "Suggestions:\n")
+
+ for suggestion <- suggestions do
+ print_suggestion(file, suggestion, true)
+ end
+ else
+ IO.write(file, " Suggestion: ")
+
+ print_suggestion(file, List.first(suggestions))
+ end
+ end
+end
diff --git a/lib/pleroma/docs/translator.ex b/lib/pleroma/docs/translator.ex
new file mode 100644
index 0000000..13e33c8
--- /dev/null
+++ b/lib/pleroma/docs/translator.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Docs.Translator do
+ require Pleroma.Docs.Translator.Compiler
+ require Pleroma.Web.Gettext
+
+ @before_compile Pleroma.Docs.Translator.Compiler
+end
diff --git a/lib/pleroma/docs/translator/compiler.ex b/lib/pleroma/docs/translator/compiler.ex
new file mode 100644
index 0000000..5d27d9f
--- /dev/null
+++ b/lib/pleroma/docs/translator/compiler.ex
@@ -0,0 +1,119 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Docs.Translator.Compiler do
+ @external_resource "config/description.exs"
+ @raw_config Pleroma.Config.Loader.read("config/description.exs")
+ @raw_descriptions @raw_config[:pleroma][:config_description]
+
+ defmacro __before_compile__(_env) do
+ strings =
+ __MODULE__.descriptions()
+ |> __MODULE__.extract_strings()
+
+ quote do
+ def placeholder do
+ unquote do
+ Enum.map(
+ strings,
+ fn {path, type, string} ->
+ ctxt = msgctxt_for(path, type)
+
+ quote do
+ Pleroma.Web.Gettext.dpgettext_noop(
+ "config_descriptions",
+ unquote(ctxt),
+ unquote(string)
+ )
+ end
+ end
+ )
+ end
+ end
+ end
+ end
+
+ def descriptions do
+ Pleroma.Web.ActivityPub.MRF.config_descriptions()
+ |> Enum.reduce(@raw_descriptions, fn description, acc -> [description | acc] end)
+ |> Pleroma.Docs.Generator.convert_to_strings()
+ end
+
+ def extract_strings(descriptions) do
+ descriptions
+ |> Enum.reduce(%{strings: [], path: []}, &process_item/2)
+ |> Map.get(:strings)
+ end
+
+ defp process_item(entity, acc) do
+ current_level =
+ acc
+ |> process_desc(entity)
+ |> process_label(entity)
+
+ process_children(entity, current_level)
+ end
+
+ defp process_desc(acc, %{description: desc} = item) do
+ %{
+ strings: [{acc.path ++ [key_for(item)], "description", desc} | acc.strings],
+ path: acc.path
+ }
+ end
+
+ defp process_desc(acc, _) do
+ acc
+ end
+
+ defp process_label(acc, %{label: label} = item) do
+ %{
+ strings: [{acc.path ++ [key_for(item)], "label", label} | acc.strings],
+ path: acc.path
+ }
+ end
+
+ defp process_label(acc, _) do
+ acc
+ end
+
+ defp process_children(%{children: children} = item, acc) do
+ current_level = Map.put(acc, :path, acc.path ++ [key_for(item)])
+
+ children
+ |> Enum.reduce(current_level, &process_item/2)
+ |> Map.put(:path, acc.path)
+ end
+
+ defp process_children(_, acc) do
+ acc
+ end
+
+ def msgctxt_for(path, type) do
+ "config #{type} at #{Enum.join(path, " > ")}"
+ end
+
+ defp convert_group({_, group}) do
+ group
+ end
+
+ defp convert_group(group) do
+ group
+ end
+
+ def key_for(%{group: group, key: key}) do
+ "#{convert_group(group)}-#{key}"
+ end
+
+ def key_for(%{group: group}) do
+ convert_group(group)
+ end
+
+ def key_for(%{key: key}) do
+ key
+ end
+
+ def key_for(_) do
+ nil
+ end
+end