Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: RSS extension #23

Merged
merged 1 commit into from
Oct 8, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -38,14 +38,14 @@ jobs:

formatter:
runs-on: ubuntu-latest
name: Formatter (1.14.x.x/25.x)
name: Formatter (1.15.x/26.x)

steps:
- uses: actions/checkout@v2
- uses: erlef/setup-beam@v1
with:
otp-version: 25.x
elixir-version: 1.14.x
otp-version: 26.x
elixir-version: 1.15.x
- uses: actions/cache@v3
id: cache
with:
6 changes: 6 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import Config

config :logger, level: :warning

config :tableau, :config, url: "http://localhost:4999"
config :tableau, Tableau.RSSExtension, enabled: false
config :tableau, Tableau.PostExtension, enabled: false
config :tableau, Mix.Tasks.Tableau.LogExtension, enabled: true
config :tableau, Mix.Tasks.Tableau.FailExtension, enabled: true
83 changes: 72 additions & 11 deletions lib/mix/tasks/tableau.build.ex
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ defmodule Mix.Tasks.Tableau.Build do
@impl Mix.Task
def run(argv) do
{:ok, config} = Tableau.Config.new(@config)
site = %{config: config}
token = %{site: %{config: config}}
Mix.Task.run("app.start", ["--preload-modules"])

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

mods = :code.all_available()

for module <- pre_build_extensions(mods) do
with :error <- module.run(%{site: site}) do
Logger.error("#{inspect(module)} failed to run")
token =
for module <- pre_build_extensions(mods), reduce: token do
token ->
config_mod = Module.concat([module, Config])

raw_config =
Application.get_env(:tableau, module, %{}) |> Map.new()

if raw_config[:enabled] do
{:ok, config} =
raw_config
|> config_mod.new()

{:ok, key} = Tableau.Extension.key(module)

token = put_in(token[key], config)

case module.run(token) do
{:ok, token} ->
token

:error ->
Logger.error("#{inspect(module)} failed to run")
token
end
else
token
end
end
end

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

{mods, pages} = Enum.unzip(pages)
{page_mods, pages} = Enum.unzip(pages)

site = Map.put(site, :pages, pages)
token = put_in(token.site[:pages], pages)

for mod <- mods do
content = Tableau.Document.render(graph, mod, %{site: site})
for mod <- page_mods do
content = Tableau.Document.render(graph, mod, token)
permalink = mod.__tableau_permalink__()
dir = Path.join(out, permalink)

@@ -52,13 +76,50 @@ defmodule Mix.Tasks.Tableau.Build do
if File.exists?(config.include_dir) do
File.cp_r!(config.include_dir, out)
end

for module <- post_write_extensions(mods), reduce: token do
token ->
config_mod = Module.concat([module, Config])

raw_config =
Application.get_env(:tableau, module, %{}) |> Map.new()

if raw_config[:enabled] do
{:ok, config} =
raw_config
|> config_mod.new()

{:ok, key} = Tableau.Extension.key(module)

token = put_in(token[key], config)

case module.run(token) do
{:ok, token} ->
token

:error ->
Logger.error("#{inspect(module)} failed to run")
token
end
else
token
end
end
end

defp pre_build_extensions(modules) do
for {mod, _, _} <- modules,
mod = Module.concat([to_string(mod)]),
match?({:ok, :pre_build}, Tableau.Extension.type(mod)),
Tableau.Extension.enabled?(mod) do
match?({:ok, :pre_build}, Tableau.Extension.type(mod)) do
mod
end
|> Enum.sort_by(& &1.__tableau_extension_priority__())
end

defp post_write_extensions(modules) do
for {mod, _, _} <- modules,
mod = Module.concat([to_string(mod)]),
match?({:ok, :post_write}, Tableau.Extension.type(mod)) do
mod
end
|> Enum.sort_by(& &1.__tableau_extension_priority__())
7 changes: 4 additions & 3 deletions lib/tableau/config.ex
Original file line number Diff line number Diff line change
@@ -4,12 +4,12 @@ defmodule Tableau.Config do

