Skip to content

Commit 975cdf4

Browse files
committed
feat: extensions
1 parent e19fbc0 commit 975cdf4

File tree

3 files changed

+105
-1
lines changed

3 files changed

+105
-1
lines changed

lib/mix/tasks/tableau.build.ex

+14
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ defmodule Mix.Tasks.Tableau.Build do
1919
graph = Tableau.Graph.new(mods)
2020
File.mkdir_p!(out)
2121

22+
for module <- pre_write_extensions(mods) do
23+
with :error <- module.run(graph, %{site: %{}}) do
24+
Logger.error("#{inspect(module)} failed to run")
25+
end
26+
end
27+
2228
for mod <- Graph.vertices(graph), {:ok, :page} == Tableau.Graph.Node.type(mod) do
2329
content = Tableau.Document.render(graph, mod, %{site: %{}})
2430
permalink = mod.__tableau_permalink__()
@@ -33,4 +39,12 @@ defmodule Mix.Tasks.Tableau.Build do
3339
File.cp_r!(@include_dir, out)
3440
end
3541
end
42+
43+
defp pre_write_extensions(modules) do
44+
for {mod, _, _} <- modules,
45+
mod = Module.concat([to_string(mod)]),
46+
match?({:ok, :pre_build}, Tableau.Extension.type(mod)) do
47+
mod
48+
end
49+
end
3650
end

lib/tableau/extension.ex

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
defmodule Tableau.Extension do
2+
@moduledoc ~s'''
3+
A tableau extension.
4+
5+
An extension can be used to generate other kinds of content.
6+
7+
There are currently the following extension types:
8+
9+
- `:pre_build` - executed before tableau builds your site and writes anything to disk.
10+
11+
```elixir
12+
defmodule MySite.PostsExtension do
13+
use Tableau.Extension, type: :pre_build
14+
15+
def run(_graph, _site) do
16+
posts = Path.wildcard("_posts/**/*.md")
17+
18+
for post <- post do
19+
post
20+
|> Markdown.render()
21+
|> then(&File.write(Path.join(Path.rootname(post), "index.html"), &1))
22+
end
23+
24+
:ok
25+
end
26+
end
27+
```
28+
'''
29+
30+
@typep extension_type :: :pre_build
31+
32+
@doc """
33+
The extension entry point.
34+
35+
The function is passed the layout graph and a set of default assigns.
36+
37+
The graph is an instance of a [Graph.t](https://hexdocs.pm/libgraph/0.16.0/Graph.html#t:t/0) from the library `libgraph`.
38+
"""
39+
@callback run(Graph.t(), map()) :: :ok | :error
40+
41+
defmacro __using__(opts) do
42+
opts = Keyword.validate!(opts, [:type])
43+
44+
prelude =
45+
quote do
46+
def __tableau_extension_type__, do: unquote(opts)[:type]
47+
end
48+
49+
postlude =
50+
quote do
51+
@behaviour unquote(__MODULE__)
52+
end
53+
54+
[prelude, postlude]
55+
end
56+
57+
@doc false
58+
@spec type(module()) :: extension_type()
59+
def type(module) do
60+
if function_exported?(module, :__tableau_extension_type__, 0) do
61+
{:ok, module.__tableau_extension_type__()}
62+
else
63+
:error
64+
end
65+
end
66+
end

test/mix/tasks/tableau.build_test.exs

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
defmodule Mix.Tasks.Tableau.LogExtension do
2+
use Tableau.Extension, type: :pre_build
3+
4+
def run(_graph, _site) do
5+
IO.puts("hi!")
6+
:ok
7+
end
8+
end
9+
10+
defmodule Mix.Tasks.Tableau.FailExtension do
11+
use Tableau.Extension, type: :pre_build
12+
13+
def run(_graph, _site) do
14+
:error
15+
end
16+
end
17+
118
defmodule Mix.Tasks.Tableau.BuildTest.About do
219
import Tableau.Strung
320
require EEx
@@ -85,11 +102,18 @@ end
85102
defmodule Mix.Tasks.Tableau.BuildTest do
86103
use ExUnit.Case, async: true
87104

105+
import ExUnit.CaptureIO
106+
import ExUnit.CaptureLog
107+
88108
alias Mix.Tasks.Tableau.Build
89109

90110
@tag :tmp_dir
91111
test "renders all pages", %{tmp_dir: out} do
92-
_documents = Build.run(["--out", out])
112+
assert capture_io(fn ->
113+
assert capture_log(fn ->
114+
_documents = Build.run(["--out", out])
115+
end) =~ "FailExtension failed to run"
116+
end) =~ "hi!"
93117

94118
# # FIXME: this is due to the way the page modules are compiled in the tests
95119
# assert 8 == length(documents)

0 commit comments

Comments
 (0)