Initial YAML support

See #225
This commit is contained in:
Jasper Van der Jeugt 2016-04-06 14:26:46 +02:00
parent 3f3e09672d
commit e81468e0f6
17 changed files with 287 additions and 195 deletions

3
.gitignore vendored
View file

@ -1,4 +1,4 @@
# Ignore swap files and cabal output. # Ignore swap files and stack/cabal output.
*.hi *.hi
*.o *.o
*.swo *.swo
@ -10,6 +10,7 @@ dist
tags tags
cabal.sandbox.config cabal.sandbox.config
.cabal-sandbox/ .cabal-sandbox/
.stack-work
# Ignore test builds. # Ignore test builds.
tests/Main tests/Main

View file

@ -121,6 +121,8 @@ Library
Hakyll.Web.Template.List Hakyll.Web.Template.List
Other-Modules: Other-Modules:
Data.List.Extended
Data.Yaml.Extended
Hakyll.Check Hakyll.Check
Hakyll.Commands Hakyll.Commands
Hakyll.Core.Compiler.Internal Hakyll.Core.Compiler.Internal
@ -140,33 +142,36 @@ Library
Paths_hakyll Paths_hakyll
Build-Depends: Build-Depends:
base >= 4 && < 5, base >= 4 && < 5,
binary >= 0.5 && < 0.8, binary >= 0.5 && < 0.8,
blaze-html >= 0.5 && < 0.9, blaze-html >= 0.5 && < 0.9,
blaze-markup >= 0.5.1 && < 0.8, blaze-markup >= 0.5.1 && < 0.8,
bytestring >= 0.9 && < 0.11, bytestring >= 0.9 && < 0.11,
cmdargs >= 0.10 && < 0.11, cmdargs >= 0.10 && < 0.11,
containers >= 0.3 && < 0.6, containers >= 0.3 && < 0.6,
cryptohash >= 0.7 && < 0.12, cryptohash >= 0.7 && < 0.12,
data-default >= 0.4 && < 0.6, data-default >= 0.4 && < 0.6,
deepseq >= 1.3 && < 1.5, deepseq >= 1.3 && < 1.5,
directory >= 1.0 && < 1.3, directory >= 1.0 && < 1.3,
filepath >= 1.0 && < 1.5, filepath >= 1.0 && < 1.5,
lrucache >= 1.1.1 && < 1.3, lrucache >= 1.1.1 && < 1.3,
mtl >= 1 && < 2.3, mtl >= 1 && < 2.3,
network >= 2.6 && < 2.7, network >= 2.6 && < 2.7,
network-uri >= 2.6 && < 2.7, network-uri >= 2.6 && < 2.7,
pandoc >= 1.14 && < 1.18, pandoc >= 1.14 && < 1.18,
pandoc-citeproc >= 0.4 && < 0.10, pandoc-citeproc >= 0.4 && < 0.10,
parsec >= 3.0 && < 3.2, parsec >= 3.0 && < 3.2,
process >= 1.0 && < 1.3, process >= 1.0 && < 1.3,
random >= 1.0 && < 1.2, random >= 1.0 && < 1.2,
regex-base >= 0.93 && < 0.94, regex-base >= 0.93 && < 0.94,
regex-tdfa >= 1.1 && < 1.3, regex-tdfa >= 1.1 && < 1.3,
tagsoup >= 0.13.1 && < 0.14, tagsoup >= 0.13.1 && < 0.14,
text >= 0.11 && < 1.3, text >= 0.11 && < 1.3,
time >= 1.4 && < 1.6, time >= 1.4 && < 1.6,
time-locale-compat >= 0.1 && < 0.2 time-locale-compat >= 0.1 && < 0.2,
unordered-containers >= 0.2 && < 0.3,
vector >= 0.11 && < 0.12,
yaml >= 0.8 && < 0.9
If flag(previewServer) If flag(previewServer)
Build-depends: Build-depends:
@ -226,33 +231,36 @@ Test-suite hakyll-tests
test-framework-hunit >= 0.3 && < 0.4, test-framework-hunit >= 0.3 && < 0.4,
test-framework-quickcheck2 >= 0.3 && < 0.4, test-framework-quickcheck2 >= 0.3 && < 0.4,
-- Copy pasted from hakyll dependencies: -- Copy pasted from hakyll dependencies:
base >= 4 && < 5, base >= 4 && < 5,
binary >= 0.5 && < 0.8, binary >= 0.5 && < 0.8,
blaze-html >= 0.5 && < 0.9, blaze-html >= 0.5 && < 0.9,
blaze-markup >= 0.5.1 && < 0.8, blaze-markup >= 0.5.1 && < 0.8,
bytestring >= 0.9 && < 0.11, bytestring >= 0.9 && < 0.11,
cmdargs >= 0.10 && < 0.11, cmdargs >= 0.10 && < 0.11,
containers >= 0.3 && < 0.6, containers >= 0.3 && < 0.6,
cryptohash >= 0.7 && < 0.12, cryptohash >= 0.7 && < 0.12,
data-default >= 0.4 && < 0.6, data-default >= 0.4 && < 0.6,
deepseq >= 1.3 && < 1.5, deepseq >= 1.3 && < 1.5,
directory >= 1.0 && < 1.3, directory >= 1.0 && < 1.3,
filepath >= 1.0 && < 1.5, filepath >= 1.0 && < 1.5,
lrucache >= 1.1.1 && < 1.3, lrucache >= 1.1.1 && < 1.3,
mtl >= 1 && < 2.3, mtl >= 1 && < 2.3,
network >= 2.6 && < 2.7, network >= 2.6 && < 2.7,
network-uri >= 2.6 && < 2.7, network-uri >= 2.6 && < 2.7,
pandoc >= 1.14 && < 1.18, pandoc >= 1.14 && < 1.18,
pandoc-citeproc >= 0.4 && < 0.10, pandoc-citeproc >= 0.4 && < 0.10,
parsec >= 3.0 && < 3.2, parsec >= 3.0 && < 3.2,
process >= 1.0 && < 1.3, process >= 1.0 && < 1.3,
random >= 1.0 && < 1.2, random >= 1.0 && < 1.2,
regex-base >= 0.93 && < 0.94, regex-base >= 0.93 && < 0.94,
regex-tdfa >= 1.1 && < 1.3, regex-tdfa >= 1.1 && < 1.3,
tagsoup >= 0.13.1 && < 0.14, tagsoup >= 0.13.1 && < 0.14,
text >= 0.11 && < 1.3, text >= 0.11 && < 1.3,
time >= 1.5 && < 1.6, time >= 1.4 && < 1.6,
time-locale-compat >= 0.1 && < 0.2 time-locale-compat >= 0.1 && < 0.2,
unordered-containers >= 0.2 && < 0.3,
vector >= 0.11 && < 0.12,
yaml >= 0.8 && < 0.9
If flag(previewServer) If flag(previewServer)
Build-depends: Build-depends:

