{-# LANGUAGE LambdaCase #-} module Shpadoinkle.Template.TH where import Data.Text (Text, cons, unpack) import Data.Text.IO import Language.Haskell.TH.Syntax import Prelude hiding (readFile) import Text.HTML.Parser (Attr (..), Token (..), parseTokens) embedHtml :: FilePath -> Q Exp embedHtml path = do ts <- runIO $ parseTokens <$> readFile path pure . ListE $ tokenToExp ts tokenToExp :: [Token] -> [Exp] tokenToExp = let h = UnboundVarE $ mkName "h" text = UnboundVarE $ mkName "text" in \case TagOpen tn attrs:ts -> let attrs' = ListE $ attrToExp <$> attrs name = asText tn (children, siblings) = break (\case TagClose tn' | tn' == tn -> True; _ -> False) ts in AppE (AppE (AppE h name) attrs') (ListE $ tokenToExp children) : tokenToExp (drop 1 siblings) TagSelfClose tn attrs:ts -> let attrs' = ListE $ attrToExp <$> attrs name = asText tn in AppE (AppE (AppE h name) attrs') (ListE []) : tokenToExp ts TagClose _:ts -> tokenToExp ts ContentText content:ts -> let content' = asText content in AppE text content' : tokenToExp ts ContentChar char:ts -> let char' = asText $ cons char mempty in AppE text char' : tokenToExp ts Comment _:ts -> tokenToExp ts Doctype _:ts -> tokenToExp ts [] -> [] attrToExp :: Attr -> Exp attrToExp (Attr name value) = TupE [name', AppE textProp value'] where textProp = UnboundVarE $ mkName "textProp" name' = asText name value' = asText value asText :: Text -> Exp asText = AppE (UnboundVarE $ mkName "pack") . LitE . StringL . unpack