Skip to content

Commit

Permalink
revisit plugins, turn them into GenServers
Browse files Browse the repository at this point in the history
  • Loading branch information
goncalotomas committed May 5, 2024
1 parent f13b454 commit 9aa094e
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 121 deletions.
3 changes: 3 additions & 0 deletions lib/griffin_ssg.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ defmodule GriffinSSG do
@typedoc "configuration of hooks used during the build process"
@type hooks :: %{before: list(hook()), post_parse: list(hook()), after: list(hook())}

@typedoc "griffin application configuration"
@type config :: map()

@doc """
Parses the string contents of a file into two components: front matter and file content.
Expand Down
28 changes: 25 additions & 3 deletions lib/plugin/behaviour.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,30 @@
defmodule GriffinSSG.Plugin.Behaviour do
@moduledoc """
This is the behaviour for all plugins.
The behaviour for a Griffin Plugin.
A Plugin is a stateful process that is started at the beginning of the
Griffin build run and that requests callbacks at specific stages of the build
run by setting up hooks. The arguments passed in to each callback depend on
the hook itself, see `GriffinSSG.hooks()` for more information on hooks.
## Process instances
At this moment, Plugins functions are called globally, e.g. `MyPlugin.list_hooks()`.
This assumes there won't be multiple instances of the same Plugin.
## Errors
Plugins are expected to return errors and not raise if there are issues, so
that the global Griffin run process can show a human friendly error message
and gracefully terminate.
"""

@callback init(GriffinSSG.Config.t(), any()) ::
{:ok, %{state: term(), hooks: GriffinSSG.hooks()}}
@doc """
The Plugin start_link function. People creating plugins are encouraged to use
GenServers for their implementation.
"""
@callback start_link(griffin_config :: GriffinSSG.Config.t(), plugin_opts :: any()) ::
{:ok, pid()} | {:error, reason :: atom()}

@doc """
Returns a list of Griffin hooks that the Plugin requires.
"""
@callback list_hooks() :: GriffinSSG.hooks()
end
81 changes: 56 additions & 25 deletions lib/plugin/collections.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,53 +6,84 @@ defmodule GriffinSSG.Plugin.Collections do
"""
@behaviour GriffinSSG.Plugin.Behaviour

use GenServer

alias GriffinSSG.Layouts

@collection_config_opts [:permalink, :list_layout, :show_layout]

# GriffinSSG.Plugin callbacks

@impl true
def init(_opts, plugin_opts) do
def start_link(griffin_config, plugin_opts) do
GenServer.start_link(__MODULE__, %{griffin_config: griffin_config, opts: plugin_opts},
name: __MODULE__
)
end

@impl true
def list_hooks() do
%{
before: [],
post_parse: [&__MODULE__.compile_collections/1],
after: [&__MODULE__.render_collection_pages/1]
}
end

# Griffin callbacks
def compile_collections({_, parsed_files, _, _}) do
GenServer.call(__MODULE__, {:compile_collections, parsed_files})
end

def render_collection_pages({_, _results, _, _}) do
GenServer.call(__MODULE__, :render_collection_pages)
end

# GenServer callbacks

@impl true
def init(%{griffin_config: griffin_config, opts: plugin_opts}) do
case validate_config(plugin_opts) do
{:ok, config} ->
config =
if Enum.empty?(config) do
config
else
%{
state: config,
hooks: %{
post_parse: [&__MODULE__.compile_collections/1],
after: [&__MODULE__.run/1]
}
}
end
{:ok, %{griffin_config: griffin_config, opts: config}}

{:ok, config}

error ->
{:error, _msg} = error ->
error
end
end

def compile_collections({_, parsed_files, _, _}) do
opts = GriffinSSG.Config.get_all()

@impl true
def handle_call(
{:compile_collections, parsed_files},
_from,
%{griffin_config: griffin_config, opts: opts} = state
) do
collections =
opts.collections_config
opts
|> Enum.map(fn {collection_name, config} ->
# refactor: terrible efficiency, we're traversing the parsed list files
# once per collection. Since most sites will have 1-2 collections max,
# we're fine with this for now.
{collection_name,
compile_collection(collection_name, parsed_files, Map.merge(opts, config))}
compile_collection(collection_name, parsed_files, Map.merge(griffin_config, config))}
end)
|> Enum.into(%{})

{:reply, :ok, Map.put(state, :collections, collections)}
end

def run({_, _results, _, _}, config) do
render_collections_pages(config.collections, config)
def handle_call(
:render_collection_pages,
_from,
%{collections: collections, griffin_config: griffin_config} = state
) do
result = do_render_collections_pages(collections, griffin_config)

{:reply, result, state}
end

# internal functions

defp validate_config(plugin_opts) do
config = Keyword.get(plugin_opts, :collections, %{})

