on master help sign in
Initial YAML support

See #225

commite81468e0f64fdbe05794d5f8ccaebc00ee474ee2 authorJasper Van der Jeugt <m@jaspervdj.be> dateWed 6 Apr 2016, 14:26 (11 years ago) parent3f3e096
17 files changed+285−193
.gitignore+2−1 hakyll.cabal+62−54 src/Data/List/Extended.hs+15 src/Data/Yaml/Extended.hs+17 src/Hakyll/Core/Metadata.hs+81−7 src/Hakyll/Core/Provider/Metadata.hs+47−64 src/Hakyll/Core/Provider/MetadataCache.hs+3−6 src/Hakyll/Core/Routes.hs−1 src/Hakyll/Core/Runtime.hs−2 src/Hakyll/Web/Pandoc/Biblio.hs+7−10 src/Hakyll/Web/Tags.hs+7−5 src/Hakyll/Web/Template/Context.hs+3−7 stack.yaml+12 tests/Hakyll/Core/Provider/Metadata/Tests.hs+14−13 tests/Hakyll/Core/Provider/Tests.hs+5−8 tests/Hakyll/Core/Routes/Tests.hs+5−7 tests/Hakyll/Core/Rules/Tests.hs+5−8
.gitignore+2−1
@@ -1,4 +1,4 @@
1−# Ignore swap files and cabal output.
1+# Ignore swap files and stack/cabal output.
22*.hi
33*.o
44*.swo
@@ -10,6 +10,7 @@ dist
1010tags
1111cabal.sandbox.config
1212.cabal-sandbox/
13+.stack-work
1314
1415# Ignore test builds.
1516tests/Main
hakyll.cabal+62−54
@@ -121,6 +121,8 @@ Library
121121 Hakyll.Web.Template.List
122122
123123 Other-Modules:
124+ Data.List.Extended
125+ Data.Yaml.Extended
124126 Hakyll.Check
125127 Hakyll.Commands
126128 Hakyll.Core.Compiler.Internal
@@ -140,33 +142,36 @@ Library
140142 Paths_hakyll
141143
142144 Build-Depends:
143− base >= 4 && < 5,
144− binary >= 0.5 && < 0.8,
145− blaze-html >= 0.5 && < 0.9,
146− blaze-markup >= 0.5.1 && < 0.8,
147− bytestring >= 0.9 && < 0.11,
148− cmdargs >= 0.10 && < 0.11,
149− containers >= 0.3 && < 0.6,
150− cryptohash >= 0.7 && < 0.12,
151− data-default >= 0.4 && < 0.6,
152− deepseq >= 1.3 && < 1.5,
153− directory >= 1.0 && < 1.3,
154− filepath >= 1.0 && < 1.5,
155− lrucache >= 1.1.1 && < 1.3,
156− mtl >= 1 && < 2.3,
157− network >= 2.6 && < 2.7,
158− network-uri >= 2.6 && < 2.7,
159− pandoc >= 1.14 && < 1.18,
160− pandoc-citeproc >= 0.4 && < 0.10,
161− parsec >= 3.0 && < 3.2,
162− process >= 1.0 && < 1.3,
163− random >= 1.0 && < 1.2,
164− regex-base >= 0.93 && < 0.94,
165− regex-tdfa >= 1.1 && < 1.3,
166− tagsoup >= 0.13.1 && < 0.14,
167− text >= 0.11 && < 1.3,
168− time >= 1.4 && < 1.6,
169− time-locale-compat >= 0.1 && < 0.2
145+ base >= 4 && < 5,
146+ binary >= 0.5 && < 0.8,
147+ blaze-html >= 0.5 && < 0.9,
148+ blaze-markup >= 0.5.1 && < 0.8,
149+ bytestring >= 0.9 && < 0.11,
150+ cmdargs >= 0.10 && < 0.11,
151+ containers >= 0.3 && < 0.6,
152+ cryptohash >= 0.7 && < 0.12,
153+ data-default >= 0.4 && < 0.6,
154+ deepseq >= 1.3 && < 1.5,
155+ directory >= 1.0 && < 1.3,
156+ filepath >= 1.0 && < 1.5,
157+ lrucache >= 1.1.1 && < 1.3,
158+ mtl >= 1 && < 2.3,
159+ network >= 2.6 && < 2.7,
160+ network-uri >= 2.6 && < 2.7,
161+ pandoc >= 1.14 && < 1.18,
162+ pandoc-citeproc >= 0.4 && < 0.10,
163+ parsec >= 3.0 && < 3.2,
164+ process >= 1.0 && < 1.3,
165+ random >= 1.0 && < 1.2,
166+ regex-base >= 0.93 && < 0.94,
167+ regex-tdfa >= 1.1 && < 1.3,
168+ tagsoup >= 0.13.1 && < 0.14,
169+ text >= 0.11 && < 1.3,
170+ time >= 1.4 && < 1.6,
171+ time-locale-compat >= 0.1 && < 0.2,
172+ unordered-containers >= 0.2 && < 0.3,
173+ vector >= 0.11 && < 0.12,
174+ yaml >= 0.8 && < 0.9
170175
171176 If flag(previewServer)
172177 Build-depends:
@@ -226,33 +231,36 @@ Test-suite hakyll-tests
226231 test-framework-hunit >= 0.3 && < 0.4,
227232 test-framework-quickcheck2 >= 0.3 && < 0.4,
228233 -- Copy pasted from hakyll dependencies:
229− base >= 4 && < 5,
230− binary >= 0.5 && < 0.8,
231− blaze-html >= 0.5 && < 0.9,
232− blaze-markup >= 0.5.1 && < 0.8,
233− bytestring >= 0.9 && < 0.11,
234− cmdargs >= 0.10 && < 0.11,
235− containers >= 0.3 && < 0.6,
236− cryptohash >= 0.7 && < 0.12,
237− data-default >= 0.4 && < 0.6,
238− deepseq >= 1.3 && < 1.5,
239− directory >= 1.0 && < 1.3,
240− filepath >= 1.0 && < 1.5,
241− lrucache >= 1.1.1 && < 1.3,
242− mtl >= 1 && < 2.3,
243− network >= 2.6 && < 2.7,
244− network-uri >= 2.6 && < 2.7,
245− pandoc >= 1.14 && < 1.18,
246− pandoc-citeproc >= 0.4 && < 0.10,
247− parsec >= 3.0 && < 3.2,
248− process >= 1.0 && < 1.3,
249− random >= 1.0 && < 1.2,
250− regex-base >= 0.93 && < 0.94,
251− regex-tdfa >= 1.1 && < 1.3,
252− tagsoup >= 0.13.1 && < 0.14,
253− text >= 0.11 && < 1.3,
254− time >= 1.5 && < 1.6,
255− time-locale-compat >= 0.1 && < 0.2
234+ base >= 4 && < 5,
235+ binary >= 0.5 && < 0.8,
236+ blaze-html >= 0.5 && < 0.9,
237+ blaze-markup >= 0.5.1 && < 0.8,
238+ bytestring >= 0.9 && < 0.11,
239+ cmdargs >= 0.10 && < 0.11,
240+ containers >= 0.3 && < 0.6,
241+ cryptohash >= 0.7 && < 0.12,
242+ data-default >= 0.4 && < 0.6,
243+ deepseq >= 1.3 && < 1.5,
244+ directory >= 1.0 && < 1.3,
245+ filepath >= 1.0 && < 1.5,
246+ lrucache >= 1.1.1 && < 1.3,
247+ mtl >= 1 && < 2.3,
248+ network >= 2.6 && < 2.7,
249+ network-uri >= 2.6 && < 2.7,
250+ pandoc >= 1.14 && < 1.18,
251+ pandoc-citeproc >= 0.4 && < 0.10,
252+ parsec >= 3.0 && < 3.2,
253+ process >= 1.0 && < 1.3,
254+ random >= 1.0 && < 1.2,
255+ regex-base >= 0.93 && < 0.94,
256+ regex-tdfa >= 1.1 && < 1.3,
257+ tagsoup >= 0.13.1 && < 0.14,
258+ text >= 0.11 && < 1.3,
259+ time >= 1.4 && < 1.6,
260+ time-locale-compat >= 0.1 && < 0.2,
261+ unordered-containers >= 0.2 && < 0.3,
262+ vector >= 0.11 && < 0.12,
263+ yaml >= 0.8 && < 0.9
256264
257265 If flag(previewServer)
258266 Build-depends:
src/Data/List/Extended.hsnew file+15
@@ -0,0 +1,15 @@
1+module Data.List.Extended
2+ ( module Data.List
3+ , breakWhen
4+ ) where
5+
6+import Data.List
7+
8+-- | Like 'break', but can act on the entire tail of the list.
9+breakWhen :: ([a] -> Bool) -> [a] -> ([a], [a])
10+breakWhen predicate = go []
11+ where
12+ go buf [] = (reverse buf, [])
13+ go buf (x : xs)
14+ | predicate (x : xs) = (reverse buf, x : xs)
15+ | otherwise = go (x : buf) xs
src/Data/Yaml/Extended.hsnew file+17
@@ -0,0 +1,17 @@
1+module Data.Yaml.Extended
2+ ( module Data.Yaml
3+ , toString
4+ , toList
5+ ) where
6+
7+import qualified Data.Text as T
8+import qualified Data.Vector as V
9+import Data.Yaml
10+
11+toString :: Value -> Maybe String
12+toString (String t) = Just (T.unpack t)
13+toString _ = Nothing
14+
15+toList :: Value -> Maybe [Value]
16+toList (Array a) = Just (V.toList a)
17+toList _ = Nothing
src/Hakyll/Core/Metadata.hs+81−7
@@ -1,28 +1,46 @@
11--------------------------------------------------------------------------------
22module Hakyll.Core.Metadata
33 ( Metadata
4+ , lookupString
5+ , lookupStringList
6+
47 , MonadMetadata (..)
58 , getMetadataField
69 , getMetadataField'
710 , makePatternDependency
11+
12+ , BinaryMetadata (..)
813 ) where
914
1015
1116--------------------------------------------------------------------------------
17+import Control.Arrow (second)
1218import Control.Monad (forM)
13−import Data.Map (Map)
14−import qualified Data.Map as M
19+import Data.Binary (Binary (..), getWord8,
20+ putWord8, Get)
21+import qualified Data.HashMap.Strict as HMS
1522import qualified Data.Set as S
16−
17−
18−--------------------------------------------------------------------------------
23+import qualified Data.Text as T
24+import qualified Data.Vector as V
25+import qualified Data.Yaml.Extended as Yaml
1926import Hakyll.Core.Dependencies
2027import Hakyll.Core.Identifier
2128import Hakyll.Core.Identifier.Pattern
2229
2330
2431--------------------------------------------------------------------------------
25−type Metadata = Map String String
32+type Metadata = Yaml.Object
33+
34+
35+--------------------------------------------------------------------------------
36+lookupString :: String -> Metadata -> Maybe String
37+lookupString key meta = HMS.lookup (T.pack key) meta >>= Yaml.toString
38+
39+
40+--------------------------------------------------------------------------------
41+lookupStringList :: String -> Metadata -> Maybe [String]
42+lookupStringList key meta =
43+ HMS.lookup (T.pack key) meta >>= Yaml.toList >>= mapM Yaml.toString
2644
2745
2846--------------------------------------------------------------------------------
@@ -42,7 +60,7 @@ class Monad m => MonadMetadata m where
4260getMetadataField :: MonadMetadata m => Identifier -> String -> m (Maybe String)
4361getMetadataField identifier key = do
4462 metadata <- getMetadata identifier
45− return $ M.lookup key metadata
63+ return $ lookupString key metadata
4664
4765
4866--------------------------------------------------------------------------------
@@ -62,3 +80,59 @@ makePatternDependency :: MonadMetadata m => Pattern -> m Dependency
6280makePatternDependency pattern = do
6381 matches' <- getMatches pattern
6482 return $ PatternDependency pattern (S.fromList matches')
83+
84+
85+--------------------------------------------------------------------------------
86+-- | Newtype wrapper for serialization.
87+newtype BinaryMetadata = BinaryMetadata
88+ {unBinaryMetadata :: Metadata}
89+
90+
91+instance Binary BinaryMetadata where
92+ put (BinaryMetadata obj) = put (BinaryYaml $ Yaml.Object obj)
93+ get = do
94+ BinaryYaml (Yaml.Object obj) <- get
95+ return $ BinaryMetadata obj
96+
97+
98+--------------------------------------------------------------------------------
99+newtype BinaryYaml = BinaryYaml {unBinaryYaml :: Yaml.Value}
100+
101+
102+--------------------------------------------------------------------------------
103+instance Binary BinaryYaml where
104+ put (BinaryYaml yaml) = case yaml of
105+ Yaml.Object obj -> do
106+ putWord8 0
107+ let list :: [(T.Text, BinaryYaml)]
108+ list = map (second BinaryYaml) $ HMS.toList obj
109+ put list
110+
111+ Yaml.Array arr -> do
112+ putWord8 1
113+ let list = map BinaryYaml (V.toList arr) :: [BinaryYaml]
114+ put list
115+
116+ Yaml.String s -> putWord8 2 >> put s
117+ Yaml.Number n -> putWord8 3 >> put n
118+ Yaml.Bool b -> putWord8 4 >> put b
119+ Yaml.Null -> putWord8 5
120+
121+ get = do
122+ tag <- getWord8
123+ case tag of
124+ 0 -> do
125+ list <- get :: Get [(T.Text, BinaryYaml)]
126+ return $ BinaryYaml $ Yaml.Object $
127+ HMS.fromList $ map (second unBinaryYaml) list
128+
129+ 1 -> do
130+ list <- get :: Get [BinaryYaml]
131+ return $ BinaryYaml $
132+ Yaml.Array $ V.fromList $ map unBinaryYaml list
133+
134+ 2 -> BinaryYaml . Yaml.String <$> get
135+ 3 -> BinaryYaml . Yaml.Number <$> get
136+ 4 -> BinaryYaml . Yaml.Bool <$> get
137+ 5 -> return $ BinaryYaml Yaml.Null
138+ _ -> fail "Data.Binary.get: Invalid Binary Metadata"
src/Hakyll/Core/Provider/Metadata.hs+47−64
@@ -1,33 +1,32 @@
11--------------------------------------------------------------------------------
22-- | Internal module to parse metadata
3+{-# LANGUAGE BangPatterns #-}
34module Hakyll.Core.Provider.Metadata
45 ( loadMetadata
5− , metadata
6− , page
7−
8− -- This parser can be reused in some places
9− , metadataKey
6+ , parsePage
107 ) where
118
129
1310--------------------------------------------------------------------------------
1411import Control.Applicative
1512import Control.Arrow (second)
13+import Control.Monad (guard)
1614import qualified Data.ByteString.Char8 as BC
15+import qualified Data.HashMap.Strict as HMS
1716import Data.List (intercalate)
17+import Data.List.Extended (breakWhen)
1818import qualified Data.Map as M
19−import System.IO as IO
20−import Text.Parsec ((<?>))
21−import qualified Text.Parsec as P
22−import Text.Parsec.String (Parser)
23−
24−
25−--------------------------------------------------------------------------------
19+import Data.Maybe (fromMaybe)
20+import Data.Monoid ((<>))
21+import qualified Data.Text as T
22+import qualified Data.Text.Encoding as T
23+import qualified Data.Yaml as Yaml
2624import Hakyll.Core.Identifier
2725import Hakyll.Core.Metadata
2826import Hakyll.Core.Provider.Internal
2927import Hakyll.Core.Util.Parser
3028import Hakyll.Core.Util.String
29+import System.IO as IO
3130
3231
3332--------------------------------------------------------------------------------
@@ -36,13 +35,13 @@ loadMetadata p identifier = do
3635 hasHeader <- probablyHasMetadataHeader fp
3736 (md, body) <- if hasHeader
3837 then second Just <$> loadMetadataHeader fp
39− else return (M.empty, Nothing)
38+ else return (mempty, Nothing)
4039
4140 emd <- case mi of
42− Nothing -> return M.empty
41+ Nothing -> return mempty
4342 Just mi' -> loadMetadataFile $ resourceFilePath p mi'
4443
45− return (M.union md emd, body)
44+ return (md <> emd, body)
4645 where
4746 normal = setVersion Nothing identifier
4847 fp = resourceFilePath p identifier
@@ -52,19 +51,15 @@ loadMetadata p identifier = do
5251--------------------------------------------------------------------------------
5352loadMetadataHeader :: FilePath -> IO (Metadata, String)
5453loadMetadataHeader fp = do
55− contents <- readFile fp
56− case P.parse page fp contents of
57− Left err -> error (show err)
58− Right (md, b) -> return (M.fromList md, b)
54+ fileContent <- readFile fp
55+ either fail return (parsePage fileContent)
5956
6057
6158--------------------------------------------------------------------------------
6259loadMetadataFile :: FilePath -> IO Metadata
6360loadMetadataFile fp = do
64− contents <- readFile fp
65− case P.parse metadata fp contents of
66− Left err -> error (show err)
67− Right md -> return $ M.fromList md
61+ errOrMeta <- Yaml.decodeFileEither fp
62+ either (fail . show) return errOrMeta
6863
6964
7065--------------------------------------------------------------------------------
@@ -83,53 +78,41 @@ probablyHasMetadataHeader fp = do
8378
8479
8580--------------------------------------------------------------------------------
86−-- | Space or tab, no newline
87−inlineSpace :: Parser Char
88−inlineSpace = P.oneOf ['\t', ' '] <?> "space"
89−
90−
91−--------------------------------------------------------------------------------
92−-- | Parse Windows newlines as well (i.e. "\n" or "\r\n")
93−newline :: Parser String
94−newline = P.string "\n" <|> P.string "\r\n"
95−
96−
97−--------------------------------------------------------------------------------
98−-- | Parse a single metadata field
99−metadataField :: Parser (String, String)
100−metadataField = do
101− key <- metadataKey
102− _ <- P.char ':'
103− P.skipMany1 inlineSpace <?> "space followed by metadata for: " ++ key
104− value <- P.manyTill P.anyChar newline
105− trailing' <- P.many trailing
106− return (key, trim $ intercalate " " $ value : trailing')
81+-- | Parse the page metadata and body.
82+splitMetadata :: String -> (Maybe String, String)
83+splitMetadata str0 = fromMaybe (Nothing, str0) $ do
84+ guard $ leading >= 3
85+ let !(!meta, !content0) = breakWhen isTrailing (drop leading str0)
86+ guard $ not $ null content0
87+ let !content1 = drop (leading + 1) content0
88+ !content2 = dropWhile isNewline $ dropWhile isInlineSpace content1
89+ return (Just meta, content2)
10790 where
108− trailing = P.many1 inlineSpace *> P.manyTill P.anyChar newline
91+ -- Parse the leading "---"
92+ !leading = length $ takeWhile (== '-') str0
10993
94+ -- Predicate to recognize the trailing "---" or "..."
95+ isTrailing [] = False
96+ isTrailing (x : xs) =
97+ isNewline x && length (takeWhile isDash xs) == leading
11098
111−--------------------------------------------------------------------------------
112−-- | Parse a metadata block
113−metadata :: Parser [(String, String)]
114−metadata = P.many metadataField
99+ -- Characters
100+ isNewline c = c == '\n' || c == '\r'
101+ isDash c = c == '-' || c == '.'
102+ isInlineSpace c = c == '\t' || c == ' '
115103
116104
117105--------------------------------------------------------------------------------
118−-- | Parse a metadata block, including delimiters and trailing newlines
119−metadataBlock :: Parser [(String, String)]
120−metadataBlock = do
121− open <- P.many1 (P.char '-') <* P.many inlineSpace <* newline
122− metadata' <- metadata
123− _ <- P.choice $ map (P.string . replicate (length open)) ['-', '.']
124− P.skipMany inlineSpace
125− P.skipMany1 newline
126− return metadata'
106+parseMetadata :: String -> Either String Metadata
107+parseMetadata = Yaml.decodeEither . T.encodeUtf8 . T.pack
127108
128109
129110--------------------------------------------------------------------------------
130−-- | Parse a page consisting of a metadata header and a body
131−page :: Parser ([(String, String)], String)
132−page = do
133− metadata' <- P.option [] metadataBlock
134− body <- P.many P.anyChar
135− return (metadata', body)
111+parsePage :: String -> Either String (Metadata, String)
112+parsePage fileContent = case mbMetaBlock of
113+ Nothing -> return (mempty, content)
114+ Just metaBlock -> case parseMetadata metaBlock of
115+ Left err -> Left err
116+ Right meta -> return (meta, content)
117+ where
118+ !(!mbMetaBlock, !content) = splitMetadata fileContent
src/Hakyll/Core/Provider/MetadataCache.hs+3−6
@@ -8,9 +8,6 @@ module Hakyll.Core.Provider.MetadataCache
88
99--------------------------------------------------------------------------------
1010import Control.Monad (unless)
11−import qualified Data.Map as M
12−
13−--------------------------------------------------------------------------------
1411import Hakyll.Core.Identifier
1512import Hakyll.Core.Metadata
1613import Hakyll.Core.Provider.Internal
@@ -21,11 +18,11 @@ import qualified Hakyll.Core.Store as Store
2118--------------------------------------------------------------------------------
2219resourceMetadata :: Provider -> Identifier -> IO Metadata
2320resourceMetadata p r
24− | not (resourceExists p r) = return M.empty
21+ | not (resourceExists p r) = return mempty
2522 | otherwise = do
2623 -- TODO keep time in md cache
2724 load p r
28− Store.Found md <- Store.get (providerStore p)
25+ Store.Found (BinaryMetadata md) <- Store.get (providerStore p)
2926 [name, toFilePath r, "metadata"]
3027 return md
3128
@@ -52,7 +49,7 @@ load p r = do
5249 mmof <- Store.isMember store mdk
5350 unless mmof $ do
5451 (md, body) <- loadMetadata p r
55− Store.set store mdk md
52+ Store.set store mdk (BinaryMetadata md)
5653 Store.set store bk body
5754 where
5855 store = providerStore p
src/Hakyll/Core/Routes.hs−1
@@ -42,7 +42,6 @@ module Hakyll.Core.Routes
4242
4343
4444--------------------------------------------------------------------------------
45−import Data.Monoid (Monoid, mappend, mempty)
4645import System.FilePath (replaceExtension)
4746
4847
src/Hakyll/Core/Runtime.hs−2
@@ -5,7 +5,6 @@ module Hakyll.Core.Runtime
55
66
77--------------------------------------------------------------------------------
8−import Control.Applicative ((<$>))
98import Control.Monad (unless)
109import Control.Monad.Error (ErrorT, runErrorT, throwError)
1110import Control.Monad.Reader (ask)
@@ -15,7 +14,6 @@ import Control.Monad.Trans (liftIO)
1514import Data.List (intercalate)
1615import Data.Map (Map)
1716import qualified Data.Map as M
18−import Data.Monoid (mempty)
1917import Data.Set (Set)
2018import qualified Data.Set as S
2119import System.Exit (ExitCode (..))
src/Hakyll/Web/Pandoc/Biblio.hs+7−10
@@ -23,22 +23,19 @@ module Hakyll.Web.Pandoc.Biblio
2323
2424
2525--------------------------------------------------------------------------------
26−import Control.Applicative ((<$>))
27−import Control.Monad (replicateM, liftM)
28−import Data.Binary (Binary (..))
29−import Data.Default (def)
30−import Data.Typeable (Typeable)
31−import qualified Text.CSL as CSL
32−import Text.CSL.Pandoc (processCites)
33−import Text.Pandoc (Pandoc, ReaderOptions (..))
34−
35−--------------------------------------------------------------------------------
26+import Control.Monad (liftM, replicateM)
27+import Data.Binary (Binary (..))
28+import Data.Default (def)
29+import Data.Typeable (Typeable)
3630import Hakyll.Core.Compiler
3731import Hakyll.Core.Identifier
3832import Hakyll.Core.Item
3933import Hakyll.Core.Writable
4034import Hakyll.Web.Pandoc
4135import Hakyll.Web.Pandoc.Binary ()
36+import qualified Text.CSL as CSL
37+import Text.CSL.Pandoc (processCites)
38+import Text.Pandoc (Pandoc, ReaderOptions (..))
4239
4340
4441--------------------------------------------------------------------------------
src/Hakyll/Web/Tags.hs+7−5
@@ -63,13 +63,12 @@ module Hakyll.Web.Tags
6363
6464--------------------------------------------------------------------------------
6565import Control.Arrow ((&&&))
66−import Control.Monad (foldM, forM, forM_)
66+import Control.Monad (foldM, forM, forM_, mplus)
6767import Data.Char (toLower)
6868import Data.List (intercalate, intersperse,
6969 sortBy)
7070import qualified Data.Map as M
7171import Data.Maybe (catMaybes, fromMaybe)
72−import Data.Monoid (mconcat)
7372import Data.Ord (comparing)
7473import qualified Data.Set as S
7574import System.FilePath (takeBaseName, takeDirectory)
@@ -88,8 +87,8 @@ import Hakyll.Core.Item
8887import Hakyll.Core.Metadata
8988import Hakyll.Core.Rules
9089import Hakyll.Core.Util.String
91−import Hakyll.Web.Template.Context
9290import Hakyll.Web.Html
91+import Hakyll.Web.Template.Context
9392
9493
9594--------------------------------------------------------------------------------
@@ -103,11 +102,14 @@ data Tags = Tags
103102
104103--------------------------------------------------------------------------------
105104-- | Obtain tags from a page in the default way: parse them from the @tags@
106−-- metadata field.
105+-- metadata field. This can either be a list or a comma-separated string.
107106getTags :: MonadMetadata m => Identifier -> m [String]
108107getTags identifier = do
109108 metadata <- getMetadata identifier
110− return $ maybe [] (map trim . splitAll ",") $ M.lookup "tags" metadata
109+ return $ fromMaybe [] $
110+ (lookupStringList "tags" metadata) `mplus`
111+ (map trim . splitAll "," <$> lookupString "tags" metadata)
112+
111113
112114--------------------------------------------------------------------------------
113115-- | Obtain categories from a page.
src/Hakyll/Web/Template/Context.hs+3−7
@@ -31,18 +31,13 @@ module Hakyll.Web.Template.Context
3131
3232
3333--------------------------------------------------------------------------------
34−import Control.Applicative (Alternative (..), pure, (<$>))
34+import Control.Applicative (Alternative (..))
3535import Control.Monad (msum)
3636import Data.List (intercalate)
37−import qualified Data.Map as M
38−import Data.Monoid (Monoid (..))
3937import Data.Time.Clock (UTCTime (..))
4038import Data.Time.Format (formatTime)
4139import qualified Data.Time.Format as TF
4240import Data.Time.Locale.Compat (TimeLocale, defaultTimeLocale)
43−import System.FilePath (splitDirectories, takeBaseName)
44−
45−--------------------------------------------------------------------------------
4641import Hakyll.Core.Compiler
4742import Hakyll.Core.Compiler.Internal
4843import Hakyll.Core.Identifier
@@ -51,6 +46,7 @@ import Hakyll.Core.Metadata
5146import Hakyll.Core.Provider
5247import Hakyll.Core.Util.String (needlePrefix, splitAll)
5348import Hakyll.Web.Html
49+import System.FilePath (splitDirectories, takeBaseName)
5450
5551
5652--------------------------------------------------------------------------------
@@ -274,7 +270,7 @@ getItemUTC :: MonadMetadata m
274270 -> m UTCTime -- ^ Parsed UTCTime
275271getItemUTC locale id' = do
276272 metadata <- getMetadata id'
277− let tryField k fmt = M.lookup k metadata >>= parseTime' fmt
273+ let tryField k fmt = lookupString k metadata >>= parseTime' fmt
278274 paths = splitDirectories $ toFilePath id'
279275
280276 maybe empty' return $ msum $
stack.yamlnew file+12
@@ -0,0 +1,12 @@
1+resolver: lts-5.11
2+extra-deps: []
3+extra-package-dbs: []
4+
5+flags:
6+ hakyll:
7+ previewServer: True
8+ watchServer: True
9+ checkExternal: True
10+
11+packages:
12+ - '.'
tests/Hakyll/Core/Provider/Metadata/Tests.hs+14−13
@@ -5,14 +5,13 @@ module Hakyll.Core.Provider.Metadata.Tests
55
66
77--------------------------------------------------------------------------------
8+import qualified Data.HashMap.Strict as HMS
9+import qualified Data.Text as T
10+import qualified Data.Yaml as Yaml
11+import Hakyll.Core.Metadata
12+import Hakyll.Core.Provider.Metadata
813import Test.Framework (Test, testGroup)
914import Test.HUnit (Assertion, (@=?))
10−import Text.Parsec as P
11−import Text.Parsec.String (Parser)
12−
13−
14−--------------------------------------------------------------------------------
15−import Hakyll.Core.Provider.Metadata
1615import TestSuite.Util
1716
1817
@@ -22,9 +21,11 @@ tests = testGroup "Hakyll.Core.Provider.Metadata.Tests" $
2221 fromAssertions "page" [testPage01, testPage02]
2322
2423
24+
2525--------------------------------------------------------------------------------
2626testPage01 :: Assertion
27−testPage01 = testParse page ([("foo", "bar")], "qux\n")
27+testPage01 =
28+ Right (meta [("foo", "bar")], "qux\n") @=? parsePage
2829 "---\n\
2930 \foo: bar\n\
3031 \---\n\
@@ -33,21 +34,21 @@ testPage01 = testParse page ([("foo", "bar")], "qux\n")
3334
3435--------------------------------------------------------------------------------
3536testPage02 :: Assertion
36−testPage02 = testParse page
37− ([("description", descr)], "Hello I am dog\n")
37+testPage02 =
38+ Right (meta [("description", descr)], "Hello I am dog\n") @=?
39+ parsePage
3840 "---\n\
3941 \description: A long description that would look better if it\n\
4042 \ spanned multiple lines and was indented\n\
4143 \---\n\
4244 \Hello I am dog\n"
4345 where
46+ descr :: String
4447 descr =
4548 "A long description that would look better if it \
4649 \spanned multiple lines and was indented"
4750
4851
4952--------------------------------------------------------------------------------
50−testParse :: (Eq a, Show a) => Parser a -> a -> String -> Assertion
51−testParse parser expected input = case P.parse parser "<inline>" input of
52− Left err -> error $ show err
53− Right x -> expected @=? x
53+meta :: Yaml.ToJSON a => [(String, a)] -> Metadata
54+meta pairs = HMS.fromList [(T.pack k, Yaml.toJSON v) | (k, v) <- pairs]
tests/Hakyll/Core/Provider/Tests.hs+5−8
@@ -6,14 +6,11 @@ module Hakyll.Core.Provider.Tests
66
77
88--------------------------------------------------------------------------------
9−import qualified Data.Map as M
9+import Hakyll.Core.Metadata
10+import Hakyll.Core.Provider
1011import Test.Framework (Test, testGroup)
1112import Test.Framework.Providers.HUnit (testCase)
1213import Test.HUnit (Assertion, assert, (@=?))
13−
14−
15−--------------------------------------------------------------------------------
16−import Hakyll.Core.Provider
1714import TestSuite.Util
1815
1916
@@ -32,9 +29,9 @@ case01 = do
3229 assert $ resourceExists provider "example.md"
3330
3431 metadata <- resourceMetadata provider "example.md"
35− Just "An example" @=? M.lookup "title" metadata
36− Just "External data" @=? M.lookup "external" metadata
32+ Just "An example" @=? lookupString "title" metadata
33+ Just "External data" @=? lookupString "external" metadata
3734
3835 doesntExist <- resourceMetadata provider "doesntexist.md"
39− M.empty @=? doesntExist
36+ mempty @=? doesntExist
4037 cleanTestEnv
tests/Hakyll/Core/Routes/Tests.hs+5−7
@@ -6,15 +6,13 @@ module Hakyll.Core.Routes.Tests
66
77
88--------------------------------------------------------------------------------
9−import qualified Data.Map as M
9+import Data.Maybe (fromMaybe)
10+import Hakyll.Core.Identifier
11+import Hakyll.Core.Metadata
12+import Hakyll.Core.Routes
1013import System.FilePath ((</>))
1114import Test.Framework (Test, testGroup)
1215import Test.HUnit (Assertion, (@=?))
13−
14−
15−--------------------------------------------------------------------------------
16−import Hakyll.Core.Identifier
17−import Hakyll.Core.Routes
1816import TestSuite.Util
1917
2018
@@ -37,7 +35,7 @@ tests = testGroup "Hakyll.Core.Routes.Tests" $ fromAssertions "runRoutes"
3735 "tags/rss/bar"
3836
3937 , testRoutes "food/example.md" (metadataRoute $ \md -> customRoute $ \id' ->
40− M.findWithDefault "?" "subblog" md </> toFilePath id')
38+ fromMaybe "?" (lookupString "subblog" md) </> toFilePath id')
4139 "example.md"
4240 ]
4341
tests/Hakyll/Core/Rules/Tests.hs+5−8
@@ -8,22 +8,19 @@ module Hakyll.Core.Rules.Tests
88--------------------------------------------------------------------------------
99import Data.IORef (IORef, newIORef, readIORef,
1010 writeIORef)
11−import qualified Data.Map as M
1211import qualified Data.Set as S
13−import System.FilePath ((</>))
14−import Test.Framework (Test, testGroup)
15−import Test.HUnit (Assertion, assert, (@=?))
16−
17−
18−--------------------------------------------------------------------------------
1912import Hakyll.Core.Compiler
2013import Hakyll.Core.File
2114import Hakyll.Core.Identifier
2215import Hakyll.Core.Identifier.Pattern
16+import Hakyll.Core.Metadata
2317import Hakyll.Core.Routes
2418import Hakyll.Core.Rules
2519import Hakyll.Core.Rules.Internal
2620import Hakyll.Web.Pandoc
21+import System.FilePath ((</>))
22+import Test.Framework (Test, testGroup)
23+import Test.HUnit (Assertion, assert, (@=?))
2724import TestSuite.Util
2825
2926
@@ -89,7 +86,7 @@ rules01 ioref = do
8986 compile getResourceString
9087
9188 version "metadataMatch" $
92− matchMetadata "*.md" (\md -> M.lookup "subblog" md == Just "food") $ do
89+ matchMetadata "*.md" (\md -> lookupString "subblog" md == Just "food") $ do
9390 route $ customRoute $ \id' -> "food" </> toFilePath id'
9491 compile getResourceString
9592