jaspervdj / hakyll

A static website compiler library in Haskell
jaspervdj.be/hakyll
Other
2.7k stars 409 forks source link

Using render<Feed>WithTemplates Gives Runtime Type Errors/Produces Invalid Binary Feed #1043

Open CrystalSplitter opened 2 months ago

CrystalSplitter commented 2 months ago

Hello! This is potentially a documentation issue, or an issue that's been around since 2019 (hakyll version 4.13.0.0)?

I'm trying to figure out how to pass Template types to renderAtomWithTemplates (though I suspect this also happens with renderRSSWithTemplates).

The following compiles fine...

-- | Generate the Atom Feed
atomFeedRules
    :: (HK.Identifier, HK.FeedConfiguration)
    -- ^ Feed name and the feed configuration to use.
    -> HK.Pattern
    -- ^ File pattern to match entries for the atom feed.
    -> HK.Rules ()
atomFeedRules (feedName, feedConf) pattern = HK.create [feedName] $ do
    HK.route HK.idRoute
    HK.compile $ do
        atomTemplateString <- HK.load "templates/atom_template.xml" :: HK.Compiler (HK.Item String)
        atomItemTemplateString <- HK.load "templates/atom_item_template.xml" :: HK.Compiler (HK.Item String)
        atomTemplate <- HK.compileTemplateItem atomTemplateString
        atomItemTemplate <- HK.compileTemplateItem atomItemTemplateString
        allPosts <- HK.loadAllSnapshots pattern feedSnapshot
        posts <- mapM HK.relativizeUrls . take 10 =<< HK.recentFirst allPosts
        let feedCtx =
                postCtx <> HK.field "summary" (\_ -> pure "Summary goes here") <> HK.bodyField "content"
        HK.renderAtomWithTemplates atomTemplate atomItemTemplate feedConf feedCtx posts

However, when actually running this, one of three things happen, and which happens seems to be nondeterministic.

  1. It doesn't build the site, and produces this error:

    ╰ cabal run exe:site -- build
    Initialising...
      Creating store...
      Creating provider...
      Running rules...
    Checking for out-of-date items
    Compiling
      Using async runtime with 1 threads...
      updated about.html
      updated posts/2023-03-12-hs_anon_records_new.html
      updated templates/atom_item_template.xml
      cached  art_posts/28_pages.md
      [ERROR] article_feed.atom: Hakyll.Core.Compiler.Require.load: templates/atom_item_template.xml (snapshot _final) was found in the cache, but does not have the right type: expected [Char] but got Template
  2. It also doesn't build the site, but complains about atom_template.xml instead.

      [ERROR] article_feed.atom: Hakyll.Core.Compiler.Require.load: templates/atom_template.xml (snapshot _final) was found in the cache, but does not have the right type: expected [Char] but got Template
  3. It DOES build, but it generates an invalid atom_template.xml??? This is by far the most confusing to me. This produces a binary file, with some control characters and some bits of XML:

    <!-- article_feed.atom -->
    ^@^@^@^@^@^@^@^@]<?xml vers

No idea what's happening here, but this seems to be a bug?

I can't find anyone who's using renderAtomWithTemplates or renderRSSWithTemplates to work, so I'm extremely curious how to use it. But also, this appears to be some bug.


This was all spurred on by the fact that I noticed that the atom feeds incorrectly put the "description" field into the atom <summary> element instead of the atom <content> element. (See RFC4287)

CrystalSplitter commented 2 months ago

Two updates on this:

  1. I have a working version now. It's perhaps instead the "correct" way to do render<Feed>WithTemplates calls, which I'll detail below. I don't know if it's the idiomatic way to go about this, but it's reasonably clean, and it reveals the runtime type problem fairly clearly.
  2. It's not actually nondeterministic, it's three stages of cache miss/erroneous hits. The first failure is atom_item_template.xml, re-running without cleaning the cache gets me to the second error, and re-running a third time gets me the third error. It's not cleaning the cache after the error, so it progresses to the next failure mode on-reruns.

The underlying problem was, of course, loading a file as an Item String makes it a String in the Store. Later converting it to a Template by passing it to renderAtomWithTemplates doesn't change the type in the Store. When it it looks up atomItemTemplate expecting a Template, it instead gets [Char]. This makes sense once you remember load puts whatever is loaded in to the Store . What I did not expect was that it also stores its type there. It makes sense given how Hakyll works, but it only clicked for me now after walking through with the debugger.

-- | Generate the Atom Feed
atomFeedRules
    :: (HK.Identifier, HK.FeedConfiguration)
    -- ^ Feed name and the feed configuration to use.
    -> HK.Pattern
    -- ^ File pattern to match entries for the atom feed.
    -> HK.Rules ()
atomFeedRules (feedName, feedConf) pattern = HK.create [feedName] $ do
    HK.route HK.idRoute
    HK.compile $ do
        (HK.Item _atPath atomTemplate) <- HK.load "templates/atom_template.xml"
        (HK.Item _aitPath atomItemTemplate) <- HK.load "templates/atom_item_template.xml"
        allPosts <- HK.loadAllSnapshots pattern feedSnapshot
        posts <- mapM HK.relativizeUrls . take 10 =<< HK.recentFirst allPosts
        let feedCtx =
                postCtx
                    <> HK.field "summary" sanitiseItemHTML
                    <> HK.bodyField "content"
        HK.renderAtomWithTemplates atomTemplate atomItemTemplate feedConf feedCtx posts
  where
    sanitiseItemHTML :: HK.Item String -> HK.Compiler String
    sanitiseItemHTML item =
        pure
            . TagSoup.innerText
            . TagSoup.parseTags
            . HK.itemBody
            $ item

So up to maintainers to close this out as "user error", or if you want to write some documentation examples for usage of renderRSSWithTemplates or renderAtomWithTemplates.

I would still recommend "fixing" the atom template to at least pass the "description" into the "" tag.