Expand Down Expand Up @@ -98,9 +129,9 @@ defmodule GriffinSSG.Plugin.Collections do
end)
end

defp render_collections_pages(collections, _opts) when collections == %{}, do: :ok
defp do_render_collections_pages(collections, _opts) when collections == %{}, do: :ok

defp render_collections_pages(collections, opts) do
defp do_render_collections_pages(collections, opts) do
# Generate collections pages (example of tags below):
# render `/tags/` page listing all tags
# render `/tags/:tag` page listing all pages with that tag
Expand Down
10 changes: 5 additions & 5 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
%{
"assertions": {:hex, :assertions, "0.19.0", "f177fcc22b55df6a41a58b151e430d189d0a1083dae3240522503dedaf8e1174", [:mix], [], "hexpm", "759bdbf977fe3e2d2b79f8a65d8674f29ac757781136a6cb4adf88f1574af1d8"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
"cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
"cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
"credo": {:hex, :credo, "1.7.2-rc.2", "cb0abefeb34b513818f91f07c52d3bd6daa8e04f1c6201658f9ef944d64ef7e4", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "82e580361796d4841140457d08a4cc5e4c7df010c35d495aab0dbb0b805769da"},
"earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
"earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
Expand All @@ -16,9 +16,9 @@
"makeup_html": {:hex, :makeup_html, "0.1.1", "c3d4abd39d5f7e925faca72ada6e9cc5c6f5fa7cd5bc0158315832656cf14d7f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "44f2a61bc5243645dd7fafeaa6cc28793cd22f3c76b861e066168f9a5b2c26a4"},
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"plug": {:hex, :plug, "1.15.2", "94cf1fa375526f30ff8770837cb804798e0045fd97185f0bb9e5fcd858c792a3", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615"},
"plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
"plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"},
"plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"},
"plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"},
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
"plug_live_reload": {:hex, :plug_live_reload, "0.2.0", "61154a657ad48e856fe9c3056080c2ececcfa236793f2de98185fbe291c1333e", [:mix], [{:cowboy, "~> 2.10", [hex: :cowboy, repo: "hexpm", optional: false]}, {:file_system, "~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ff953f55b7eeacf832f77800824b60a14818dc2749b61b5e6e07adef6de5d93a"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"slugify": {:hex, :slugify, "1.3.1", "0d3b8b7e5c1eeaa960e44dce94382bee34a39b3ea239293e457a9c5b47cc6fd3", [:mix], [], "hexpm", "cb090bbeb056b312da3125e681d98933a360a70d327820e4b7f91645c4d8be76"},
Expand Down
11 changes: 5 additions & 6 deletions test/griffin_ssg/file/parser_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ defmodule GriffinSSG.File.ParserTest do
use ExUnit.Case, async: true

alias GriffinSSG.File.Parser
alias GriffinSSG.ContentFile

describe "from_string/1" do
test "works with a simple text file" do
Expand All @@ -12,7 +11,7 @@ defmodule GriffinSSG.File.ParserTest do
It has multiple lines
"""

assert {:ok, %ContentFile{front_matter: %{}, content: string}} ==
assert {:ok, %{front_matter: %{}, content: string}} ==
Parser.from_string(string)
end

Expand All @@ -29,7 +28,7 @@ defmodule GriffinSSG.File.ParserTest do
---
"""

assert {:ok, %ContentFile{front_matter: front_matter, content: ""}} =
assert {:ok, %{front_matter: front_matter, content: ""}} =
Parser.from_string(string)

assert title == front_matter.title
Expand Down Expand Up @@ -65,7 +64,7 @@ defmodule GriffinSSG.File.ParserTest do
[this is a link](http://example.com)
"""

assert {:ok, %ContentFile{front_matter: front_matter, content: content}} =
assert {:ok, %{front_matter: front_matter, content: content}} =
Parser.from_string(string)

assert title == front_matter.title
Expand Down Expand Up @@ -100,7 +99,7 @@ defmodule GriffinSSG.File.ParserTest do
| col 3 is | right-aligned | $1 |
"""

assert {:ok, %ContentFile{front_matter: %{}, content: content}} =
assert {:ok, %{front_matter: %{}, content: content}} =
Parser.from_string(string)

lines = String.split(string, "\n", trim: true)
Expand Down Expand Up @@ -136,7 +135,7 @@ defmodule GriffinSSG.File.ParserTest do
| col 3 is | right-aligned | $1 |
"""

assert {:ok, %ContentFile{front_matter: front_matter, content: content}} =
assert {:ok, %{front_matter: front_matter, content: content}} =
Parser.from_string(string)

assert title == front_matter.title
Expand Down
Loading

0 comments on commit 9aa094e

Please sign in to comment.