Skip to content

Elixir blog engine based on Phoenix powered by markdown

Notifications You must be signed in to change notification settings

erikmueller/jelly_shot

Repository files navigation

title separator verticalSeparator theme revealOptions
Jelly Shot
<!--s-->
<!--v-->
black
controls transition
false
slide

CircleCI codecov

Jelly Shot

A semi static blog engine

starting point

Static markdown blog by @seilund

A million static site generators...

💁 let's write another one

  • Phoenix
  • No database
  • No static file generation

Let's take this further

  • Generator -> markdown |> struct
  • Repository -> Agent storing data
  • Watcher -> GenServer for auto update

Generator

defp compile_file(file) do
  with{:ok, matter, body} <- split_frontmatter(file),
      {:ok, html, _} <- Earmark.as_html(body),
  do: {:ok, into_post(file, matter, html)}
end
defp into_post(file, meta, html) do
  data = %{
    slug: file_to_slug(file),
    content: html,
  } |> Map.merge(meta)

  struct(JellyShot.Post, data)
end

Repository

def start_link do
  Agent.start_link(&get_initial_state/0, name: __MODULE__)
end
posts = File.ls!("priv/posts")
|> Enum.filter(&(Path.extname(&1) == ".md"))
|> Enum.map(&compile_async/1)
|> Enum.map(&Task.await/1)
|> Enum.reduce([], &valid_into_list/2)
|> Enum.sort(&sort/2)

Watcher

def init(state) do
  path = Path.expand("priv/posts")

  :fs.start_link(:fs_watcher, path)
  :fs.subscribe(:fs_watcher)

  {:ok, state}
end
def handle_info({_pid, {:fs, :file_event}, {path, ev}}, _) do
  new_state = cond do
    Enum.member?(ev, :modified) ->
      path
      |> JellyShot.Post.file_to_slug
      |> JellyShot.Repo.upsert_by_slug
  end
end

Integrating into Phoenix 🐣🔥

Listing posts

def index(conn, params) do
  {tmpl, headline, {:ok, posts}} = case params do
    %{"author" => author} ->
      {"list", "by author",  Repo.get_by_author(author)}
    %{"category" => category} ->
      {"list", "by category", Repo.get_by_category(category)}
    _ ->
      {"index", "recent posts", Repo.list()}
  end

  render conn, "#{tmpl}.html", head: head, posts: posts
end

JellySHot

Limitations

Filling the repository...

~ 250 sloc / file

  • 12 posts in 406ms 🐰
  • 384 posts in 3844ms 🐢
  • ... 🐌

We might hit a cap at some point

Anyway, I Learned a lot

  • Pattern matching
  • Agents
  • GenServer
  • with {:ok}

Thanks

https://github.com/erikmueller/jelly_shot

<style> .reveal code {font-family: hasklig, monospace} </style>