From 1b93fbf1594ea4d2e0dcd3d054ffed4711128213 Mon Sep 17 00:00:00 2001 From: Mitchell Hanberg Date: Sun, 11 Oct 2026 10:37:14 -0400 Subject: [PATCH 1/3] docs: update readme to account for tableau dev server --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 05a7190..5e754db 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ defmodule MySite.RootLayout do end if Mix.env() == :dev do - c &Tableau.live_reload/1 + c &TableauDevServer.live_reload/1 end end end @@ -187,7 +187,7 @@ config :tableau, :reloader, ] ``` -All you need to do is render the `Tableau.live_reload/1` component right after your `body` tag. +All you need to do is render the `TableauDevServer.live_reload/1` component right after your `body` tag. ```elixir # lib/layouts/app.ex From 502275d4096e5b41bef47b5dfcb1565832394138 Mon Sep 17 00:00:00 2001 From: Mitchell Hanberg Date: Sun, 11 Oct 2026 10:46:00 -0400 Subject: [PATCH 2/3] docs(posts): dates are strings not sigils --- lib/tableau/extensions/post_extension.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tableau/extensions/post_extension.ex b/lib/tableau/extensions/post_extension.ex index b21234f..3b0129f 100644 --- a/lib/tableau/extensions/post_extension.ex +++ b/lib/tableau/extensions/post_extension.ex @@ -10,7 +10,7 @@ defmodule Tableau.PostExtension do * `:title` - The title of the post. * `:permalink` - The permalink of the post. `:title` will be replaced with the posts title and non alphanumeric characters removed. Optional. - * `:date` - A string representation of an Elixir `NaiveDateTime`, often presented as a `sigil_N`. This will be converted to your configured timezone. + * `:date` - A string representation of a date time, parsed by `DateTimeParser`. Supports timezones. * `:layout` - A string representation of a Tableau layout module. * `:converter` - A string representation of a converter module. (optional) From 17915364f75ddd10417bfee6d28b957d2958b881 Mon Sep 17 00:00:00 2001 From: Mitchell Hanberg Date: Sun, 11 Oct 2026 10:52:07 -0400 Subject: [PATCH 3/3] feat!: migrate MDExConverter to MDExExtension This changes the MDEx integration from being a converter to being its own extension. The main goal of this is to make mdex an optional dependency for Tableau, so a Tableau release is not required when mdex updates or makes breaking changes. - Adds a `convert` callback to `Tableau.Extension` - Adds a `Tableau.Converter.convert` function for extensions to use - Post and Page extensions now will copy all files in the respective directory, and will convert them if there is a matching converter for that extension. Frontmatter is still required. - MDex and converter options are now set by their extension module instead of the `:config` key. - The contract for `Tableau.Converter.convert` now just takes content and opts. - `Tableau.Converter` is no longer a behaviour. - Converters now are extensions that implement the `convert` callback. - `Tableau.markdown` is removed. Use `Tableau.Converter` instead. --- .formatter.exs | 1 + lib/tableau.ex | 35 +----- lib/tableau/config.ex | 3 +- lib/tableau/converter.ex | 53 ++++++-- lib/tableau/converters/mdex_converter.ex | 8 -- lib/tableau/extension.ex | 10 +- lib/tableau/extensions/common.ex | 17 ++- lib/tableau/extensions/mdex_extension.ex | 115 ++++++++++++++++++ lib/tableau/extensions/page_extension.ex | 31 +++-- lib/tableau/extensions/post_extension.ex | 61 +++++++--- lib/tableau/extensions/rss_extension.ex | 2 +- lib/tableau/yaml_front_matter.ex | 34 +++--- mix.exs | 18 +-- mix.lock | 4 +- .../extensions/page_extension_test.exs | 24 ++-- .../extensions/post_extension_test.exs | 15 ++- .../tableau/extensions/tag_extension_test.exs | 4 +- 17 files changed, 313 insertions(+), 122 deletions(-) delete mode 100644 lib/tableau/converters/mdex_converter.ex create mode 100644 lib/tableau/extensions/mdex_extension.ex diff --git a/.formatter.exs b/.formatter.exs index 17fe611..e69200f 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -6,6 +6,7 @@ locals_without_parens = [ [ locals_without_parens: locals_without_parens, import_deps: [], + line_length: 120, plugins: [Styler], inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], export: [locals_without_parens: locals_without_parens] diff --git a/lib/tableau.ex b/lib/tableau.ex index a3b0619..5772fab 100644 --- a/lib/tableau.ex +++ b/lib/tableau.ex @@ -7,11 +7,11 @@ defmodule Tableau do * `:timezone` - string - Timezone to use when parsing date times. Defaults to `Etc/UTC`. * `:base_path` - string - Development server root. Defaults to '/'. * `:url` - string (required) - The URL of your website. - * `:converters` - mapping of file extensions to converter module. Defaults to `[md: Tableau.MDExConverter]` - * `:markdown` - keyword - * `:mdex` - keyword - Options to pass to `MDEx.to_html/2` + * `:converters` - mapping of file extensions to converter module. Defaults to `[md: Tableau.MDExExtension]` * `:slug` - keyword - Options to pass to `Slug.slugify/2` + You'll want to check out each extension's documentation to see configuration options. + ### Example ```elixir @@ -23,39 +23,12 @@ defmodule Tableau do url: "http://localhost:8080", timezone: "America/Indiana/Indianapolis", converters: [ - md: Tableau.MDExConverter, + md: Tableau.MDExExtension, dj: MySite.DjotConverter ], slug: [ lowercase: false - ], - markdown: [ - mdex: [ - extension: [ - table: true, - header_ids: "", - tasklist: true, - strikethrough: true, - autolink: true, - alerts: true, - footnotes: true - ], - render: [unsafe: true], - syntax_highlight: [formatter: {:html_inline, theme: "neovim_dark"}], - plugins: [MDExGFM] - ] ] ``` """ - - @doc """ - Convert markdown content to HTML using `MDEx.to_html!/2`. - - Will use the globally configured options, but you can also pass it overrides. - """ - def markdown(content, overrides \\ []) do - {:ok, config} = Tableau.Config.get() - - MDEx.to_html!(content, Keyword.merge(config.markdown[:mdex], overrides)) - end end diff --git a/lib/tableau/config.ex b/lib/tableau/config.ex index 59beaad..82291ac 100644 --- a/lib/tableau/config.ex +++ b/lib/tableau/config.ex @@ -11,8 +11,7 @@ defmodule Tableau.Config do timezone: "Etc/UTC", reload_log: false, slug: [], - converters: [md: Tableau.MDExConverter], - markdown: [mdex: []] + converters: [] ] def new(config) do diff --git a/lib/tableau/converter.ex b/lib/tableau/converter.ex index c0626b3..7e73164 100644 --- a/lib/tableau/converter.ex +++ b/lib/tableau/converter.ex @@ -1,13 +1,52 @@ defmodule Tableau.Converter do @moduledoc """ - Defines the interface for the converter module used by extensions to parse markup files. - """ + Convenience module for converting content into HTML. Useful for extension authors to convert content consistently with other + extensions without making beskope code. + + By default, will convert markdown content into HTML using `Tableau.MDExExtension`. + + You can configure other converters (an extension that implements the convert callback) with: + + ```elixir + config :tableau, :config, + converters: [ + ext: ConverterModule + ] + ``` + + This will be merged in with the default of `[md: Tableau.MDExExtension]`. - @doc """ - Converts content into HTML. + This can be called via an explicit converter or with an extension that will be looked up via the configuration. - Is given the file path, the frontmatter, the content of the files (sans front matter), and a list of options. + ```elixir + Tableau.Converter.convert(content, converter: MyConverter) + + Tableau.Converter.convert(content, ext: :md) + ``` + + This makes it somewhat easier to swap out your converter without needing to make big application changes, but you can still + call a converter explicitly in your layout/page code. Doing so will allow you to pass configuration options to the + converter itself (assuming the implementation allows that). + + ```elixir + Tableau.MDExExtension.convert(content, extension: [autolink: false]) + ``` """ - @callback convert(filepath :: String.t(), front_matter :: map(), content :: String.t(), opts :: Keyword.t()) :: - String.t() + @default_converters [ + md: Tableau.MDExExtension + ] + def convert(content, opts \\ []) do + converters = + Keyword.merge( + @default_converters, + Application.get_env(:tableau, :config, [])[:converters] || [] + ) + + with nil <- opts[:converter], + nil <- converters[opts[:ext]] do + content + else + converter -> converter.convert(content) + end + end end diff --git a/lib/tableau/converters/mdex_converter.ex b/lib/tableau/converters/mdex_converter.ex deleted file mode 100644 index 597e773..00000000 --- a/lib/tableau/converters/mdex_converter.ex +++ /dev/null @@ -1,8 +0,0 @@ -defmodule Tableau.MDExConverter do - @moduledoc """ - Converter to parse markdown content with `MDEx` - """ - def convert(_filepath, _front_matter, body, %{site: %{config: config}}) do - MDEx.to_html!(body, config.markdown[:mdex]) - end -end diff --git a/lib/tableau/extension.ex b/lib/tableau/extension.ex index e8df2a9..a725fec 100644 --- a/lib/tableau/extension.ex +++ b/lib/tableau/extension.ex @@ -93,12 +93,20 @@ defmodule Tableau.Extension do """ @callback config(Keyword.t() | map()) :: {:ok, map()} | {:error, any()} + @doc """ + Called by content generators to convert content for a specific content type. + """ + @callback convert(String.t(), any()) :: String.t() + @callback convert(String.t()) :: String.t() + @optional_callbacks [ config: 1, pre_build: 1, pre_render: 1, pre_write: 1, - post_write: 1 + post_write: 1, + convert: 1, + convert: 2 ] defmacro __using__(opts) do diff --git a/lib/tableau/extensions/common.ex b/lib/tableau/extensions/common.ex index 9eb9937..e593526 100644 --- a/lib/tableau/extensions/common.ex +++ b/lib/tableau/extensions/common.ex @@ -36,9 +36,22 @@ defmodule Tableau.Extension.Common do """ def entries(paths, callback) do for path <- paths do - {front_matter, body} = Tableau.YamlFrontMatter.parse!(File.read!(path), atoms: true) + content = File.read!(path) + + {front_matter, body} = + case Tableau.YamlFrontMatter.parse(content) do + {:ok, fm, body} -> {fm, body} + {:error, _} -> {%{}, content} + end + "." <> ext = Path.extname(path) - callback.(%{path: path, ext: String.to_atom(ext), front_matter: front_matter, pre_convert_body: body}) + + callback.(%{ + path: path, + ext: String.to_atom(ext), + front_matter: front_matter, + pre_convert_body: body + }) end end diff --git a/lib/tableau/extensions/mdex_extension.ex b/lib/tableau/extensions/mdex_extension.ex new file mode 100644 index 00000000..19acd8e --- /dev/null +++ b/lib/tableau/extensions/mdex_extension.ex @@ -0,0 +1,115 @@ +defmodule Tableau.MDExExtension do + @moduledoc """ + Converter to parse markdown content with `MDEx`. + + > #### Warning {: .warning} + > [MDEx](https://github.com/leandrocp/mdex), [mdex_native](https://github.com/leandrocp/mdex_native), and [Lumis](https://github.com/leandrocp/lumis) are constantly evolving projects that change quickly. This module only relies on the `MDEx.to_html/2` function, so breaking changes are unlikely from a Tableau perspective, but if you update any of these libraries, you must check for breaking configuration changes and update your site accordingly. + + Configure `MDEx` options (these are passed directly to `MDEx.to_html/2`) with: + + ```elixir + config :mdex_native, :syntax_highlighter, :lumis + + config :tableau, Tableau.MDExExtension, + extension: [ + table: true, + header_id_prefix: "", + tasklist: true, + strikethrough: true, + autolink: true, + alerts: true, + footnotes: true + ], + render: [unsafe: true], + syntax_highlight: [ + engine: :lumis, + opts: [ + formatter: {:html_inline, theme: "neovim_dark"} + ] + ] + ``` + + ### Syntax Highlighting + + > #### mdex_native {: .tip} + > MDEx allows you to highlight your code with [Lumis](https://github.com/leandrocp/lumis/) or [Syntect](https://docs.rs/syntect/latest/syntect/). Lumis requires you to add `:lumis` to your dependencies (Syntect does not require this). + > + > You need to configure `:mdex_native` which syntax highlighter to use. + > ```elixir + > # config/config.exs + > + > config :mdex_native, :syntax_highlighter, :lumis + > # or + > config :mdex_native, :syntax_highlighter, :syntect + > ``` + + #### Lumis + + Lumis requires you to install the `:lumis` dependency as well as the parsers as dependencies as well. You can see the documentation for this [here](https://docs.lumis.sh/). + + In addition to setting the `:mdex_native` configuration, you must also configure the `:engine` option for `:mdex` + + You can find full configuration optiosn in the MDEx documentation. + + ```elixir + # mix.exs + defp deps() do + [ + {:lumis, "~> 0.10"}, + ] + end + + # config/config.exs + config :mdex_native, :syntax_highlighter, :lumis + + config :tableau, Tableau.MDExExtension, + syntax_highlight: [ + engine: :lumis, + opts: [ + formatter: {:html_inline, theme: "neovim_dark"} + ] + ] + ``` + + #### Syntect + + In addition to setting the `:mdex_native` configuration, you must also configure the `:engine` option for `:mdex` + + You can find full configuration optiosn in the MDEx documentation. + + ```elixir + # config/config.exs + config :mdex_native, :syntax_highlighter, :syntect + + config :tableau, Tableau.MDExExtension, + syntax_highlight: [ + engine: :syntect, + opts: [ + theme: "Catppuccin Macchiato" + ] + ] + ``` + """ + + use Tableau.Extension, key: :mdex + + @impl true + def convert(body, config_overrides \\ []) do + if Code.ensure_loaded?(MDEx) do + config = + :tableau + |> Application.get_env(__MODULE__, []) + |> Keyword.merge(config_overrides) + + MDEx.to_html!(body, config) + else + raise """ + MDEx is not installed! + + You'll need to add `:mdex` as a dependency in mix.exs. + + `{:mdex, "~> 0.14"}` + """ + end + end +end diff --git a/lib/tableau/extensions/page_extension.ex b/lib/tableau/extensions/page_extension.ex index 98bf67b..3e5598e 100644 --- a/lib/tableau/extensions/page_extension.ex +++ b/lib/tableau/extensions/page_extension.ex @@ -2,6 +2,8 @@ defmodule Tableau.PageExtension do @moduledoc """ Content files (with YAML frontmatter) in the configured pages directory will be automatically compiled into Tableau pages. + All files in the directory are copied to the output directory, and if their filetype has a matching converter, they are converted. + Certain frontmatter keys are required and all keys are passed as options to the `Tableau.Page`. ## Options @@ -89,16 +91,14 @@ defmodule Tableau.PageExtension do @impl Tableau.Extension def pre_build(token) do - %{site: %{config: %{converters: converters}}, extensions: %{pages: %{config: config}}} = token - - exts = Enum.map_join(converters, ",", fn {ext, _} -> to_string(ext) end) + %{extensions: %{pages: %{config: config}}} = token pages = config.dir |> List.wrap() |> Enum.flat_map(fn path -> path - |> Path.join("**/*.{#{exts}}") + |> Path.join("**/*.*") |> Common.paths() end) |> Common.entries(fn entry -> @@ -110,13 +110,7 @@ defmodule Tableau.PageExtension do } = entry build(path, front_matter, body, config, fn assigns -> - converter = - case front_matter[:converter] do - nil -> converters[ext] - converter -> Module.concat([converter]) - end - - converter.convert(path, front_matter, body, assigns) + Tableau.Converter.convert(body, converter: assigns.page[:converter], ext: ext) end) end) @@ -129,7 +123,12 @@ defmodule Tableau.PageExtension do Tableau.Graph.insert( token.graph, Enum.map(token.pages, fn page -> - %Tableau.Page{parent: page.layout, permalink: page.permalink, template: page.renderer, opts: page} + %Tableau.Page{ + parent: page.layout, + permalink: page.permalink, + template: page.renderer, + opts: page + } end) ) @@ -137,12 +136,20 @@ defmodule Tableau.PageExtension do end defp build(filename, front_matter, body, pages_config, renderer) do + converter = + case front_matter[:converter] do + nil -> nil + converter -> Module.concat([converter]) + end + front_matter |> Map.put(:__tableau_page_extension__, true) |> Map.put(:body, body) + |> Map.put(:title, front_matter[:title] || filename |> Path.basename() |> Path.rootname()) |> Map.put(:file, filename) |> Map.put(:renderer, renderer) |> Map.put(:layout, Module.concat([front_matter[:layout] || pages_config.layout])) + |> Map.put(:converter, converter) |> Common.build_permalink(pages_config) end end diff --git a/lib/tableau/extensions/post_extension.ex b/lib/tableau/extensions/post_extension.ex index 3b0129f..621d7be 100644 --- a/lib/tableau/extensions/post_extension.ex +++ b/lib/tableau/extensions/post_extension.ex @@ -2,6 +2,8 @@ defmodule Tableau.PostExtension do @moduledoc """ Content files (with YAML frontmatter) in the configured posts directory will be automatically compiled into Tableau pages. + All files in the directory are copied to the output directory, and if their filetype has a matching converter, they are converted. + Certain frontmatter keys are required and all keys are passed as options to the `Tableau.Page`. ## Options @@ -104,26 +106,26 @@ defmodule Tableau.PostExtension do @impl Tableau.Extension def pre_build(token) do - %{site: %{config: %{converters: converters}}, extensions: %{posts: %{config: config}}} = token - exts = Enum.map_join(converters, ",", fn {ext, _} -> to_string(ext) end) + %{extensions: %{posts: %{config: config}}} = token posts = config.dir |> List.wrap() |> Enum.flat_map(fn path -> path - |> Path.join("**/*.{#{exts}}") + |> Path.join("**/*.*") |> Common.paths() end) - |> Common.entries(fn %{path: path, front_matter: front_matter, pre_convert_body: body, ext: ext} -> - build(path, front_matter, body, config, fn assigns -> - converter = - case front_matter[:converter] do - nil -> converters[ext] - converter -> Module.concat([converter]) - end + |> Common.entries(fn entry -> + %{ + path: path, + front_matter: front_matter, + pre_convert_body: body, + ext: ext + } = entry - converter.convert(path, front_matter, body, assigns) + build(path, front_matter, body, config, fn assigns -> + Tableau.Converter.convert(body, converter: assigns.page[:converter], ext: ext) end) end) |> Enum.sort_by(fn post -> post.date end, {:desc, DateTime}) @@ -144,23 +146,50 @@ defmodule Tableau.PostExtension do Tableau.Graph.insert( token.graph, Enum.map(token.posts, fn post -> - %Tableau.Page{parent: post.layout, permalink: post.permalink, template: post.renderer, opts: post} + %Tableau.Page{ + parent: post.layout, + permalink: post.permalink, + template: post.renderer, + opts: post + } end) ) {:ok, Map.put(token, :graph, graph)} end - defp build(filename, attrs, body, posts_config, renderer) do + defp build(filename, front_matter, body, posts_config, renderer) do Application.put_env(:date_time_parser, :include_zones_from, ~N[2010-01-01T00:00:00]) - attrs + converter = + case front_matter[:converter] do + nil -> nil + converter -> Module.concat([converter]) + end + + front_matter |> Map.put(:__tableau_post_extension__, true) |> Map.put(:body, body) + |> Map.put(:title, front_matter[:title] || filename |> Path.basename() |> Path.rootname()) |> Map.put(:file, filename) |> Map.put(:renderer, renderer) - |> Map.put(:layout, Module.concat([attrs[:layout] || posts_config.layout])) - |> Map.put(:date, DateTimeParser.parse_datetime!(attrs.date, assume_time: true, assume_utc: true)) + |> Map.put(:layout, Module.concat([front_matter[:layout] || posts_config.layout])) + |> Map.put(:converter, converter) + |> Map.put( + :date, + parse_datetime!(front_matter[:date], filename) + ) |> Common.build_permalink(posts_config) end + + defp parse_datetime!(nil, filename) do + raise "Missing required front matter missing from post #{filename}: date" + end + + defp parse_datetime!(datetime, _filename) do + DateTimeParser.parse_datetime!(datetime, + assume_time: true, + assume_utc: true + ) + end end diff --git a/lib/tableau/extensions/rss_extension.ex b/lib/tableau/extensions/rss_extension.ex index 1ca9bf9..79e0777 100644 --- a/lib/tableau/extensions/rss_extension.ex +++ b/lib/tableau/extensions/rss_extension.ex @@ -113,7 +113,7 @@ defmodule Tableau.RSSExtension do #{URI.merge(url, post.permalink)} #{Calendar.strftime(post.date, "%a, %d %b %Y %X %Z")} #{URI.merge(url, post.permalink)} - + """ end diff --git a/lib/tableau/yaml_front_matter.ex b/lib/tableau/yaml_front_matter.ex index 0d25ef8..fbba57d 100644 --- a/lib/tableau/yaml_front_matter.ex +++ b/lib/tableau/yaml_front_matter.ex @@ -5,8 +5,6 @@ defmodule Tableau.YamlFrontMatter.Error do @moduledoc false defexception [:message] - # message: "Error parsing yaml front matter" - @impl true def exception(value) do %__MODULE__{message: "Error parsing yaml front matter: #{inspect(value)}"} @@ -15,15 +13,15 @@ end defmodule Tableau.YamlFrontMatter do @moduledoc false - def parse(string, opts \\ []) do + def parse(string) do string |> split_string() - |> process_parts(opts) + |> process_parts() end - def parse!(string, opts \\ []) do - case parse(string, opts) do - {:ok, matter, body} -> {atomicize(matter), body} + def parse!(string) do + case parse(string) do + {:ok, matter, body} -> {matter, body} {:error, error} -> raise Tableau.YamlFrontMatter.Error, error end end @@ -37,32 +35,32 @@ defmodule Tableau.YamlFrontMatter do |> then(&Regex.split(split_pattern, &1, parts: 3)) end - defp process_parts([_, yaml | rest], opts) do - case parse_yaml(yaml, opts) do - {:ok, yaml} -> {:ok, yaml, List.first(rest, "")} + defp process_parts([_, yaml | rest]) do + case parse_yaml(yaml) do + {:ok, yaml} -> {:ok, atomize(yaml), List.first(rest, "")} {:error, error} -> {:error, error} end end - defp process_parts(_, _), do: {:error, :invalid_front_matter} + defp process_parts(_), do: {:error, :invalid_front_matter} - defp parse_yaml(yaml, opts) do - case YamlElixir.read_from_string(yaml, opts) do + defp parse_yaml(yaml) do + case YamlElixir.read_from_string(yaml) do {:ok, parsed} -> {:ok, parsed} error -> error end end - defp atomicize(map) when is_map(map) do + defp atomize(map) when is_map(map) do Map.new(map, fn - {k, v} when is_binary(k) -> {String.to_atom(k), atomicize(v)} + {k, v} when is_binary(k) -> {String.to_atom(k), atomize(v)} other -> other end) end - defp atomicize(list) when is_list(list) do - Enum.map(list, &atomicize/1) + defp atomize(list) when is_list(list) do + Enum.map(list, &atomize/1) end - defp atomicize(other), do: other + defp atomize(other), do: other end diff --git a/mix.exs b/mix.exs index a14124c..9889a9d 100644 --- a/mix.exs +++ b/mix.exs @@ -35,12 +35,14 @@ defmodule Tableau.MixProject do {:date_time_parser, "~> 1.2"}, {:html_entities, "~> 0.5.2"}, {:libgraph, "~> 0.16.0"}, - {:mdex, "~> 0.11.1"}, {:schematic, "~> 0.5.1"}, {:slugify, "~> 1.3"}, {:tz, "~> 0.28.1"}, {:yaml_elixir, "~> 2.9"}, + # optional + {:mdex, "~> 0.14", optional: true}, + # dev {:floki, "~> 0.34", only: :test}, {:ex_doc, ">= 0.0.0", only: :dev}, @@ -76,18 +78,16 @@ defmodule Tableau.MixProject do Tableau.Layout, Tableau.Page, Tableau.Document.Helper, - Tableau.Extension - ], - Converters: [ - Tableau.Converter, - Tableau.MDExConverter + Tableau.Extension, + Tableau.Converter ], Extensions: [ - Tableau.PostExtension, + Tableau.DataExtension, + Tableau.MDExExtension, Tableau.PageExtension, - Tableau.SitemapExtension, + Tableau.PostExtension, Tableau.RSSExtension, - Tableau.DataExtension, + Tableau.SitemapExtension, Tableau.TagExtension ] ] diff --git a/mix.lock b/mix.lock index 38c835c..e25a453 100644 --- a/mix.lock +++ b/mix.lock @@ -7,11 +7,11 @@ "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"}, "kday": {:hex, :kday, "1.1.0", "64efac85279a12283eaaf3ad6f13001ca2dff943eda8c53288179775a8c057a0", [:mix], [{:ex_doc, "~> 0.21", [hex: :ex_doc, repo: "hexpm", optional: true]}], "hexpm", "69703055d63b8d5b260479266c78b0b3e66f7aecdd2022906cd9bf09892a266d"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, - "lumis": {:hex, :lumis, "0.6.3", "a14ea311298e45f31f3dff17d3b41b9717c0757ce1aa5275e190e46aa95ea04d", [:mix], [{:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:rustler, "~> 0.29", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "c0b3dd853f5d44a604202a732fe8c4d0575c4cca263d4e2b4a6f7794d00fab6e"}, "makeup": {:hex, :makeup, "1.2.2", "882d46dc0905e9ff7abf2aab61a7e6b3dcc555533977d8a23b06019e6c89ac94", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "9a1a24e5b343b8ae16abea0822c10a6f75da27af7fa802ada5251f7579bfccfa"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [: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", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.1.0", "835f7e60792e08824cda445639555d7bf1bbbddb1b60b306e33cb6f6db24dc74", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "1cd6780fb1dd1a03979abaed0fe82712b0625118fd5257d3ebbf73f960c73c3c"}, - "mdex": {:hex, :mdex, "0.11.7", "583b35c7160c4e26deab6233f967669d51de6f8b5f7b605adc55037abf7a20d2", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:lumis, "~> 0.1", [hex: :lumis, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:rustler, "~> 0.32", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.7", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "6dac3deb3ecb1a0a5ac9f383c9c57e46510b0a519e6fb21883d9ead34d0e4ffd"}, + "mdex": {:hex, :mdex, "0.14.0", "6bb28b49682465f802e9a129db4319e48a34edf9626e8186c1d5ea29de3f2a81", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:lumis, "~> 0.9", [hex: :lumis, repo: "hexpm", optional: true]}, {:mdex_native, ">= 0.2.9", [hex: :mdex_native, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.20.0 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}], "hexpm", "60e591041b7f5811004cc275e8d3ae1dffb6958159812402427264b444de5080"}, + "mdex_native": {:hex, :mdex_native, "0.2.9", "898cc5514d556c4d46fa43035d6fc925116413bb3821c5a71d4bd42f87a90b85", [:mix], [{:rustler, "~> 0.32", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.8", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "7470e6051b6193c620be02acbc12f1acd758250a60c13695ed36ebda512267ac"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "rustler_precompiled": {:hex, :rustler_precompiled, "0.9.0", "3a052eda09f3d2436364645cc1f13279cf95db310eb0c17b0d8f25484b233aa0", [:mix], [{:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "471d97315bd3bf7b64623418b3693eedd8e47de3d1cb79a0ac8f9da7d770d94c"}, diff --git a/test/tableau/extensions/page_extension_test.exs b/test/tableau/extensions/page_extension_test.exs index b6e8838..ba29393 100644 --- a/test/tableau/extensions/page_extension_test.exs +++ b/test/tableau/extensions/page_extension_test.exs @@ -31,7 +31,8 @@ defmodule Tableau.PageExtensionTest do describe "run" do setup %{tmp_dir: dir} do - assert {:ok, config} = PageExtension.config(%{dir: dir, enabled: true, layout: Blog.DefaultPageLayout}) + assert {:ok, config} = + PageExtension.config(%{dir: dir, enabled: true, layout: Blog.DefaultPageLayout}) token = %{ site: %{config: %{converters: [md: Tableau.MDExConverter]}}, @@ -165,8 +166,13 @@ defmodule Tableau.PageExtensionTest do vertices = Graph.vertices(graph) - assert Enum.any?(vertices, fn v -> is_struct(v, Tableau.Page) and v.permalink == page_1.permalink end) - assert Enum.any?(vertices, fn v -> is_struct(v, Tableau.Page) and v.permalink == page_2.permalink end) + assert Enum.any?(vertices, fn v -> + is_struct(v, Tableau.Page) and v.permalink == page_1.permalink + end) + + assert Enum.any?(vertices, fn v -> + is_struct(v, Tableau.Page) and v.permalink == page_2.permalink + end) end test "configured permalink works when you dont specify one", %{tmp_dir: dir, token: token} do @@ -180,7 +186,8 @@ defmodule Tableau.PageExtensionTest do A great page """) - assert {:ok, config} = PageExtension.config(%{dir: dir, enabled: true, permalink: "/page/:title"}) + assert {:ok, config} = + PageExtension.config(%{dir: dir, enabled: true, permalink: "/page/:title"}) token = put_in(token.extensions.pages.config, config) @@ -202,7 +209,10 @@ defmodule Tableau.PageExtensionTest do } = token end - test "generates permalink from file path if not configured or in front matter", %{tmp_dir: dir, token: token} do + test "generates permalink from file path if not configured or in front matter", %{ + tmp_dir: dir, + token: token + } do assert {:ok, config} = PageExtension.config(%{dir: [dir], enabled: true}) token = put_in(token.extensions.pages.config, config) @@ -339,7 +349,7 @@ defmodule Tableau.PageExtensionTest do assert {:ok, token} = PageExtension.pre_render(token) assert %{ - pages: [%{body: "\nA great page\n", converter: "Tableau.PageExtensionTest"}], + pages: [%{body: "\nA great page\n", converter: Tableau.PageExtensionTest}], graph: graph } = token @@ -361,5 +371,5 @@ defmodule Tableau.PageExtensionTest do end end - def convert(_, _, body, _), do: String.upcase(body) + def convert(body), do: String.upcase(body) end diff --git a/test/tableau/extensions/post_extension_test.exs b/test/tableau/extensions/post_extension_test.exs index 167f7a1..afd2966 100644 --- a/test/tableau/extensions/post_extension_test.exs +++ b/test/tableau/extensions/post_extension_test.exs @@ -33,7 +33,8 @@ defmodule Tableau.PostExtensionTest do describe "run" do setup %{tmp_dir: dir} do - assert {:ok, config} = PostExtension.config(%{dir: dir, enabled: true, layout: Blog.DefaultPostLayout}) + assert {:ok, config} = + PostExtension.config(%{dir: dir, enabled: true, layout: Blog.DefaultPostLayout}) token = %{ site: %{config: %{converters: [md: Tableau.MDExConverter]}}, @@ -247,7 +248,8 @@ defmodule Tableau.PostExtensionTest do A great post """) - assert {:ok, config} = PostExtension.config(%{dir: dir, enabled: true, permalink: "/post/:title"}) + assert {:ok, config} = + PostExtension.config(%{dir: dir, enabled: true, permalink: "/post/:title"}) token = put_in(token.extensions.posts.config, config) @@ -269,7 +271,10 @@ defmodule Tableau.PostExtensionTest do } = token end - test "generates permalink from file path if not configured or in front matter", %{tmp_dir: dir, token: token} do + test "generates permalink from file path if not configured or in front matter", %{ + tmp_dir: dir, + token: token + } do assert {:ok, config} = PostExtension.config(%{dir: [dir], enabled: true}) token = put_in(token.extensions.posts.config, config) @@ -412,7 +417,7 @@ defmodule Tableau.PostExtensionTest do assert {:ok, token} = PostExtension.pre_build(token) assert {:ok, token} = PostExtension.pre_render(token) - assert %{posts: [%{converter: "Tableau.PostExtensionTest"}], graph: graph} = token + assert %{posts: [%{converter: Tableau.PostExtensionTest}], graph: graph} = token page = graph @@ -432,5 +437,5 @@ defmodule Tableau.PostExtensionTest do end end - def convert(_, _, body, _), do: String.upcase(body) + def convert(body), do: String.upcase(body) end diff --git a/test/tableau/extensions/tag_extension_test.exs b/test/tableau/extensions/tag_extension_test.exs index 0da2ccb..1221983 100644 --- a/test/tableau/extensions/tag_extension_test.exs +++ b/test/tableau/extensions/tag_extension_test.exs @@ -56,7 +56,9 @@ defmodule Tableau.TagExtensionTest do } => [ %{title: "Post 2"} ], - %{tag: "C++", title: "C++", permalink: "/tags/c-plus-plus", slug: "c-plus-plus"} => [%{title: "Post 2"}] + %{tag: "C++", title: "C++", permalink: "/tags/c-plus-plus", slug: "c-plus-plus"} => [ + %{title: "Post 2"} + ] }, graph: graph } = token