Working with Soupault

I wrote this website using Soupault as the SSG1. You might have not heard of it before, its a smaller tool and it is not written in JavaScript.

The idea behind soupault is to chain together other tools that do one thing (and do them well). That's why I also use these tools in my SSG pipeline:

Use Case Tool
Syntax Highlighting highlight
Style Pre-processing Sass
Markdown rendering Pandoc
PNG Compression pngquant

The pipeline

It essentially works like this:

  1. Transform page files (in my case markdown files) through the applicable page processor (pandoc)
  2. Run widgets, which are small functions that transform the generated HTML
    1. Identify <code> blocks and run their content through highlight
    2. Run some other widgets (see below)
  3. Transform assets using asset processors
    1. Convert scss files to css
    2. Compress png files with pngquant

Widgets

Some interesting widgets I use on my site:

Inject MathJax on blog posts

[widgets.inject-mathjax]
widget = "insert_html"
html = '<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@4/tex-mml-chtml.js"></script>'
selector = "body"
section = "blog"

Highlight code blocks

[widgets.unwrap-code]
widget = "unwrap" # Custom plugin, see plugins below
selector = 'pre > code'

[widgets.highlight]
widget = "preprocess_element"
selector = 'pre[class]'
command = 'highlight -O html -f --syntax=$(echo $ATTR_CLASS) --inline-css --style=darkplus'
after = "unwrap-code"

Plugins

I wrote some plugins for this website, which you might have a use for as well:

Fill the content of <time> tags with a formatted date

time_tags = HTML.select(page, "time")

local index = 1
while time_tags[index] do
  time_tag = time_tags[index]
  datetime = HTML.get_attribute(time_tag, "datetime")
  if datetime then
    formatted_date = Date.reformat(datetime, { "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d" }, "%b %d, %Y")
    HTML.replace_content(time_tag, HTML.create_text(formatted_date))
  end
  index = index + 1
end

Unwrap nodes

This will remove the parent node, but keeps the content.

selector = config["selector"]

tags = HTML.select(page, selector)

local index = 1
while tags[index] do
    tag = tags[index]

    HTML.unwrap(tag)

    index = index + 1
end

Use it:

[widgets.unwrap-code]
widget = "unwrap"
selector = 'pre > code'

Transforms wikilinks (created from pandoc) to absolute urls.

wikilink_tags = HTML.select(page, "a.wikilink")

current_href = nil

function is_match(item)
  file = Sys.strip_extensions(Sys.basename(item.page_file))

  if file == current_href then
    return 1
  end

  return nil
end

index = 1
while wikilink_tags[index] do
  wikilink_tag = wikilink_tags[index]
  href = HTML.get_attribute(wikilink_tag, "href")

  if href then
    current_href = href
    values = Table.find_values(is_match, site_index)

    value = values[1]

    if value then
      HTML.set_attribute(wikilink_tag, "href", value["url"])
      
      if value["title"] then
        HTML.replace_content(wikilink_tag, HTML.create_text(value["title"]))
      end

    else
      Log.warning("No link target found for href: " .. href)
      HTML.set_attribute(wikilink_tag, "href", "#")
      HTML.add_class(wikilink_tag, "unresolved")
    end
  end

  index = index + 1
end

And for images and similar tags:

tags = HTML.select_all_of(page, { "img[src]", "audio[src]" })

index = 1
while tags[index] do
  tag = tags[index]
  src = HTML.get_attribute(tag, "src")

  if src and not String.starts_with(src, "http") then
    fixed_src = "/assets/attachments/" .. src

    HTML.set_attribute(tag, "src", fixed_src)
  end

  index = index + 1
end

Other config snippets

Snippets that are neither plugins nor widgets, but still might be interesting:

My pandoc config

[preprocessors]
md = "pandoc --no-highlight --template=pandoc/template.html --from=gfm+wikilinks_title_after_pipe+autolink_bare_uris+alerts --mathjax"

Template

$if(title)$
  <h1>$title$</h1>
$endif$

$if(pubDate)$
<div class="date-meta">
  <time datetime="$pubDate$" class="pubDate"><!-- filled by soupault --></time></p>

  $if(updatedDate)$
    <div class="last-update">
      <span>(Last update: </span><time datetime="$updatedDate$" class="updatedDate"><!-- filled by soupault --></time><span>)</span>
    </div>
  $endif$
  </div>
$endif$

$body$

$if(featured)$
  <meta name="x-featured" content="true">
$endif$

  1. Static Site Generator↩︎