@@ -12,7 +12,7 @@ import Data.Maybe (fromMaybe)
1212import Control.Parallel.Strategies (rdeepseq, ($|))
1313import Control.Monad.Reader (liftIO)
1414import System.FilePath (takeExtension)
15−import System.IO (Handle, IOMode(..), openFile, hClose)
15+import System.IO (IOMode(..), openFile, hClose)
1616import qualified System.IO.UTF8 as U
1717
1818import Text.Pandoc
@@ -22,6 +22,7 @@ import Text.Hakyll.File
2222import Text.Hakyll.Util (trim)
2323import Text.Hakyll.Context (Context)
2424import Text.Hakyll.Renderable
25+import Text.Hakyll.Regex (substituteRegex, matchesRegex)
2526
2627-- | A Page is basically key-value mapping. Certain keys have special
2728-- meanings, like for example url, body and title.
@@ -56,25 +57,22 @@ writerOptions :: WriterOptions
5657writerOptions = defaultWriterOptions
5758
5859-- | Get a render function for a given extension.
59−renderFunction :: String -> (String -> String)
60−renderFunction ".html" = id
61−renderFunction ext = writeHtmlString writerOptions
62− . readFunction ext defaultParserState
60+getRenderFunction :: String -> (String -> String)
61+getRenderFunction ".html" = id
62+getRenderFunction ext = writeHtmlString writerOptions
63+ . readFunction ext defaultParserState
6364 where
6465 readFunction ".rst" = readRST
6566 readFunction ".tex" = readLaTeX
6667 readFunction _ = readMarkdown
6768
68−-- | Read metadata header from a file handle.
69−readMetaData :: Handle -> Hakyll [(String, String)]
70−readMetaData handle = do
71− line <- liftIO $ U.hGetLine handle
72− if isDelimiter line
73− then return []
74− else do others <- readMetaData handle
75− return $ (trimPair . break (== ':')) line : others
76− where
77− trimPair (key, value) = (trim key, trim $ tail value)
69+-- | Split a page into sections.
70+splitAtDelimiters :: [String] -> [[String]]
71+splitAtDelimiters [] = []
72+splitAtDelimiters ls@(x:xs)
73+ | isDelimiter x = let (content, rest) = break isDelimiter xs
74+ in (x : content) : splitAtDelimiters rest
75+ | otherwise = [ls]
7876
7977-- | Check if the given string is a metadata delimiter.
8078isDelimiter :: String -> Bool
@@ -106,6 +104,31 @@ cachePage page@(Page mapping) = do
106104
107105 destination = toCache $ getURL page
108106
107+-- | Read one section of a page.
108+readSection :: (String -> String) -- ^ Render function.
109+ -> Bool -- ^ If this section is the first section in the page.
110+ -> [String] -- ^ Lines in the section.
111+ -> [(String, String)] -- ^ Key-values extracted.
112+readSection _ _ [] = []
113+readSection renderFunction True ls
114+ | isDelimiter (head ls) = readSimpleMetaData (tail ls)
115+ | otherwise = [("body", renderFunction $ unlines ls)]
116+ where
117+ readSimpleMetaData = map readPair
118+ readPair = (trimPair . break (== ':'))
119+ trimPair (key, value) = (trim key, trim $ tail value)
120+
121+readSection renderFunction False ls
122+ | isDelimiter (head ls) = readSectionMetaData ls
123+ | otherwise = error $ "Page parsing error at: " ++ head ls
124+ where
125+ readSectionMetaData [] = []
126+ readSectionMetaData (header:value) =
127+ let key = if header `matchesRegex` "----* *[a-zA-Z][a-zA-Z]*"
128+ then substituteRegex "[^a-zA-Z]" "" header
129+ else "body"
130+ in [(key, renderFunction $ unlines value)]
131+
109132-- | Read a page from a file. Metadata is supported, and if the filename
110133-- has a @.markdown@ extension, it will be rendered using pandoc.
111134readPage :: FilePath -> Hakyll Page
@@ -113,27 +136,22 @@ readPage pagePath = do
113136 -- Check cache.
114137 getFromCache <- isCacheValid cacheFile [pagePath]
115138 let path = if getFromCache then cacheFile else pagePath
139+ renderFunction = getRenderFunction $ takeExtension path
140+ sectionFunctions = map (readSection renderFunction)
141+ (True : repeat False)
116142
117143 -- Read file.
118144 handle <- liftIO $ openFile path ReadMode
119− line <- liftIO $ U.hGetLine handle
120− (metaData, body) <-
121− if isDelimiter line
122− then do md <- readMetaData handle
123− b <- liftIO $ U.hGetContents handle
124− return (md, b)
125− else do b <- liftIO $ U.hGetContents handle
126− return ([], line ++ "\n" ++ b)
127−
128− -- Render file
129− let rendered = (renderFunction $ takeExtension path) body
145+ sections <- fmap (splitAtDelimiters . lines )
146+ (liftIO $ U.hGetContents handle)
147+
148+ let context = concat $ zipWith ($) sectionFunctions sections
130149 page = fromContext $ M.fromList $
131− [ ("body", rendered)
132− , ("url", url)
150+ [ ("url", url)
133151 , ("path", pagePath)
134− ] ++ metaData
152+ ] ++ context
135153
136− seq (($|) id rdeepseq rendered) $ liftIO $ hClose handle
154+ seq (($|) id rdeepseq context) $ liftIO $ hClose handle
137155
138156 -- Cache if needed
139157 if getFromCache then return () else cachePage page