Skip to content

Commit 4d67147

Browse files
authored
feat: RSS extension (#23)
1 parent 16d1c14 commit 4d67147

File tree

9 files changed

+220
-53
lines changed

9 files changed

+220
-53
lines changed

.github/workflows/ci.yaml

+3-3
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@ jobs:
3838

3939
formatter:
4040
runs-on: ubuntu-latest
41-
name: Formatter (1.14.x.x/25.x)
41+
name: Formatter (1.15.x/26.x)
4242

4343
steps:
4444
- uses: actions/checkout@v2
4545
- uses: erlef/setup-beam@v1
4646
with:
47-
otp-version: 25.x
48-
elixir-version: 1.14.x
47+
otp-version: 26.x
48+
elixir-version: 1.15.x
4949
- uses: actions/cache@v3
5050
id: cache
5151
with:

config/test.exs

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
import Config
22

33
config :logger, level: :warning
4+
5+
config :tableau, :config, url: "http://localhost:4999"
6+
config :tableau, Tableau.RSSExtension, enabled: false
7+
config :tableau, Tableau.PostExtension, enabled: false
8+
config :tableau, Mix.Tasks.Tableau.LogExtension, enabled: true
9+
config :tableau, Mix.Tasks.Tableau.FailExtension, enabled: true

lib/mix/tasks/tableau.build.ex

+72-11
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ defmodule Mix.Tasks.Tableau.Build do
1111
@impl Mix.Task
1212
def run(argv) do
1313
{:ok, config} = Tableau.Config.new(@config)
14-
site = %{config: config}
14+
token = %{site: %{config: config}}
1515
Mix.Task.run("app.start", ["--preload-modules"])
1616

1717
{opts, _argv} = OptionParser.parse!(argv, strict: [out: :string])
@@ -20,11 +20,35 @@ defmodule Mix.Tasks.Tableau.Build do
2020

2121
mods = :code.all_available()
2222

23-
for module <- pre_build_extensions(mods) do
24-
with :error <- module.run(%{site: site}) do
25-
Logger.error("#{inspect(module)} failed to run")
23+
token =
24+
for module <- pre_build_extensions(mods), reduce: token do
25+
token ->
26+
config_mod = Module.concat([module, Config])
27+
28+
raw_config =
29+
Application.get_env(:tableau, module, %{}) |> Map.new()
30+
31+
if raw_config[:enabled] do
32+
{:ok, config} =
33+
raw_config
34+
|> config_mod.new()
35+
36+
{:ok, key} = Tableau.Extension.key(module)
37+
38+
token = put_in(token[key], config)
39+
40+
case module.run(token) do
41+
{:ok, token} ->
42+
token
43+
44+
:error ->
45+
Logger.error("#{inspect(module)} failed to run")
46+
token
47+
end
48+
else
49+
token
50+
end
2651
end
27-
end
2852

2953
mods = :code.all_available()
3054
graph = Tableau.Graph.new(mods)
@@ -35,12 +59,12 @@ defmodule Mix.Tasks.Tableau.Build do
3559
{mod, Map.new(mod.__tableau_opts__() || [])}
3660
end
3761

38-
{mods, pages} = Enum.unzip(pages)
62+
{page_mods, pages} = Enum.unzip(pages)
3963

40-
site = Map.put(site, :pages, pages)
64+
token = put_in(token.site[:pages], pages)
4165

42-
for mod <- mods do
43-
content = Tableau.Document.render(graph, mod, %{site: site})
66+
for mod <- page_mods do
67+
content = Tableau.Document.render(graph, mod, token)
4468
permalink = mod.__tableau_permalink__()
4569
dir = Path.join(out, permalink)
4670

@@ -52,13 +76,50 @@ defmodule Mix.Tasks.Tableau.Build do
5276
if File.exists?(config.include_dir) do
5377
File.cp_r!(config.include_dir, out)
5478
end
79+
80+
for module <- post_write_extensions(mods), reduce: token do
81+
token ->
82+
config_mod = Module.concat([module, Config])
83+
84+
raw_config =
85+
Application.get_env(:tableau, module, %{}) |> Map.new()
86+
87+
if raw_config[:enabled] do
88+
{:ok, config} =
89+
raw_config
90+
|> config_mod.new()
91+
92+
{:ok, key} = Tableau.Extension.key(module)
93+
94+
token = put_in(token[key], config)
95+
96+
case module.run(token) do
97+
{:ok, token} ->
98+
token
99+
100+
:error ->
101+
Logger.error("#{inspect(module)} failed to run")
102+
token
103+
end
104+
else
105+
token
106+
end
107+
end
55108
end
56109

57110
defp pre_build_extensions(modules) do
58111
for {mod, _, _} <- modules,
59112
mod = Module.concat([to_string(mod)]),
60-
match?({:ok, :pre_build}, Tableau.Extension.type(mod)),
61-
Tableau.Extension.enabled?(mod) do
113+
match?({:ok, :pre_build}, Tableau.Extension.type(mod)) do
114+
mod
115+
end
116+
|> Enum.sort_by(& &1.__tableau_extension_priority__())
117+
end
118+
119+
defp post_write_extensions(modules) do
120+
for {mod, _, _} <- modules,
121+
mod = Module.concat([to_string(mod)]),
122+
match?({:ok, :post_write}, Tableau.Extension.type(mod)) do
62123
mod
63124
end
64125
|> Enum.sort_by(& &1.__tableau_extension_priority__())

lib/tableau/config.ex

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ defmodule Tableau.Config do
44
55
* `:include_dir` - Directory that is just copied to the output directory. Defaults to `extra`.
66
* `:timezone` - Timezone to use when parsing date times. Defaults to `Etc/UTC`.
7+
* `:url` - The URL of your website.
78
"""
89

910
import Schematic
1011

11-
defstruct include_dir: "extra",
12-
timezone: "Etc/UTC"
12+
defstruct [:url, include_dir: "extra", timezone: "Etc/UTC"]
1313

1414
def new(config) do
1515
unify(schematic(), config)
@@ -20,7 +20,8 @@ defmodule Tableau.Config do
2020
__MODULE__,
2121
%{
2222
optional(:include_dir) => str(),
23-
optional(:timezone) => str()
23+
optional(:timezone) => str(),
24+
url: str()
2425
},
2526
convert: false
2627
)

lib/tableau/document.ex

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ defmodule Tableau.Document do
99
defmacro render(inner_content) do
1010
quote do
1111
case unquote(inner_content) do
12-
[{module, page_assigns} | rest] ->
13-
module.template(%{page: page_assigns, inner_content: rest})
12+
[{module, page_assigns, assigns} | rest] ->
13+
module.template(Map.merge(assigns, %{page: page_assigns, inner_content: rest}))
1414

1515
[] ->
1616
nil
@@ -32,7 +32,7 @@ defmodule Tableau.Document do
3232
end
3333

3434
page_assigns = Map.new(module.__tableau_opts__() || [])
35-
mods = for mod <- mods, do: {mod, page_assigns}
35+
mods = for mod <- mods, do: {mod, page_assigns, assigns}
3636

3737
root.template(Map.merge(assigns, %{inner_content: mods, page: page_assigns}))
3838
end

lib/tableau/extension.ex

+27-10
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,27 @@ defmodule Tableau.Extension do
44
55
An extension can be used to generate other kinds of content.
66
7+
## Options
8+
9+
* `:key` - The key in which the extensions configuration and data is loaded.
10+
* `:type` - The type of extension. See below for a description.
11+
* `:priority` - An integer used for ordering extensions of the same type.
12+
* `:enabled` - Whether or not to enable the extension. Defaults to true, and can be configured differently based on the extension.
13+
714
## Types
815
916
There are currently the following extension types:
1017
1118
- `:pre_build` - executed before tableau builds your site and writes anything to disk.
19+
- `:post_write` - executed after tableau builds your site and writes everthing to disk.
1220
13-
## Priority
14-
15-
Extensions can be assigned a numeric priority for used with sorting.
21+
## Example
1622
1723
```elixir
1824
defmodule MySite.PostsExtension do
19-
use Tableau.Extension, type: :pre_build, priority: 300
25+
use Tableau.Extension, key: :posts, type: :pre_build, priority: 300
2026
21-
def run(_site) do
27+
def run(token) do
2228
posts = Path.wildcard("_posts/**/*.md")
2329
2430
for post <- post do
@@ -27,27 +33,28 @@ defmodule Tableau.Extension do
2733
|> then(&File.write(Path.join(Path.rootname(post), "index.html"), &1))
2834
end
2935
30-
:ok
36+
{:ok, token}
3137
end
3238
end
3339
```
3440
'''
3541

36-
@typep extension_type :: :pre_build
42+
@typep extension_type :: :pre_build | :post_write
3743

3844
@doc """
3945
The extension entry point.
4046
41-
The function is passed the a set of default assigns.
47+
The function is passed a token and can return a new token with new data loaded into it.
4248
"""
43-
@callback run(map()) :: :ok | :error
49+
@callback run(map()) :: {:ok, map()} | :error
4450

4551
defmacro __using__(opts) do
46-
opts = Keyword.validate!(opts, [:enabled, :type, :priority])
52+
opts = Keyword.validate!(opts, [:key, :enabled, :type, :priority])
4753

4854
prelude =
4955
quote do
5056
def __tableau_extension_type__, do: unquote(opts)[:type]
57+
def __tableau_extension_key__, do: unquote(opts)[:key]
5158
def __tableau_extension_enabled__, do: unquote(opts)[:enabled] || true
5259
def __tableau_extension_priority__, do: unquote(opts)[:priority] || 0
5360
end
@@ -70,6 +77,16 @@ defmodule Tableau.Extension do
7077
end
7178
end
7279

80+
@doc false
81+
@spec key(module()) :: extension_type()
82+
def key(module) do
83+
if function_exported?(module, :__tableau_extension_key__, 0) do
84+
{:ok, module.__tableau_extension_key__()}
85+
else
86+
:error
87+
end
88+
end
89+
7390
@doc false
7491
@spec enabled?(module()) :: boolean()
7592
def enabled?(module) do

lib/tableau/extensions/post_extension.ex

+22-19
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ defmodule Tableau.PostExtension do
8383

8484
@config config
8585

86-
use Tableau.Extension, enabled: @config.enabled, type: :pre_build, priority: 100
86+
use Tableau.Extension, key: :posts, type: :pre_build, priority: 100
8787

88-
def run(_site) do
88+
def run(token) do
8989
Module.create(
9090
Tableau.PostExtension.Posts,
9191
quote do
@@ -111,22 +111,25 @@ defmodule Tableau.PostExtension do
111111
Macro.Env.location(__ENV__)
112112
)
113113

114-
for post <- apply(Tableau.PostExtension.Posts, :posts, []) do
115-
{:module, _module, _binary, _term} =
116-
Module.create(
117-
Module.concat([post.id]),
118-
quote do
119-
@external_resource unquote(post.file)
120-
use Tableau.Page, unquote(Macro.escape(Keyword.new(post)))
121-
122-
def template(_assigns) do
123-
unquote(post.body)
124-
end
125-
end,
126-
Macro.Env.location(__ENV__)
127-
)
128-
end
129-
130-
:ok
114+
posts =
115+
for post <- apply(Tableau.PostExtension.Posts, :posts, []) do
116+
{:module, _module, _binary, _term} =
117+
Module.create(
118+
Module.concat([post.id]),
119+
quote do
120+
@external_resource unquote(post.file)
121+
use Tableau.Page, unquote(Macro.escape(Keyword.new(post)))
122+
123+
def template(_assigns) do
124+
unquote(post.body)
125+
end
126+
end,
127+
Macro.Env.location(__ENV__)
128+
)
129+
130+
post
131+
end
132+
133+
{:ok, Map.put(token, :posts, posts)}
131134
end
132135
end

0 commit comments

Comments
 (0)