Simplify Blog.Wording module and change conditional template blocks syntax a tiny bit

This commit is contained in:
Tissevert 2019-08-27 13:23:17 +02:00
parent ccb7491170
commit 94e323d715
9 changed files with 66 additions and 75 deletions

View File

@ -1,5 +1,11 @@
# Revision history for hablo # Revision history for hablo
## 1.0.2.0 -- 2019-08-27
* Format for [conditional blocks](/Tissevert/hablo/wiki/Template-variables#metadata) changed to allow an internal simplification. This is transparent if you're creating a new blog or using the default wording but be sure to edit your wording if you're using a custom one :
`${? … ?}` becomes `{? … ?}`
## 1.0.1.0 -- 2019-04-28 ## 1.0.1.0 -- 2019-04-28
* Print warnings about malformed Markdown articles instead of ignoring them silently * Print warnings about malformed Markdown articles instead of ignoring them silently

View File

@ -60,10 +60,10 @@ The template of the text used to present the metadata associated to each article
- `$date` - `$date`
- `$tags` - `$tags`
As mentioned in the introduction, some of them may be null so you may want to protect the whole `metadata` template with `${? ?}` like it is done in this variable's default value : As mentioned in the introduction, some of them may be null so you may want to protect the whole `metadata` template with `{? ?}` like it is done in this variable's default value :
``` ```
metadata = ${?by ${author} ?}on ${date}${? tagged ${tags}?} metadata = {?by ${author} ?}on ${date}{? tagged ${tags}?}
``` ```
If an article has an author, the rendered `metadata` string will start with «by <AUTHOR>», otherwise it will directly start with «on <SOME DATE>». Likewise all articles with tags will have their `metadata` end with « tagged » and then the list of comma-separated tags but if an article doesn't have tags, it will simply end after the date. If an article has an author, the rendered `metadata` string will start with «by <AUTHOR>», otherwise it will directly start with «on <SOME DATE>». Likewise all articles with tags will have their `metadata` end with « tagged » and then the list of comma-separated tags but if an article doesn't have tags, it will simply end after the date.

View File

@ -3,7 +3,7 @@ cabal-version: >= 1.10
-- For further documentation, see http://haskell.org/cabal/users-guide/ -- For further documentation, see http://haskell.org/cabal/users-guide/
name: hablo name: hablo
version: 1.0.1.1 version: 1.0.2.0
synopsis: A minimalist static blog generator synopsis: A minimalist static blog generator
description: description:
Hablo is a fediverse-oriented static blog generator for articles written Hablo is a fediverse-oriented static blog generator for articles written

View File

@ -7,5 +7,5 @@ dateFormat = en-US
latestLink = See only latest latestLink = See only latest
latestPage = Latest articles latestPage = Latest articles
latestTaggedPage = Latest articles tagged ${tag} latestTaggedPage = Latest articles tagged ${tag}
metadata = ${?by ${author} ?}on ${date}${? tagged ${tags}?} metadata = {?by ${author} ?}on ${date}{? tagged ${tags}?}
tagsList = Tags tagsList = Tags

View File

@ -7,7 +7,7 @@ function Template() {
if(blog.wording[template] != undefined) { if(blog.wording[template] != undefined) {
var template = blog.wording[template]; var template = blog.wording[template];
} }
template = template.replace(/\${\?((?:[^?]|\?[^}])*)\?}/g, renderSub(environment)); template = template.replace(/{\?((?:[^?]|\?[^}])*)\?}/g, renderSub(environment));
var failed = [false]; var failed = [false];
var result = template.replace( var result = template.replace(
/([^$]|^)\$(?:{(\w+)}|(\w+)\b)/g, /([^$]|^)\$(?:{(\w+)}|(\w+)\b)/g,

View File

@ -8,13 +8,11 @@ module ArticlesList (
) where ) where
import Article (Article) import Article (Article)
import Blog (Blog(..), Wording(..), get) import Blog (Blog(..), get)
import Blog.Wording (render)
import Control.Monad.Reader (MonadReader) import Control.Monad.Reader (MonadReader)
import Data.Text (Text, pack) import Data.Text (Text, pack)
import Data.Text.Lazy (toStrict)
import Data.Text.Template (render)
import Files (absoluteLink) import Files (absoluteLink)
import Pretty ((.$))
import System.FilePath.Posix ((</>)) import System.FilePath.Posix ((</>))
data ArticlesList = ArticlesList { data ArticlesList = ArticlesList {
@ -28,10 +26,13 @@ otherUrl (ArticlesList {full, tagged}) = absoluteLink $
(if full then id else (</> "all.html")) $ maybe "" id tagged (if full then id else (</> "all.html")) $ maybe "" id tagged
pageTitle :: (MonadReader Blog m) => ArticlesList -> m Text pageTitle :: (MonadReader Blog m) => ArticlesList -> m Text
pageTitle (ArticlesList {full, tagged}) = do pageTitle (ArticlesList {full, tagged}) = title (full, tagged) <$> Blog.get wording
template <- Blog.get $wording.$(if full then allTaggedPage else latestTaggedPage)
untagged <- Blog.get $wording.$(if full then allPage else latestPage)
return $ maybe untagged (toStrict . render template . tag) tagged
where where
tag :: String -> Text -> Text title (True, Nothing) = render "allPage" []
tag t = \"tag" -> pack t title (True, Just tag) = render "allTaggedPage" [("tag", pack tag)]
title (False, Nothing) = render "latestPage" []
title (False, Just tag) = render "latestTaggedPage" [("tag", pack tag)]

View File

@ -4,7 +4,7 @@ module Blog (
Blog(..) Blog(..)
, Path(..) , Path(..)
, Skin(..) , Skin(..)
, Wording(..) , Wording
, build , build
, get , get
) where ) where
@ -17,7 +17,7 @@ import Blog.Path (Path(..))
import qualified Blog.Path as Path (build) import qualified Blog.Path as Path (build)
import Blog.Skin (Skin(..)) import Blog.Skin (Skin(..))
import qualified Blog.Skin as Skin (build) import qualified Blog.Skin as Skin (build)
import Blog.Wording (Wording(..)) import Blog.Wording (Wording)
import qualified Blog.Wording as Wording (build) import qualified Blog.Wording as Wording (build)
import Control.Monad ((>=>), filterM, foldM, forM) import Control.Monad ((>=>), filterM, foldM, forM)
import Control.Monad.Reader (MonadReader, ask) import Control.Monad.Reader (MonadReader, ask)

View File

@ -3,18 +3,20 @@
module Blog.Wording ( module Blog.Wording (
Wording(..) Wording(..)
, build , build
, render
) where ) where
import Arguments (Arguments(..)) import Arguments (Arguments(..))
import Control.Monad (foldM) import Control.Monad (foldM)
import Data.Aeson (ToJSON(..), (.=), object, pairs) import Data.Aeson (ToJSON(..))
import Data.List (intercalate) import Data.List (intercalate)
import Data.Map (Map, (!)) import Data.Map (Map, (!))
import qualified Data.Map as Map (empty, fromList, map, union) import qualified Data.Map as Map (empty, fromList, insert, keys, map, union)
import Data.Monoid ((<>))
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as Text (pack, unpack) import qualified Data.Text as Text (pack, unpack)
import Data.Text.Lazy (toStrict)
import Data.Text.Template (Template, renderA, showTemplate, templateSafe) import Data.Text.Template (Template, renderA, showTemplate, templateSafe)
import qualified Data.Text.Template as Template (render)
import Paths_hablo (getDataFileName) import Paths_hablo (getDataFileName)
import Text.ParserCombinators.Parsec ( import Text.ParserCombinators.Parsec (
Parser Parser
@ -23,40 +25,31 @@ import Text.ParserCombinators.Parsec (
) )
import System.Exit (die) import System.Exit (die)
data Wording = Wording { newtype Wording = Wording (Map String Template)
allLink :: Text
, allPage :: Text
, allTaggedPage :: Template
, commentsLink :: Text
, commentsSection :: Text
, dateFormat :: Text
, latestLink :: Text
, latestPage :: Text
, latestTaggedPage :: Template
, metadata :: Text
, tagsList :: Text
}
keys :: [String] variables :: Map String [Text]
keys = [ variables = Map.fromList [
"allLink", "allPage", "allTaggedPage", "commentsLink", "commentsSection" ("allLink", [])
, "dateFormat", "latestLink", "latestPage", "latestTaggedPage", "metadata" , ("allPage", [])
, "tagsList" , ("allTaggedPage", ["tag"])
, ("commentsLink", [])
, ("commentsSection", [])
, ("dateFormat", [])
, ("latestLink", [])
, ("latestPage", [])
, ("latestTaggedPage", ["tag"])
, ("metadata", ["author", "date", "tags"])
, ("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 instance ToJSON Wording where
toJSON = object . zipWith (.=) (Text.pack <$> keys) . texts toJSON (Wording m) = toJSON (showTemplate <$> m)
toEncoding = pairs . foldl (<>) mempty . zipWith (.=) (Text.pack <$> keys) . texts 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 !)
addWording :: Map String Text -> FilePath -> IO (Map String Text) addWording :: Map String Text -> FilePath -> IO (Map String Text)
addWording currentWording wordingFile = do addWording currentWording wordingFile = do
@ -72,41 +65,30 @@ wordingP = Map.map Text.pack . Map.fromList <$>
restOfLine = many $ noneOf "\r\n" restOfLine = many $ noneOf "\r\n"
eol = try (string "\r\n") <|> string "\r" <|> string "\n" eol = try (string "\r\n") <|> string "\r" <|> string "\n"
skip = optional (char '#' *> restOfLine) *> eol skip = optional (char '#' *> restOfLine) *> eol
line = (,) <$> (choice (try . string <$> keys) <* equal) <*> restOfLine line = (,) <$> (choice (try . string <$> Map.keys variables) <* equal) <*> restOfLine
equal = many (char ' ') *> char '=' *> many (char ' ') equal = many (char ' ') *> char '=' *> many (char ' ')
checkTemplateWith :: [Text] -> String -> Map String Text -> IO Template makeTemplate :: String -> Map String Text -> IO Template
checkTemplateWith variables key wording = makeTemplate key wording =
let templateText = wording ! key in let templateText = wording ! key in
let testEnvironment = flip lookup [(s, "") | s <- variables] in let testEnvironment = flip lookup [(s, "") | s <- availableVariables] in
case templateSafe templateText of case templateSafe templateText of
Left (row, col) -> die $ syntaxError (show row) (show col) Left (row, col) -> die $ syntaxError (show row) (show col)
Right template -> Right template ->
maybe (die badTemplate) (return . const template) (renderA template testEnvironment) maybe (die badTemplate) (return . const template) (renderA template testEnvironment)
where where
availableVariables = availableVariables = variables ! key
" (available variables: " ++ intercalate ", " (Text.unpack <$> variables) ++ ")" variablesMessage =
" (available variables: " ++ intercalate ", " (Text.unpack <$> availableVariables) ++ ")"
syntaxError row col = syntaxError row col =
"Syntax error in template for variable " ++ key ++ "at l." ++ row ++ ", c." ++ col "Syntax error in template for variable " ++ key ++ "at l." ++ row ++ ", c." ++ col
badTemplate = "Invalid template for variable " ++ key ++ availableVariables badTemplate = "Invalid template for variable " ++ key ++ variablesMessage
build :: Arguments -> IO Wording build :: Arguments -> IO Wording
build arguments = do build arguments = do
defaultWording <- getDataFileName "defaultWording.conf" defaultWording <- getDataFileName "defaultWording.conf"
let wordingFiles = maybe id (:) (wording arguments) $ [defaultWording] let wordingFiles = maybe id (:) (wording arguments) $ [defaultWording]
wording <- foldM addWording Map.empty wordingFiles wordindMap <- foldM addWording Map.empty wordingFiles
allTaggedPage <- checkTemplateWith ["tag"] "allTaggedPage" wording Wording <$> foldM (
latestTaggedPage <- checkTemplateWith ["tag"] "latestTaggedPage" wording \templated key -> flip (Map.insert key) templated <$> makeTemplate key wordindMap
return Wording { ) Map.empty (Map.keys variables)
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"
}

View File

@ -7,8 +7,9 @@ module Dom (
import Article (Article(..)) import Article (Article(..))
import qualified Article (preview) import qualified Article (preview)
import ArticlesList (ArticlesList(..), otherUrl, pageTitle) import ArticlesList (ArticlesList(..), otherUrl, pageTitle)
import Blog (Blog(..), Path(..), Skin(..), Wording(..)) import Blog (Blog(..), Path(..), Skin(..))
import qualified Blog (get) import qualified Blog (get)
import Blog.Wording (render)
import Control.Applicative ((<|>)) import Control.Applicative ((<|>))
import Control.Monad.Reader (ReaderT) import Control.Monad.Reader (ReaderT)
import qualified Data.Map as Map (keys, lookup) import qualified Data.Map as Map (keys, lookup)
@ -52,7 +53,8 @@ instance Page ArticlesList where
mapM_ (article False . preview) featured mapM_ (article False . preview) featured
) )
where where
otherLink = Blog.get $wording.$(if full then latestLink else allLink) link = render (if full then "latestLink" else "allLink") []
otherLink = Blog.get $wording.$(link)
article :: Bool -> Article -> HtmlGenerator () article :: Bool -> Article -> HtmlGenerator ()
article raw (Article {key, body, title}) = do article raw (Article {key, body, title}) = do
@ -107,7 +109,7 @@ page aPage =
body_ (do body_ (do
maybe defaultBanner toHtmlRaw =<< (Blog.get $skin.$banner) maybe defaultBanner toHtmlRaw =<< (Blog.get $skin.$banner)
div_ [id_ "navigator"] (do div_ [id_ "navigator"] (do
h2_ =<< (Blog.get $wording.$tagsList.$toHtml) h2_ =<< (Blog.get $wording.$(render "tagsList" []).$toHtml)
ul_ . mapM_ tag . Map.keys =<< Blog.get tags ul_ . mapM_ tag . Map.keys =<< Blog.get tags
) )
div_ [id_ "contents"] $ content aPage div_ [id_ "contents"] $ content aPage