💾 Archived View for spam.works › users › emery › dhall-site-generator.gmi captured on 2023-06-14 at 14:31:40. Gemini links have been rewritten to link to archived content

View Raw

More Information

-=-=-=-=-=-=-

Dhall Gemini Capsule Generator

Today I procrastinated my more important projects and duties to finally write a site generator for this capsule.

First I did a little research on Atom, and came across this gem on IndieWeb:

The RSS Atom wars (or syndication wars) were a toxic plumbing debate about the merits of using Atom vs RSS that dragged in and distracted numerous high level web technologists from 2003-2007 while social silos (Facebook, Flickr, Twitter, etc.) emerged, rapidly innovated UX, and thus gained popular adoption.

https://indieweb.org/RSS_Atom_wars

Right. Then I looked at the Atom format RFC.

https://datatracker.ietf.org/doc/html/rfc4287

I didn't have the patience to actually read it, so I looked at the example on page 3 and then wrote a Dhall type for that. Then wrote an XML generator.

https://git.sr.ht/~ehmry/dhall-atom

Dhall Atom

I recreated the example on page 3 and fed that into the W3C validator and with a few tweaks it passed. I can read the other 40 pages some other day.

https://validator.w3.org/feed/

Now I can start writing my generator. I import the Atom library and define my type for posts:

let Atom = https://git.sr.ht/~ehmry/dhall-atom/blob/trunk/package.dhall

let Article =
      { Type =
          { file : Text
          , text : Text
          , title : Text
          , summary : Optional Text
          , updated : Atom.Date.Type
          , mimeType : Optional Text
          }
      , default = { summary = None Text, mimeType = None Text }
      }

Fill out some Article records using "import … at Text" to read files into Text items:

let gemlog =
      [ Article::{
        , file = "dhall-site-generator.gmi"
        , text = ./dhall-site-generator.gmi as Text
        , title = "Dhall Gemini Capsule Generator"
        , summary = Some "Writing a pure-Dhall site generator for Gemini"
        , updated = Atom.Date.parse 2021 5 12
        }
      ]

Fill out an Atom.Feed.Type:

let author = { name = "Emery" }

let baseUrl = "gemini.spam.works/~emery"

let feed =
      let updated = Atom.Date.parse 2021 5 12

      in  Article::{
          , file = "atom.feed"
          , title = "Atom feed"
          , updated
          , mimeType = Some "application/atom+xml"
          , text =
              Atom.Feed.render
                Atom.Feed::{
                , title = "~emery"
                , author
                , link = "gemini://${baseUrl}/"
                , id = "urn:uuid:1dfa135b-eca3-46dd-aae0-f4570c89565d"
                , updated
                , entries =
                    Prelude.List.map
                      Article.Type
                      Atom.Entry.Type
                      ( λ(article : Article.Type) →
                          let link = "${baseUrl}/${article.file}"

                          in  Atom.Entry::{
                              , title = article.title
                              , updated = article.updated
                              , summary = article.summary
                              , link
                              , id = "gemini://" ++ link
                              }
                      )
                      gemlog
                }
          }

Generate a Molly-Brown configuration:

let `.molly` =
          ''
          [MimeOverrides]
          ".dhall$" = "text/x-dhall"
          ''
      ++  Prelude.Text.concatMap
            Article.Type
            ( λ(article : Article.Type) →
                merge
                  { Some =
                      λ(mimeType : Text) →
                        ''
                        "${article.file}$" = "${mimeType}"
                        ''
                  , None = ""
                  }
                  article.mimeType
            )
            articles

I interpolate the index.gmi text with links to the articles, and then generate a list of `{ mapKey : Text, mapValue : Text }` tuples that Dhall will translate into a directory of files. I prepend each text with a title that I define in my list of articles:

  Prelude.List.map
    Article.Type
    (Prelude.Map.Entry Text Text)
    ( λ(article : Article.Type) →
        { mapKey = article.file
        , mapValue =
            merge
              { Some = λ(mimeType : Text) → article.text
              , None =
                  ''
                  # ${article.title}

                  ${article.text}
                  ''
              }
              article.mimeType
        }
    )
    articles
# toMap { `.molly`, `index.gmi` }


I run `dhall to-directory-tree` and then rsync the results to my server.

That's it.

The complete implementation is here:

generate-gemini.dhall

atom.feed

Validate my current feed