pencil-1.0.0: src/Pencil/Content/Nodes.hs
{-|
Internal functions that deal with nodes.
-}
module Pencil.Content.Nodes where
import Pencil.App.Internal
import Pencil.Env.Internal
import Pencil.Parser
import Pencil.Content.Internal
import Pencil.Config
import Control.Monad.Reader
import Control.Monad.Except
import Control.Exception (tryJust)
import Data.Text.Encoding (decodeUtf8)
import qualified Data.HashMap.Strict as H
import qualified Data.Text as T
import qualified Text.Pandoc as P
import qualified Text.Sass as Sass
import qualified Data.Text.IO as TIO
-- | Evaluate the nodes in the given environment. Note that it returns an IO
-- because of @${partial(..)}@ calls that requires us to load a file.
evalNodes :: Env -> [PNode] -> PencilApp [PNode]
evalNodes _ [] = return []
evalNodes env (PVar var : rest) = do
nodes <- evalNodes env rest
case H.lookup var env of
Nothing ->
-- Can't find var in env. Skip over it for now and we'll just render the
-- directive to help user debug missing variables. Later on we'll do some
-- nice error handling w/o crashing the system (throw warnings instead of
-- errors).
return $ PVar var : nodes
Just envData -> do
displayValue <- asks getDisplayValue
return $ PText (displayValue envData) : nodes
evalNodes env (PIf var nodes : rest) = do
rest' <- evalNodes env rest
case H.lookup var env of
Nothing ->
-- Can't find var in env; everything inside the if-statement is thrown away
return rest'
Just _ -> do
-- Render nodes inside the if-statement
nodes' <- evalNodes env nodes
return $ nodes' ++ rest'
evalNodes env (PFor var nodes : rest) = do
rest' <- evalNodes env rest
case H.lookup var env of
Nothing ->
-- Can't find var in env; everything inside the for-statement is throw away
return rest'
Just (VEnvList envs) -> do
-- Render the for nodes once for each given env, and append them together
forNodes <-
foldM
(\accNodes e -> do
nodes' <- evalNodes (H.union e env) nodes
return $ accNodes ++ nodes')
[] envs
return $ forNodes ++ rest'
-- Var is not an VEnvList; everything inside the for-statement is thrown away
Just _ -> return rest'
evalNodes env (PPartial fp : rest) = do
(_, nodes) <- parseAndConvertTextFiles (T.unpack fp)
nodes' <- evalNodes env nodes
rest' <- evalNodes env rest
return $ nodes' ++ rest'
evalNodes env (n : rest) = do
rest' <- evalNodes env rest
return $ n : rest'
-- | Loads and parses the given file path. Converts 'Markdown' files to HTML,
-- compiles 'Sass' files into CSS, and leaves everything else alone.
parseAndConvertTextFiles :: FilePath -> PencilApp (T.Text, [PNode])
parseAndConvertTextFiles fp = do
content <- loadTextFile fp
content' <-
case fileType fp of
Markdown -> do
pandocReaderOptions <- asks getPandocReaderOptions
pandocWriterOptions <- asks getPandocWriterOptions
result <- liftIO $ P.runIO (readMarkdownWriteHtml pandocReaderOptions pandocWriterOptions content)
case result of
Left _ -> return content
Right text -> return text
Sass -> do
sassOptions <- asks getSassOptions
sitePrefix <- asks getSourceDir
-- Use compileFile so that SASS @import works
result <- liftIO $ Sass.compileFile (sitePrefix ++ fp) sassOptions
case result of
Left _ -> return content
Right byteStr -> return $ decodeUtf8 byteStr
_ -> return content
let nodes = case parseText content' of
Left _ -> []
Right n -> n
return (content', nodes)
-- | Loads the given file as a text file. Throws an exception into the ExceptT
-- monad transformer if the file is not a text file.
loadTextFile :: FilePath -> PencilApp T.Text
loadTextFile fp = do
sitePrefix <- asks getSourceDir
-- Try to read the file. If it fails because it's not a text file, capture the
-- exception and convert it to a "checked" exception in the ExceptT stack via
-- 'throwError'.
eitherContent <- liftIO $ tryJust toPencilException (TIO.readFile (sitePrefix ++ fp))
case eitherContent of
Left e -> throwError e
Right a -> return a
-- | Converts Markdown to HTML using the given options.
readMarkdownWriteHtml :: P.PandocMonad m => P.ReaderOptions -> P.WriterOptions -> T.Text -> m T.Text
readMarkdownWriteHtml readerOptions writerOptions content = do
pandoc <- P.readMarkdown readerOptions content
P.writeHtml5String writerOptions pandoc