2013-11-02 22:42:57 +00:00
|
|
|
---------------------------------------------------------------------------------------------------------
|
2015-06-24 01:04:46 +00:00
|
|
|
{-# LANGUAGE OverloadedStrings, TupleSections, FlexibleContexts #-}
|
2013-11-02 22:42:57 +00:00
|
|
|
---------------------------------------------------------------------------------------------------------
|
2014-03-05 23:20:03 +00:00
|
|
|
-- (C) Copyright Collin Doering 2013
|
2013-11-02 22:42:57 +00:00
|
|
|
--
|
|
|
|
-- This program is free software: you can redistribute it and/or modify
|
|
|
|
-- it under the terms of the GNU General Public License as published by
|
|
|
|
-- the Free Software Foundation, either version 3 of the License, or
|
|
|
|
-- (at your option) any later version.
|
2014-12-19 13:32:21 +00:00
|
|
|
--
|
2013-11-02 22:42:57 +00:00
|
|
|
-- This program is distributed in the hope that it will be useful,
|
|
|
|
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
-- GNU General Public License for more details.
|
2014-12-19 13:32:21 +00:00
|
|
|
--
|
2013-11-02 22:42:57 +00:00
|
|
|
-- You should have received a copy of the GNU General Public License
|
|
|
|
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
-- File: site.hs
|
|
|
|
-- Author: Collin J. Doering <rekahsoft@gmail.com>
|
|
|
|
-- Date: Oct 11, 2013
|
2015-01-12 06:44:51 +00:00
|
|
|
-- Description: The static site generator for my personal technical blog
|
2013-11-02 22:42:57 +00:00
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
import Hakyll
|
|
|
|
import Control.Monad
|
2014-09-13 06:19:44 +00:00
|
|
|
import Data.Monoid (mconcat,(<>))
|
2014-12-16 05:43:55 +00:00
|
|
|
import Data.List (sortBy)
|
|
|
|
import Data.Map (toList, size)
|
2014-03-13 00:59:58 +00:00
|
|
|
import qualified Data.Set as S
|
2013-11-02 22:42:57 +00:00
|
|
|
import Data.Ord (comparing)
|
2014-03-05 23:20:03 +00:00
|
|
|
import System.Random
|
2015-07-11 07:52:47 +00:00
|
|
|
import System.FilePath (takeBaseName, (</>))
|
2015-01-07 13:16:01 +00:00
|
|
|
import System.Process
|
|
|
|
import System.Exit
|
|
|
|
import System.IO (hGetContents)
|
2013-12-11 04:31:48 +00:00
|
|
|
|
|
|
|
import Text.Parsec
|
2014-03-13 00:59:58 +00:00
|
|
|
import Text.Pandoc.Options
|
2013-12-11 04:31:48 +00:00
|
|
|
import Control.Applicative hiding ((<|>),many)
|
2013-11-02 22:42:57 +00:00
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------------------------
|
|
|
|
|
2014-03-13 00:59:58 +00:00
|
|
|
pandocReaderOptions :: ReaderOptions
|
|
|
|
pandocReaderOptions = defaultHakyllReaderOptions
|
|
|
|
{ readerExtensions = S.fromList
|
|
|
|
[ Ext_footnotes
|
|
|
|
, Ext_inline_notes
|
|
|
|
, Ext_pandoc_title_block
|
|
|
|
, Ext_yaml_metadata_block
|
|
|
|
, Ext_table_captions
|
|
|
|
, Ext_implicit_figures
|
|
|
|
, Ext_simple_tables
|
|
|
|
, Ext_multiline_tables
|
|
|
|
, Ext_grid_tables
|
|
|
|
, Ext_pipe_tables
|
|
|
|
, Ext_citations
|
|
|
|
, Ext_raw_tex
|
|
|
|
, Ext_raw_html
|
|
|
|
, Ext_tex_math_dollars
|
|
|
|
, Ext_latex_macros
|
|
|
|
, Ext_fenced_code_blocks
|
|
|
|
, Ext_fenced_code_attributes
|
|
|
|
, Ext_backtick_code_blocks
|
|
|
|
, Ext_inline_code_attributes
|
|
|
|
, Ext_markdown_in_html_blocks
|
|
|
|
, Ext_escaped_line_breaks
|
|
|
|
, Ext_fancy_lists
|
|
|
|
, Ext_startnum
|
|
|
|
, Ext_definition_lists
|
|
|
|
, Ext_example_lists
|
|
|
|
, Ext_all_symbols_escapable
|
|
|
|
, Ext_intraword_underscores
|
|
|
|
, Ext_blank_before_blockquote
|
|
|
|
, Ext_blank_before_header
|
|
|
|
, Ext_strikeout
|
|
|
|
, Ext_superscript
|
|
|
|
, Ext_subscript
|
|
|
|
, Ext_auto_identifiers
|
|
|
|
, Ext_header_attributes
|
|
|
|
, Ext_implicit_header_references
|
|
|
|
, Ext_line_blocks ]
|
|
|
|
}
|
|
|
|
|
|
|
|
pandocWriterOptions :: WriterOptions
|
|
|
|
pandocWriterOptions = defaultHakyllWriterOptions
|
|
|
|
{ writerHtml5 = True
|
|
|
|
, writerHTMLMathMethod = MathJax ""
|
|
|
|
, writerEmailObfuscation = NoObfuscation -- ReferenceObfuscation
|
|
|
|
}
|
2014-12-19 13:32:21 +00:00
|
|
|
|
2013-11-02 22:42:57 +00:00
|
|
|
myConfig :: Configuration
|
|
|
|
myConfig = defaultConfiguration
|
2015-01-25 05:00:55 +00:00
|
|
|
{ deployCommand = "echo 'Removing empty files...' && " ++
|
|
|
|
"find _site -type f -empty -exec rm -v {} \\; && " ++
|
2015-01-30 09:49:02 +00:00
|
|
|
"echo '\nDeploying website...' && " ++
|
2015-01-25 05:00:55 +00:00
|
|
|
"rsync -rpogtzcv --delete -e ssh _site/ collin@rekahsoft.ca:~/public_html/blog/"
|
2013-11-02 22:42:57 +00:00
|
|
|
, previewPort = 3000
|
|
|
|
}
|
|
|
|
|
|
|
|
main :: IO ()
|
2014-03-05 23:20:03 +00:00
|
|
|
main = do
|
2014-03-13 00:59:58 +00:00
|
|
|
-- Get a random number generator before going into Rules monad
|
2014-03-05 23:20:03 +00:00
|
|
|
stdGen <- getStdGen
|
2014-12-19 13:32:21 +00:00
|
|
|
|
2014-03-05 23:20:03 +00:00
|
|
|
hakyllWith myConfig $ do
|
2015-08-05 06:42:12 +00:00
|
|
|
match ("action/**" .||. "files/**" .||. "images/**" .||. "fonts/**" .||. "robots.txt") $ do
|
2014-03-13 00:59:58 +00:00
|
|
|
route idRoute
|
|
|
|
compile copyFileCompiler
|
|
|
|
|
2015-07-13 01:20:51 +00:00
|
|
|
match "css/**" $ do
|
|
|
|
route idRoute
|
|
|
|
compile compressCssCompiler
|
|
|
|
|
|
|
|
match "lib/Skeleton/*.css" $ do
|
|
|
|
route $ gsubRoute "Skeleton" (const "css")
|
|
|
|
compile compressCssCompiler
|
|
|
|
|
|
|
|
match "templates/**" $ compile $ getResourceBody >>= saveSnapshot "original"
|
|
|
|
>> templateCompiler
|
|
|
|
|
2015-08-11 05:36:42 +00:00
|
|
|
-- Generate tags
|
2014-03-13 00:59:58 +00:00
|
|
|
tags <- buildTags ("posts/**" .&&. hasNoVersion) (fromCapture "tags/*.html")
|
2014-12-16 05:43:55 +00:00
|
|
|
|
2015-08-11 05:36:42 +00:00
|
|
|
-- Generate paginate
|
2014-12-16 05:43:55 +00:00
|
|
|
paginatedPosts <- buildPaginateWith
|
2014-12-19 13:39:38 +00:00
|
|
|
(fmap (paginateEvery numPaginatePages) . sortRecentFirst)
|
2014-12-16 05:43:55 +00:00
|
|
|
("posts/**" .&&. hasNoVersion)
|
2015-08-11 05:36:42 +00:00
|
|
|
(\n -> fromCapture "blog*.html" (show n))
|
2014-12-19 13:32:21 +00:00
|
|
|
|
2015-07-11 06:38:42 +00:00
|
|
|
pageIds <- getMatches ("pages/**" .&&. complement "pages/blog.markdown")
|
|
|
|
fontIds <- getMatches "fonts/**"
|
2014-03-05 23:20:03 +00:00
|
|
|
imageIds <- getMatches "images/**"
|
2015-07-11 06:38:42 +00:00
|
|
|
cssIds <- getMatches "css/**"
|
|
|
|
jsIds <- getMatches "js/**"
|
|
|
|
libIds <- getMatches "lib/**"
|
2014-12-19 13:32:21 +00:00
|
|
|
|
2015-01-07 13:16:01 +00:00
|
|
|
clayIds <- getMatches "clay/**.hs"
|
|
|
|
let manifestIds = clayIds ++ fontIds ++ imageIds ++ pageIds ++ cssIds ++ libIds ++ jsIds
|
2014-03-05 23:20:03 +00:00
|
|
|
|
2015-07-11 06:38:42 +00:00
|
|
|
clayDeps <- makePatternDependency $ fromList clayIds
|
2014-03-05 23:20:03 +00:00
|
|
|
manifestDeps <- makePatternDependency $ fromList manifestIds
|
2014-12-19 13:32:21 +00:00
|
|
|
|
2015-01-07 13:16:01 +00:00
|
|
|
rulesExtraDependencies [clayDeps] $ create ["default.css"] $ do
|
|
|
|
route idRoute
|
|
|
|
compile $ makeItem =<< (unsafeCompiler $ do
|
|
|
|
(_, hout, _, ph) <- createProcess $ shell "cabal build gencss"
|
|
|
|
exitCode <- waitForProcess ph
|
|
|
|
if exitCode == ExitSuccess
|
2015-01-10 21:31:10 +00:00
|
|
|
then readProcess "cabal" ["run", "--verbose=0", "gencss", "compact"] ""
|
2015-01-07 13:16:01 +00:00
|
|
|
else case hout of
|
|
|
|
Nothing -> fail "Error running 'cabal build gencss'"
|
|
|
|
Just hout' -> hGetContents hout' >>= fail)
|
2014-03-05 23:20:03 +00:00
|
|
|
|
|
|
|
rulesExtraDependencies [manifestDeps] $ create ["manifest.appcache"] $ do
|
|
|
|
route idRoute
|
|
|
|
compile $ do
|
|
|
|
manifestCacheRoutesMaybe <- sequence $ liftM getRoute (fontIds ++ pageIds ++ imageIds ++ cssIds ++ libIds ++ jsIds)
|
|
|
|
let randomNum = random stdGen :: (Int, StdGen)
|
2014-12-16 05:43:55 +00:00
|
|
|
randomStr = show . abs . fst $ randomNum
|
|
|
|
manifestStart = [ "CACHE MANIFEST"
|
2015-01-06 09:08:40 +00:00
|
|
|
, "# " ++ randomStr ]
|
2014-12-16 05:43:55 +00:00
|
|
|
manifestCacheSingles = [ "/index.html"
|
|
|
|
, "/default.css" ]
|
2015-08-11 07:41:17 +00:00
|
|
|
paginatedPostsCache = take 2 $ map (\(n,_) -> "/blog" ++ (show n) ++ ".html") $ toList $ paginateMap paginatedPosts
|
2014-12-19 13:39:38 +00:00
|
|
|
tagsCache = concatMap (\(t,ids) -> take 2 $ ["/tags/" ++ t ++ show n ++ ".html" | n <- [1..length $ paginateEvery numPaginatePages ids]]) $ tagsMap tags
|
2014-12-16 05:43:55 +00:00
|
|
|
manifestCacheFromIds = filter (not . null) $ fmap (maybe "" ("/"++)) manifestCacheRoutesMaybe
|
|
|
|
manifestCache = manifestCacheFromIds ++ tagsCache ++ paginatedPostsCache
|
|
|
|
manifestNetwork = [ "NETWORK:"
|
|
|
|
, "*"
|
|
|
|
, "http://*"
|
2015-01-06 09:08:40 +00:00
|
|
|
, "https://*" ]
|
|
|
|
makeItem . unlines $ manifestStart ++ [""] ++
|
|
|
|
manifestCacheSingles ++ manifestCache ++ [""] ++
|
|
|
|
manifestNetwork ++ [""]
|
2013-12-11 04:31:48 +00:00
|
|
|
|
|
|
|
match "css/**" $ do
|
|
|
|
route idRoute
|
|
|
|
compile compressCssCompiler
|
2014-12-19 13:32:21 +00:00
|
|
|
|
2014-03-05 23:20:03 +00:00
|
|
|
match "lib/Skeleton/*.css" $ do
|
|
|
|
route $ gsubRoute "Skeleton" (const "css")
|
2013-12-11 04:31:48 +00:00
|
|
|
compile compressCssCompiler
|
2013-11-02 22:42:57 +00:00
|
|
|
|
2015-01-13 19:02:31 +00:00
|
|
|
match "templates/**" $ compile $ getResourceBody >>= saveSnapshot "original"
|
|
|
|
>> templateCompiler
|
2015-07-11 06:38:42 +00:00
|
|
|
|
2014-03-13 00:59:58 +00:00
|
|
|
-- Generate tag pages
|
2015-07-19 06:19:51 +00:00
|
|
|
paginateTagsRules "tags" tags
|
2013-12-11 04:31:48 +00:00
|
|
|
|
2015-08-11 05:36:42 +00:00
|
|
|
let navgenRoute = customRoute (\r -> if toFilePath r == "pages/home.markdown"
|
|
|
|
then "pages/index.markdown"
|
|
|
|
else toFilePath r) `composeRoutes`
|
|
|
|
gsubRoute "pages/" (const "") `composeRoutes`
|
|
|
|
setExtension "html"
|
2015-07-13 01:20:51 +00:00
|
|
|
|
2015-08-11 05:36:42 +00:00
|
|
|
match "pages/*" $ version "nav-gen" $ do
|
|
|
|
route navgenRoute
|
|
|
|
compile $ pandocCompiler
|
2015-07-13 01:20:51 +00:00
|
|
|
|
2015-08-11 05:36:42 +00:00
|
|
|
paginateRules paginatedPosts $ \pageNum pattern -> do
|
2015-07-13 01:20:51 +00:00
|
|
|
route idRoute
|
|
|
|
compile $ do
|
2015-07-19 06:19:51 +00:00
|
|
|
pages <- sortByM pageWeight =<< loadAll ("pages/*" .&&. hasVersion "nav-gen")
|
2015-07-13 01:20:51 +00:00
|
|
|
posts <- recentFirst =<< loadAllSnapshots pattern "content"
|
2015-08-11 05:36:42 +00:00
|
|
|
|
2015-07-19 06:19:51 +00:00
|
|
|
let (pagesFirst, pagesLast') = flip span pages $ \x ->
|
|
|
|
(toFilePath . itemIdentifier $ x) /= "pages/blog.markdown"
|
|
|
|
pageMid = [head pagesLast']
|
|
|
|
pagesLast = if not . null $ pagesLast' then tail pagesLast' else []
|
2015-08-11 05:36:42 +00:00
|
|
|
|
2015-08-13 05:09:24 +00:00
|
|
|
indexCtx = listField "pagesFirst" defaultContext (return pagesFirst) <>
|
|
|
|
listField "pageMid" defaultContext (return pageMid) <>
|
|
|
|
listField "pagesLast" defaultContext (return pagesLast) <>
|
2015-07-19 06:19:51 +00:00
|
|
|
defaultContext
|
|
|
|
|
2015-08-14 01:49:55 +00:00
|
|
|
ctx = taggedPostCtx tags <>
|
|
|
|
paginateContext paginatedPosts pageNum <>
|
|
|
|
constField "weight" "0" <>
|
2015-08-11 05:36:42 +00:00
|
|
|
listField "posts" (taggedPostCtx tags) (return posts)
|
2015-07-13 01:20:51 +00:00
|
|
|
makeItem ""
|
|
|
|
>>= loadAndApplyTemplate "templates/pages/blog.html" ctx
|
2015-08-11 05:36:42 +00:00
|
|
|
>>= loadAndApplyTemplate "templates/default.html" indexCtx
|
2015-07-11 07:52:47 +00:00
|
|
|
|
2015-08-11 05:36:42 +00:00
|
|
|
match "pages/*" $ do
|
2015-08-10 21:35:46 +00:00
|
|
|
route navgenRoute
|
2015-07-11 07:52:47 +00:00
|
|
|
compile $ do
|
2015-08-11 05:36:42 +00:00
|
|
|
posts <- recentFirst =<< loadAllSnapshots "posts/**" "content"
|
2015-07-11 07:52:47 +00:00
|
|
|
|
|
|
|
-- Generate nav-bar from pages/*
|
|
|
|
pages <- sortByM pageWeight =<< loadAll ("pages/*" .&&. hasVersion "nav-gen")
|
|
|
|
|
2015-07-13 01:20:51 +00:00
|
|
|
-- Get the current page name
|
|
|
|
pageName <- takeBaseName . toFilePath <$> getUnderlying
|
|
|
|
|
2015-07-11 07:52:47 +00:00
|
|
|
-- Get the current Identifier
|
|
|
|
curId <- getUnderlying
|
|
|
|
|
|
|
|
let (pagesFirst, pagesLast') = flip span pages $ \x ->
|
|
|
|
toFilePath curId /= (toFilePath . itemIdentifier $ x)
|
2015-07-13 01:20:51 +00:00
|
|
|
pageMid = [head pagesLast']
|
2015-07-11 07:52:47 +00:00
|
|
|
pagesLast = if not . null $ pagesLast' then tail pagesLast' else []
|
2015-07-13 01:20:51 +00:00
|
|
|
|
|
|
|
recentPosts = take 5 posts
|
|
|
|
pageTemplate = "templates/pages/" ++ pageName ++ ".html"
|
|
|
|
|
2015-08-11 05:36:42 +00:00
|
|
|
masterCtx =
|
|
|
|
listField "recentPosts" (taggedPostCtx tags) (return recentPosts) <>
|
|
|
|
listField "posts" (taggedPostCtx tags) (return posts) <>
|
|
|
|
tagCloudField "tagCloud" 65 135 tags <>
|
|
|
|
defaultContext
|
|
|
|
|
|
|
|
indexCtx =
|
2015-08-14 01:49:55 +00:00
|
|
|
listField "pagesFirst" defaultContext (return pagesFirst) <>
|
|
|
|
listField "pageMid" defaultContext (return pageMid) <>
|
|
|
|
listField "pagesLast" defaultContext (return pagesLast) <>
|
2015-07-11 07:52:47 +00:00
|
|
|
defaultContext
|
|
|
|
|
2015-07-13 01:20:51 +00:00
|
|
|
sectionCtx <- getResourceBody >>= genSectionContext
|
|
|
|
pg <- loadSnapshot (fromFilePath pageTemplate) "original"
|
2015-08-11 05:36:42 +00:00
|
|
|
>>= applyAsTemplate (sectionCtx <> masterCtx)
|
2015-07-13 01:20:51 +00:00
|
|
|
|
|
|
|
(makeItem . itemBody) pg
|
2015-08-11 05:36:42 +00:00
|
|
|
>>= loadAndApplyTemplate "templates/default.html" indexCtx
|
|
|
|
|
|
|
|
match "posts/**" $ do
|
|
|
|
route $ setExtension "html"
|
|
|
|
compile $ do
|
|
|
|
pages <- sortByM pageWeight =<< loadAll ("pages/*" .&&. hasVersion "nav-gen")
|
|
|
|
|
|
|
|
let (pagesFirst, pagesLast') = flip span pages $ \x ->
|
|
|
|
(toFilePath . itemIdentifier $ x) /= "pages/blog.markdown"
|
|
|
|
pageMid = [head pagesLast']
|
|
|
|
pagesLast = if not . null $ pagesLast' then tail pagesLast' else []
|
|
|
|
|
|
|
|
indexCtx =
|
2015-08-13 05:09:24 +00:00
|
|
|
listField "pagesFirst" defaultContext (return pagesFirst) <>
|
|
|
|
listField "pageMid" defaultContext (return pageMid) <>
|
|
|
|
listField "pagesLast" defaultContext (return pagesLast) <>
|
2015-08-11 05:36:42 +00:00
|
|
|
defaultContext
|
|
|
|
|
|
|
|
pandocCompilerWith pandocReaderOptions pandocWriterOptions
|
|
|
|
>>= saveSnapshot "content"
|
|
|
|
>>= loadAndApplyTemplate "templates/partials/post.html" (taggedPostCtx tags)
|
|
|
|
>>= loadAndApplyTemplate "templates/default.html" indexCtx
|
2015-07-11 07:52:47 +00:00
|
|
|
|
2015-08-11 05:36:42 +00:00
|
|
|
create ["atom.xml"] $ do
|
|
|
|
route idRoute
|
|
|
|
compile $ do
|
|
|
|
let feedCtx = postCtx <> bodyField "description"
|
|
|
|
blogPosts <- loadAllSnapshots ("posts/**" .&&. hasNoVersion) "content"
|
|
|
|
>>= fmap (take 10) . recentFirst
|
|
|
|
renderAtom (feedConfiguration Nothing) feedCtx blogPosts
|
|
|
|
|
|
|
|
forM_ [("js/**", idRoute),
|
2015-08-13 05:58:07 +00:00
|
|
|
("lib/JQuery/*", gsubRoute "JQuery" $ const "js")] $ \(p, r) ->
|
2015-08-11 05:36:42 +00:00
|
|
|
match p $ do
|
|
|
|
route r
|
|
|
|
compile $ getResourceString >>= withItemBody (unixFilter "jsmin" [])
|
|
|
|
|
2014-12-16 05:43:55 +00:00
|
|
|
---------------------------------------------------------------------------------------------------------
|
2013-11-02 22:42:57 +00:00
|
|
|
-- Functions & Constants --------------------------------------------------------------------------------
|
2013-12-11 04:31:48 +00:00
|
|
|
feedConfiguration :: Maybe String -> FeedConfiguration
|
|
|
|
feedConfiguration title = FeedConfiguration
|
|
|
|
{ feedTitle = title'
|
|
|
|
, feedDescription = "My encounters with math, programming, science and the world!"
|
|
|
|
, feedAuthorName = "Collin J. Doering"
|
2014-03-13 00:59:58 +00:00
|
|
|
, feedAuthorEmail = "collin.doering@rekahsoft.ca"
|
2013-12-11 04:31:48 +00:00
|
|
|
, feedRoot = "http://blog.rekahsoft.ca"
|
|
|
|
} where title' = maybe defaultTitle ((defaultTitle ++ "; Specifically on the topic of ") ++) title
|
|
|
|
defaultTitle = "Technical Musings of a Minimalist"
|
|
|
|
|
2014-12-19 13:39:38 +00:00
|
|
|
numPaginatePages :: Int
|
|
|
|
numPaginatePages = 6
|
|
|
|
|
2015-07-19 06:19:51 +00:00
|
|
|
paginateTagsRules :: String -> Tags -> Rules ()
|
|
|
|
paginateTagsRules loc tags =
|
2014-12-19 13:39:38 +00:00
|
|
|
forM_ (tagsMap tags) $ \(tag, identifiers) -> do
|
|
|
|
paginatedTaggedPosts <- buildPaginateWith
|
|
|
|
(fmap (paginateEvery numPaginatePages) . sortRecentFirst)
|
|
|
|
(fromList identifiers)
|
2015-07-19 06:19:51 +00:00
|
|
|
(\n -> fromCapture (fromGlob $ loc ++ "/" ++ tag ++ "*.html") (show n))
|
2014-12-19 13:39:38 +00:00
|
|
|
|
|
|
|
paginateRules paginatedTaggedPosts $ \pageNum pattern -> do
|
|
|
|
route idRoute
|
|
|
|
compile $ do
|
2015-08-11 05:36:42 +00:00
|
|
|
pages <- sortByM pageWeight =<< loadAll ("pages/*" .&&. hasVersion "nav-gen")
|
2014-12-19 13:39:38 +00:00
|
|
|
posts <- recentFirst =<< loadAllSnapshots pattern "content"
|
2015-08-11 05:36:42 +00:00
|
|
|
|
|
|
|
let (pagesFirst, pagesLast') = flip span pages $ \x ->
|
|
|
|
(toFilePath . itemIdentifier $ x) /= "pages/blog.markdown"
|
|
|
|
pageMid = [head pagesLast']
|
|
|
|
pagesLast = if not . null $ pagesLast' then tail pagesLast' else []
|
|
|
|
|
2015-08-13 05:09:24 +00:00
|
|
|
indexCtx = listField "pagesFirst" defaultContext (return pagesFirst) <>
|
|
|
|
listField "pageMid" defaultContext (return pageMid) <>
|
|
|
|
listField "pagesLast" defaultContext (return pagesLast) <>
|
2015-08-11 05:36:42 +00:00
|
|
|
defaultContext
|
|
|
|
|
|
|
|
ctx = taggedPostCtx tags <>
|
2014-12-19 13:39:38 +00:00
|
|
|
paginateContext paginatedTaggedPosts pageNum <>
|
|
|
|
constField "tag" tag <>
|
|
|
|
listField "posts" (taggedPostCtx tags) (return posts)
|
2015-08-11 05:36:42 +00:00
|
|
|
|
2014-12-19 13:39:38 +00:00
|
|
|
makeItem ""
|
2015-01-13 19:02:31 +00:00
|
|
|
>>= loadAndApplyTemplate "templates/tag-page.html" ctx
|
2015-08-11 05:36:42 +00:00
|
|
|
>>= loadAndApplyTemplate "templates/default.html" indexCtx
|
2014-12-19 13:39:38 +00:00
|
|
|
|
|
|
|
rulesExtraDependencies [tagsDependency tags] $ do
|
|
|
|
create [tagsMakeId tags tag] $ do
|
|
|
|
route $ gsubRoute " " (const "-")
|
|
|
|
compile $ makeItem ("" :: String)
|
|
|
|
|
|
|
|
version "rss" $ do
|
|
|
|
route $ gsubRoute " " (const "-") `composeRoutes` setExtension "xml"
|
|
|
|
compile $ loadAllSnapshots (fromList identifiers) "content"
|
|
|
|
>>= fmap (take 10) . recentFirst
|
|
|
|
>>= renderAtom (feedConfiguration $ Just tag) (bodyField "description" <> defaultContext)
|
|
|
|
|
2013-12-11 04:31:48 +00:00
|
|
|
postCtx :: Context String
|
|
|
|
postCtx = dateField "date" "%B %e, %Y" <>
|
|
|
|
teaserField "teaser" "content" <>
|
2013-11-02 22:42:57 +00:00
|
|
|
defaultContext
|
|
|
|
|
2013-12-11 04:31:48 +00:00
|
|
|
taggedPostCtx :: Tags -> Context String
|
|
|
|
taggedPostCtx tags = tagsField "tags" tags <> postCtx
|
2014-12-19 13:32:21 +00:00
|
|
|
|
2013-11-02 22:42:57 +00:00
|
|
|
pageWeight :: (Functor f, MonadMetadata f) => Item a -> f Int
|
|
|
|
pageWeight i = fmap (maybe 0 read) $ getMetadataField (itemIdentifier i) "weight"
|
|
|
|
|
|
|
|
sortByM :: (Monad m, Ord k) => (a -> m k) -> [a] -> m [a]
|
|
|
|
sortByM f xs = liftM (map fst . sortBy (comparing snd)) $
|
|
|
|
mapM (\x -> liftM (x,) (f x)) xs
|
2014-12-16 05:43:55 +00:00
|
|
|
|
2013-11-02 22:42:57 +00:00
|
|
|
---------------------------------------------------------------------------------------------------------
|
2014-12-16 05:43:55 +00:00
|
|
|
-- Page section parser
|
2013-12-11 04:31:48 +00:00
|
|
|
---------------------------------------------------------------------------------------------------------
|
2014-12-16 05:43:55 +00:00
|
|
|
genSectionContext :: Item String -> Compiler (Context a)
|
2013-12-11 04:31:48 +00:00
|
|
|
genSectionContext = fmap mconcat . sequence . map makeField . unSections . readSections . itemBody
|
2015-06-24 01:04:46 +00:00
|
|
|
where makeField (k, b) = constField k . itemBody <$> (makeItem b >>= readPandocWith pandocReaderOptions >>= return . writePandocWith pandocWriterOptions)
|
2013-12-11 04:31:48 +00:00
|
|
|
|
|
|
|
readSections :: String -> [Section String String]
|
|
|
|
readSections s = case parse sections "" s of
|
|
|
|
Left err -> error $ "Cannot parse sections: " ++ show err
|
|
|
|
Right t -> t
|
|
|
|
|
|
|
|
data Section k a = NonSection a
|
|
|
|
| Section k a
|
|
|
|
| GlobalSection a
|
|
|
|
deriving (Eq, Show)
|
|
|
|
|
|
|
|
instance Functor (Section k) where
|
|
|
|
fmap f (NonSection b) = NonSection $ f b
|
|
|
|
fmap f (Section k b) = Section k (f b)
|
|
|
|
fmap f (GlobalSection b) = GlobalSection $ f b
|
|
|
|
|
|
|
|
unSections :: [Section String String] -> [(String, String)]
|
|
|
|
unSections = snd . foldr unSection (0, []) . filter (not . isGlobalSection) . applyGlobalSections
|
2015-02-18 08:13:38 +00:00
|
|
|
where unSection :: Section String String -> (Integer, [(String, String)]) -> (Integer, [(String, String)])
|
|
|
|
unSection (NonSection b) (n, ys) = (n + 1, ("body" ++ show n, b) : ys)
|
2013-12-11 04:31:48 +00:00
|
|
|
unSection (Section k b) (n, ys) = (n, (k, b) : ys)
|
|
|
|
unSection (GlobalSection _) _ = error "Internal error! This should never happen!"
|
|
|
|
|
|
|
|
isGlobalSection :: Section k a -> Bool
|
|
|
|
isGlobalSection (GlobalSection _) = True
|
|
|
|
isGlobalSection _ = False
|
|
|
|
|
|
|
|
applyGlobalSections :: [Section k String] -> [Section k String]
|
|
|
|
applyGlobalSections xs = flip map xs $ fmap $ trim . (++globalSectionBody)
|
|
|
|
where globalSectionBody = let gs = foldr unGlobalSection [] xs in
|
|
|
|
if null gs then gs else "\n" ++ gs
|
|
|
|
unGlobalSection (GlobalSection b) ys
|
|
|
|
| null ys = b
|
|
|
|
| otherwise = b ++ "\n" ++ ys
|
|
|
|
unGlobalSection _ ys = ys
|
|
|
|
|
2014-12-16 05:43:55 +00:00
|
|
|
-- **TODO** parser has error with escapes within pages; that is $$someExample$$ and $$section("example")$$
|
2013-12-11 04:31:48 +00:00
|
|
|
sections :: Parsec String a [Section String String]
|
|
|
|
sections = many (section <|> globalSection <|> nonSection)
|
|
|
|
|
|
|
|
section :: Parsec String a (Section String String)
|
|
|
|
section = do
|
|
|
|
key <- sectionStart
|
|
|
|
body <- sectionBody
|
2015-02-18 08:13:38 +00:00
|
|
|
void $ sectionEnd
|
2013-12-11 04:31:48 +00:00
|
|
|
return $ Section key body
|
|
|
|
|
|
|
|
sectionStart :: Parsec String a String
|
|
|
|
sectionStart = between (try $ string "$section(") (string ")$") sectionId
|
|
|
|
|
|
|
|
sectionId :: Parsec String a String
|
|
|
|
sectionId = between (char '"') (char '"') (many1 (noneOf "\""))
|
|
|
|
|
|
|
|
sectionEnd :: Parsec String a String
|
|
|
|
sectionEnd = string "$endsection$"
|
|
|
|
|
|
|
|
sectionBody :: Parsec String a String
|
|
|
|
sectionBody = try escapedDollar <|> many1 (noneOf "$")
|
|
|
|
|
|
|
|
escapedDollar :: Parsec String a String
|
|
|
|
escapedDollar = try (between (char '$') (char '$')
|
|
|
|
(try ((sectionStart >>= (\s -> return $ "$section(\"" ++ s ++ "\")$")) <|>
|
|
|
|
sectionEnd <|> string "$section$"))) <|>
|
|
|
|
try sectionEnd <|>
|
|
|
|
(string "$$" *> return "$")
|
|
|
|
|
|
|
|
globalSection :: Parsec String a (Section k String)
|
|
|
|
globalSection = GlobalSection <$> between (string "$section$") sectionEnd sectionBody
|
|
|
|
|
|
|
|
nonSection :: Parsec String a (Section k String)
|
|
|
|
nonSection = many1 (many1 (noneOf "$") <|> try escapedDollar) >>= return . NonSection . concat
|