on master help sign in
Merge branch 'master' into dev-metadata-route
commit61dcb5f454fcbd912b09839021f4c79ca60973fe authorJasper Van der Jeugt <m@jaspervdj.be> dateThu 14 Feb 2013, 10:08 (14 years ago) parent2912fcd02a92d5
32 files changed+309−218
hakyll.cabal+5−4 src/Hakyll/Check.hs+2−1 src/Hakyll/Core/Compiler.hs+1−1 src/Hakyll/Core/Configuration.hs+1−1 src/Hakyll/Core/Provider.hs+22−26 src/Hakyll/Core/Provider/Internal.hs+133−24 src/Hakyll/Core/Provider/Metadata.hs+4−3 src/Hakyll/Core/Provider/MetadataCache.hs+1 src/Hakyll/Core/Provider/Modified.hs−101 src/Hakyll/Core/Rules/Internal.hs+13−7 src/Hakyll/Core/Runtime.hs+4−3 src/Hakyll/Core/Store.hs+8 src/Hakyll/Core/UnixFilter.hs+1−1 src/Hakyll/Core/Util/File.hs+6−5 src/Hakyll/Init.hs+1−1 src/Hakyll/Web/Pandoc/FileType.hs+25−21 src/Hakyll/Web/Tags.hs+5−8 src/Hakyll/Web/Template/Context.hs+1−2 tests/Hakyll/Core/Provider/Tests.hs+1 tests/Hakyll/Core/Rules/Tests.hs+1 tests/Hakyll/Core/Store/Tests.hs+1 tests/Hakyll/Web/Pandoc/FileType/Tests.hs+26 tests/Hakyll/Web/Template/Tests.hs+2 tests/TestSuite.hs+2 web/about.markdown+1 web/examples.markdown+4 web/images/hakyll-system-2.png web/releases.markdown+12 web/tutorials/01-installation.markdown+3−2 web/tutorials/03-rules-routes-compilers.markdown+1−1 web/tutorials/04-compilers.markdown+9−5 web/tutorials/faq.markdown+13−1
hakyll.cabal+5−4
@@ -1,5 +1,5 @@
11Name: hakyll
2−Version: 4.1.2.0
2+Version: 4.1.4.0
33
44Synopsis: A static website compiler library
55Description:
@@ -64,8 +64,10 @@ Data-files:
6464Extra-source-files:
6565 tests/data/example.md
6666 tests/data/example.md.metadata
67+ tests/data/russian.md
6768 tests/data/template.html
6869 tests/data/template.html.out
70+ tests/data/posts/2010-08-26-birthday.md
6971
7072Source-Repository head
7173 Type: git
@@ -93,7 +95,7 @@ Library
9395 directory >= 1.0 && < 1.3,
9496 filepath >= 1.0 && < 1.4,
9597 http-conduit >= 1.8 && < 1.9,
96− http-types >= 0.7 && < 0.8,
98+ http-types >= 0.7 && < 0.9,
9799 lrucache >= 1.1.1 && < 1.2,
98100 mtl >= 1 && < 2.2,
99101 old-locale >= 1.0 && < 1.1,
@@ -149,7 +151,6 @@ Library
149151 Hakyll.Core.Provider.Internal
150152 Hakyll.Core.Provider.Metadata
151153 Hakyll.Core.Provider.MetadataCache
152− Hakyll.Core.Provider.Modified
153154 Hakyll.Core.Rules.Internal
154155 Hakyll.Core.Runtime
155156 Hakyll.Core.Store
@@ -192,7 +193,7 @@ Test-suite hakyll-tests
192193 directory >= 1.0 && < 1.3,
193194 filepath >= 1.0 && < 1.4,
194195 http-conduit >= 1.8 && < 1.9,
195− http-types >= 0.7 && < 0.8,
196+ http-types >= 0.7 && < 0.9,
196197 lrucache >= 1.1.1 && < 1.2,
197198 mtl >= 1 && < 2.2,
198199 old-locale >= 1.0 && < 1.1,
src/Hakyll/Check.hs+2−1
@@ -94,7 +94,8 @@ runChecker checker config verbosity check' = do
9494checkDestination :: Checker ()
9595checkDestination = do
9696 config <- checkerConfig <$> ask
97− files <- liftIO $ getRecursiveContents (destinationDirectory config)
97+ files <- liftIO $
98+ getRecursiveContents (const False) (destinationDirectory config)
9899
99100 let htmls =
100101 [ destinationDirectory config </> file
src/Hakyll/Core/Compiler.hs+1−1
@@ -136,7 +136,7 @@ cached name compiler = do
136136 id' <- compilerUnderlying <$> compilerAsk
137137 store <- compilerStore <$> compilerAsk
138138 provider <- compilerProvider <$> compilerAsk
139− modified <- compilerUnsafeIO $ resourceModified provider id'
139+ let modified = resourceModified provider id'
140140 if modified
141141 then do
142142 x <- compiler
src/Hakyll/Core/Configuration.hs+1−1
@@ -37,7 +37,7 @@ data Configuration = Configuration
3737 --
3838 -- Note that the files in 'destinationDirectory' and 'storeDirectory' will
3939 -- also be ignored. Note that this is the configuration parameter, if you
40− -- want to use the test, you should use 'shouldIgnoreFile'.
40+ -- want to use the test, you should use 'shouldIgnoreFile'.
4141 --
4242 ignoreFile :: FilePath -> Bool
4343 , -- | Here, you can plug in a system command to upload/deploy your site.
src/Hakyll/Core/Provider.hs+22−26
@@ -3,44 +3,40 @@
33-- caching.
44module Hakyll.Core.Provider
55 ( -- * Constructing resource providers
6− Provider
6+ Internal.Provider
77 , newProvider
88
99 -- * Querying resource properties
10− , resourceList
11− , resourceExists
12− , resourceModified
13− , resourceModificationTime
10+ , Internal.resourceList
11+ , Internal.resourceExists
12+ , Internal.resourceModified
13+ , Internal.resourceModificationTime
1414
1515 -- * Access to raw resource content
16− , resourceString
17− , resourceLBS
16+ , Internal.resourceString
17+ , Internal.resourceLBS
1818
1919 -- * Access to metadata and body content
20− , resourceMetadata
21− , resourceBody
20+ , Internal.resourceMetadata
21+ , Internal.resourceBody
2222 ) where
2323
2424
2525--------------------------------------------------------------------------------
26−import Hakyll.Core.Identifier
27−import Hakyll.Core.Metadata
28−import Hakyll.Core.Provider.Internal
26+import qualified Hakyll.Core.Provider.Internal as Internal
2927import qualified Hakyll.Core.Provider.MetadataCache as Internal
30−import Hakyll.Core.Provider.Modified
28+import Hakyll.Core.Store (Store)
3129
3230
3331--------------------------------------------------------------------------------
34−-- | Wrapper to ensure metadata cache is invalidated if necessary
35−resourceMetadata :: Provider -> Identifier -> IO Metadata
36−resourceMetadata rp r = do
37− _ <- resourceModified rp r
38− Internal.resourceMetadata rp r
39−
40−
41−--------------------------------------------------------------------------------
42−-- | Wrapper to ensure metadata cache is invalidated if necessary
43−resourceBody :: Provider -> Identifier -> IO String
44−resourceBody rp r = do
45− _ <- resourceModified rp r
46− Internal.resourceBody rp r
32+-- | Create a resource provider
33+newProvider :: Store -- ^ Store to use
34+ -> (FilePath -> Bool) -- ^ Should we ignore this file?
35+ -> FilePath -- ^ Search directory
36+ -> IO Internal.Provider -- ^ Resulting provider
37+newProvider store ignore directory = do
38+ -- Delete metadata cache where necessary
39+ p <- Internal.newProvider store ignore directory
40+ mapM_ (Internal.resourceInvalidateMetadataCache p) $
41+ filter (Internal.resourceModified p) $ Internal.resourceList p
42+ return p
src/Hakyll/Core/Provider/Internal.hs+133−24
@@ -1,46 +1,103 @@
11--------------------------------------------------------------------------------
2+{-# LANGUAGE CPP #-}
3+{-# LANGUAGE DeriveDataTypeable #-}
4+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
25module Hakyll.Core.Provider.Internal
3− ( Provider (..)
6+ ( ResourceInfo (..)
7+ , Provider (..)
48 , newProvider
59
610 , resourceList
711 , resourceExists
8− , resourceMetadataResource
912
1013 , resourceFilePath
1114 , resourceString
1215 , resourceLBS
16+
17+ , resourceModified
18+ , resourceModificationTime
1319 ) where
1420
1521
1622--------------------------------------------------------------------------------
17−import Control.Applicative ((<$>))
23+import Control.Applicative ((<$>), (<*>))
24+import Control.DeepSeq (NFData (..), deepseq)
25+import Control.Monad (forM)
26+import Data.Binary (Binary (..))
1827import qualified Data.ByteString.Lazy as BL
19−import Data.IORef
2028import Data.Map (Map)
2129import qualified Data.Map as M
30+import Data.Maybe (fromMaybe)
31+import Data.Monoid (mempty)
2232import Data.Set (Set)
2333import qualified Data.Set as S
34+import Data.Time (Day (..), UTCTime (..),
35+ secondsToDiffTime)
36+import Data.Typeable (Typeable)
37+import System.Directory (getModificationTime)
2438import System.FilePath (addExtension, (</>))
2539
2640
41+--------------------------------------------------------------------------------
42+#if !MIN_VERSION_directory(1,2,0)
43+import Data.Time (readTime)
44+import System.Locale (defaultTimeLocale)
45+import System.Time (formatCalendarTime, toCalendarTime)
46+#endif
47+
48+
2749--------------------------------------------------------------------------------
2850import Hakyll.Core.Identifier
29−import Hakyll.Core.Store
51+import Hakyll.Core.Store (Store)
52+import qualified Hakyll.Core.Store as Store
3053import Hakyll.Core.Util.File
3154
3255
56+--------------------------------------------------------------------------------
57+-- | Because UTCTime doesn't have a Binary instance...
58+newtype BinaryTime = BinaryTime {unBinaryTime :: UTCTime}
59+ deriving (Eq, NFData, Ord, Show, Typeable)
60+
61+
62+--------------------------------------------------------------------------------
63+instance Binary BinaryTime where
64+ put (BinaryTime (UTCTime (ModifiedJulianDay d) dt)) =
65+ put d >> put (floor dt :: Integer)
66+
67+ get = fmap BinaryTime $ UTCTime
68+ <$> (ModifiedJulianDay <$> get)
69+ <*> (secondsToDiffTime <$> get)
70+
71+
72+--------------------------------------------------------------------------------
73+data ResourceInfo = ResourceInfo
74+ { resourceInfoModified :: BinaryTime
75+ , resourceInfoMetadata :: Maybe Identifier
76+ } deriving (Show, Typeable)
77+
78+
79+--------------------------------------------------------------------------------
80+instance Binary ResourceInfo where
81+ put (ResourceInfo mtime meta) = put mtime >> put meta
82+ get = ResourceInfo <$> get <*> get
83+
84+
85+--------------------------------------------------------------------------------
86+instance NFData ResourceInfo where
87+ rnf (ResourceInfo mtime meta) = rnf mtime `seq` rnf meta `seq` ()
88+
89+
3390--------------------------------------------------------------------------------
3491-- | Responsible for retrieving and listing resources
3592data Provider = Provider
3693 { -- Top of the provided directory
37− providerDirectory :: FilePath
94+ providerDirectory :: FilePath
3895 , -- | A list of all files found
39− providerSet :: Set Identifier
40− , -- | Cache keeping track of modified files
41− providerModifiedCache :: IORef (Map Identifier Bool)
96+ providerFiles :: Map Identifier ResourceInfo
97+ , -- | A list of the files from the previous run
98+ providerOldFiles :: Map Identifier ResourceInfo
4299 , -- | Underlying persistent store for caching
43− providerStore :: Store
100+ providerStore :: Store
44101 }
45102
46103
@@ -51,30 +108,47 @@ newProvider :: Store -- ^ Store to use
51108 -> FilePath -- ^ Search directory
52109 -> IO Provider -- ^ Resulting provider
53110newProvider store ignore directory = do
54− list <- map fromFilePath . filter (not . ignore) <$>
55− getRecursiveContents directory
56− cache <- newIORef M.empty
57− return $ Provider directory (S.fromList list) cache store
111+ list <- map fromFilePath <$> getRecursiveContents ignore directory
112+ let universe = S.fromList list
113+ files <- fmap (maxmtime . M.fromList) $ forM list $ \identifier -> do
114+ rInfo <- getResourceInfo directory universe identifier
115+ return (identifier, rInfo)
116+
117+ -- Get the old files from the store, and then immediately replace them by
118+ -- the new files.
119+ oldFiles <- fromMaybe mempty . Store.toMaybe <$> Store.get store oldKey
120+ oldFiles `deepseq` Store.set store oldKey files
121+
122+ return $ Provider directory files oldFiles store
123+ where
124+ oldKey = ["Hakyll.Core.Provider.Internal.newProvider", "oldFiles"]
125+
126+ -- Update modified if metadata is modified
127+ maxmtime files = flip M.map files $ \rInfo@(ResourceInfo mtime meta) ->
128+ let metaMod = fmap resourceInfoModified $ meta >>= flip M.lookup files
129+ in rInfo {resourceInfoModified = maybe mtime (max mtime) metaMod}
130+
131+
132+--------------------------------------------------------------------------------
133+getResourceInfo :: FilePath -> Set Identifier -> Identifier -> IO ResourceInfo
134+getResourceInfo directory universe identifier = do
135+ mtime <- fileModificationTime $ directory </> toFilePath identifier
136+ return $ ResourceInfo (BinaryTime mtime) $
137+ if mdRsc `S.member` universe then Just mdRsc else Nothing
138+ where
139+ mdRsc = fromFilePath $ flip addExtension "metadata" $ toFilePath identifier
58140
59141
60142--------------------------------------------------------------------------------
61143resourceList :: Provider -> [Identifier]
62−resourceList = S.toList . providerSet
144+resourceList = M.keys . providerFiles
63145
64146
65147--------------------------------------------------------------------------------
66148-- | Check if a given resource exists
67149resourceExists :: Provider -> Identifier -> Bool
68150resourceExists provider =
69− (`S.member` providerSet provider) . setVersion Nothing
70−
71−
72−--------------------------------------------------------------------------------
73−-- | Each resource may have an associated metadata resource (with a @.metadata@
74−-- filename)
75−resourceMetadataResource :: Identifier -> Identifier
76−resourceMetadataResource =
77− fromFilePath . flip addExtension "metadata" . toFilePath
151+ (`M.member` providerFiles provider) . setVersion Nothing
78152
79153
80154--------------------------------------------------------------------------------
@@ -92,3 +166,38 @@ resourceString p i = readFile $ resourceFilePath p i
92166-- | Get the raw body of a resource of a lazy bytestring
93167resourceLBS :: Provider -> Identifier -> IO BL.ByteString
94168resourceLBS p i = BL.readFile $ resourceFilePath p i
169+
170+
171+--------------------------------------------------------------------------------
172+-- | A resource is modified if it or its metadata has changed
173+resourceModified :: Provider -> Identifier -> Bool
174+resourceModified p r = case (ri, oldRi) of
175+ (Nothing, _) -> False
176+ (Just _, Nothing) -> True
177+ (Just n, Just o) -> resourceInfoModified n > resourceInfoModified o
178+ where
179+ normal = setVersion Nothing r
180+ ri = M.lookup normal (providerFiles p)
181+ oldRi = M.lookup normal (providerOldFiles p)
182+
183+
184+--------------------------------------------------------------------------------
185+resourceModificationTime :: Provider -> Identifier -> UTCTime
186+resourceModificationTime p i =
187+ case M.lookup (setVersion Nothing i) (providerFiles p) of
188+ Just ri -> unBinaryTime $ resourceInfoModified ri
189+ Nothing -> error $
190+ "Hakyll.Core.Provider.Internal.resourceModificationTime: " ++
191+ "resource " ++ show i ++ " does not exist"
192+
193+
194+--------------------------------------------------------------------------------
195+fileModificationTime :: FilePath -> IO UTCTime
196+fileModificationTime fp = do
197+#if MIN_VERSION_directory(1,2,0)
198+ getModificationTime fp
199+#else
200+ ct <- toCalendarTime =<< getModificationTime fp
201+ let str = formatCalendarTime defaultTimeLocale "%s" ct
202+ return $ readTime defaultTimeLocale "%s" str
203+#endif
src/Hakyll/Core/Provider/Metadata.hs+4−3
@@ -31,13 +31,14 @@ loadMetadata p identifier = do
3131 then second Just <$> loadMetadataHeader fp
3232 else return (M.empty, Nothing)
3333
34− emd <- if resourceExists p mi then loadMetadataFile mfp else return M.empty
34+ emd <- case mi of
35+ Nothing -> return M.empty
36+ Just mi' -> loadMetadataFile $ resourceFilePath p mi'
3537
3638 return (M.union md emd, body)
3739 where
3840 fp = resourceFilePath p identifier
39− mi = resourceMetadataResource identifier
40− mfp = resourceFilePath p mi
41+ mi = M.lookup identifier (providerFiles p) >>= resourceInfoMetadata
4142
4243
4344--------------------------------------------------------------------------------
src/Hakyll/Core/Provider/MetadataCache.hs+1
@@ -23,6 +23,7 @@ resourceMetadata :: Provider -> Identifier -> IO Metadata
2323resourceMetadata p r
2424 | not (resourceExists p r) = return M.empty
2525 | otherwise = do
26+ -- TODO keep time in md cache
2627 load p r
2728 Store.Found md <- Store.get (providerStore p)
2829 [name, toFilePath r, "metadata"]
src/Hakyll/Core/Provider/Modified.hsdeleted−101
@@ -1,101 +0,0 @@
1−--------------------------------------------------------------------------------
2−{-# LANGUAGE CPP #-}
3−module Hakyll.Core.Provider.Modified
4− ( resourceModified
5− , resourceModificationTime
6− ) where
7−
8−
9−--------------------------------------------------------------------------------
10−import Control.Applicative ((<$>), (<*>))
11−import Control.Monad (when)
12−import qualified Crypto.Hash.MD5 as MD5
13−import qualified Data.ByteString as B
14−import qualified Data.ByteString.Lazy as BL
15−import Data.IORef
16−import qualified Data.Map as M
17−import Data.Time (UTCTime)
18−import System.Directory (getModificationTime)
19−
20−
21−--------------------------------------------------------------------------------
22−#if !MIN_VERSION_directory(1,2,0)
23−import Data.Time (readTime)
24−import System.Locale (defaultTimeLocale)
25−import System.Time (formatCalendarTime,
26− toCalendarTime)
27−#endif
28−
29−
30−--------------------------------------------------------------------------------
31−import Hakyll.Core.Identifier
32−import Hakyll.Core.Provider.Internal
33−import Hakyll.Core.Provider.MetadataCache
34−import Hakyll.Core.Store (Store)
35−import qualified Hakyll.Core.Store as Store
36−
37−
38−--------------------------------------------------------------------------------
39−-- | A resource is modified if it or its metadata has changed
40−resourceModified :: Provider -> Identifier -> IO Bool
41−resourceModified p r
42− | not exists = return False
43− | otherwise = do
44− cache <- readIORef cacheRef
45− case M.lookup normalized cache of
46− Just m -> return m
47− Nothing -> do
48− -- Check if the actual file was modified, and do a recursive
49− -- call to check if the metadata file was modified
50− m <- (||)
51− <$> fileDigestModified store filePath
52− <*> resourceModified p (resourceMetadataResource r)
53− modifyIORef cacheRef (M.insert normalized m)
54−
55− -- Important! (But ugly)
56− when m $ resourceInvalidateMetadataCache p r
57−
58− return m
59− where
60− normalized = setVersion Nothing r
61− exists = resourceExists p r
62− store = providerStore p
63− cacheRef = providerModifiedCache p
64− filePath = resourceFilePath p r
65−
66−
67−--------------------------------------------------------------------------------
68−-- | Utility: Check if a the digest of a file was modified
69−fileDigestModified :: Store -> FilePath -> IO Bool
70−fileDigestModified store fp = do
71− -- Get the latest seen digest from the store, and calculate the current
72− -- digest for the
73− lastDigest <- Store.get store key
74− newDigest <- fileDigest fp
75− if Store.Found newDigest == lastDigest
76− -- All is fine, not modified
77− then return False
78− -- Resource modified; store new digest
79− else do
80− Store.set store key newDigest
81− return True
82− where
83− key = ["Hakyll.Core.Resource.Provider.fileModified", fp]
84−
85−
86−--------------------------------------------------------------------------------
87−-- | Utility: Retrieve a digest for a given file
88−fileDigest :: FilePath -> IO B.ByteString
89−fileDigest = fmap MD5.hashlazy . BL.readFile
90−
91−
92−--------------------------------------------------------------------------------
93−resourceModificationTime :: Provider -> Identifier -> IO UTCTime
94−resourceModificationTime p i = do
95−#if MIN_VERSION_directory(1,2,0)
96− getModificationTime $ resourceFilePath p i
97−#else
98− ct <- toCalendarTime =<< getModificationTime (resourceFilePath p i)
99− let str = formatCalendarTime defaultTimeLocale "%s" ct
100− return $ readTime defaultTimeLocale "%s" str
101−#endif
src/Hakyll/Core/Rules/Internal.hs+13−7
@@ -16,9 +16,9 @@ import Control.Applicative (Applicative, (<$>))
1616import Control.Monad.Reader (ask)
1717import Control.Monad.RWS (RWST, runRWST)
1818import Control.Monad.Trans (liftIO)
19−import qualified Data.Map as M
2019import Data.Monoid (Monoid, mappend, mempty)
2120import Data.Set (Set)
21+import qualified Data.Set as S
2222
2323
2424--------------------------------------------------------------------------------
@@ -92,7 +92,12 @@ instance MonadMetadata Rules where
9292runRules :: Rules a -> Provider -> IO RuleSet
9393runRules rules provider = do
9494 (_, _, ruleSet) <- runRWST (unRules rules) env emptyRulesState
95− return $ nubCompilers ruleSet
95+ case findDuplicate (map fst $ rulesCompilers ruleSet) of
96+ Nothing -> return ruleSet
97+ Just id' -> error $
98+ "Hakyll.Core.Rules.Internal: two different rules for " ++
99+ show id' ++ " exist, bailing out"
100+
96101 where
97102 env = RulesRead
98103 { rulesProvider = provider
@@ -102,9 +107,10 @@ runRules rules provider = do
102107
103108
104109--------------------------------------------------------------------------------
105−-- | Remove duplicate compilers from the 'RuleSet'. When two compilers match an
106−-- item, we prefer the first one
107−nubCompilers :: RuleSet -> RuleSet
108−nubCompilers set = set {rulesCompilers = nubCompilers' (rulesCompilers set)}
110+findDuplicate :: Ord a => [a] -> Maybe a
111+findDuplicate = go S.empty
109112 where
110− nubCompilers' = M.toList . M.fromListWith (flip const)
113+ go _ [] = Nothing
114+ go s (x : xs)
115+ | x `S.member` s = Just x
116+ | otherwise = go (S.insert x s) xs
src/Hakyll/Core/Runtime.hs+4−3
@@ -6,7 +6,7 @@ module Hakyll.Core.Runtime
66
77--------------------------------------------------------------------------------
88import Control.Applicative ((<$>))
9−import Control.Monad (filterM, unless)
9+import Control.Monad (unless)
1010import Control.Monad.Error (ErrorT, runErrorT, throwError)
1111import Control.Monad.Reader (ask)
1212import Control.Monad.RWS (RWST, runRWST)
@@ -140,8 +140,9 @@ scheduleOutOfDate = do
140140 todo <- runtimeTodo <$> get
141141
142142 let identifiers = M.keys universe
143− modified <- fmap S.fromList $ flip filterM identifiers $
144− liftIO . resourceModified provider
143+ modified = S.fromList $ flip filter identifiers $
144+ resourceModified provider
145+
145146 let (ood, facts', msgs) = outOfDate identifiers modified facts
146147 todo' = M.filterWithKey
147148 (\id' _ -> id' `S.member` ood) universe
src/Hakyll/Core/Store.hs+8
@@ -5,6 +5,7 @@
55module Hakyll.Core.Store
66 ( Store
77 , Result (..)
8+ , toMaybe
89 , new
910 , set
1011 , get
@@ -53,6 +54,13 @@ data Result a
5354 deriving (Show, Eq)
5455
5556
57+--------------------------------------------------------------------------------
58+-- | Convert result to 'Maybe'
59+toMaybe :: Result a -> Maybe a
60+toMaybe (Found x) = Just x
61+toMaybe _ = Nothing
62+
63+
5664--------------------------------------------------------------------------------
5765-- | Initialize the store
5866new :: Bool -- ^ Use in-memory caching
src/Hakyll/Core/UnixFilter.hs+1−1
@@ -57,7 +57,7 @@ unixFilter = unixFilterWith writer reader
5757--
5858-- > match "music.wav" $ do
5959-- > route $ setExtension "ogg"
60−-- > compile $ getResourceLBS >>= withItemBody (unixFilter "oggenc" ["-"])
60+-- > compile $ getResourceLBS >>= withItemBody (unixFilterLBS "oggenc" ["-"])
6161unixFilterLBS :: String -- ^ Program name
6262 -> [String] -- ^ Program args
6363 -> ByteString -- ^ Program input
src/Hakyll/Core/Util/File.hs+6−5
@@ -25,12 +25,13 @@ makeDirectories = createDirectoryIfMissing True . takeDirectory
2525
2626--------------------------------------------------------------------------------
2727-- | Get all contents of a directory.
28−getRecursiveContents :: FilePath -- ^ Directory to search
29− -> IO [FilePath] -- ^ List of files found
30−getRecursiveContents top = go ""
28+getRecursiveContents :: (FilePath -> Bool) -- ^ Ignore this file/directory
29+ -> FilePath -- ^ Directory to search
30+ -> IO [FilePath] -- ^ List of files found
31+getRecursiveContents ignore top = go ""
3132 where
32− isProper = (`notElem` [".", ".."])
33− go dir = do
33+ isProper x = notElem x [".", ".."] && not (ignore x)
34+ go dir = do
3435 dirExists <- doesDirectoryExist (top </> dir)
3536 if not dirExists
3637 then return []
src/Hakyll/Init.hs+1−1
@@ -23,7 +23,7 @@ main = do
2323 progName <- getProgName
2424 args <- getArgs
2525 srcDir <- getDataFileName "example"
26− files <- getRecursiveContents srcDir
26+ files <- getRecursiveContents (const False) srcDir
2727
2828 case args of
2929 [dstDir] -> forM_ files $ \file -> do
src/Hakyll/Web/Pandoc/FileType.hs+25−21
@@ -8,7 +8,7 @@ module Hakyll.Web.Pandoc.FileType
88
99
1010--------------------------------------------------------------------------------
11−import System.FilePath (takeExtension)
11+import System.FilePath (splitExtension)
1212
1313
1414--------------------------------------------------------------------------------
@@ -36,27 +36,31 @@ data FileType
3636--------------------------------------------------------------------------------
3737-- | Get the file type for a certain file. The type is determined by extension.
3838fileType :: FilePath -> FileType
39−fileType = fileType' . takeExtension
39+fileType = uncurry fileType' . splitExtension
4040 where
41− fileType' ".css" = Css
42− fileType' ".htm" = Html
43− fileType' ".html" = Html
44− fileType' ".lhs" = LiterateHaskell Markdown
45− fileType' ".markdown" = Markdown
46− fileType' ".md" = Markdown
47− fileType' ".mdn" = Markdown
48− fileType' ".mdown" = Markdown
49− fileType' ".mdwn" = Markdown
50− fileType' ".mkd" = Markdown
51− fileType' ".mkdwn" = Markdown
52− fileType' ".org" = OrgMode
53− fileType' ".page" = Markdown
54− fileType' ".rst" = Rst
55− fileType' ".tex" = LaTeX
56− fileType' ".text" = PlainText
57− fileType' ".textile" = Textile
58− fileType' ".txt" = PlainText
59− fileType' _ = Binary -- Treat unknown files as binary
41+ fileType' _ ".css" = Css
42+ fileType' _ ".htm" = Html
43+ fileType' _ ".html" = Html
44+ fileType' f ".lhs" = LiterateHaskell $ case fileType f of
45+ -- If no extension is given, default to Markdown + LiterateHaskell
46+ Binary -> Markdown
47+ -- Otherwise, LaTeX + LiterateHaskell or whatever the user specified
48+ x -> x
49+ fileType' _ ".markdown" = Markdown
50+ fileType' _ ".md" = Markdown
51+ fileType' _ ".mdn" = Markdown
52+ fileType' _ ".mdown" = Markdown
53+ fileType' _ ".mdwn" = Markdown
54+ fileType' _ ".mkd" = Markdown
55+ fileType' _ ".mkdwn" = Markdown
56+ fileType' _ ".org" = OrgMode
57+ fileType' _ ".page" = Markdown
58+ fileType' _ ".rst" = Rst
59+ fileType' _ ".tex" = LaTeX
60+ fileType' _ ".text" = PlainText
61+ fileType' _ ".textile" = Textile
62+ fileType' _ ".txt" = PlainText
63+ fileType' _ _ = Binary -- Treat unknown files as binary
6064
6165
6266--------------------------------------------------------------------------------
src/Hakyll/Web/Tags.hs+5−8
@@ -41,12 +41,13 @@
4141{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4242{-# LANGUAGE OverloadedStrings #-}
4343module Hakyll.Web.Tags
44− ( Tags
44+ ( Tags (..)
4545 , getTags
4646 , buildTagsWith
4747 , buildTags
4848 , buildCategories
4949 , tagsRules
50+ , renderTags
5051 , renderTagCloud
5152 , renderTagList
5253 , tagsField
@@ -149,7 +150,7 @@ tagsRules tags rules =
149150
150151
151152--------------------------------------------------------------------------------
152−-- | Render tags in HTML
153+-- | Render tags in HTML (the flexible higher-order function)
153154renderTags :: (String -> String -> Int -> Int -> Int -> String)
154155 -- ^ Produce a tag item: tag, url, count, min count, max count
155156 -> ([String] -> String)
@@ -218,13 +219,9 @@ renderTagList = renderTags makeLink (intercalate ", ")
218219
219220
220221--------------------------------------------------------------------------------
221−-- | Render tags with links with custom function to get tags. It is typically
222−-- together with 'getTags' like this:
223−--
224−-- > renderTagsFieldWith (customFunction . getTags)
225−-- > "tags" (fromCapture "tags/*")
222+-- | Render tags with links with custom function to get tags
226223tagsFieldWith :: (Identifier -> Compiler [String]) -- ^ Get the tags
227− -> String -- ^ Destination key
224+ -> String -- ^ Destination field
228225 -> Tags -- ^ Tags structure
229226 -> Context a -- ^ Resulting context
230227tagsFieldWith getTags' key tags = field key $ \item -> do
src/Hakyll/Web/Template/Context.hs+1−2
@@ -207,8 +207,7 @@ modificationTimeFieldWith :: TimeLocale -- ^ Time output locale
207207 -> Context a -- ^ Resulting context
208208modificationTimeFieldWith locale key fmt = field key $ \i -> do
209209 provider <- compilerProvider <$> compilerAsk
210− mtime <- compilerUnsafeIO $
211− resourceModificationTime provider $ itemIdentifier i
210+ let mtime = resourceModificationTime provider $ itemIdentifier i
212211 return $ formatTime locale fmt mtime
213212
214213
tests/Hakyll/Core/Provider/Tests.hs+1
@@ -37,3 +37,4 @@ case01 = do
3737
3838 doesntExist <- resourceMetadata provider "doesntexist.md"
3939 M.empty @=? doesntExist
40+ cleanTestEnv
tests/Hakyll/Core/Rules/Tests.hs+1
@@ -53,6 +53,7 @@ rulesTest = do
5353 checkRoute "example.mv1" (sv "mv1" "example.md")
5454 checkRoute "example.mv2" (sv "mv2" "example.md")
5555 readIORef ioref >>= assert
56+ cleanTestEnv
5657 where
5758 sv g = setVersion (Just g)
5859 expected =
tests/Hakyll/Core/Store/Tests.hs+1
@@ -67,3 +67,4 @@ wrongType = do
6767 e == typeOf (undefined :: Int) &&
6868 t == typeOf (undefined :: String)
6969 _ -> False
70+ cleanTestEnv
tests/Hakyll/Web/Pandoc/FileType/Tests.hsnew file+26
@@ -0,0 +1,26 @@
1+--------------------------------------------------------------------------------
2+{-# LANGUAGE OverloadedStrings #-}
3+module Hakyll.Web.Pandoc.FileType.Tests
4+ ( tests
5+ ) where
6+
7+
8+--------------------------------------------------------------------------------
9+import Test.Framework (Test, testGroup)
10+import Test.HUnit ((@=?))
11+
12+
13+--------------------------------------------------------------------------------
14+import Hakyll.Web.Pandoc.FileType
15+import TestSuite.Util
16+
17+
18+--------------------------------------------------------------------------------
19+tests :: Test
20+tests = testGroup "Hakyll.Web.Pandoc.FileType.Tests" $
21+ fromAssertions "fileType"
22+ [ Markdown @=? fileType "index.md"
23+ , Rst @=? fileType "about/foo.rst"
24+ , LiterateHaskell Markdown @=? fileType "posts/bananas.lhs"
25+ , LiterateHaskell LaTeX @=? fileType "posts/bananas.tex.lhs"
26+ ]
tests/Hakyll/Web/Template/Tests.hs+2
@@ -44,6 +44,7 @@ case01 = do
4444 pandocCompiler >>= applyTemplate (itemBody tpl) testContext
4545
4646 out @=? itemBody item
47+ cleanTestEnv
4748
4849
4950--------------------------------------------------------------------------------
@@ -63,6 +64,7 @@ testApplyJoinTemplateList = do
6364 applyJoinTemplateList ", " tpl defaultContext [i1, i2]
6465
6566 str @?= "<b>Hello</b>, <b>World</b>"
67+ cleanTestEnv
6668 where
6769 i1 = Item "item1" "Hello"
6870 i2 = Item "item2" "World"
tests/TestSuite.hs+2
@@ -20,6 +20,7 @@ import qualified Hakyll.Core.UnixFilter.Tests
2020import qualified Hakyll.Core.Util.String.Tests
2121import qualified Hakyll.Web.Html.RelativizeUrls.Tests
2222import qualified Hakyll.Web.Html.Tests
23+import qualified Hakyll.Web.Pandoc.FileType.Tests
2324import qualified Hakyll.Web.Template.Context.Tests
2425import qualified Hakyll.Web.Template.Tests
2526
@@ -38,6 +39,7 @@ main = defaultMain
3839 , Hakyll.Core.Util.String.Tests.tests
3940 , Hakyll.Web.Html.RelativizeUrls.Tests.tests
4041 , Hakyll.Web.Html.Tests.tests
42+ , Hakyll.Web.Pandoc.FileType.Tests.tests
4143 , Hakyll.Web.Template.Context.Tests.tests
4244 , Hakyll.Web.Template.Tests.tests
4345 ]
web/about.markdown+1
@@ -38,3 +38,4 @@ who still maintains the package. Contributors:
3838- [seschwar](https://github.com/seschwar)
3939- [favonia](https://github.com/favonia)
4040- [Robin Windels](https://github.com/rwindelz)
41+- [Miikka Koskinen](http://miikka.me/)
web/examples.markdown+4
@@ -17,6 +17,10 @@ this list. This list has no particular ordering.
1717 [source](https://github.com/Keruspe/blog/)
1818- <http://blog.clement.delafargue.name/>,
1919 [source](https://github.com/divarvel/blog)
20+- <http://bneijt.nl>,
21+ [source](https://github.com/bneijt/bneijt.nl)
22+- <http://brianshourd.com/>,
23+ [source](https://github.com/brianshourd/brianshourd.com)
2024
2125## Hakyll 3.X
2226
web/images/hakyll-system-2.png

Binary file not shown.

web/releases.markdown+12
@@ -4,6 +4,18 @@ title: Releases
44
55# Releases
66
7+## Hakyll 4.1.4.0
8+
9+*January 26, 2013*
10+
11+- Export the flexible `renderTags` function
12+
13+## Hakyll 4.1.3.0
14+
15+*January 26, 2013*
16+
17+- Export the constructor of the `Tags` datatype
18+
719## Hakyll 4.1.2.0
820
921*January 20, 2013*
web/tutorials/01-installation.markdown+3−2
@@ -32,8 +32,9 @@ started:
3232
3333 $ hakyll-init my-site
3434
35−If `hakyll-init` is not found, you should make sure `$HOME/.cabal/bin` is in
36−your `$PATH`.
35+This creates a folder `my-site` in the current directory, with some example
36+content and a generic configuration. If `hakyll-init` is not found, you should
37+make sure `$HOME/.cabal/bin` is in your `$PATH`.
3738
3839The file `site.hs` holds the configuration of your site, as an executable
3940haskell program. We can compile and run it like this:
web/tutorials/03-rules-routes-compilers.markdown+1−1
@@ -61,7 +61,7 @@ this will become clear soon. The real question here is why we use `create`
6161instead of `match`.
6262
6363The answer is simple: there is no `archive.html` file in our project directory!
64−So if we were to use `match`, no a file would be matched, and hence, nothing
64+So if we were to use `match`, no file would be matched, and hence, nothing
6565would appear in the output directory. `create`, however, ensures the items
6666listed are always produced.
6767
web/tutorials/04-compilers.markdown+9−5
@@ -41,9 +41,11 @@ Templates
4141
4242Let's have a look at a simple template:
4343
44− <h1>$title$</h1>
45− <div class="info">Posted on $date$</div>
46− $body$
44+```html
45+<h1>$title$</h1>
46+<div class="info">Posted on $date$</div>
47+$body$
48+```
4749
4850As you can probably guess, template files just contain text and only the `$`
4951character has special meaning: text between dollar signs ("fields") is replaced
@@ -53,7 +55,9 @@ use `$$`.
5355You usually compile the templates from disk using the aptly named
5456`templateCompiler`:
5557
56− match "templates/*" $ compile templateCompiler
58+```haskell
59+match "templates/*" $ compile templateCompiler
60+```
5761
5862Notice the lack of `route` here: this is because we don't need to write the
5963templates to your `_site` folder, we just want to use them elsewhere.
@@ -86,7 +90,7 @@ And `$title$` like this:
8690
8791```haskell
8892titleContext :: Context a
89−titleContext :: field "title" $ \item -> do
93+titleContext = field "title" $ \item -> do
9094 metadata <- getMetadata (itemIdentifier item)
9195 return $ fromMaybe "No title" $ M.lookup "title" metadata
9296```
web/tutorials/faq.markdown+13−1
@@ -31,13 +31,25 @@ recent version of Pandoc (1.9 and onwards). Note that you also need to include
3131some CSS in order for this to work! This site, for example, uses the [default
3232Pandoc syntax CSS file][syntax-css].
3333
34+To highlight a code block, you need to use Pandoc's fenced code block syntax to
35+set the block's language. For example, here's how you highlight Haskell code:
36+
37+ ``` haskell
38+ fac n = foldr (*) 1 [1..n]
39+ ```
40+
41+For details, see Pandoc's user guide on [fenced code
42+blocks][pandoc-code-blocks] and [inline code][pandoc-inline-code].
43+
3444[syntax-css]: https://github.com/jaspervdj/hakyll/blob/master/web/css/syntax.css
45+[pandoc-code-blocks]: http://johnmacfarlane.net/pandoc/README.html#fenced-code-blocks
46+[pandoc-inline-code]: http://johnmacfarlane.net/pandoc/README.html#verbatim
3547
3648## When should I rebuild and when should I build?
3749
3850If you execute a `./site build`, Hakyll will build your site incrementally.
3951However, we can not detect if you edited `site.hs`. In this case, you first want
40−to compile it again `site.hs` again, and then do a `./site rebuild`.
52+to compile `site.hs` again, and then do a `./site rebuild`.
4153
4254After rebuilding your site, all files will look as "modified" to the filesystem.
4355This means that when you upload your site, it will usually transfer all files --