on master help sign in
Allow ... as metadata delimiter
308323bJasper Van der Jeugt
hakyll.cabal+1
@@ -68,6 +68,7 @@ Library
68 old-locale >= 1.0 && < 2.0,
68 old-locale >= 1.0 && < 2.0,
69 old-time >= 1.0 && < 1.3,
69 old-time >= 1.0 && < 1.3,
70 pandoc >= 1.6 && < 2.0,
70 pandoc >= 1.6 && < 2.0,
71+ parsec >= 2.1 && < 3.2,
71 process >= 1.0 && < 1.4,
72 process >= 1.0 && < 1.4,
72 regex-base >= 0.93 && < 1.0,
73 regex-base >= 0.93 && < 1.0,
73 regex-pcre >= 0.93 && < 1.0,
74 regex-pcre >= 0.93 && < 1.0,
src/Hakyll/Web/Page/Read.hs+38−42
@@ -4,57 +4,53 @@ module Hakyll.Web.Page.Read
4 ( readPage
4 ( readPage
5 ) where
5 ) where
6
6
7−import Control.Applicative ((<$>), (<*>))
7+import Control.Applicative ((<$>), (<*>), (<*))
8−import Control.Arrow (second, (***))
9−import Control.Monad.State (State, get, put, evalState)
10−import Data.List (isPrefixOf)
11−import Data.Map (Map)
12import qualified Data.Map as M
8import qualified Data.Map as M
13
9
14−import Hakyll.Web.Page.Internal
10+import Text.Parsec.Char (alphaNum, anyChar, char, newline, oneOf, string)
11+import Text.Parsec.Combinator (choice, many1, manyTill, option, skipMany1)
12+import Text.Parsec.Prim (many, parse, skipMany, (<?>))
13+import Text.Parsec.String (Parser)
14+
15import Hakyll.Core.Util.String
15import Hakyll.Core.Util.String
16+import Hakyll.Web.Page.Internal
16
17
17−-- | We're using a simple state monad as parser
18+-- | Space or tab, no newline
18−--
19+inlineSpace :: Parser Char
19−type LineParser = State [String]
20+inlineSpace = oneOf ['\t', ' '] <?> "space"
20
21
21−-- | Read the metadata section from a page
22+-- | Parse a single metadata field
22--
23--
23−parseMetadata :: LineParser (Map String String)
24+metadataField :: Parser (String, String)
24−parseMetadata = get >>= \content -> case content of
25+metadataField = do
25− -- No lines means no metadata
26+ key <- manyTill alphaNum $ char ':'
26− [] -> return M.empty
27+ skipMany1 inlineSpace
27− -- Check if the file begins with a delimiter
28+ value <- manyTill anyChar newline
28− (l : ls) -> if not (isPossibleDelimiter l)
29+ trailing' <- many trailing
29− then -- No delimiter means no metadata
30+ return (key, trim $ value ++ concat trailing')
30− return M.empty
31− else do -- Break the metadata section
32− let (metadata, rest) = second (drop 1) $ break (== l) ls
33− -- Put the rest back
34− put rest
35− -- Parse the metadata
36− return $ M.fromList $ map parseMetadata' metadata
37 where
31 where
38− -- Check if a line can be a delimiter
32+ trailing = (++) <$> many1 inlineSpace <*> manyTill anyChar newline
39− isPossibleDelimiter = isPrefixOf "---"
40
33
41− -- Parse a "key: value" string to a (key, value) tupple
34+-- | Parse a metadata block, including delimiters and trailing newlines
42− parseMetadata' = (trim *** trim . drop 1) . break (== ':')
43−
44−-- | Read the body section of a page
45--
35--
46−parseBody :: LineParser String
36+metadata :: Parser [(String, String)]
47−parseBody = do
37+metadata = do
48− body <- get
38+ open <- many1 (char '-') <* many inlineSpace <* newline
49− put []
39+ metadata' <- many metadataField
50− return $ unlines body
40+ _ <- choice $ map (string . replicate (length open)) ['-', '.']
51−
41+ skipMany inlineSpace
52−-- | Read an entire page
42+ skipMany1 newline
43+ return metadata'
44+
45+-- | Parse a Hakyll page
53--
46--
54−parsePage :: LineParser (Page String)
47+page :: Parser ([(String, String)], String)
55−parsePage = Page <$> parseMetadata <*> parseBody
48+page = do
49+ metadata' <- option [] metadata
50+ body <- many anyChar
51+ return (metadata', body)
56
52
57−-- | Read a page from a string
58−--
59readPage :: String -> Page String
53readPage :: String -> Page String
60−readPage = evalState parsePage . lines
54+readPage input = case parse page "page" input of
55+ Left err -> error (show err)
56+ Right (md, b) -> Page (M.fromList md) b
tests/Hakyll/Web/Page/Tests.hs+11−3
@@ -14,13 +14,13 @@ import TestSuite.Util
14
14
15tests :: [Test]
15tests :: [Test]
16tests = fromAssertions "readPage"
16tests = fromAssertions "readPage"
17− [ Page (M.singleton "foo" "bar") "body\n" @=? readPage
17+ [ Page (M.singleton "foo" "bar") "body" @=? readPage
18 "--- \n\
18 "--- \n\
19 \foo: bar \n\
19 \foo: bar \n\
20 \--- \n\
20 \--- \n\
21 \body"
21 \body"
22
22
23− , Page M.empty "line one\nlijn twee\n" @=? readPage
23+ , Page M.empty "line one\nlijn twee" @=? readPage
24 "line one\n\
24 "line one\n\
25 \lijn twee"
25 \lijn twee"
26
26
@@ -28,5 +28,13 @@ tests = fromAssertions "readPage"
28 "---\n\
28 "---\n\
29 \veld02: deux\n\
29 \veld02: deux\n\
30 \field1: unos\n\
30 \field1: unos\n\
31− \---"
31+ \---\n"
32+
33+ , Page (M.fromList [("author", "jasper"), ("title", "lol")]) "O hai\n"
34+ @=? readPage
35+ "---\n\
36+ \author: jasper\n\
37+ \title: lol\n\
38+ \...\n\
39+ \O hai\n"
32 ]
40 ]