15
src/Data/List/Extended.hs Normal file
View file

@ -0,0 +1,15 @@
module Data.List.Extended
( module Data.List
, breakWhen
) where
import Data.List
-- | Like 'break', but can act on the entire tail of the list.
breakWhen :: ([a] -> Bool) -> [a] -> ([a], [a])
breakWhen predicate = go []
where
go buf [] = (reverse buf, [])
go buf (x : xs)
| predicate (x : xs) = (reverse buf, x : xs)
| otherwise = go (x : buf) xs

17
src/Data/Yaml/Extended.hs Normal file
View file

@ -0,0 +1,17 @@
module Data.Yaml.Extended
( module Data.Yaml
, toString
, toList
) where
import qualified Data.Text as T
import qualified Data.Vector as V
import Data.Yaml
toString :: Value -> Maybe String
toString (String t) = Just (T.unpack t)
toString _ = Nothing
toList :: Value -> Maybe [Value]
toList (Array a) = Just (V.toList a)
toList _ = Nothing

View file

@ -1,28 +1,46 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
module Hakyll.Core.Metadata module Hakyll.Core.Metadata
( Metadata ( Metadata
, lookupString
, lookupStringList
, MonadMetadata (..) , MonadMetadata (..)
, getMetadataField , getMetadataField
, getMetadataField' , getMetadataField'
, makePatternDependency , makePatternDependency
, BinaryMetadata (..)
) where ) where
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Control.Arrow (second)
import Control.Monad (forM) import Control.Monad (forM)
import Data.Map (Map) import Data.Binary (Binary (..), getWord8,
import qualified Data.Map as M putWord8, Get)
import qualified Data.HashMap.Strict as HMS
import qualified Data.Set as S import qualified Data.Set as S
import qualified Data.Text as T
import qualified Data.Vector as V
-------------------------------------------------------------------------------- import qualified Data.Yaml.Extended as Yaml
import Hakyll.Core.Dependencies import Hakyll.Core.Dependencies
import Hakyll.Core.Identifier import Hakyll.Core.Identifier
import Hakyll.Core.Identifier.Pattern import Hakyll.Core.Identifier.Pattern
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
type Metadata = Map String String type Metadata = Yaml.Object
--------------------------------------------------------------------------------
lookupString :: String -> Metadata -> Maybe String
lookupString key meta = HMS.lookup (T.pack key) meta >>= Yaml.toString
--------------------------------------------------------------------------------
lookupStringList :: String -> Metadata -> Maybe [String]
lookupStringList key meta =
HMS.lookup (T.pack key) meta >>= Yaml.toList >>= mapM Yaml.toString
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -42,7 +60,7 @@ class Monad m => MonadMetadata m where
getMetadataField :: MonadMetadata m => Identifier -> String -> m (Maybe String) getMetadataField :: MonadMetadata m => Identifier -> String -> m (Maybe String)
getMetadataField identifier key = do getMetadataField identifier key = do
metadata <- getMetadata identifier metadata <- getMetadata identifier
return $ M.lookup key metadata return $ lookupString key metadata
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -62,3 +80,59 @@ makePatternDependency :: MonadMetadata m => Pattern -> m Dependency
makePatternDependency pattern = do makePatternDependency pattern = do
matches' <- getMatches pattern matches' <- getMatches pattern
return $ PatternDependency pattern (S.fromList matches') return $ PatternDependency pattern (S.fromList matches')
--------------------------------------------------------------------------------
-- | Newtype wrapper for serialization.
newtype BinaryMetadata = BinaryMetadata
{unBinaryMetadata :: Metadata}
instance Binary BinaryMetadata where
put (BinaryMetadata obj) = put (BinaryYaml $ Yaml.Object obj)
get = do
BinaryYaml (Yaml.Object obj) <- get
return $ BinaryMetadata obj
--------------------------------------------------------------------------------
newtype BinaryYaml = BinaryYaml {unBinaryYaml :: Yaml.Value}
--------------------------------------------------------------------------------
instance Binary BinaryYaml where
put (BinaryYaml yaml) = case yaml of
Yaml.Object obj -> do
putWord8 0
let list :: [(T.Text, BinaryYaml)]
list = map (second BinaryYaml) $ HMS.toList obj
put list
Yaml.Array arr -> do
putWord8 1
let list = map BinaryYaml (V.toList arr) :: [BinaryYaml]
put list
Yaml.String s -> putWord8 2 >> put s
Yaml.Number n -> putWord8 3 >> put n
Yaml.Bool b -> putWord8 4 >> put b
Yaml.Null -> putWord8 5
get = do
tag <- getWord8
case tag of
0 -> do
list <- get :: Get [(T.Text, BinaryYaml)]
return $ BinaryYaml $ Yaml.Object $
HMS.fromList $ map (second unBinaryYaml) list
1 -> do
list <- get :: Get [BinaryYaml]
return $ BinaryYaml $
Yaml.Array $ V.fromList $ map unBinaryYaml list
2 -> BinaryYaml . Yaml.String <$> get
3 -> BinaryYaml . Yaml.Number <$> get
4 -> BinaryYaml . Yaml.Bool <$> get
5 -> return $ BinaryYaml Yaml.Null
_ -> fail "Data.Binary.get: Invalid Binary Metadata"