* `:include_dir` - Directory that is just copied to the output directory. Defaults to `extra`.
* `:timezone` - Timezone to use when parsing date times. Defaults to `Etc/UTC`.
* `:url` - The URL of your website.
"""

import Schematic

defstruct include_dir: "extra",
timezone: "Etc/UTC"
defstruct [:url, include_dir: "extra", timezone: "Etc/UTC"]

def new(config) do
unify(schematic(), config)
@@ -20,7 +20,8 @@ defmodule Tableau.Config do
__MODULE__,
%{
optional(:include_dir) => str(),
optional(:timezone) => str()
optional(:timezone) => str(),
url: str()
},
convert: false
)
6 changes: 3 additions & 3 deletions lib/tableau/document.ex
Original file line number Diff line number Diff line change
@@ -9,8 +9,8 @@ defmodule Tableau.Document do
defmacro render(inner_content) do
quote do
case unquote(inner_content) do
[{module, page_assigns} | rest] ->
module.template(%{page: page_assigns, inner_content: rest})
[{module, page_assigns, assigns} | rest] ->
module.template(Map.merge(assigns, %{page: page_assigns, inner_content: rest}))

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

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

root.template(Map.merge(assigns, %{inner_content: mods, page: page_assigns}))
end
37 changes: 27 additions & 10 deletions lib/tableau/extension.ex
Original file line number Diff line number Diff line change
@@ -4,21 +4,27 @@ defmodule Tableau.Extension do

An extension can be used to generate other kinds of content.

## Options

* `:key` - The key in which the extensions configuration and data is loaded.
* `:type` - The type of extension. See below for a description.
* `:priority` - An integer used for ordering extensions of the same type.
* `:enabled` - Whether or not to enable the extension. Defaults to true, and can be configured differently based on the extension.

## Types

There are currently the following extension types:

- `:pre_build` - executed before tableau builds your site and writes anything to disk.
- `:post_write` - executed after tableau builds your site and writes everthing to disk.

## Priority

Extensions can be assigned a numeric priority for used with sorting.
## Example

```elixir
defmodule MySite.PostsExtension do
use Tableau.Extension, type: :pre_build, priority: 300
use Tableau.Extension, key: :posts, type: :pre_build, priority: 300

def run(_site) do
def run(token) do
posts = Path.wildcard("_posts/**/*.md")

for post <- post do
@@ -27,27 +33,28 @@ defmodule Tableau.Extension do
|> then(&File.write(Path.join(Path.rootname(post), "index.html"), &1))
end

:ok
{:ok, token}
end
end
```
'''

@typep extension_type :: :pre_build
@typep extension_type :: :pre_build | :post_write

@doc """
The extension entry point.

The function is passed the a set of default assigns.
The function is passed a token and can return a new token with new data loaded into it.
"""
@callback run(map()) :: :ok | :error
@callback run(map()) :: {:ok, map()} | :error

defmacro __using__(opts) do
opts = Keyword.validate!(opts, [:enabled, :type, :priority])
opts = Keyword.validate!(opts, [:key, :enabled, :type, :priority])

prelude =
quote do
def __tableau_extension_type__, do: unquote(opts)[:type]
def __tableau_extension_key__, do: unquote(opts)[:key]
def __tableau_extension_enabled__, do: unquote(opts)[:enabled] || true
def __tableau_extension_priority__, do: unquote(opts)[:priority] || 0
end
@@ -70,6 +77,16 @@ defmodule Tableau.Extension do
end
end

@doc false
@spec key(module()) :: extension_type()
def key(module) do
if function_exported?(module, :__tableau_extension_key__, 0) do
{:ok, module.__tableau_extension_key__()}
else
:error
end
end

@doc false
@spec enabled?(module()) :: boolean()
def enabled?(module) do
41 changes: 22 additions & 19 deletions lib/tableau/extensions/post_extension.ex
Original file line number Diff line number Diff line change
@@ -83,9 +83,9 @@ defmodule Tableau.PostExtension do

@config config

use Tableau.Extension, enabled: @config.enabled, type: :pre_build, priority: 100
use Tableau.Extension, key: :posts, type: :pre_build, priority: 100

def run(_site) do
def run(token) do
Module.create(
Tableau.PostExtension.Posts,
quote do
@@ -111,22 +111,25 @@ defmodule Tableau.PostExtension do
Macro.Env.location(__ENV__)
)

for post <- apply(Tableau.PostExtension.Posts, :posts, []) do
{:module, _module, _binary, _term} =
Module.create(
Module.concat([post.id]),
quote do
@external_resource unquote(post.file)
use Tableau.Page, unquote(Macro.escape(Keyword.new(post)))

def template(_assigns) do
unquote(post.body)
end
end,
Macro.Env.location(__ENV__)
)
end

:ok
posts =
for post <- apply(Tableau.PostExtension.Posts, :posts, []) do
{:module, _module, _binary, _term} =
Module.create(
Module.concat([post.id]),
quote do
@external_resource unquote(post.file)
use Tableau.Page, unquote(Macro.escape(Keyword.new(post)))

def template(_assigns) do
unquote(post.body)
end
end,
Macro.Env.location(__ENV__)
)

post
end

{:ok, Map.put(token, :posts, posts)}
end
end
Loading