packages feed

slab-0.0.3.0: src/Slab/Generate/Haskell.hs

-- |
-- Module      : Slab.Generate.Haskell
-- Description : Translate from the Slab syntax to Haskell
--
-- @Slab.Generate.Haskell@ is an attempt to translate Slab to Haskell. This
-- could make it possible to use Slab within Haskell projects without needing
-- to interpret Slab templates at runtime. If this proves useful, other
-- languages could also be supported.
module Slab.Generate.Haskell
  ( renderHs
  ) where

import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.IO qualified as T
import Prettyprinter
import Prettyprinter.Render.Text
import Slab.Error qualified as Error
import Slab.PreProcess qualified as PreProcess
import Slab.Syntax qualified as Syntax

--------------------------------------------------------------------------------
renderHs :: FilePath -> IO ()
renderHs path = do
  blocks <- PreProcess.preprocessFile path >>= Error.unwrap
  T.putStrLn $ renderModule blocks

renderModule :: [Syntax.Block] -> Text
renderModule = renderStrict . layoutPretty defaultLayoutOptions . prettyModule

renderBlocks :: [Syntax.Block] -> Text
renderBlocks = renderStrict . layoutPretty defaultLayoutOptions . prettyBlocks

--------------------------------------------------------------------------------
prettyModule :: [Syntax.Block] -> Doc ann
prettyModule blocks =
  vsep
    [ moduleHeader
    , indent 2 $ prettyBlocks blocks
    ]

moduleHeader :: Doc ann
moduleHeader =
  vsep
    [ "module Main where"
    , mempty
    , "import Data.Text (Text)"
    , "import Text.Blaze.Html5 (Html, (!))"
    , "import Text.Blaze.Html5 qualified as H"
    , "import Text.Blaze.Html5.Attributes qualified as A"
    , "import Text.Blaze.Html.Renderer.Pretty (renderHtml)"
    , mempty
    , "main :: IO ()"
    , "main = putStrLn . renderHtml $"
    ]

--------------------------------------------------------------------------------
prettyBlocks :: [Syntax.Block] -> Doc ann
prettyBlocks = vsep . map prettyBlock

prettyBlock :: Syntax.Block -> Doc ann
prettyBlock (Syntax.BlockElem name _ attrs children) = prettyBlockElem (name, attrs', children)
 where
  attrs' = Syntax.groupAttrs attrs

-- | Render an element, aligning the @!@ character:
--
-- @
--   elem ! a
--        ! b $ do
--     child0
--     child1
-- @
prettyBlockElem :: (Syntax.Elem, [Syntax.Attr], [Syntax.Block]) -> Doc ann
prettyBlockElem (t1, ts_, as) =
  case ts_ of
    [] ->
      let header = prettyT1 <+> dollar
       in vsep [header, footer]
    [t] ->
      let header = prettyT1 <+> "!" <+> prettyAttr t <+> dollar
       in vsep [header, footer]
    t : ts ->
      let header = prettyT1 <+> "!" <+> prettyAttr t
          body = indent lengthT1 $ vsep (map (("!" <+>) . prettyAttr) ts) <+> dollar
       in vsep [header, body, footer]
 where
  prettyT1 = prettyElem t1
  lengthT1 = succ . T.length . renderStrict $ layoutPretty defaultLayoutOptions prettyT1
  dollar = if length as > 1 then "$ do" else "$"
  footer = case as of
    [] -> indent 2 "mempty"
    _ -> vsep $ map (indent 2 . prettyBlock) as

prettyElem :: Syntax.Elem -> Doc ann
prettyElem = \case
  Syntax.Html -> "H.html"
  Syntax.Body -> "H.body"
  Syntax.Div -> "H.div"

prettyAttr :: Syntax.Attr -> Doc ann
prettyAttr (Syntax.Id t) = pretty $ "A.id (H.toValue (\"" <> t <> "\" :: Text))"
prettyAttr (Syntax.Class t) = pretty $ "A.class_ (H.toValue (\"" <> t <> "\" :: Text))"
prettyAttr (Syntax.Attr a b) =
  "H.customAttribute" <+> pretty a <+> prettyExpr b

prettyExpr :: Syntax.Expr -> Doc ann
prettyExpr _ = "TODO"