Skip to content

Commit 19f4ce5

Browse files
authored
feat: allow extensions to manually insert pages into the graph (#96)
fix: bump MDEx and fix breaking change
1 parent 2cb4951 commit 19f4ce5

18 files changed

+164
-119
lines changed

lib/mix/tasks/tableau.build.ex

+12-33
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,22 @@ defmodule Mix.Tasks.Tableau.Build do
1212
def run(argv) do
1313
Application.ensure_all_started(:telemetry)
1414
{:ok, config} = Tableau.Config.new(@config)
15-
token = %{site: %{config: config}}
15+
token = %{site: %{config: config}, graph: Graph.new()}
1616
Mix.Task.run("app.start", ["--preload-modules"])
1717

1818
{opts, _argv} = OptionParser.parse!(argv, strict: [out: :string])
1919

2020
out = Keyword.get(opts, :out, "_site")
2121

22-
mods = :code.all_available()
22+
mods =
23+
:code.all_available()
24+
|> Task.async_stream(fn {mod, _, _} -> Module.concat([to_string(mod)]) end)
25+
|> Stream.map(fn {:ok, mod} -> mod end)
26+
|> Enum.to_list()
2327

24-
token = mods |> pre_build_extensions() |> run_extensions(token)
28+
token = mods |> extensions_for(:pre_build) |> run_extensions(token)
2529

26-
mods = :code.all_available()
27-
graph = Tableau.Graph.new(mods)
30+
graph = Tableau.Graph.insert(token.graph, mods)
2831

2932
File.mkdir_p!(out)
3033

@@ -37,7 +40,7 @@ defmodule Mix.Tasks.Tableau.Build do
3740

3841
token = put_in(token.site[:pages], just_pages)
3942

40-
token = mods |> pre_write_extensions() |> run_extensions(token)
43+
token = mods |> extensions_for(:pre_write) |> run_extensions(token)
4144

4245
for {mod, page} <- Enum.zip(page_mods, token.site.pages) do
4346
content = Tableau.Document.render(graph, mod, token, page)
@@ -53,7 +56,7 @@ defmodule Mix.Tasks.Tableau.Build do
5356
File.cp_r!(config.include_dir, out)
5457
end
5558

56-
token = run_extensions(post_write_extensions(mods), token)
59+
token = mods |> extensions_for(:post_write) |> run_extensions(token)
5760

5861
token
5962
end
@@ -66,33 +69,9 @@ defmodule Mix.Tasks.Tableau.Build do
6669
end
6770
end
6871

69-
defp pre_build_extensions(modules) do
72+
defp extensions_for(modules, type) do
7073
extensions =
71-
for {mod, _, _} <- modules,
72-
mod = Module.concat([to_string(mod)]),
73-
match?({:ok, :pre_build}, Tableau.Extension.type(mod)) do
74-
mod
75-
end
76-
77-
Enum.sort_by(extensions, & &1.__tableau_extension_priority__())
78-
end
79-
80-
defp pre_write_extensions(modules) do
81-
extensions =
82-
for {mod, _, _} <- modules,
83-
mod = Module.concat([to_string(mod)]),
84-
match?({:ok, :pre_write}, Tableau.Extension.type(mod)) do
85-
mod
86-
end
87-
88-
Enum.sort_by(extensions, & &1.__tableau_extension_priority__())
89-
end
90-
91-
defp post_write_extensions(modules) do
92-
extensions =
93-
for {mod, _, _} <- modules,
94-
mod = Module.concat([to_string(mod)]),
95-
match?({:ok, :post_write}, Tableau.Extension.type(mod)) do
74+
for mod <- modules, {:ok, type} == Tableau.Extension.type(mod) do
9675
mod
9776
end
9877

lib/tableau/extension.ex

+26-9
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,39 @@ defmodule Tableau.Extension do
1919
- `:pre_write` - executed after tableau builds your site but before it writes anything to disk.
2020
- `:post_write` - executed after tableau builds your site and writes everything to disk.
2121
22+
## The Graph
23+
24+
Tableau pages and layuts are a DAG, a Directed Acyclic Graph, and this graph is used to build each page when writing to disk.
25+
26+
Your extension can create data to to insert into the extension token and can also inserted pages into the graph to be written to disk when the time comes.
27+
2228
## Example
2329
30+
In this example, we create a simple post extension that reads markdown files from disk, inserts them into the graph, and inserts them into the token.
31+
32+
By inserting them into the token, you are able to access them inside your templates, for example, a posts listing page.
33+
2434
```elixir
2535
defmodule MySite.PostsExtension do
2636
use Tableau.Extension, key: :posts, type: :pre_build, priority: 300
2737
2838
def run(token) do
29-
posts = Path.wildcard("_posts/**/*.md")
30-
31-
for post <- post do
32-
post
33-
|> Markdown.render()
34-
|> then(&File.write(Path.join(Path.rootname(post), "index.html"), &1))
35-
end
36-
37-
{:ok, token}
39+
posts =
40+
for post <- Path.wildcard("_posts/**/*.md") do
41+
%Tableau.Page{
42+
parent: MySite.RootLayout,
43+
permalink: Path.join("posts", Path.rootname(post)),
44+
template: Markdown.render(post),
45+
opts: %{}
46+
}
47+
end
48+
49+
graph = Tableau.Graph.insert(token.graph, posts)
50+
51+
{:ok,
52+
token
53+
|> Map.put(:posts, posts)
54+
|> Map.put(:graph, graph)}
3855
end
3956
end
4057
```

lib/tableau/extensions/page_extension.ex

+14-2
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,19 @@ defmodule Tableau.PageExtension do
4848
use Tableau.Extension, key: :pages, type: :pre_build, priority: 100
4949

5050
def run(token) do
51-
token = put_in(token.pages, Tableau.PageExtension.Pages.pages())
52-
{:ok, token}
51+
pages = Tableau.PageExtension.Pages.pages()
52+
53+
graph =
54+
Tableau.Graph.insert(
55+
token.graph,
56+
Enum.map(pages, fn page ->
57+
%Tableau.Page{parent: page.layout, permalink: page.permalink, template: page.body, opts: page}
58+
end)
59+
)
60+
61+
{:ok,
62+
token
63+
|> Map.put(:pages, pages)
64+
|> Map.put(:graph, graph)}
5365
end
5466
end

lib/tableau/extensions/page_extension/pages.ex

-16
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,7 @@ defmodule Tableau.PageExtension.Pages do
44

55
@config Map.new(Application.compile_env(:tableau, Tableau.PageExtension, %{}))
66

7-
def __tableau_type__, do: :pages
8-
97
def pages(opts \\ []) do
10-
opts
11-
|> pages2()
12-
|> Enum.map(fn page ->
13-
%{
14-
type: :page,
15-
parent: page.layout,
16-
permalink: page.permalink,
17-
template: page.body,
18-
opts: page
19-
}
20-
end)
21-
end
22-
23-
def pages2(opts \\ []) do
248
{:ok, config} =
259
Tableau.PageExtension.Config.new(@config)
2610

lib/tableau/extensions/page_extension/pages/html_converter.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ defmodule Tableau.PageExtension.Pages.HTMLConverter do
33
def convert(_filepath, body, _attrs, _opts) do
44
{:ok, config} = Tableau.Config.new(Map.new(Application.get_env(:tableau, :config, %{})))
55

6-
MDEx.to_html(body, config.markdown[:mdex])
6+
MDEx.to_html!(body, config.markdown[:mdex])
77
end
88
end

lib/tableau/extensions/post_extension.ex

+14-2
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,19 @@ defmodule Tableau.PostExtension do
5454
use Tableau.Extension, key: :posts, type: :pre_build, priority: 100
5555

5656
def run(token) do
57-
token = put_in(token.posts, Tableau.PostExtension.Posts.posts())
58-
{:ok, token}
57+
posts = Tableau.PostExtension.Posts.posts()
58+
59+
graph =
60+
Tableau.Graph.insert(
61+
token.graph,
62+
Enum.map(posts, fn post ->
63+
%Tableau.Page{parent: post.layout, permalink: post.permalink, template: post.body, opts: post}
64+
end)
65+
)
66+
67+
{:ok,
68+
token
69+
|> Map.put(:posts, posts)
70+
|> Map.put(:graph, graph)}
5971
end
6072
end

lib/tableau/extensions/post_extension/posts.ex

-16
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,6 @@ defmodule Tableau.PostExtension.Posts do
44

55
@config Map.new(Application.compile_env(:tableau, Tableau.PostExtension, %{}))
66

7-
def __tableau_type__, do: :pages
8-
9-
def pages(opts \\ []) do
10-
opts
11-
|> posts()
12-
|> Enum.map(fn post ->
13-
%{
14-
type: :page,
15-
parent: post.layout,
16-
permalink: post.permalink,
17-
template: post.body,
18-
opts: post
19-
}
20-
end)
21-
end
22-
237
def posts(opts \\ []) do
248
{:ok, config} =
259
Tableau.PostExtension.Config.new(@config)

lib/tableau/extensions/post_extension/posts/html_converter.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ defmodule Tableau.PostExtension.Posts.HTMLConverter do
33
def convert(_filepath, body, _attrs, _opts) do
44
{:ok, config} = Tableau.Config.new(Map.new(Application.get_env(:tableau, :config, %{})))
55

6-
MDEx.to_html(body, config.markdown[:mdex])
6+
MDEx.to_html!(body, config.markdown[:mdex])
77
end
88
end

lib/tableau/extensions/sitemap_extension.ex

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
defmodule Tableau.SitemapExtension.Config do
2-
@moduledoc false
3-
4-
def new(input), do: {:ok, input}
5-
end
6-
71
defmodule Tableau.SitemapExtension do
82
@moduledoc """
93
Generate a sitemap.xml for your site, for improved search-engine indexing.
@@ -52,6 +46,8 @@ defmodule Tableau.SitemapExtension do
5246

5347
use Tableau.Extension, key: :sitemap, type: :post_write, priority: 300
5448

49+
require Logger
50+
5551
def run(%{site: %{config: %{url: root}, pages: pages}} = token) do
5652
urls =
5753
for page <- pages, uniq: true do
@@ -79,6 +75,10 @@ defmodule Tableau.SitemapExtension do
7975
File.write!("_site/sitemap.xml", XmlBuilder.generate_iodata(xml))
8076

8177
{:ok, token}
78+
rescue
79+
e ->
80+
Logger.error(Exception.format(:error, e, __STACKTRACE__))
81+
{:error, :fail}
8282
end
8383

8484
defp prepend_lastmod(body, %{date: date}) do

lib/tableau/graph.ex

+5-19
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,12 @@
11
defmodule Tableau.Graph do
22
@moduledoc false
3-
alias Tableau.Graph.Node
3+
alias Tableau.Graph.Nodable
44

5-
def new(modules) do
6-
graph = Graph.new()
7-
8-
for {mod, _, _} <- modules,
9-
mod = Module.concat([to_string(mod)]),
10-
Node.type(mod) != :error,
11-
reduce: graph do
5+
def insert(graph, nodes) do
6+
for node <- nodes, Nodable.type(node) != :error, reduce: graph do
127
graph ->
13-
case Node.type(mod) do
14-
{:ok, :pages} ->
15-
for page <- mod.pages(), reduce: graph do
16-
graph ->
17-
Graph.add_edge(graph, page, page.parent)
18-
end
19-
20-
_ ->
21-
{:ok, parent} = Node.parent(mod)
22-
Graph.add_edge(graph, mod, parent)
23-
end
8+
{:ok, parent} = Nodable.parent(node)
9+
Graph.add_edge(graph, node, parent)
2410
end
2511
end
2612
end

lib/tableau/graph/nodable.ex

+2-7
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,15 @@ defprotocol Tableau.Graph.Nodable do
22
@moduledoc false
33
def template(nodable, assigns)
44
def type(nodable)
5+
def parent(nodable)
56
def permalink(nodable)
67
def opts(nodable)
78
end
89

9-
defimpl Tableau.Graph.Nodable, for: Map do
10-
def template(nodable, _assigns), do: nodable.template
11-
def type(nodable), do: {:ok, nodable.type}
12-
def opts(nodable), do: nodable.opts
13-
def permalink(nodable), do: nodable.permalink
14-
end
15-
1610
defimpl Tableau.Graph.Nodable, for: Atom do
1711
def template(nodable, assigns), do: nodable.template(assigns)
1812
def type(nodable), do: Tableau.Graph.Node.type(nodable)
13+
def parent(nodable), do: Tableau.Graph.Node.parent(nodable)
1914
def opts(nodable), do: nodable.__tableau_opts__()
2015
def permalink(nodable), do: nodable.__tableau_permalink__()
2116
end

lib/tableau/page.ex

+13
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@ defmodule Tableau.Page do
2626
@type assigns :: map()
2727
@type template :: any()
2828

29+
@doc """
30+
The page struct allows you to create pages as data structures and manually insert them into the Graph.
31+
"""
32+
defstruct [:parent, :permalink, :template, :opts]
33+
34+
defimpl Tableau.Graph.Nodable do
35+
def template(nodable, _assigns), do: nodable.template
36+
def type(_nodable), do: {:ok, :page}
37+
def parent(nodable), do: {:ok, nodable.parent}
38+
def opts(nodable), do: nodable.opts
39+
def permalink(nodable), do: nodable.permalink
40+
end
41+
2942
@doc """
3043
The page template.
3144
"""

mix.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ defmodule Tableau.MixProject do
3838
{:floki, floki_constraint()},
3939
{:html_entities, "~> 0.5.2"},
4040
{:libgraph, "~> 0.16.0"},
41-
{:mdex, "~> 0.1"},
41+
{:mdex, "~> 0.2.0"},
4242
{:plug_static_index_html, "~> 1.0"},
4343
{:schematic, "~> 0.3.1"},
4444
{:tz, "~> 0.28.1"},

mix.lock

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@
1313
"makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
1414
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
1515
"makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"},
16-
"mdex": {:hex, :mdex, "0.1.11", "83bac0b339811310362c86087c1ea1d37cf3190f41993a7de41fea81ccdbc8a1", [:mix], [{:rustler, "~> 0.29", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "303510829f3c59295e13b27992ef542356db8276a3a514f1369ae91afb62f60b"},
16+
"mdex": {:hex, :mdex, "0.2.0", "af93e03bc964f2628c3940d22ba03435b119e070bd423fd62d31772d428a7e6c", [:mix], [{:rustler, "~> 0.32", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.7", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "d8d21d3d6ecb0b2a10f88b539f3a61df974f9570226bf6bd24404c9e361c8089"},
1717
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
1818
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
1919
"plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [: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", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
2020
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
2121
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
22-
"rustler_precompiled": {:hex, :rustler_precompiled, "0.7.0", "5d0834fc06dbc76dd1034482f17b1797df0dba9b491cef8bb045fcaca94bcade", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "fdf43a6835f4e4de5bfbc4c019bfb8c46d124bd4635fefa3e20d9a2bbbec1512"},
22+
"rustler_precompiled": {:hex, :rustler_precompiled, "0.8.2", "5f25cbe220a8fac3e7ad62e6f950fcdca5a5a5f8501835d2823e8c74bf4268d5", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "63d1bd5f8e23096d1ff851839923162096364bac8656a4a3c00d1fff8e83ee0a"},
2323
"schematic": {:hex, :schematic, "0.3.1", "be633c1472959dc0ace22dd0e1f1445b099991fec39f6d6e5273d35ebd217ac4", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "52c419b5c405286e2d0369b9ca472b00b850c59a8b0bdf0dd69172ad4418d5ea"},
2424
"styler": {:hex, :styler, "0.9.7", "c06ebe565db5d2d8cf749f623d5c1f019da33bf837e6b06e575528b355770ae2", [:mix], [], "hexpm", "5762579d2faa76ad9e1ba92ed5f0905c923a5670af652b8e118aef5d79b9dc58"},
2525
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},

0 commit comments

Comments
 (0)