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