jaspervdj / hakyll

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

`functionField` context matching #1013

Closed pittma closed 10 months ago

pittma commented 10 months ago

Hi there. I am trying to implement a transclude context via a function field, it looks like this:

transcludeContext :: Context a
transcludeContext =
  functionField "transclude" $ \args _ ->
    case args of
      [id] -> do
        item <-
          load (fromFilePath ("forest/" ++ id ++ ".md")) :: Compiler
            (Item String)
        body <- getResourceBody
        content <- applyAsTemplate (defaultContext <> basenameContext) body
        return (itemBody content)
      _ -> fail "transclude should receive a single argument"

And I'm essentially double-compiling the target:

noteCompiler :: Compiler (Item String)
noteCompiler = do
  body <- getResourceBody
  content <-
    applyAsTemplate
      (defaultContext <> basenameContext <> transcludeContext)
      body
  p <- pandocWithSidenotes content
  let ident = itemIdentifier p
  imd <- getMetadata ident
  let links = maybe [] words (lookupString "related" imd)
  items <- mapM f links
  let context =
        defaultContext
          <> listField
               "links"
               (defaultContext <> basenameContext <> transcludeContext)
               (return items)
  loadAndApplyTemplate "templates/base.html" context p
  where
    f id = load (fromFilePath ("forest/" ++ id))

When the context keys match, I see this:

  [ERROR] forest/dsp-0001.md: Hakyll.Web.Template.applyTemplate: Failed to interpolate template in item forest/dsp-0001.md:
    In expr '$transclude("dsp-0003")$',
    Hakyll.Web.Template.applyTemplate: Failed to interpolate template in item forest/dsp-0001.md:
    In expr '$transclude("dsp-0003")$',
    Tried field body,
    No 'transclude' field in metadata of item forest/dsp-0001.md,
    Tried field url,
    Tried field path,
    Tried field title,
    Tried field basename,
    Missing field 'transclude' in context

But when I change either the invocation or the definition, I see this

transcludeContext :: Context a
transcludeContext =
  functionField "wtf" $ \args _ ->
    case args of
    -- …
  [ERROR] forest/dsp-0001.md: Hakyll.Web.Template.applyTemplate: Failed to interpolate template in item forest/dsp-0001.md:
    In expr '$transclude("dsp-0003")$',
    Tried field body,
    No 'transclude' field in metadata of item forest/dsp-0001.md,
    Tried field url,
    Tried field path,
    Tried field title,
    Tried field basename,
    Tried function field wtf,
    Missing field 'transclude' in context

Note that the function field wtf is present. I'm not sure what's going on here!

pittma commented 10 months ago

Okay, so I figured it out—it was the use of getResourceBody in the transclude context; that was causing an infinite loop, but it was getting caught the second time around because transclude was missing as it had been interpolated already. When I included it in the context here, it spins forever trying to compile anything with transclude used. What I actually need to do is extract the raw body from item, not use getResourceBody as it has the (wrong) item implicitly included.

pittma commented 10 months ago

fwiw, if someone else ends up here by the mention of transclude, this is what I ended up doing…

transcludeContext :: Context a
transcludeContext =
  functionField "transclude" $ \args _ ->
    case args of
      [id'] ->
        compilerUnsafeIO
          $ transclude id' <$> readFile ("forest/" ++ id' ++ ".md")
      _ -> fail "transclude should receive a single argument"
  where
    transclude id' content = -- some absolutely filthy formatting and string concatenation

And my noteCompiler from above stayed the same.