jaspervdj / hakyll

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

Add variants of pandoc renderer and compiler for items #1020

Closed slotThe closed 4 weeks ago

slotThe commented 9 months ago

Add renderPandocItemWithTransformM and pandocItemCompilerWithTransformM, which work like the respective functions without the "item" infix, but the transformation function is a monadic "Item Pandoc -> Item Pandoc" instead of just a "Pandoc -> Pandoc". This allows one to more seamlessly compose functions that do require the extra information that an item provides, like bibliographic transformations.


My specific use-case for this is exactly adding a nicely formatted bibliography at the end of certain pages. Here is a real world example with entirely too much code:

myPandocCompiler :: Compiler (Item String)
myPandocCompiler =
  pandocItemCompilerWithTransformM                     -- ⋘ HERE
    readerOpts
    writerOpts
    (traverse moreSettings <=< processBib)
 where
  processBib :: Item Pandoc -> Compiler (Item Pandoc)  -- Due to this type
  processBib pandoc = do
    csl <- load @CSL    "bib/style.csl"
    bib <- load @Biblio "bib/bibliography.bib"
    -- We do want to link citations.
    p <- withItemBody
           (\(Pandoc (Meta meta) bs) -> pure $
             Pandoc (Meta $ Map.insert "link-citations" (MetaBool True) meta)
                    bs)
           pandoc
    fmap (tableiseBib . insertRefHeading) <$> processPandocBiblio csl bib p
   where
    -- Insert a heading for the citations.
    insertRefHeading :: Pandoc -> Pandoc
    insertRefHeading = walk $ concatMap \case
      d@(Div ("refs", _, _) _) -> [Header 1 ("references", [], []) [Str "References"], d]
      block                    -> [block]

    -- Arrange all citations in a table, so that they are nicely aligned.
    -- This probably only works with label or numerical styles.
    tableiseBib :: Pandoc -> Pandoc
    tableiseBib = walk \case
      Div a@("refs", _, _) body -> Div a (Many.toList (simpleTable [] (map citToRow body)))
      body                      -> body
     where
      citToRow :: Block -> [Many Block]
      citToRow = map Many.singleton . \case
        Div attr [Para (s1 : ss)] -> [Div attr [Plain [s1]], Plain [Space], Plain ss]
        d                         -> error $ "citToRow: unexpected citation format: " <> show d
chungyc commented 9 months ago

Out of curiosity, what would the code look like if you used renderPandocItemWithTransformM and pandocItemCompilerWithTransformM?

Would it be simpler than implementing a Compiler (Item String) function directly without bothering with a derived compiler like pandocItemCompilerWithTransformM? E.g., I do something similar with my own website:

articleCompiler :: Compiler (Item String)
articleCompiler = do
  let readerOptions = mathReaderWith defaultHakyllReaderOptions
  writerOptions <- getTocOptionsWith $ mathWriterWith defaultHakyllWriterOptions
  bibFile <- load "article/bibliography/references.bib"
  cslFile <- load "article/bibliography/acm.csl"
  getResourceBody
    >>= readPandocWith readerOptions
    >>= pure . fmap (setMeta "link-citations" True)
    >>= processPandocBiblio cslFile bibFile
    >>= pure . writePandocWith writerOptions
slotThe commented 9 months ago

Out of curiosity, what would the code look like if you used renderPandocItemWithTransformM and pandocItemCompilerWithTransformM?

The basic setup wouldn't be much more difficult, if a bit uglier in my opinion:

myPandocCompiler :: Compiler (Item String)
myPandocCompiler = do
  csl <- load @CSL    "bib/style.csl"
  bib <- load @Biblio "bib/bibliography.bib"
  getResourceBody
    >>= readPandocWith defaultHakyllReaderOptions
    >>= traverse (    pure . usingSideNotesHTML myWriter
                  <=< pygmentsHighlight
                  .   addSectionLinks
                  .   smallCaps
                  .   setMeta "link-citations" True
                 )
    >>= ((fmap . fmap) (tableiseBib . insertRefHeading) . processPandocBiblio csl bib)
    <&> writePandocWith myWriter

The bigger problem is that this doesn't work with my setup, as I also have a dedicated RSS compiler that skips many of these steps, as e.g. sidenotes do not make sense without CSS. Since now the main compiler has bibliographic information baked into it, I would have to rewrite this in some clever way, and make the big traverse block more modular; I reckon I would end up at exactly something like pandocItemCompilerWithTransformM.

slotThe commented 1 month ago

@Minoru friendly ping :)

LaurentRDC commented 4 weeks ago

Great, thank you!

LaurentRDC commented 4 weeks ago

Released in version 4.16.3.0

jaspervdj commented 4 weeks ago

Thanks @LaurentRDC!