packages feed

hablo-1.0.0.0: src/Blog/Wording.hs

{-# 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
    , commentsLink :: Text
    , commentsSection :: Text
    , dateFormat :: Text
    , latestLink :: Text
    , latestPage :: Text
    , latestTaggedPage :: Template
    , metadata :: Text
    , tagsList :: Text
  }

keys :: [String]
keys = [
      "allLink", "allPage", "allTaggedPage", "commentsLink", "commentsSection"
    , "dateFormat", "latestLink", "latestPage", "latestTaggedPage", "metadata"
    , "tagsList"
  ]

values :: [Wording -> Text]
values = [
      allLink, allPage, showTemplate . allTaggedPage, commentsLink, 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
      , commentsLink = wording ! "commentsLink"
      , commentsSection = wording ! "commentsSection"
      , dateFormat = wording ! "dateFormat"
      , latestLink = wording ! "latestLink"
      , latestPage = wording ! "latestPage"
      , latestTaggedPage
      , metadata = wording ! "metadata"
      , tagsList = wording ! "tagsList"
    }