Adding RSS Feed To Serum Static Site Generator
Published: 2020-07-08 16:15:39

---
title: Adding RSS Feed To Serum Static Site Generator
published: true
date: 2020-07-08 15:33:45 UTC
tags: 
canonical_url: https://www.rasjonell.tech/posts/2020-05-29-adding-rss-to-serum.html
---

<main><article><div>
<header>
<p>About 4 minutes to read</p>
<hr></header><ul id="toc">
<li><a href="#creating-the-plugin"><span>1 </span>Creating The Plugin</a></li>
<li><a href="#serum.plugin"><span>1.1 </span>Serum.Plugin</a></li>
<li><a href="#channels"><span>1.2 </span>Channels</a></li>
<li><a href="#items"><span>1.3 </span>Items</a></li>
<li><a href="#build-succeeded"><span>1.4 </span>Build Succeeded</a></li>
</ul>
<h2 id="creating-the-plugin">Creating The Plugin</h2>
<p>I decided not to talk about the importance, simplicity, and necessity of having an RSS feed for your website. So I’m just going to demonstrate how I made a plugin for <a href="https://github.com/Dalgona/Serum">Serum</a>, the static site generator tool(written in Elixir), that I use for this blog.</p>
<p>Serum has a plugin system. For example the Table Of Contents at the top of the article was generated with a plugin. They have a Plugin <a href="https://elixir-lang.org/getting-started/typespecs-and-behaviours.html#behaviours">Behaviour</a> which you can use to define your own plugin module.</p>
<h3 id="serum.plugin">Serum.Plugin</h3>
<p>Serum exposes a number of events(full list can be found <a href="https://hexdocs.pm/serum/Serum.Plugin.html#content">here</a>) which you can handle. The one I find most useful for the RSS feed generator is <code>build_succeeded/3</code>. This particular event is triggered when, as the name suggests, the build step succeeds. After a successful build we want to update our RSS feed so the subscribers can get notified about new posts!</p>
<p>First we need some boilerplate to correctly implement the <code>Serum.Plugin</code> behavour.</p>
<pre class="highlight elixir"><code>defmodule Blog.Rss do
  @moduledoc """
  A Serum plugin that create an RSS feed.

  ## Using the Plugin

      # serum.exs:
      %{
        server_root: "https://example.io",
        plugins: [
          Rss
        ]
      }
  """

  @behaviour Serum.Plugin

  @title "Rasjonell's Blog"
  @url "https://www.rasjonell.tech"
  @blog_desc "Random rants about technology"

  @impl true
  def name, do: "RSS Feed Generator"

  @impl true
  def version, do: "0.1.0"

  @impl true
  def elixir, do: "&gt;= 1.7.0"

  @impl true
  def serum, do: "&gt;= 1.2.0"

  @impl true
  def description do
    "Generates an RSS feed for /posts"
  end

  @impl true
  def implements do
    [build_succeeded: 3]
  end
end</code></pre>
<p><em>Here apart from the necessary function implementations, I’ve also defined some module attributes such as title and description which will be used latter for generating the RSS feed.</em></p>
<p>Now that we have our module defined we can start thinking about how we should generate the feed.</p>
<p>RSS, as the name suggests, is really simple.
It’s an XML file so we should start with: <code>&lt;?xml version="1.0" encoding="utf-8"?&gt;</code>.
Then we need to wrap all of our feed in an <code>rss</code> tag like so: <code>&lt;rss version="2.0"&gt;&lt;/rss&gt;</code></p>
<p>So what are the things that need to be wrapped.</p>
<h3 id="channels">Channels</h3>
<p>Remember the module attributes? We need them to define our channel.
We can define a simple function that returns all the necessary tags with up-to-date information.</p>
<pre><code>defp channel do
  """
    &lt;title&gt;#{@title}&lt;/title&gt;
    &lt;link&gt;#{@url}&lt;/link&gt;
    &lt;description&gt;#{@blog_desc}&lt;/description&gt;
    &lt;lastBuildDate&gt;#{current_date()}&lt;/lastBuildDate&gt;
    &lt;language&gt;en-us&lt;/language&gt;
  """
end</code></pre>
<p>See the call to <code>current_date/0</code> there? This was the most painful part of developing this plugin. RSS requires dates in the RFC-822 format but Elixir’s date-related modules, understandably, don’t come with built-in formatting options. So I had to install a dependancy. Timex is the library I chose, as it also is a dependancy of Serum, so I wouldn’t add any more code to the final bundle.</p>
<p>The <code>current_date/0</code> function has this definition:</p>
<pre><code class="elixir">defp current_date do
  {:ok, current_date} = Timex.now
    |&gt; Timex.format("{RFC822}")
  
  current_date
end</code></pre>
<h3 id="items">Items</h3>
<p>Now that we have defined our channel description, it’s time we add some items.</p>
<pre><code>defp item(title, desc, link) do
  """
  &lt;item&gt;
    &lt;title&gt;#{title}&lt;/title&gt;
    &lt;description&gt;&lt;![CDATA[#{desc}]]&gt;&lt;/description&gt;
    &lt;pubDate&gt;#{current_date()}&lt;/pubDate&gt;
    &lt;link&gt;#{link}&lt;/link&gt;
    &lt;guid&gt;#{link}&lt;/guid&gt;
  &lt;/item&gt;
  """
end</code></pre>
<p>Now we can just glue all of these together to generate a feed:</p>
<pre><code>defp feed(channel, items) do
  """
  &lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;rss version="2.0"&gt;
  &lt;channel&gt;
    #{channel}
    #{Enum.join items, ""}
  &lt;/channel&gt;
  &lt;/rss&gt;
  """
end</code></pre>
<p>This pretty much gives us all the building blocks that we need to complete this plugin by reading the posts, generating a feed, and writing it to a file accessable publicly on our website.</p>
<h3 id="build-succeeded">Build Succeeded</h3>
<p>To handle the <code>build_succeeded/3</code> event we need to implement that behavour:</p>
<pre><code>@impl true
def build_succeeded(_src, dest, _args) do
  :all_posts
  |&gt; Serum.GlobalBindings.get()
  |&gt; build_feed(dest)
  |&gt; create_file(dest)
  |&gt; File.write()

  :ok
end</code></pre>
<p>I’ll let Elixirs <strong>beautiful</strong> pipe operator do the explaining here.</p>
<p>If you want to use this plugin or just check the final code checkout the <a href="https://github.com/rasjonell/rasjonell.github.io/blob/master/lib/blog/rss.ex">source code</a>.</p>
</div></article></main>