on 877cb21 help sign in
125 lines 4.6 kB history raw
1--------------------------------------------------------------------------------
2-- | Internal module to parse metadata
3module Hakyll.Core.Provider.Metadata
4 ( loadMetadata
5 ) where
8--------------------------------------------------------------------------------
9import Control.Applicative
10import Control.Arrow (second)
11import qualified Data.ByteString.Char8 as BC
12import qualified Data.Map as M
13import System.IO as IO
14import Text.Parsec ((<?>))
15import qualified Text.Parsec as P
16import Text.Parsec.String (Parser)
19--------------------------------------------------------------------------------
20import Hakyll.Core.Identifier
21import Hakyll.Core.Metadata
22import Hakyll.Core.Provider.Internal
23import Hakyll.Core.Util.String
26--------------------------------------------------------------------------------
27loadMetadata :: Provider -> Identifier -> IO (Metadata, Maybe String)
28loadMetadata rp identifier = do
29 hasHeader <- probablyHasMetadataHeader fp
30 (md, body) <- if hasHeader
31 then second Just <$> loadMetadataHeader fp
32 else return (M.empty, Nothing)
34 emd <- if resourceExists rp mi then loadMetadataFile mfp else return M.empty
36 return (M.union md emd, body)
37 where
38 fp = toFilePath identifier
39 mi = resourceMetadataResource identifier
40 mfp = toFilePath mi
43--------------------------------------------------------------------------------
44loadMetadataHeader :: FilePath -> IO (Metadata, String)
45loadMetadataHeader fp = do
46 contents <- readFile fp
47 case P.parse page fp contents of
48 Left err -> error (show err)
49 Right (md, b) -> return (M.fromList md, b)
52--------------------------------------------------------------------------------
53loadMetadataFile :: FilePath -> IO Metadata
54loadMetadataFile fp = do
55 contents <- readFile fp
56 case P.parse metadata fp contents of
57 Left err -> error (show err)
58 Right md -> return $ M.fromList md
61--------------------------------------------------------------------------------
62-- | Check if a file "probably" has a metadata header. The main goal of this is
63-- to exclude binary files (which are unlikely to start with "---").
64probablyHasMetadataHeader :: FilePath -> IO Bool
65probablyHasMetadataHeader fp = do
66 handle <- IO.openFile fp IO.ReadMode
67 bs <- BC.hGet handle 1024
68 IO.hClose handle
69 return $ isMetadataHeader bs
70 where
71 isMetadataHeader bs =
72 let pre = BC.takeWhile (\x -> x /= '\n' && x /= '\r') bs
73 in BC.length pre >= 3 && BC.all (== '-') pre
76--------------------------------------------------------------------------------
77-- | Space or tab, no newline
78inlineSpace :: Parser Char
79inlineSpace = P.oneOf ['\t', ' '] <?> "space"
82--------------------------------------------------------------------------------
83-- | Parse Windows newlines as well (i.e. "\n" or "\r\n")
84newline :: Parser String
85newline = P.string "\n" <|> P.string "\r\n"
88--------------------------------------------------------------------------------
89-- | Parse a single metadata field
90metadataField :: Parser (String, String)
91metadataField = do
92 key <- P.manyTill P.alphaNum $ P.char ':'
93 P.skipMany1 inlineSpace <?> "space followed by metadata for: " ++ key
94 value <- P.manyTill P.anyChar newline
95 trailing' <- P.many trailing
96 return (key, trim $ value ++ concat trailing')
97 where
98 trailing = (++) <$> P.many1 inlineSpace <*> P.manyTill P.anyChar newline
101--------------------------------------------------------------------------------
102-- | Parse a metadata block
103metadata :: Parser [(String, String)]
104metadata = P.many metadataField
107--------------------------------------------------------------------------------
108-- | Parse a metadata block, including delimiters and trailing newlines
109metadataBlock :: Parser [(String, String)]
110metadataBlock = do
111 open <- P.many1 (P.char '-') <* P.many inlineSpace <* newline
112 metadata' <- metadata
113 _ <- P.choice $ map (P.string . replicate (length open)) ['-', '.']
114 P.skipMany inlineSpace
115 P.skipMany1 newline
116 return metadata'
119--------------------------------------------------------------------------------
120-- | Parse a page consisting of a metadata header and a body
121page :: Parser ([(String, String)], String)
122page = do
123 metadata' <- P.option [] metadataBlock
124 body <- P.many P.anyChar
125 return (metadata', body)