hablo/src/Blog/Wording.hs

95 lines
3.4 KiB
Haskell
Raw Normal View History

2019-02-17 19:52:28 +01:00
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
module Blog.Wording (
Wording(..)
, build
, render
2019-02-17 19:52:28 +01:00
) where
import Arguments (Arguments(..))
import Control.Monad (foldM)
import Data.Aeson (ToJSON(..))
2019-02-17 19:52:28 +01:00
import Data.List (intercalate)
import Data.Map (Map, (!))
import qualified Data.Map as Map (empty, fromList, insert, keys, map, union)
2019-02-17 19:52:28 +01:00
import Data.Text (Text)
import qualified Data.Text as Text (pack, unpack)
import Data.Text.Lazy (toStrict)
import Data.Text.Template (Template, renderA, showTemplate, templateSafe)
import qualified Data.Text.Template as Template (render)
2019-02-17 19:52:28 +01:00
import Paths_hablo (getDataFileName)
import Text.ParserCombinators.Parsec (
Parser
, (<|>)
, char, choice, endBy, eof, many, many1, noneOf, optional, parse, string, try
2019-02-17 19:52:28 +01:00
)
import System.Exit (die)
newtype Wording = Wording (Map String Template)
2019-02-17 19:52:28 +01:00
variables :: Map String [Text]
variables = Map.fromList [
("allLink", [])
, ("allPage", [])
, ("allTaggedPage", ["tag"])
, ("commentsLink", [])
, ("commentsSection", [])
, ("dateFormat", [])
, ("latestLink", [])
, ("latestPage", [])
, ("latestTaggedPage", ["tag"])
, ("metadata", ["author", "date", "tags"])
, ("tagsList", [])
2019-02-17 19:52:28 +01:00
]
instance ToJSON Wording where
toJSON (Wording m) = toJSON (showTemplate <$> m)
toEncoding (Wording m) = toEncoding (showTemplate <$> m)
render :: String -> [(Text, Text)] -> Wording -> Text
render key env (Wording wMap) =
toStrict $ Template.render (wMap ! key) (Map.fromList env !)
2019-02-17 19:52:28 +01:00
addWording :: Map String Text -> FilePath -> IO (Map String Text)
addWording currentWording wordingFile = do
parsed <- parse wordingP wordingFile <$> readFile wordingFile
case parsed of
Left errorMessage -> die $ show errorMessage
Right newWording -> return $ Map.union currentWording newWording
wordingP :: Parser (Map String Text)
wordingP = Map.map Text.pack . Map.fromList <$>
(many skip *> line `endBy` (many1 skip) <* eof)
2019-02-17 19:52:28 +01:00
where
restOfLine = many $ noneOf "\r\n"
2019-02-17 19:52:28 +01:00
eol = try (string "\r\n") <|> string "\r" <|> string "\n"
skip = optional (char '#' *> restOfLine) *> eol
line = (,) <$> (choice (try . string <$> Map.keys variables) <* equal) <*> restOfLine
equal = many (char ' ') *> char '=' *> many (char ' ')
2019-02-17 19:52:28 +01:00
makeTemplate :: String -> Map String Text -> IO Template
makeTemplate key wording =
2019-02-17 19:52:28 +01:00
let templateText = wording ! key in
let testEnvironment = flip lookup [(s, "") | s <- availableVariables] in
2019-02-17 19:52:28 +01:00
case templateSafe templateText of
Left (row, col) -> die $ syntaxError (show row) (show col)
Right template ->
maybe (die badTemplate) (return . const template) (renderA template testEnvironment)
where
availableVariables = variables ! key
variablesMessage =
" (available variables: " ++ intercalate ", " (Text.unpack <$> availableVariables) ++ ")"
2019-02-17 19:52:28 +01:00
syntaxError row col =
"Syntax error in template for variable " ++ key ++ "at l." ++ row ++ ", c." ++ col
badTemplate = "Invalid template for variable " ++ key ++ variablesMessage
2019-02-17 19:52:28 +01:00
build :: Arguments -> IO Wording
build arguments = do
defaultWording <- getDataFileName "defaultWording.conf"
let wordingFiles = maybe id (:) (wording arguments) $ [defaultWording]
wordindMap <- foldM addWording Map.empty wordingFiles
Wording <$> foldM (
\templated key -> flip (Map.insert key) templated <$> makeTemplate key wordindMap
) Map.empty (Map.keys variables)