Config/Docs: Expand behaviour suggestions at runtime
authorhref <href@random.sh>
Sun, 12 Jul 2020 15:23:33 +0000 (17:23 +0200)
committerhref <href@random.sh>
Sun, 12 Jul 2020 15:23:33 +0000 (17:23 +0200)
config/description.exs
lib/pleroma/application.ex
lib/pleroma/docs/generator.ex
lib/pleroma/docs/json.ex
lib/pleroma/docs/markdown.ex
lib/pleroma/web/admin_api/controllers/config_controller.ex
test/docs/generator_test.exs

index b0cc8d527525be63040448659eef58f18dc7957c..61d1d055eb3373b9686463b9a22191818bf39ec1 100644 (file)
@@ -23,18 +23,14 @@ config :pleroma, :config_description, [
         key: :uploader,
         type: :module,
         description: "Module which will be used for uploads",
-        suggestions: [Pleroma.Uploaders.Local, Pleroma.Uploaders.S3]
+        suggestions: {:list_behaviour_implementations, Pleroma.Uploaders.Uploader}
       },
       %{
         key: :filters,
         type: {:list, :module},
         description:
           "List of filter modules for uploads. Module names are shortened (removed leading `Pleroma.Upload.Filter.` part), but on adding custom module you need to use full name.",
-        suggestions:
-          Generator.list_modules_in_dir(
-            "lib/pleroma/upload/filter",
-            "Elixir.Pleroma.Upload.Filter."
-          )
+        suggestions: {:list_behaviour_implementations, Pleroma.Upload.Filter}
       },
       %{
         key: :link_name,
@@ -1404,11 +1400,7 @@ config :pleroma, :config_description, [
         type: [:module, {:list, :module}],
         description:
           "A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
-        suggestions:
-          Generator.list_modules_in_dir(
-            "lib/pleroma/web/activity_pub/mrf",
-            "Elixir.Pleroma.Web.ActivityPub.MRF."
-          )
+        suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF}
       },
       %{
         key: :transparency,
index 84f3aa82deca12658ab12f67f7b83a706bf32935..b68a373a448c7d2c3f96f051bd1b4744e03eff98 100644 (file)
@@ -42,6 +42,7 @@ defmodule Pleroma.Application do
     Pleroma.ApplicationRequirements.verify!()
     setup_instrumenters()
     load_custom_modules()
+    Pleroma.Docs.JSON.compile()
 
     adapter = Application.get_env(:tesla, :adapter)
 
index e0fc8cd02144de31766dbb22a204d1e53e7aa5d3..a671a62787cc33df4359012985c2c0b8b89fb729 100644 (file)
@@ -6,16 +6,21 @@ defmodule Pleroma.Docs.Generator do
     implementation.process(descriptions)
   end
 
-  @spec list_modules_in_dir(String.t(), String.t()) :: [module()]
-  def list_modules_in_dir(dir, start) do
-    with {:ok, files} <- File.ls(dir) do
-      files
-      |> Enum.filter(&String.ends_with?(&1, ".ex"))
-      |> Enum.map(fn filename ->
-        module = filename |> String.trim_trailing(".ex") |> Macro.camelize()
-        String.to_atom(start <> module)
-      end)
-    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 """
@@ -87,6 +92,12 @@ defmodule Pleroma.Docs.Generator do
       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
index d1cf1f487b447ae994f982dd5359c1211402a1d4..feeb4320e62b10ed56b9160eea548fe83fe9c77d 100644 (file)
@@ -1,5 +1,19 @@
 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
+    :persistent_term.put(@term, Pleroma.Docs.Generator.convert_to_strings(@raw_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
@@ -13,11 +27,4 @@ defmodule Pleroma.Docs.JSON do
       {:ok, path}
     end
   end
-
-  def compile do
-    with config <- Pleroma.Config.Loader.read("config/description.exs") do
-      config[:pleroma][:config_description]
-      |> Pleroma.Docs.Generator.convert_to_strings()
-    end
-  end
 end
index 68b10649955ab2ab4a7cfc4a6adee1c36b9611e3..da3f20f4341ae46b0cd29e10acd94e1092f081f5 100644 (file)
@@ -68,6 +68,11 @@ defmodule Pleroma.Docs.Markdown do
     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
index 7f60470cbb7a4a237131f4688df9e3a1ba0a2650..0df13007f67ccd01fc78ff5da859c0fd6b963924 100644 (file)
@@ -9,8 +9,6 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
   alias Pleroma.ConfigDB
   alias Pleroma.Plugs.OAuthScopesPlug
 
-  @descriptions Pleroma.Docs.JSON.compile()
-
   plug(Pleroma.Web.ApiSpec.CastAndValidate)
   plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :update)
 
@@ -25,7 +23,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ConfigOperation
 
   def descriptions(conn, _params) do
-    descriptions = Enum.filter(@descriptions, &whitelisted_config?/1)
+    descriptions = Enum.filter(Pleroma.Docs.JSON.compiled_descriptions(), &whitelisted_config?/1)
 
     json(conn, descriptions)
   end
index 9c9f4357b2836a116d84c0d05d528e4aee2484ab..b32918a69339dd166e3644f1bc11751c69256b90 100644 (file)
@@ -13,21 +13,13 @@ defmodule Pleroma.Docs.GeneratorTest do
           key: :uploader,
           type: :module,
           description: "",
-          suggestions:
-            Generator.list_modules_in_dir(
-              "lib/pleroma/upload/filter",
-              "Elixir.Pleroma.Upload.Filter."
-            )
+          suggestions: {:list_behaviour_implementations, Pleroma.Upload.Filter}
         },
         %{
           key: :filters,
           type: {:list, :module},
           description: "",
-          suggestions:
-            Generator.list_modules_in_dir(
-              "lib/pleroma/web/activity_pub/mrf",
-              "Elixir.Pleroma.Web.ActivityPub.MRF."
-            )
+          suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF}
         },
         %{
           key: Pleroma.Upload,