packages feed

slab-0.0.3.0: src/Slab/Render.hs

-- |
-- Module      : Slab.Render
-- Description : Render an AST to HTML
--
-- @Slab.Render@ transforms an evaluated syntax tree to HTML. In practice this
-- can be @blaze-html@'s @Html@, or @Text@ and lazy @ByteString@.
module Slab.Render
  ( prettyHtmls
  , renderHtmls
  , renderHtmlsUtf8
  , renderBlocks
  , extractTexts
  ) where

import Data.ByteString.Lazy qualified as BSL
import Data.String (fromString)
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.Lazy qualified as TL
import Slab.Syntax qualified as Syntax
import Text.Blaze.Html.Renderer.Pretty qualified as Pretty (renderHtml)
import Text.Blaze.Html.Renderer.Text (renderHtml)
import Text.Blaze.Html.Renderer.Utf8 qualified as Utf8 (renderHtml)
import Text.Blaze.Html5 (Html, (!))
import Text.Blaze.Html5 qualified as H
import Text.Blaze.Html5.Attributes qualified as A
import Text.Blaze.Internal qualified as I
import Text.Blaze.Svg11 qualified as S

--------------------------------------------------------------------------------
prettyHtmls :: [Html] -> Text
prettyHtmls = T.pack . concat . map Pretty.renderHtml

renderHtmls :: [Html] -> TL.Text
renderHtmls = TL.concat . map renderHtml

renderHtmlsUtf8 :: [Html] -> BSL.ByteString
renderHtmlsUtf8 = BSL.concat . map Utf8.renderHtml

--------------------------------------------------------------------------------
renderBlocks :: [Syntax.Block] -> [H.Html]
renderBlocks = map renderBlock

