Merge branch 'issue/1878' into 'develop'
[akkoma] / lib / pleroma / docs / generator.ex
1 defmodule Pleroma.Docs.Generator do
2 @callback process(keyword()) :: {:ok, String.t()}
3
4 @spec process(module(), keyword()) :: {:ok, String.t()}
5 def process(implementation, descriptions) do
6 implementation.process(descriptions)
7 end
8
9 @spec list_behaviour_implementations(behaviour :: module()) :: [module()]
10 def list_behaviour_implementations(behaviour) do
11 :code.all_loaded()
12 |> Enum.filter(fn {module, _} ->
13 # This shouldn't be needed as all modules are expected to have module_info/1,
14 # but in test enviroments some transient modules `:elixir_compiler_XX`
15 # are loaded for some reason (where XX is a random integer).
16 if function_exported?(module, :module_info, 1) do
17 module.module_info(:attributes)
18 |> Keyword.get_values(:behaviour)
19 |> List.flatten()
20 |> Enum.member?(behaviour)
21 end
22 end)
23 |> Enum.map(fn {module, _} -> module end)
24 end
25
26 @doc """
27 Converts:
28 - atoms to strings with leading `:`
29 - module names to strings, without leading `Elixir.`
30 - add humanized labels to `keys` if label is not defined, e.g. `:instance` -> `Instance`
31 """
32 @spec convert_to_strings([map()]) :: [map()]
33 def convert_to_strings(descriptions) do
34 Enum.map(descriptions, &format_entity(&1))
35 end
36
37 defp format_entity(entity) do
38 entity
39 |> format_key()
40 |> Map.put(:group, atom_to_string(entity[:group]))
41 |> format_children()
42 end
43
44 defp format_key(%{key: key} = entity) do
45 entity
46 |> Map.put(:key, atom_to_string(key))
47 |> Map.put(:label, entity[:label] || humanize(key))
48 end
49
50 defp format_key(%{group: group} = entity) do
51 Map.put(entity, :label, entity[:label] || humanize(group))
52 end
53
54 defp format_key(entity), do: entity
55
56 defp format_children(%{children: children} = entity) do
57 Map.put(entity, :children, Enum.map(children, &format_child(&1)))
58 end
59
60 defp format_children(entity), do: entity
61
62 defp format_child(%{suggestions: suggestions} = entity) do
63 entity
64 |> Map.put(:suggestions, format_suggestions(suggestions))
65 |> format_key()
66 |> format_group()
67 |> format_children()
68 end
69
70 defp format_child(entity) do
71 entity
72 |> format_key()
73 |> format_group()
74 |> format_children()
75 end
76
77 defp format_group(%{group: group} = entity) do
78 Map.put(entity, :group, format_suggestion(group))
79 end
80
81 defp format_group(entity), do: entity
82
83 defp atom_to_string(entity) when is_binary(entity), do: entity
84
85 defp atom_to_string(entity) when is_atom(entity), do: inspect(entity)
86
87 defp humanize(entity) do
88 string = inspect(entity)
89
90 if String.starts_with?(string, ":"),
91 do: Phoenix.Naming.humanize(entity),
92 else: string
93 end
94
95 defp format_suggestions({:list_behaviour_implementations, behaviour}) do
96 behaviour
97 |> list_behaviour_implementations()
98 |> format_suggestions()
99 end
100
101 defp format_suggestions([]), do: []
102
103 defp format_suggestions([suggestion | tail]) do
104 [format_suggestion(suggestion) | format_suggestions(tail)]
105 end
106
107 defp format_suggestion(entity) when is_atom(entity) do
108 atom_to_string(entity)
109 end
110
111 defp format_suggestion([head | tail] = entity) when is_list(entity) do
112 [format_suggestion(head) | format_suggestions(tail)]
113 end
114
115 defp format_suggestion(entity) when is_tuple(entity) do
116 format_suggestions(Tuple.to_list(entity)) |> List.to_tuple()
117 end
118
119 defp format_suggestion(entity), do: entity
120 end
121
122 defimpl Jason.Encoder, for: Tuple do
123 def encode(tuple, opts), do: Jason.Encode.list(Tuple.to_list(tuple), opts)
124 end
125
126 defimpl Jason.Encoder, for: [Regex, Function] do
127 def encode(term, opts), do: Jason.Encode.string(inspect(term), opts)
128 end
129
130 defimpl String.Chars, for: Regex do
131 def to_string(term), do: inspect(term)
132 end