View file

@ -1,33 +1,32 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- | Internal module to parse metadata -- | Internal module to parse metadata
{-# LANGUAGE BangPatterns #-}
module Hakyll.Core.Provider.Metadata module Hakyll.Core.Provider.Metadata
( loadMetadata ( loadMetadata
, metadata , parsePage
, page
-- This parser can be reused in some places
, metadataKey
) where ) where
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Control.Applicative import Control.Applicative
import Control.Arrow (second) import Control.Arrow (second)
import Control.Monad (guard)
import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Char8 as BC
import qualified Data.HashMap.Strict as HMS
import Data.List (intercalate) import Data.List (intercalate)
import Data.List.Extended (breakWhen)
import qualified Data.Map as M import qualified Data.Map as M
import System.IO as IO import Data.Maybe (fromMaybe)
import Text.Parsec ((<?>)) import Data.Monoid ((<>))
import qualified Text.Parsec as P import qualified Data.Text as T
import Text.Parsec.String (Parser) import qualified Data.Text.Encoding as T
import qualified Data.Yaml as Yaml
--------------------------------------------------------------------------------
import Hakyll.Core.Identifier import Hakyll.Core.Identifier
import Hakyll.Core.Metadata import Hakyll.Core.Metadata
import Hakyll.Core.Provider.Internal import Hakyll.Core.Provider.Internal
import Hakyll.Core.Util.Parser import Hakyll.Core.Util.Parser
import Hakyll.Core.Util.String import Hakyll.Core.Util.String
import System.IO as IO
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -36,13 +35,13 @@ loadMetadata p identifier = do
hasHeader <- probablyHasMetadataHeader fp hasHeader <- probablyHasMetadataHeader fp
(md, body) <- if hasHeader (md, body) <- if hasHeader
then second Just <$> loadMetadataHeader fp then second Just <$> loadMetadataHeader fp
else return (M.empty, Nothing) else return (mempty, Nothing)
emd <- case mi of emd <- case mi of
Nothing -> return M.empty Nothing -> return mempty
Just mi' -> loadMetadataFile $ resourceFilePath p mi' Just mi' -> loadMetadataFile $ resourceFilePath p mi'
return (M.union md emd, body) return (md <> emd, body)
where where
normal = setVersion Nothing identifier normal = setVersion Nothing identifier
fp = resourceFilePath p identifier fp = resourceFilePath p identifier
@ -52,19 +51,15 @@ loadMetadata p identifier = do
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
loadMetadataHeader :: FilePath -> IO (Metadata, String) loadMetadataHeader :: FilePath -> IO (Metadata, String)
loadMetadataHeader fp = do loadMetadataHeader fp = do
contents <- readFile fp fileContent <- readFile fp
case P.parse page fp contents of either fail return (parsePage fileContent)
Left err -> error (show err)
Right (md, b) -> return (M.fromList md, b)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
loadMetadataFile :: FilePath -> IO Metadata loadMetadataFile :: FilePath -> IO Metadata
loadMetadataFile fp = do loadMetadataFile fp = do
contents <- readFile fp errOrMeta <- Yaml.decodeFileEither fp
case P.parse metadata fp contents of either (fail . show) return errOrMeta
Left err -> error (show err)
Right md -> return $ M.fromList md
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -83,53 +78,41 @@ probablyHasMetadataHeader fp = do
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- | Space or tab, no newline -- | Parse the page metadata and body.
inlineSpace :: Parser Char splitMetadata :: String -> (Maybe String, String)
inlineSpace = P.oneOf ['\t', ' '] <?> "space" splitMetadata str0 = fromMaybe (Nothing, str0) $ do
guard $ leading >= 3
let !(!meta, !content0) = breakWhen isTrailing (drop leading str0)
-------------------------------------------------------------------------------- guard $ not $ null content0
-- | Parse Windows newlines as well (i.e. "\n" or "\r\n") let !content1 = drop (leading + 1) content0
newline :: Parser String !content2 = dropWhile isNewline $ dropWhile isInlineSpace content1
newline = P.string "\n" <|> P.string "\r\n" return (Just meta, content2)
--------------------------------------------------------------------------------
-- | Parse a single metadata field
metadataField :: Parser (String, String)
metadataField = do
key <- metadataKey
_ <- P.char ':'
P.skipMany1 inlineSpace <?> "space followed by metadata for: " ++ key
value <- P.manyTill P.anyChar newline
trailing' <- P.many trailing
return (key, trim $ intercalate " " $ value : trailing')
where where
trailing = P.many1 inlineSpace *> P.manyTill P.anyChar newline -- Parse the leading "---"
!leading = length $ takeWhile (== '-') str0
-- Predicate to recognize the trailing "---" or "..."
isTrailing [] = False
isTrailing (x : xs) =
isNewline x && length (takeWhile isDash xs) == leading
-- Characters
isNewline c = c == '\n' || c == '\r'
isDash c = c == '-' || c == '.'
isInlineSpace c = c == '\t' || c == ' '
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- | Parse a metadata block parseMetadata :: String -> Either String Metadata
metadata :: Parser [(String, String)] parseMetadata = Yaml.decodeEither . T.encodeUtf8 . T.pack
metadata = P.many metadataField
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- | Parse a metadata block, including delimiters and trailing newlines parsePage :: String -> Either String (Metadata, String)
metadataBlock :: Parser [(String, String)] parsePage fileContent = case mbMetaBlock of
metadataBlock = do Nothing -> return (mempty, content)
open <- P.many1 (P.char '-') <* P.many inlineSpace <* newline Just metaBlock -> case parseMetadata metaBlock of
metadata' <- metadata Left err -> Left err
_ <- P.choice $ map (P.string . replicate (length open)) ['-', '.'] Right meta -> return (meta, content)
P.skipMany inlineSpace where
P.skipMany1 newline !(!mbMetaBlock, !content) = splitMetadata fileContent
return metadata'
--------------------------------------------------------------------------------
-- | Parse a page consisting of a metadata header and a body
page :: Parser ([(String, String)], String)
page = do
metadata' <- P.option [] metadataBlock
body <- P.many P.anyChar
return (metadata', body)

View file

@ -8,9 +8,6 @@ module Hakyll.Core.Provider.MetadataCache
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Control.Monad (unless) import Control.Monad (unless)
import qualified Data.Map as M
--------------------------------------------------------------------------------
import Hakyll.Core.Identifier import Hakyll.Core.Identifier
import Hakyll.Core.Metadata import Hakyll.Core.Metadata
import Hakyll.Core.Provider.Internal import Hakyll.Core.Provider.Internal
@ -21,11 +18,11 @@ import qualified Hakyll.Core.Store as Store
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
resourceMetadata :: Provider -> Identifier -> IO Metadata resourceMetadata :: Provider -> Identifier -> IO Metadata
resourceMetadata p r resourceMetadata p r
| not (resourceExists p r) = return M.empty | not (resourceExists p r) = return mempty
| otherwise = do | otherwise = do
-- TODO keep time in md cache -- TODO keep time in md cache
load p r load p r
Store.Found md <- Store.get (providerStore p) Store.Found (BinaryMetadata md) <- Store.get (providerStore p)
[name, toFilePath r, "metadata"] [name, toFilePath r, "metadata"]
return md return md
@ -52,7 +49,7 @@ load p r = do
mmof <- Store.isMember store mdk mmof <- Store.isMember store mdk
unless mmof $ do unless mmof $ do
(md, body) <- loadMetadata p r (md, body) <- loadMetadata p r
Store.set store mdk md Store.set store mdk (BinaryMetadata md)
Store.set store bk body Store.set store bk body
where where
store = providerStore p store = providerStore p

View file

@ -42,7 +42,6 @@ module Hakyll.Core.Routes
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Data.Monoid (Monoid, mappend, mempty)
import System.FilePath (replaceExtension) import System.FilePath (replaceExtension)

View file

@ -5,7 +5,6 @@ module Hakyll.Core.Runtime
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Control.Applicative ((<$>))
import Control.Monad (unless) import Control.Monad (unless)
import Control.Monad.Error (ErrorT, runErrorT, throwError) import Control.Monad.Error (ErrorT, runErrorT, throwError)
import Control.Monad.Reader (ask) import Control.Monad.Reader (ask)
@ -15,7 +14,6 @@ import Control.Monad.Trans (liftIO)
import Data.List (intercalate) import Data.List (intercalate)
import Data.Map (Map) import Data.Map (Map)
import qualified Data.Map as M import qualified Data.Map as M
import Data.Monoid (mempty)
import Data.Set (Set) import Data.Set (Set)
import qualified Data.Set as S import qualified Data.Set as S
import System.Exit (ExitCode (..)) import System.Exit (ExitCode (..))

View file

@ -23,22 +23,19 @@ module Hakyll.Web.Pandoc.Biblio
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Control.Applicative ((<$>)) import Control.Monad (liftM, replicateM)
import Control.Monad (replicateM, liftM) import Data.Binary (Binary (..))
import Data.Binary (Binary (..)) import Data.Default (def)
import Data.Default (def) import Data.Typeable (Typeable)
import Data.Typeable (Typeable)
import qualified Text.CSL as CSL
import Text.CSL.Pandoc (processCites)
import Text.Pandoc (Pandoc, ReaderOptions (..))
--------------------------------------------------------------------------------
import Hakyll.Core.Compiler import Hakyll.Core.Compiler
import Hakyll.Core.Identifier import Hakyll.Core.Identifier
import Hakyll.Core.Item import Hakyll.Core.Item
import Hakyll.Core.Writable import Hakyll.Core.Writable
import Hakyll.Web.Pandoc import Hakyll.Web.Pandoc
import Hakyll.Web.Pandoc.Binary () import Hakyll.Web.Pandoc.Binary ()
import qualified Text.CSL as CSL
import Text.CSL.Pandoc (processCites)
import Text.Pandoc (Pandoc, ReaderOptions (..))
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View file

@ -63,13 +63,12 @@ module Hakyll.Web.Tags
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Control.Arrow ((&&&)) import Control.Arrow ((&&&))
import Control.Monad (foldM, forM, forM_) import Control.Monad (foldM, forM, forM_, mplus)
import Data.Char (toLower) import Data.Char (toLower)
import Data.List (intercalate, intersperse, import Data.List (intercalate, intersperse,
sortBy) sortBy)
import qualified Data.Map as M import qualified Data.Map as M
import Data.Maybe (catMaybes, fromMaybe) import Data.Maybe (catMaybes, fromMaybe)
import Data.Monoid (mconcat)
import Data.Ord (comparing) import Data.Ord (comparing)
import qualified Data.Set as S import qualified Data.Set as S
import System.FilePath (takeBaseName, takeDirectory) import System.FilePath (takeBaseName, takeDirectory)
@ -88,8 +87,8 @@ import Hakyll.Core.Item
import Hakyll.Core.Metadata import Hakyll.Core.Metadata
import Hakyll.Core.Rules import Hakyll.Core.Rules
import Hakyll.Core.Util.String import Hakyll.Core.Util.String
import Hakyll.Web.Template.Context
import Hakyll.Web.Html import Hakyll.Web.Html
import Hakyll.Web.Template.Context
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -103,11 +102,14 @@ data Tags = Tags
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- | Obtain tags from a page in the default way: parse them from the @tags@ -- | Obtain tags from a page in the default way: parse them from the @tags@
-- metadata field. -- metadata field. This can either be a list or a comma-separated string.
getTags :: MonadMetadata m => Identifier -> m [String] getTags :: MonadMetadata m => Identifier -> m [String]
getTags identifier = do getTags identifier = do
metadata <- getMetadata identifier metadata <- getMetadata identifier
return $ maybe [] (map trim . splitAll ",") $ M.lookup "tags" metadata return $ fromMaybe [] $
(lookupStringList "tags" metadata) `mplus`
(map trim . splitAll "," <$> lookupString "tags" metadata)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- | Obtain categories from a page. -- | Obtain categories from a page.

View file

@ -31,18 +31,13 @@ module Hakyll.Web.Template.Context
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Control.Applicative (Alternative (..), pure, (<$>)) import Control.Applicative (Alternative (..))
import Control.Monad (msum) import Control.Monad (msum)
import Data.List (intercalate) import Data.List (intercalate)
import qualified Data.Map as M
import Data.Monoid (Monoid (..))
import Data.Time.Clock (UTCTime (..)) import Data.Time.Clock (UTCTime (..))
import Data.Time.Format (formatTime) import Data.Time.Format (formatTime)
import qualified Data.Time.Format as TF import qualified Data.Time.Format as TF
import Data.Time.Locale.Compat (TimeLocale, defaultTimeLocale) import Data.Time.Locale.Compat (TimeLocale, defaultTimeLocale)
import System.FilePath (splitDirectories, takeBaseName)
--------------------------------------------------------------------------------
import Hakyll.Core.Compiler import Hakyll.Core.Compiler
import Hakyll.Core.Compiler.Internal import Hakyll.Core.Compiler.Internal
import Hakyll.Core.Identifier import Hakyll.Core.Identifier
@ -51,6 +46,7 @@ import Hakyll.Core.Metadata
import Hakyll.Core.Provider import Hakyll.Core.Provider
import Hakyll.Core.Util.String (needlePrefix, splitAll) import Hakyll.Core.Util.String (needlePrefix, splitAll)
import Hakyll.Web.Html import Hakyll.Web.Html
import System.FilePath (splitDirectories, takeBaseName)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -274,7 +270,7 @@ getItemUTC :: MonadMetadata m
-> m UTCTime -- ^ Parsed UTCTime -> m UTCTime -- ^ Parsed UTCTime
getItemUTC locale id' = do getItemUTC locale id' = do
metadata <- getMetadata id' metadata <- getMetadata id'
let tryField k fmt = M.lookup k metadata >>= parseTime' fmt let tryField k fmt = lookupString k metadata >>= parseTime' fmt
paths = splitDirectories $ toFilePath id' paths = splitDirectories $ toFilePath id'
maybe empty' return $ msum $ maybe empty' return $ msum $

12
stack.yaml Normal file
View file

@ -0,0 +1,12 @@
resolver: lts-5.11
extra-deps: []
extra-package-dbs: []
flags:
hakyll:
previewServer: True
watchServer: True
checkExternal: True
packages:
- '.'

View file

@ -5,14 +5,13 @@ module Hakyll.Core.Provider.Metadata.Tests
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import qualified Data.HashMap.Strict as HMS
import qualified Data.Text as T
import qualified Data.Yaml as Yaml
import Hakyll.Core.Metadata
import Hakyll.Core.Provider.Metadata
import Test.Framework (Test, testGroup) import Test.Framework (Test, testGroup)
import Test.HUnit (Assertion, (@=?)) import Test.HUnit (Assertion, (@=?))
import Text.Parsec as P
import Text.Parsec.String (Parser)
--------------------------------------------------------------------------------
import Hakyll.Core.Provider.Metadata
import TestSuite.Util import TestSuite.Util
@ -22,9 +21,11 @@ tests = testGroup "Hakyll.Core.Provider.Metadata.Tests" $
fromAssertions "page" [testPage01, testPage02] fromAssertions "page" [testPage01, testPage02]
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
testPage01 :: Assertion testPage01 :: Assertion
testPage01 = testParse page ([("foo", "bar")], "qux\n") testPage01 =
Right (meta [("foo", "bar")], "qux\n") @=? parsePage
"---\n\ "---\n\
\foo: bar\n\ \foo: bar\n\
\---\n\ \---\n\
@ -33,21 +34,21 @@ testPage01 = testParse page ([("foo", "bar")], "qux\n")
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
testPage02 :: Assertion testPage02 :: Assertion
testPage02 = testParse page testPage02 =
([("description", descr)], "Hello I am dog\n") Right (meta [("description", descr)], "Hello I am dog\n") @=?
parsePage
"---\n\ "---\n\
\description: A long description that would look better if it\n\ \description: A long description that would look better if it\n\
\ spanned multiple lines and was indented\n\ \ spanned multiple lines and was indented\n\
\---\n\ \---\n\
\Hello I am dog\n" \Hello I am dog\n"
where where
descr :: String
descr = descr =
"A long description that would look better if it \ "A long description that would look better if it \
\spanned multiple lines and was indented" \spanned multiple lines and was indented"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
testParse :: (Eq a, Show a) => Parser a -> a -> String -> Assertion meta :: Yaml.ToJSON a => [(String, a)] -> Metadata
testParse parser expected input = case P.parse parser "<inline>" input of meta pairs = HMS.fromList [(T.pack k, Yaml.toJSON v) | (k, v) <- pairs]
Left err -> error $ show err
Right x -> expected @=? x

View file

@ -6,14 +6,11 @@ module Hakyll.Core.Provider.Tests
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import qualified Data.Map as M import Hakyll.Core.Metadata
import Hakyll.Core.Provider
import Test.Framework (Test, testGroup) import Test.Framework (Test, testGroup)
import Test.Framework.Providers.HUnit (testCase) import Test.Framework.Providers.HUnit (testCase)
import Test.HUnit (Assertion, assert, (@=?)) import Test.HUnit (Assertion, assert, (@=?))
--------------------------------------------------------------------------------
import Hakyll.Core.Provider
import TestSuite.Util import TestSuite.Util
@ -32,9 +29,9 @@ case01 = do
assert $ resourceExists provider "example.md" assert $ resourceExists provider "example.md"
metadata <- resourceMetadata provider "example.md" metadata <- resourceMetadata provider "example.md"
Just "An example" @=? M.lookup "title" metadata Just "An example" @=? lookupString "title" metadata
Just "External data" @=? M.lookup "external" metadata Just "External data" @=? lookupString "external" metadata
doesntExist <- resourceMetadata provider "doesntexist.md" doesntExist <- resourceMetadata provider "doesntexist.md"
M.empty @=? doesntExist mempty @=? doesntExist
cleanTestEnv cleanTestEnv

View file

@ -6,15 +6,13 @@ module Hakyll.Core.Routes.Tests
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import qualified Data.Map as M import Data.Maybe (fromMaybe)
import Hakyll.Core.Identifier
import Hakyll.Core.Metadata
import Hakyll.Core.Routes
import System.FilePath ((</>)) import System.FilePath ((</>))
import Test.Framework (Test, testGroup) import Test.Framework (Test, testGroup)
import Test.HUnit (Assertion, (@=?)) import Test.HUnit (Assertion, (@=?))
--------------------------------------------------------------------------------
import Hakyll.Core.Identifier
import Hakyll.Core.Routes
import TestSuite.Util import TestSuite.Util
@ -37,7 +35,7 @@ tests = testGroup "Hakyll.Core.Routes.Tests" $ fromAssertions "runRoutes"
"tags/rss/bar" "tags/rss/bar"
, testRoutes "food/example.md" (metadataRoute $ \md -> customRoute $ \id' -> , testRoutes "food/example.md" (metadataRoute $ \md -> customRoute $ \id' ->
M.findWithDefault "?" "subblog" md </> toFilePath id') fromMaybe "?" (lookupString "subblog" md) </> toFilePath id')
"example.md" "example.md"
] ]

View file

@ -8,22 +8,19 @@ module Hakyll.Core.Rules.Tests
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Data.IORef (IORef, newIORef, readIORef, import Data.IORef (IORef, newIORef, readIORef,
writeIORef) writeIORef)
import qualified Data.Map as M
import qualified Data.Set as S import qualified Data.Set as S
import System.FilePath ((</>))
import Test.Framework (Test, testGroup)
import Test.HUnit (Assertion, assert, (@=?))
--------------------------------------------------------------------------------
import Hakyll.Core.Compiler import Hakyll.Core.Compiler
import Hakyll.Core.File import Hakyll.Core.File
import Hakyll.Core.Identifier import Hakyll.Core.Identifier
import Hakyll.Core.Identifier.Pattern import Hakyll.Core.Identifier.Pattern
import Hakyll.Core.Metadata
import Hakyll.Core.Routes import Hakyll.Core.Routes
import Hakyll.Core.Rules import Hakyll.Core.Rules
import Hakyll.Core.Rules.Internal import Hakyll.Core.Rules.Internal
import Hakyll.Web.Pandoc import Hakyll.Web.Pandoc
import System.FilePath ((</>))
import Test.Framework (Test, testGroup)
import Test.HUnit (Assertion, assert, (@=?))
import TestSuite.Util import TestSuite.Util
@ -89,7 +86,7 @@ rules01 ioref = do
compile getResourceString compile getResourceString
version "metadataMatch" $ version "metadataMatch" $
matchMetadata "*.md" (\md -> M.lookup "subblog" md == Just "food") $ do matchMetadata "*.md" (\md -> lookupString "subblog" md == Just "food") $ do
route $ customRoute $ \id' -> "food" </> toFilePath id' route $ customRoute $ \id' -> "food" </> toFilePath id'
compile getResourceString compile getResourceString