renderBlock :: Syntax.Block -> H.Html
renderBlock Syntax.BlockDoctype = H.docType
renderBlock (Syntax.BlockElem name mdot attrs children) =
  mAddAttrs $
    mAddId $
      mAddClass $
        renderElem name $
          mconcat $
            if mdot == Syntax.HasDot
              then [renderTexts children]
              else map renderBlock children
 where
  mAddId :: H.Html -> H.Html
  mAddId e =
    case idNames of
      Nothing -> e
      Just names -> e ! A.id (H.toValue names)
  idNames = Syntax.idNamesFromAttrs' attrs
  mAddClass :: H.Html -> H.Html
  mAddClass e =
    if classNames == []
      then e
      else e ! A.class_ (H.toValue classNames')
  classNames = Syntax.classNamesFromAttrs attrs
  classNames' :: Text
  classNames' = T.intercalate " " classNames

  mAddAttrs :: H.Html -> H.Html
  mAddAttrs =
    flip (foldl (\e (a, b) -> e ! H.customAttribute (fromString $ T.unpack a) (H.toValue b))) attrs'
  attrs' = Syntax.namesFromAttrs attrs
renderBlock (Syntax.BlockText _ []) =
  H.preEscapedText "\n" -- This allows to force some whitespace.
renderBlock (Syntax.BlockText _ t) =
  let s = renderTemplate t
   in if s == T.empty
        then H.preEscapedText "\n" -- This allows to force some whitespace.
        else H.preEscapedText s -- TODO
renderBlock (Syntax.BlockInclude (Just "escape-html") _ (Just nodes)) =
  escapeTexts nodes
renderBlock (Syntax.BlockInclude _ _ (Just nodes)) = mapM_ renderBlock nodes
renderBlock (Syntax.BlockInclude _ path Nothing) = H.stringComment $ "include " <> path
renderBlock (Syntax.BlockFragmentDef _ _ _ _) = mempty
renderBlock (Syntax.BlockFragmentCall _ _ _ _ nodes) = mapM_ renderBlock nodes
renderBlock (Syntax.BlockFor _ _ _ nodes) = mapM_ renderBlock nodes
renderBlock (Syntax.BlockComment b content) =
  case b of
    Syntax.PassthroughComment -> H.textComment content
    Syntax.NormalComment -> mempty
renderBlock (Syntax.BlockFilter "escape-html" content) =
  H.text content
renderBlock (Syntax.BlockFilter name _) = error $ "Unknown filter name " <> T.unpack name
renderBlock (Syntax.BlockRawElem content children) = do
  H.preEscapedText content -- TODO Construct a proper tag ?
  mapM_ renderBlock children
renderBlock (Syntax.BlockDefault _ nodes) = mapM_ renderBlock nodes
renderBlock (Syntax.BlockImport _ (Just nodes) _) = mapM_ renderBlock nodes
renderBlock (Syntax.BlockRun _ _ (Just nodes)) = mapM_ renderBlock nodes
renderBlock (Syntax.BlockRun cmd _ _) = H.textComment $ "run " <> cmd
renderBlock (Syntax.BlockImport path Nothing _) = H.stringComment $ "extends " <> path
renderBlock (Syntax.BlockAssignVars _) = mempty
renderBlock (Syntax.BlockIf _ as bs) = do
  -- The evaluation code transforms a BlockIf into a BlockList, so this should
  -- not be called.
  mapM_ renderBlock as
  mapM_ renderBlock bs
renderBlock (Syntax.BlockList nodes) =
  mapM_ renderBlock nodes
renderBlock (Syntax.BlockCode (Syntax.SingleQuoteString s))
  | s == T.empty = mempty
  | otherwise = H.text s -- Should be already escaped in the AST ?
renderBlock (Syntax.BlockCode (Syntax.Variable s)) =
  H.textComment $ "code variable " <> s
renderBlock (Syntax.BlockCode (Syntax.Int i)) =
  H.string $ show i
renderBlock (Syntax.BlockCode (Syntax.Object _)) =
  H.text "<Object>"
renderBlock (Syntax.BlockCode (Syntax.Block x)) = renderBlock x
renderBlock (Syntax.BlockCode c) = error $ "renderBlock called on BlockCode " <> show c

renderTexts :: [Syntax.Block] -> H.Html
renderTexts = H.preEscapedText . extractTexts

escapeTexts :: [Syntax.Block] -> H.Html
escapeTexts = H.text . extractTexts

extractTexts :: [Syntax.Block] -> Text
extractTexts = T.intercalate "\n" . map extractText

extractText :: Syntax.Block -> Text
extractText = f
 where
  f Syntax.BlockDoctype = error "extractTexts called on a BlockDoctype"
  f (Syntax.BlockElem _ _ _ _) = error "extractTexts called on a BlockElem"
  f (Syntax.BlockText _ [Syntax.Lit s]) = s
  f (Syntax.BlockText _ _) = error "extractTexts called on unevaluated BlockText"
  f (Syntax.BlockInclude _ _ _) = error "extractTexts called on a BlockInclude"
  f (Syntax.BlockFragmentDef _ _ _ _) = error "extractTexts called on a BlockFragmentDef"
  f (Syntax.BlockFragmentCall _ _ _ _ _) = error "extractTexts called on a BlockFragmentCall"
  f (Syntax.BlockFor _ _ _ _) = error "extractTexts called on a BlockFor"
  f (Syntax.BlockComment _ _) = error "extractTexts called on a BlockComment"
  f (Syntax.BlockFilter _ _) = error "extractTexts called on a BlockFilter"
  f (Syntax.BlockRawElem _ _) = error "extractTexts called on a BlockRawElem"
  f (Syntax.BlockDefault _ _) = error "extractTexts called on a BlockDefault"
  f (Syntax.BlockImport _ _ _) = error "extractTexts called on a BlockImport"
  f (Syntax.BlockRun _ _ _) = error "extractTexts called on a BlockRun"
  f (Syntax.BlockAssignVars _) = error "extractTexts called on a BlockAssignVars"
  f (Syntax.BlockIf _ _ _) = error "extractTexts called on a BlockIf"
  f (Syntax.BlockList _) = error "extractTexts called on a BlockList"
  f (Syntax.BlockCode _) = error "extractTexts called on a BlockCode"

-- After evaluation, we should have only reduced values (e.g. no variables).
renderTemplate :: [Syntax.Inline] -> Text
renderTemplate inlines =
  let t = map renderInline inlines
   in T.concat t

renderInline :: Syntax.Inline -> Text
renderInline = \case
  Syntax.Lit s -> s
  Syntax.Place code -> do
    case code of
      Syntax.SingleQuoteString s -> s
      Syntax.Bool x -> T.pack $ show x
      Syntax.Int x -> T.pack $ show x
      Syntax.Block b -> TL.toStrict . renderHtml $ renderBlock b
      x -> error $ "renderInline: unhandled value: " <> show x

renderElem :: Syntax.Elem -> Html -> Html
renderElem = \case
  Syntax.Html -> H.html
  Syntax.Body -> H.body
  Syntax.Div -> H.div
  Syntax.Span -> H.span
  Syntax.Br -> const H.br
  Syntax.Hr -> const H.hr
  Syntax.H1 -> H.h1
  Syntax.H2 -> H.h2
  Syntax.H3 -> H.h3
  Syntax.H4 -> H.h4
  Syntax.H5 -> H.h5
  Syntax.H6 -> H.h6
  Syntax.Header -> H.header
  Syntax.Head -> H.head
  Syntax.Meta -> const H.meta
  Syntax.Main -> H.main
  Syntax.Link -> const H.link
  Syntax.A -> H.a
  Syntax.P -> H.p
  Syntax.Em -> H.em
  Syntax.Ul -> H.ul
  Syntax.Li -> H.li
  Syntax.Title -> H.title
  Syntax.Table -> H.table
  Syntax.Thead -> H.thead
  Syntax.Tbody -> H.tbody
  Syntax.Tr -> H.tr
  Syntax.Td -> H.td
  Syntax.Dl -> H.dl
  Syntax.Dt -> H.dt
  Syntax.Dd -> H.dd
  Syntax.Footer -> H.footer
  Syntax.Figure -> H.figure
  Syntax.Form -> H.form
  Syntax.Label -> H.label
  Syntax.Blockquote -> H.blockquote
  Syntax.Button -> H.button
  Syntax.Figcaption -> H.figcaption
  Syntax.Audio -> H.audio
  Syntax.Script -> H.script
  Syntax.Style -> H.style
  Syntax.Small -> H.small
  Syntax.Source -> const H.source
  Syntax.Pre -> H.pre
  Syntax.Code -> H.code
  Syntax.Img -> const H.img
  Syntax.IFrame -> H.iframe
  Syntax.Input -> const H.input
  Syntax.I -> H.i
  Syntax.Svg -> S.svg
  Syntax.Textarea -> H.textarea
  Syntax.Canvas -> H.canvas
  Syntax.Elem name -> I.customParent (H.textTag name)