on master help sign in
Separate pandoc from page reading, general caching
commit08c4d74a2fe9c667e725f2ebb41bc01006a703a1 authorJasper Van der Jeugt <jaspervdj@gmail.com> dateMon 4 Oct 2010, 01:20 (16 years ago) parentcd24697
5 files changed+116−67
src/Text/Hakyll/CreateContext.hs+4−6 src/Text/Hakyll/Internal/Cache.hs+22 src/Text/Hakyll/Internal/Page.hs+37−60 src/Text/Hakyll/Internal/Template.hs+5−1 src/Text/Hakyll/Pandoc.hs+48
src/Text/Hakyll/CreateContext.hs+4−6
@@ -13,21 +13,19 @@ import qualified Data.Map as M
1313import Control.Arrow (second)
1414import Control.Monad (liftM2)
1515import Control.Applicative ((<$>))
16+import Control.Arrow ((>>>))
1617
17−import Text.Hakyll.File
1818import Text.Hakyll.Context
1919import Text.Hakyll.HakyllAction
2020import Text.Hakyll.Render
2121import Text.Hakyll.Internal.Page
22+import Text.Hakyll.Pandoc
23+import Text.Hakyll.Internal.Cache
2224
2325-- | Create a @Context@ from a page file stored on the disk. This is probably
2426-- the most common way to create a @Context@.
2527createPage :: FilePath -> HakyllAction () Context
26−createPage path = HakyllAction
27− { actionDependencies = [path]
28− , actionUrl = Left $ toUrl path
29− , actionFunction = const (readPage path)
30− }
28+createPage path = cacheAction "pages" $ readPageAction path >>> renderAction
3129
3230-- | Create a "custom page" @Context@.
3331--
src/Text/Hakyll/Internal/Cache.hs+22
@@ -2,14 +2,17 @@ module Text.Hakyll.Internal.Cache
22 ( storeInCache
33 , getFromCache
44 , isCacheMoreRecent
5+ , cacheAction
56 ) where
67
78import Control.Monad ((<=<))
89import Control.Monad.Reader (liftIO)
910import Data.Binary
11+import System.FilePath ((</>))
1012
1113import Text.Hakyll.File
1214import Text.Hakyll.HakyllMonad (Hakyll)
15+import Text.Hakyll.HakyllAction
1316
1417-- | We can store all datatypes instantiating @Binary@ to the cache. The cache
1518-- directory is specified by the @HakyllConfiguration@, usually @_cache@.
@@ -29,3 +32,22 @@ getFromCache = liftIO . decodeFile <=< toCache
2932-- | Check if a file in the cache is more recent than a number of other files.
3033isCacheMoreRecent :: FilePath -> [FilePath] -> Hakyll Bool
3134isCacheMoreRecent file depends = toCache file >>= flip isFileMoreRecent depends
35+
36+-- | Cache an entire arrow
37+--
38+cacheAction :: Binary a
39+ => String
40+ -> HakyllAction () a
41+ -> HakyllAction () a
42+cacheAction key action = action { actionFunction = const cacheFunction }
43+ where
44+ cacheFunction = do
45+ -- Construct a filename
46+ fileName <- fmap (key </>) $ either id (const $ return "unknown")
47+ $ actionUrl action
48+ -- Check the cache
49+ cacheOk <- isCacheMoreRecent fileName $ actionDependencies action
50+ if cacheOk then getFromCache fileName
51+ else do result <- actionFunction action ()
52+ storeInCache result fileName
53+ return result
src/Text/Hakyll/Internal/Page.hs+37−60
@@ -1,47 +1,29 @@
11-- | A module for dealing with @Page@s. This module is mostly internally used.
22module Text.Hakyll.Internal.Page
3− ( readPage
3+ ( PageSection (..)
4+ , readPage
5+ , readPageAction
46 ) where
57
6−import qualified Data.Map as M
78import Data.List (isPrefixOf)
89import Data.Char (isSpace)
910import Control.Monad.Reader (liftIO)
1011import System.FilePath
1112import Control.Monad.State (State, evalState, get, put)
1213
13−import Text.Pandoc
14−
15−import Text.Hakyll.Context (Context (..))
1614import Text.Hakyll.File
1715import Text.Hakyll.HakyllMonad
16+import Text.Hakyll.HakyllAction
1817import Text.Hakyll.Regex (substituteRegex, matchesRegex)
1918import Text.Hakyll.Util (trim)
20−import Text.Hakyll.Internal.Cache
21−import Text.Hakyll.Internal.FileType
22−
23−-- | Get a render function for a given extension.
24−getRenderFunction :: FileType -> Hakyll (String -> String)
25−getRenderFunction Html = return id
26−getRenderFunction Text = return id
27−getRenderFunction UnknownFileType = return id
28−getRenderFunction fileType = do
29− parserState <- askHakyll pandocParserState
30− writerOptions <- askHakyll pandocWriterOptions
31− return $ writeHtmlString writerOptions
32− . readFunction fileType (readOptions parserState fileType)
33− where
34− readFunction ReStructuredText = readRST
35− readFunction LaTeX = readLaTeX
36− readFunction Markdown = readMarkdown
37− readFunction LiterateHaskellMarkdown = readMarkdown
38− readFunction t = error $ "Cannot render " ++ show t
3919
40− readOptions options LiterateHaskellMarkdown = options
41− { stateLiterateHaskell = True }
42− readOptions options _ = options
20+-- | Page info handle: (key, value, needs rendering)
21+--
22+data PageSection = PageSection {unPageSection :: [(String, String, Bool)]}
23+ deriving (Show)
4324
4425-- | Split a page into sections.
26+--
4527splitAtDelimiters :: [String] -> State (Maybe String) [[String]]
4628splitAtDelimiters [] = return []
4729splitAtDelimiters ls@(x:xs) = do
@@ -63,59 +45,54 @@ isPossibleDelimiter :: String -> Bool
6345isPossibleDelimiter = isPrefixOf "---"
6446
6547-- | Read one section of a page.
66−readSection :: (String -> String) -- ^ Render function.
67− -> Bool -- ^ If this section is the first section in the page.
48+--
49+readSection :: Bool -- ^ If this section is the first section in the page.
6850 -> [String] -- ^ Lines in the section.
69− -> [(String, String)] -- ^ Key-values extracted.
70−readSection _ _ [] = []
71−readSection renderFunction isFirst ls
51+ -> PageSection -- ^ Key-values extracted.
52+readSection _ [] = PageSection []
53+readSection isFirst ls
7254 | not isDelimiter' = body ls
73− | isNamedDelimiter = readSectionMetaData ls
74− | isFirst = readSimpleMetaData (drop 1 ls)
55+ | isNamedDelimiter = PageSection $ readSectionMetaData ls
56+ | isFirst = PageSection $ readSimpleMetaData (drop 1 ls)
7557 | otherwise = body (drop 1 ls)
7658 where
7759 isDelimiter' = isPossibleDelimiter (head ls)
7860 isNamedDelimiter = head ls `matchesRegex` "^----* *[a-zA-Z0-9][a-zA-Z0-9]*"
79− body ls' = [("body", renderFunction $ unlines ls')]
61+ body ls' = PageSection [("body", unlines ls', True)]
8062
8163 readSimpleMetaData = map readPair . filter (not . all isSpace)
8264 readPair = trimPair . break (== ':')
83− trimPair (key, value) = (trim key, trim $ drop 1 value)
65+ trimPair (key, value) = (trim key, trim (drop 1 value), False)
8466
8567 readSectionMetaData [] = []
8668 readSectionMetaData (header:value) =
8769 let key = substituteRegex "[^a-zA-Z0-9]" "" header
88− in [(key, renderFunction $ unlines value)]
70+ in [(key, unlines value, True)]
8971
90−-- | Read a page from a file. Metadata is supported, and if the filename
91−-- has a @.markdown@ extension, it will be rendered using pandoc.
92−readPageFromFile :: FilePath -> Hakyll Context
93−readPageFromFile path = do
94− renderFunction <- getRenderFunction $ getFileType path
95− let sectionFunctions = map (readSection renderFunction)
96− (True : repeat False)
72+-- | Read a page from a file. Metadata is supported.
73+--
74+readPage :: FilePath -> Hakyll [PageSection]
75+readPage path = do
76+ let sectionFunctions = map readSection $ True : repeat False
9777
9878 -- Read file.
9979 contents <- liftIO $ readFile path
10080 url <- toUrl path
10181 let sections = evalState (splitAtDelimiters $ lines contents) Nothing
102− sectionsData = concat $ zipWith ($) sectionFunctions sections
103− context = M.fromList $
104− ("url", url) : ("path", path) : category ++ sectionsData
82+ sectionsData = zipWith ($) sectionFunctions sections
10583
106− return $ Context context
84+ return $ PageSection [ ("url", url, False)
85+ , ("path", path, False)
86+ ] : category : sectionsData
10787 where
10888 category = let dirs = splitDirectories $ takeDirectory path
109− in [("category", last dirs) | not (null dirs)]
89+ in PageSection [("category", last dirs, False) | not (null dirs)]
11090
111−-- | Read a page. Might fetch it from the cache if available. Otherwise, it will
112−-- read it from the file given and store it in the cache.
113−readPage :: FilePath -> Hakyll Context
114−readPage path = do
115− isCacheMoreRecent' <- isCacheMoreRecent fileName [path]
116− if isCacheMoreRecent' then getFromCache fileName
117− else do page <- readPageFromFile path
118− storeInCache page fileName
119− return page
120− where
121− fileName = "pages" </> path
91+-- | Read a page from a file. Metadata is supported.
92+--
93+readPageAction :: FilePath -> HakyllAction () [PageSection]
94+readPageAction path = HakyllAction
95+ { actionDependencies = [path]
96+ , actionUrl = Left $ toUrl path
97+ , actionFunction = const $ readPage path
98+ }
src/Text/Hakyll/Internal/Template.hs+5−1
@@ -7,6 +7,7 @@ module Text.Hakyll.Internal.Template
77 , finalSubstitute
88 ) where
99
10+import Control.Arrow ((>>>))
1011import Control.Applicative ((<$>))
1112import Data.List (isPrefixOf)
1213import Data.Char (isAlphaNum)
@@ -16,6 +17,8 @@ import qualified Data.Map as M
1617
1718import Text.Hakyll.Context (Context (..))
1819import Text.Hakyll.HakyllMonad (Hakyll)
20+import Text.Hakyll.HakyllAction
21+import Text.Hakyll.Pandoc
1922import Text.Hakyll.Internal.Cache
2023import Text.Hakyll.Internal.Page
2124import Text.Hakyll.Internal.Template.Template
@@ -53,7 +56,8 @@ readTemplate path = do
5356 where
5457 fileName = "templates" </> path
5558 readDefaultTemplate = do
56− page <- unContext <$> readPage path
59+ page <- unContext <$>
60+ runHakyllAction (readPageAction path >>> renderAction)
5761 let body = fromMaybe (error $ "No body in template " ++ fileName)
5862 (M.lookup "body" page)
5963 return $ fromString body
src/Text/Hakyll/Pandoc.hsnew file+48
@@ -0,0 +1,48 @@
1+module Text.Hakyll.Pandoc where
2+
3+import Data.Maybe (fromMaybe)
4+import qualified Data.Map as M
5+import Control.Arrow (second)
6+
7+import Text.Pandoc
8+
9+import Text.Hakyll.Internal.FileType
10+import Text.Hakyll.Internal.Page
11+import Text.Hakyll.HakyllMonad
12+import Text.Hakyll.HakyllAction
13+import Text.Hakyll.Context
14+
15+-- | Get a render function for a given extension.
16+--
17+getRenderFunction :: FileType -> Hakyll (String -> String)
18+getRenderFunction Html = return id
19+getRenderFunction Text = return id
20+getRenderFunction UnknownFileType = return id
21+getRenderFunction fileType = do
22+ parserState <- askHakyll pandocParserState
23+ writerOptions <- askHakyll pandocWriterOptions
24+ return $ writeHtmlString writerOptions
25+ . readFunction fileType (readOptions parserState fileType)
26+ where
27+ readFunction ReStructuredText = readRST
28+ readFunction LaTeX = readLaTeX
29+ readFunction Markdown = readMarkdown
30+ readFunction LiterateHaskellMarkdown = readMarkdown
31+ readFunction t = error $ "Cannot render " ++ show t
32+
33+ readOptions options LiterateHaskellMarkdown = options
34+ { stateLiterateHaskell = True }
35+ readOptions options _ = options
36+
37+-- | Path must be there
38+--
39+renderAction :: HakyllAction [PageSection] Context
40+renderAction = createHakyllAction $ \sections -> do
41+ let triples = unPageSection =<< sections
42+ path = fromMaybe "unknown" $ lookup "path"
43+ $ map (\(x, y, _) -> (x, y))
44+ $ triples
45+ render' <- getRenderFunction $ getFileType path
46+ let pairs = map (\(k, v, r) -> second (if r then render' else id) (k, v))
47+ triples
48+ return $ Context $ M.fromList pairs