hablo/src/Blog/Wording.hs

110 lines
3.9 KiB
Haskell

{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
module Blog.Wording (
Wording(..)
, build
) where
import Arguments (Arguments(..))
import Control.Monad (foldM)
import Data.Aeson (ToJSON(..), (.=), object, pairs)
import Data.List (intercalate)
import Data.Map (Map, (!))
import qualified Data.Map as Map (empty, fromList, map, union)
import Data.Monoid ((<>))
import Data.Text (Text)
import qualified Data.Text as Text (pack, unpack)
import Data.Text.Template (Template, renderA, showTemplate, templateSafe)
import Paths_hablo (getDataFileName)
import Text.ParserCombinators.Parsec (
Parser
, (<|>)
, char, choice, endBy, eof, many, many1, noneOf, optional, parse, string, try
)
import System.Exit (die)
data Wording = Wording {
allLink :: Text
, allPage :: Text
, allTaggedPage :: Template
, commentsSection :: Text
, dateFormat :: Text
, latestLink :: Text
, latestPage :: Text
, latestTaggedPage :: Template
, metadata :: Text
, tagsList :: Text
}
keys :: [String]
keys = [
"allLink", "allPage", "allTaggedPage", "commentsSection", "dateFormat"
, "latestLink", "latestPage", "latestTaggedPage", "metadata", "tagsList"
]
values :: [Wording -> Text]
values = [
allLink, allPage, showTemplate . allTaggedPage, commentsSection
, dateFormat, latestLink, latestPage, showTemplate . latestTaggedPage
, metadata, tagsList
]
texts :: Wording -> [Text]
texts wording = ($ wording) <$> values
instance ToJSON Wording where
toJSON = object . zipWith (.=) (Text.pack <$> keys) . texts
toEncoding = pairs . foldl (<>) mempty . zipWith (.=) (Text.pack <$> keys) . texts
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)
where
restOfLine = many $ noneOf "\r\n"
eol = try (string "\r\n") <|> string "\r" <|> string "\n"
skip = optional (char '#' *> restOfLine) *> eol
line = (,) <$> (choice (try . string <$> keys) <* equal) <*> restOfLine
equal = many (char ' ') *> char '=' *> many (char ' ')
checkTemplateWith :: [Text] -> String -> Map String Text -> IO Template
checkTemplateWith variables key wording =
let templateText = wording ! key in
let testEnvironment = flip lookup [(s, "") | s <- variables] in
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 =
" (available variables: " ++ intercalate ", " (Text.unpack <$> variables) ++ ")"
syntaxError row col =
"Syntax error in template for variable " ++ key ++ "at l." ++ row ++ ", c." ++ col
badTemplate = "Invalid template for variable " ++ key ++ availableVariables
build :: Arguments -> IO Wording
build arguments = do
defaultWording <- getDataFileName "defaultWording.conf"
let wordingFiles = maybe id (:) (wording arguments) $ [defaultWording]
wording <- foldM addWording Map.empty wordingFiles
allTaggedPage <- checkTemplateWith ["tag"] "allTaggedPage" wording
latestTaggedPage <- checkTemplateWith ["tag"] "latestTaggedPage" wording
return Wording {
allLink = wording ! "allLink"
, allPage = wording ! "allPage"
, allTaggedPage
, commentsSection = wording ! "commentsSection"
, dateFormat = wording ! "dateFormat"
, latestLink = wording ! "latestLink"
, latestPage = wording ! "latestPage"
, latestTaggedPage
, metadata = wording ! "metadata"
, tagsList = wording ! "tagsList"
}