{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Main where import Control.Monad (forM) import Data.List (isPrefixOf, isSuffixOf) import Data.Time.Format (TimeLocale(..)) import Hakyll.Core.Compiler ( Compiler , load , loadAllSnapshots , makeItem , noResult , saveSnapshot, loadSnapshotBody ) import Hakyll.Core.Configuration (Configuration(..)) import Hakyll.Core.File (copyFileCompiler) import Hakyll.Core.Identifier (Identifier(..), fromFilePath, toFilePath) import Hakyll.Core.Identifier.Pattern (Pattern, fromCapture, fromGlob) import Hakyll.Core.Item (Item(..)) import Hakyll.Core.Metadata (getMetadataField) import Hakyll.Main (hakyllWith) import Hakyll.Core.Routes (Routes, customRoute, idRoute) import Hakyll.Core.Rules ( Rules , compile , route , match , create ) import Hakyll.Web.CompressCss (compressCssCompiler) import Hakyll.Web.Html (demoteHeaders, withUrls) import Hakyll.Web.Paginate ( Paginate(..) , PageNumber , buildPaginateWith , paginateContext , paginateEvery , paginateRules ) import Hakyll.Web.Pandoc ( defaultHakyllReaderOptions , defaultHakyllWriterOptions , pandocItemCompilerWithTransformM ) import Hakyll.Web.Pandoc.Biblio (biblioCompiler, cslCompiler, processPandocBiblio) import Hakyll.Web.Tags (Tags(..), buildTags, tagsRules) import Hakyll.Web.Template (loadAndApplyTemplate, templateBodyCompiler) import Hakyll.Web.Template.Context ( Context(..) , ContextField(..) , boolField , constField , defaultContext , field , listField , listFieldWith , dateFieldWith ) import Hakyll.Web.Template.List (recentFirst, sortRecentFirst) import System.FilePath ( (>) , addTrailingPathSeparator , joinPath , replaceExtension , splitDirectories , dropFileName ) import System.Process (rawSystem) import qualified Network.Wai.Application.Static as Static import Text.Pandoc.Options ( Extension(Ext_latex_macros) , HTMLMathMethod(..) , ReaderOptions(..) , WriterOptions(..) , extensionsFromList ) -- -- Default configuration. -- -- deployment.txt is expected to contain the remote deployment path -- as its only content. -- configuration :: Configuration configuration = Configuration { destinationDirectory = "./var/web" , storeDirectory = "./var/cache" , tmpDirectory = "./var/cache/tmp" , providerDirectory = "." , ignoreFile = ignoreFile' , watchIgnore = watchIgnore' , deployCommand = "rsync" , deploySite = deploySite' , inMemoryCache = True , previewHost = "127.0.0.1" , previewPort = 8000 , checkHtmlFile = const False , previewSettings = Static.defaultFileServerSettings } where watchIgnore' path | "src" : _ <- splitDirectories path = True | otherwise = False ignoreFile' path = isPrefixOf "." path || path == "var" deploySite' deploymentConfiguration = readFile "deployment.txt" >>= executeDeployment deploymentConfiguration executeDeployment Configuration{..} deploymentTarget = rawSystem deployCommand [ "-ave" , "ssh" , "--delete" , addTrailingPathSeparator destinationDirectory , deploymentTarget ] -- -- Helpers. -- loadAndApplyLayout :: String -> Context String -> Item String -> Compiler (Item String) loadAndApplyLayout layout context item = let layoutPath = fromFilePath $ "templates/_layouts" > layout in loadAndApplyTemplate layoutPath context item >>= loadAndApplyTemplate "templates/default.html" context createIndex :: Tags -> Rules () createIndex tags = createPaginatedPage indexRules "posts/**" "" where indexRules paginate pageNumber pagePattern = do route idRoute compile $ do posts <- recentFirst =<< loadAllSnapshots pagePattern "content" let context = listField "posts" previewContext (pure posts) <> constField "title" "Startseite" <> boolField "active-blog" (const True) <> paginateContext paginate pageNumber <> flevumContext tags makeItem "" >>= loadAndApplyLayout "blog.html" context >>= cleanIndexUrls localizedTimeLocale :: TimeLocale localizedTimeLocale = TimeLocale { wDays = [ ("Sonntag", "So") , ("Montag", "Mo") , ("Dienstag", "Di") , ("Mittwoch", "Mi") , ("Donnerstag", "Do") , ("Freitag", "Fr") , ("Samstag", "Sa") ] , months = [ ("Januar", "Jan.") , ("Februar", "Feb.") , ("März", "März") , ("April", "Apr.") , ("Mai", "Mai") , ("Juni", "Jun.") , ("Juli", "Jul.") , ("August", "Aug.") , ("September", "Sep.") , ("Oktober", "Okt.") , ("November", "Nov.") , ("Dezember", "Dez.") ] , timeFmt = "%H:%M:%S" , time12Fmt = "%I:%M:%S %p" , knownTimeZones = [] , dateTimeFmt = "%a %b %e %H:%M:%S %Z %Y" , dateFmt = "%d.%m.%y" , amPm = ("AM", "PM") } -- Reads teaser metadata field and formats it as HTML. teaserDescriptionField :: Context String teaserDescriptionField = field "teaser" withItem where withItem item = let itemIdentifier' = itemIdentifier item in getMetadataField itemIdentifier' "teaser" >>= withMetadataField itemIdentifier' paragraphMap "" = "
"
paragraphMap paragraph
| " //" `isSuffixOf` paragraph
= take (length paragraph - length (" //" :: String)) paragraph
<> "
"
| otherwise = paragraph
withMetadataField _ (Just teaser) = pure $ "
" <> unlines (paragraphMap <$> lines teaser) <> "
" withMetadataField itemIdentifier' Nothing = fail $ "No teaser defined for " <> show itemIdentifier' -- og:description meta tag content. teaserMetaField :: Context String teaserMetaField = field "teaser" withItem where withItem item = do let itemIdentifier' = itemIdentifier item body <- loadSnapshotBody itemIdentifier' "content" metadata <- getMetadataField itemIdentifier' "teaser" withMetadataField body metadata -- Keep only the first paragraph, replace newlines with whitespaces. paragraphFolder ("", True) paragraph = (paragraph, True) paragraphFolder (accumulator, True) "" = (accumulator, False) paragraphFolder (accumulator, True) paragraph = (accumulator <> " " <> paragraph, True) paragraphFolder (accumulator, False) _ = (accumulator, False) -- Remove HTML tags, keep only the first paragraph, replace explicit