packages feed

hakyll 3.1.0.0 → 3.1.1.0

raw patch · 18 files changed

+436/−283 lines, 18 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Hakyll.Core.DirectedGraph: sanitize :: Ord a => DirectedGraph a -> DirectedGraph a
- Hakyll.Core.DirectedGraph.DependencySolver: solveDependencies :: Ord a => DirectedGraph a -> [a]
- Hakyll.Core.Identifier: newtype Identifier
- Hakyll.Core.Identifier: unIdentifier :: Identifier -> String
- Hakyll.Core.Identifier.Pattern: fromCaptureString :: Pattern -> String -> Identifier
+ Hakyll.Core.DependencyAnalyzer: Build :: a -> Signal a
+ Hakyll.Core.DependencyAnalyzer: Cycle :: [a] -> Signal a
+ Hakyll.Core.DependencyAnalyzer: DependencyAnalyzer :: DirectedGraph a -> Set a -> Set a -> DirectedGraph a -> DependencyAnalyzer a
+ Hakyll.Core.DependencyAnalyzer: Done :: Signal a
+ Hakyll.Core.DependencyAnalyzer: analyzerDone :: DependencyAnalyzer a -> Set a
+ Hakyll.Core.DependencyAnalyzer: analyzerGraph :: DependencyAnalyzer a -> DirectedGraph a
+ Hakyll.Core.DependencyAnalyzer: analyzerPreviousGraph :: DependencyAnalyzer a -> DirectedGraph a
+ Hakyll.Core.DependencyAnalyzer: analyzerRemains :: DependencyAnalyzer a -> Set a
+ Hakyll.Core.DependencyAnalyzer: data DependencyAnalyzer a
+ Hakyll.Core.DependencyAnalyzer: data Signal a
+ Hakyll.Core.DependencyAnalyzer: instance (Ord a, Show a) => Monoid (DependencyAnalyzer a)
+ Hakyll.Core.DependencyAnalyzer: instance Show a => Show (DependencyAnalyzer a)
+ Hakyll.Core.DependencyAnalyzer: makeDependencyAnalyzer :: (Ord a, Show a) => DirectedGraph a -> (a -> Bool) -> DirectedGraph a -> DependencyAnalyzer a
+ Hakyll.Core.DependencyAnalyzer: step :: (Ord a, Show a) => DependencyAnalyzer a -> (Signal a, DependencyAnalyzer a)
+ Hakyll.Core.DependencyAnalyzer: stepAll :: (Ord a, Show a) => DependencyAnalyzer a -> Maybe (Set a)
+ Hakyll.Core.Identifier: data Identifier
+ Hakyll.Core.Identifier: identifierGroup :: Identifier -> Maybe String
+ Hakyll.Core.Identifier: identifierPath :: Identifier -> String
+ Hakyll.Core.Identifier: setGroup :: Maybe String -> Identifier -> Identifier
+ Hakyll.Core.Identifier.Pattern: inGroup :: Maybe String -> Pattern
+ Hakyll.Core.Resource: fromIdentifier :: Identifier -> Resource
+ Hakyll.Core.Resource: toIdentifier :: Resource -> Identifier
+ Hakyll.Core.Resource.Provider: makeResourceProvider :: [Resource] -> (Resource -> IO String) -> (Resource -> IO ByteString) -> IO ResourceProvider
+ Hakyll.Core.Rules: group :: String -> Rules -> Rules
- Hakyll.Core.Compiler: runCompiler :: Compiler () CompileRule -> Identifier -> ResourceProvider -> Routes -> Store -> Bool -> Logger -> IO (Throwing CompileRule)
+ Hakyll.Core.Compiler: runCompiler :: Compiler () CompileRule -> Identifier -> ResourceProvider -> [Identifier] -> Routes -> Store -> Bool -> Logger -> IO (Throwing CompileRule)
- Hakyll.Core.Identifier: Identifier :: String -> Identifier
+ Hakyll.Core.Identifier: Identifier :: Maybe String -> String -> Identifier
- Hakyll.Core.Identifier.Pattern: capture :: Pattern -> Identifier -> Maybe [Identifier]
+ Hakyll.Core.Identifier.Pattern: capture :: Pattern -> Identifier -> Maybe [String]
- Hakyll.Core.Identifier.Pattern: fromCapture :: Pattern -> Identifier -> Identifier
+ Hakyll.Core.Identifier.Pattern: fromCapture :: Pattern -> String -> Identifier
- Hakyll.Core.Identifier.Pattern: fromCaptures :: Pattern -> [Identifier] -> Identifier
+ Hakyll.Core.Identifier.Pattern: fromCaptures :: Pattern -> [String] -> Identifier
- Hakyll.Core.Resource: Resource :: Identifier -> Resource
+ Hakyll.Core.Resource: Resource :: String -> Resource
- Hakyll.Core.Resource: unResource :: Resource -> Identifier
+ Hakyll.Core.Resource: unResource :: Resource -> String
- Hakyll.Core.Resource.Provider: resourceExists :: ResourceProvider -> Identifier -> Bool
+ Hakyll.Core.Resource.Provider: resourceExists :: ResourceProvider -> Resource -> Bool
- Hakyll.Core.Resource.Provider: resourceModified :: ResourceProvider -> Resource -> Store -> IO Bool
+ Hakyll.Core.Resource.Provider: resourceModified :: ResourceProvider -> Store -> Resource -> IO Bool

Files

hakyll.cabal view
@@ -1,5 +1,5 @@ Name:               hakyll-Version:            3.1.0.0+Version:            3.1.1.0  Synopsis:           A simple static site generator library. Description:        A simple static site generator library, mainly aimed at@@ -82,6 +82,7 @@                       Hakyll.Web.Page.Read                       Hakyll.Web.Page.Metadata                       Hakyll.Core.Configuration+                      Hakyll.Core.DependencyAnalyzer                       Hakyll.Core.Identifier.Pattern                       Hakyll.Core.UnixFilter                       Hakyll.Core.Util.Arrow@@ -99,7 +100,6 @@                       Hakyll.Core.Writable.WritableTuple                       Hakyll.Core.Identifier                       Hakyll.Core.DirectedGraph.Dot-                      Hakyll.Core.DirectedGraph.DependencySolver                       Hakyll.Core.DirectedGraph                       Hakyll.Core.Rules                       Hakyll.Core.Routes
src-inotify/Hakyll/Web/Preview/Poll.hs view
@@ -14,7 +14,6 @@  import Hakyll.Core.Configuration import Hakyll.Core.Resource-import Hakyll.Core.Identifier  -- | Calls the given callback when the directory tree changes --@@ -27,7 +26,7 @@     inotify <- initINotify      let -- A set of file paths-        paths = S.map (toFilePath . unResource) resources+        paths = S.map unResource resources          -- A list of directories. Run it through a set so we have every         -- directory only once.
src-interval/Hakyll/Web/Preview/Poll.hs view
@@ -7,14 +7,13 @@  import Control.Applicative ((<$>)) import Control.Concurrent (threadDelay)-import Control.Monad (when)+import Control.Monad (when, filterM) import System.Time (getClockTime) import Data.Set (Set) import qualified Data.Set as S-import System.Directory (getModificationTime)+import System.Directory (getModificationTime, doesFileExist)  import Hakyll.Core.Configuration-import Hakyll.Core.Identifier import Hakyll.Core.Resource  -- | A preview thread that periodically recompiles the site.@@ -24,13 +23,14 @@             -> IO ()                -- ^ Action called when something changes             -> IO ()                -- ^ Can block forever previewPoll _ resources callback = do-    let files = map (toFilePath . unResource) $ S.toList resources+    let files = map unResource $ S.toList resources     time <- getClockTime     loop files time   where     delay = 1000000     loop files time = do         threadDelay delay-        modified <- any (time <) <$> mapM getModificationTime files-        when modified callback-        loop files =<< getClockTime+        files' <- filterM doesFileExist files+        modified <- any (time <) <$> mapM getModificationTime files'+        when (modified || files' /= files) callback+        loop files' =<< getClockTime
src/Hakyll/Core/Compiler.hs view
@@ -138,15 +138,16 @@ runCompiler :: Compiler () CompileRule    -- ^ Compiler to run             -> Identifier                 -- ^ Target identifier             -> ResourceProvider           -- ^ Resource provider+            -> [Identifier]               -- ^ Universe             -> Routes                     -- ^ Route             -> Store                      -- ^ Store             -> Bool                       -- ^ Was the resource modified?             -> Logger                     -- ^ Logger             -> IO (Throwing CompileRule)  -- ^ Resulting item-runCompiler compiler identifier provider routes store modified logger = do+runCompiler compiler id' provider universe routes store modified logger = do     -- Run the compiler job-    result <--        runCompilerJob compiler identifier provider routes store modified logger+    result <- runCompilerJob compiler id' provider universe+                             routes store modified logger      -- Inspect the result     case result of@@ -154,7 +155,7 @@         -- before we return control. This makes sure the compiled item can later         -- be accessed by e.g. require.         Right (CompileRule (CompiledItem x)) ->-            storeSet store "Hakyll.Core.Compiler.runCompiler" identifier x+            storeSet store "Hakyll.Core.Compiler.runCompiler" id' x          -- Otherwise, we do nothing here         _ -> return ()@@ -184,7 +185,7 @@ getResourceString = fromJob $ \resource -> CompilerM $ do     let identifier = unResource resource     provider <- compilerResourceProvider <$> ask-    if resourceExists provider identifier+    if resourceExists provider resource         then liftIO $ resourceString provider resource         else throwError $ error' identifier   where@@ -238,9 +239,9 @@             -> Compiler b [a] requireAll_ pattern = fromDependencies (const getDeps) >>> fromJob requireAll_'   where-    getDeps = filterMatches pattern . map unResource . resourceList+    getDeps = filterMatches pattern     requireAll_' = const $ CompilerM $ do-        deps <- getDeps . compilerResourceProvider <$> ask+        deps <- getDeps . compilerUniverse <$> ask         mapM (unCompilerM . getDependency) deps  -- | Require a number of targets. Using this function ensures automatic handling@@ -271,7 +272,7 @@     modified <- compilerResourceModified <$> ask     report logger $ "Checking cache: " ++ if modified then "modified" else "OK"     if modified-        then do v <- unCompilerM $ j $ Resource identifier+        then do v <- unCompilerM $ j $ fromIdentifier identifier                 liftIO $ storeSet store name identifier v                 return v         else do v <- liftIO $ storeGet store name identifier
src/Hakyll/Core/Compiler/Internal.hs view
@@ -39,9 +39,9 @@ -- data DependencyEnvironment = DependencyEnvironment     { -- | Target identifier-      dependencyIdentifier       :: Identifier-    , -- | Resource provider-      dependencyResourceProvider :: ResourceProvider+      dependencyIdentifier :: Identifier+    , -- | List of available identifiers we can depend upon+      dependencyUniverse   :: [Identifier]     }  -- | Environment in which a compiler runs@@ -51,6 +51,8 @@       compilerIdentifier       :: Identifier     , -- | Resource provider       compilerResourceProvider :: ResourceProvider+    , -- | List of all known identifiers+      compilerUniverse         :: [Identifier]     , -- | Site routes       compilerRoutes           :: Routes     , -- | Compiler store@@ -107,17 +109,19 @@ runCompilerJob :: Compiler () a     -- ^ Compiler to run                -> Identifier        -- ^ Target identifier                -> ResourceProvider  -- ^ Resource provider+               -> [Identifier]      -- ^ Universe                -> Routes            -- ^ Route                -> Store             -- ^ Store                -> Bool              -- ^ Was the resource modified?                -> Logger            -- ^ Logger                -> IO (Throwing a)   -- ^ Result-runCompilerJob compiler identifier provider route store modified logger =+runCompilerJob compiler id' provider universe route store modified logger =     runReaderT (runErrorT $ unCompilerM $ compilerJob compiler ()) env   where     env = CompilerEnvironment-            { compilerIdentifier       = identifier+            { compilerIdentifier       = id'             , compilerResourceProvider = provider+            , compilerUniverse         = universe             , compilerRoutes           = route             , compilerStore            = store             , compilerResourceModified = modified@@ -126,25 +130,25 @@  runCompilerDependencies :: Compiler () a                         -> Identifier-                        -> ResourceProvider+                        -> [Identifier]                         -> Dependencies-runCompilerDependencies compiler identifier provider =+runCompilerDependencies compiler identifier universe =     runReader (compilerDependencies compiler) env   where     env = DependencyEnvironment-            { dependencyIdentifier       = identifier-            , dependencyResourceProvider = provider+            { dependencyIdentifier = identifier+            , dependencyUniverse   = universe             }  fromJob :: (a -> CompilerM b)         -> Compiler a b fromJob = Compiler (return S.empty) -fromDependencies :: (Identifier -> ResourceProvider -> [Identifier])+fromDependencies :: (Identifier -> [Identifier] -> [Identifier])                  -> Compiler b b fromDependencies collectDeps = flip Compiler return $ do-    DependencyEnvironment identifier provider <- ask-    return $ S.fromList $ collectDeps identifier provider+    DependencyEnvironment identifier universe <- ask+    return $ S.fromList $ collectDeps identifier universe  -- | Wait until another compiler has finished before running this compiler --
+ src/Hakyll/Core/DependencyAnalyzer.hs view
@@ -0,0 +1,155 @@+module Hakyll.Core.DependencyAnalyzer+    ( DependencyAnalyzer (..)+    , Signal (..)+    , makeDependencyAnalyzer+    , step+    , stepAll+    ) where++import Prelude hiding (reverse)+import qualified Prelude as P (reverse)+import Control.Arrow (first)+import Data.Set (Set)+import qualified Data.Set as S+import Data.Monoid (Monoid, mappend, mempty)++import Hakyll.Core.DirectedGraph++-- | This data structure represents the state of the dependency analyzer. It+-- holds a complete graph in 'analyzerGraph', which always contains all items,+-- whether they are to be compiled or not.+--+-- The 'analyzerRemains' fields holds the items that still need to be compiled,+-- and 'analyzerDone' holds the items which are already compiled. This means+-- that initally, 'analyzerDone' is empty and 'analyzerRemains' contains the+-- items which are out-of-date (or items which have out-of-date dependencies).+--+-- We also hold the dependency graph from the previous run because we need it+-- when we want to determine when an item is out-of-date. An item is out-of-date+-- when:+--+-- * the resource from which it compiles is out-of-date, or;+--+-- * any of it's dependencies is out-of-date, or;+--+-- * it's set of dependencies has changed since the previous run.+--+data DependencyAnalyzer a = DependencyAnalyzer+    { -- | The complete dependency graph+      analyzerGraph         :: DirectedGraph a+    , -- | A set of items yet to be compiled+      analyzerRemains       :: Set a+    , -- | A set of items already compiled+      analyzerDone          :: Set a+    , -- | The dependency graph from the previous run+      analyzerPreviousGraph :: DirectedGraph a+    } deriving (Show)++data Signal a = Build a+              | Cycle [a]+              | Done++instance (Ord a, Show a) => Monoid (DependencyAnalyzer a) where+    mempty = DependencyAnalyzer mempty mempty mempty mempty+    mappend x y = growRemains $ DependencyAnalyzer+        (analyzerGraph x `mappend` analyzerGraph y)+        (analyzerRemains x `mappend` analyzerRemains y)+        (analyzerDone x `mappend` analyzerDone y)+        (analyzerPreviousGraph x `mappend` analyzerPreviousGraph y)++-- | Construct a dependency analyzer+--+makeDependencyAnalyzer :: (Ord a, Show a)+                       => DirectedGraph a       -- ^ The dependency graph+                       -> (a -> Bool)           -- ^ Is an item out-of-date?+                       -> DirectedGraph a       -- ^ The old dependency graph+                       -> DependencyAnalyzer a  -- ^ Resulting analyzer+makeDependencyAnalyzer graph isOutOfDate prev =+    growRemains $ DependencyAnalyzer graph remains S.empty prev+  where+    -- Construct the remains set by filtering using the given predicate+    remains = S.fromList $ filter isOutOfDate $ map fst $ toList graph++-- | The 'analyzerRemains' field of a 'DependencyAnalyzer' is supposed to+-- contain all out-of-date items, including the items with out-of-date+-- dependencies. However, it is easier to just set up the directly out-of-date+-- items initially -- and then grow the remains fields.+--+-- This function assumes the 'analyzerRemains' fields in incomplete, and tries+-- to correct it. Running it when the field is complete has no effect -- but it+-- is a pretty expensive function, and it should be used with care.+--+growRemains :: (Ord a, Show a) => DependencyAnalyzer a -> DependencyAnalyzer a+growRemains (DependencyAnalyzer graph remains done prev) =+    (DependencyAnalyzer graph remains' done prev)+  where+    -- Grow the remains set using the indirect and changedDeps values, then+    -- filter out the items already done+    remains' = S.filter (`S.notMember` done) indirect++    -- Select the nodes which are reachable from the remaining nodes in the+    -- reversed dependency graph: these are the indirectly out-of-date items+    indirect = reachableNodes (remains `S.union` changedDeps) $ reverse graph++    -- For all nodes in the graph, check which items have a different dependency+    -- set compared to the previous run+    changedDeps = S.fromList $ map fst $+        filter (uncurry (/=) . first (`neighbours` prev)) $ toList graph++-- | Step a dependency analyzer+--+step :: (Ord a, Show a) => DependencyAnalyzer a -> (Signal a, DependencyAnalyzer a)+step analyzer@(DependencyAnalyzer graph remains done prev)+    -- No remaining items+    | S.null remains = (Done, analyzer)+    -- An item remains, let's find a ready item+    | otherwise =+        let item = S.findMin remains+        in case findReady analyzer item of+            Done        -> (Done, analyzer)+            Cycle c     -> (Cycle c, analyzer)+            -- A ready item was found, signal a build+            Build build ->+                let remains' = S.delete build remains+                    done'    = S.insert build done+                in (Build build, DependencyAnalyzer graph remains' done' prev)++-- | Step until done, creating a set of items we need to build -- mostly used+-- for debugging purposes+--+stepAll :: (Ord a, Show a) => DependencyAnalyzer a -> Maybe (Set a)+stepAll = stepAll' S.empty+  where+    stepAll' xs analyzer = case step analyzer of+        (Build x, analyzer') -> stepAll' (S.insert x xs) analyzer'+        (Done, _)            -> Just xs+        (Cycle _, _)         -> Nothing++-- | Find an item ready to be compiled+--+findReady :: (Ord a, Show a) => DependencyAnalyzer a -> a -> Signal a+findReady analyzer = findReady' [] S.empty+  where+    -- The dependency graph+    graph = analyzerGraph analyzer++    -- Items to do+    todo = analyzerRemains analyzer `S.difference` analyzerDone analyzer++    -- Worker+    findReady' stack visited item+        -- We already visited this item, the cycle is the reversed stack+        | item `S.member` visited = Cycle $ P.reverse stack'+        -- Look at the neighbours we to do+        | otherwise = case filter (`S.member` todo) neighbours' of+            -- No neighbours available to be done: it's ready!+            []      -> Build item+            -- At least one neighbour is available, search for that one+            (x : _) -> findReady' stack' visited' x+      where+        -- Our neighbours+        neighbours' = S.toList $ neighbours item graph++        -- The new visited stack/set+        stack' = item : stack+        visited' = S.insert item visited
src/Hakyll/Core/DirectedGraph.hs view
@@ -10,7 +10,6 @@     , neighbours     , reverse     , reachableNodes-    , sanitize     ) where  import Prelude hiding (reverse)@@ -77,17 +76,9 @@   where     reachable next visited         | S.null next = visited-        | otherwise = reachable (sanitize' neighbours') (next `S.union` visited)+        | otherwise = reachable (sanitize neighbours') (next `S.union` visited)       where-        sanitize' = S.filter (`S.notMember` visited)-        neighbours' = setNeighbours (sanitize' next)+        sanitize = S.filter (`S.notMember` visited)+        neighbours' = setNeighbours (sanitize next)      setNeighbours = S.unions . map (`neighbours` graph) . S.toList-    --- | Remove all dangling pointers, i.e. references to notes that do --- not actually exist in the graph.----sanitize :: Ord a => DirectedGraph a -> DirectedGraph a-sanitize (DirectedGraph graph) = DirectedGraph $ M.map sanitize' graph-  where-    sanitize' (Node t n) = Node t $ S.filter (`M.member` graph) n
− src/Hakyll/Core/DirectedGraph/DependencySolver.hs
@@ -1,70 +0,0 @@--- | Given a dependency graph, this module provides a function that will--- generate an order in which the graph can be visited, so that all the--- dependencies of a given node have been visited before the node itself is--- visited.----module Hakyll.Core.DirectedGraph.DependencySolver-    ( solveDependencies-    ) where--import Prelude-import qualified Prelude as P-import Data.Set (Set)-import Data.Maybe (mapMaybe)-import qualified Data.Map as M-import qualified Data.Set as S--import Hakyll.Core.DirectedGraph-import Hakyll.Core.DirectedGraph.Internal---- | Solve a dependency graph. This function returns an order to run the--- different nodes----solveDependencies :: Ord a-                  => DirectedGraph a  -- ^ Graph-                  -> [a]              -- ^ Resulting plan-solveDependencies = P.reverse . order [] [] S.empty---- | Produce a reversed order using a stack----order :: Ord a-      => [a]              -- ^ Temporary result-      -> [Node a]         -- ^ Backtrace stack-      -> Set a            -- ^ Items in the stack-      -> DirectedGraph a  -- ^ Graph-      -> [a]              -- ^ Ordered result-order temp stack set graph@(DirectedGraph graph')-    -- Empty graph - return our current result-    | M.null graph' = temp-    | otherwise = case stack of--        -- Empty stack - pick a node, and add it to the stack-        [] ->-            let (tag, node) = M.findMin graph'-            in order temp (node : stack) (S.insert tag set) graph--        -- At least one item on the stack - continue using this item-        (node : stackTail) ->-            -- Check which dependencies are still in the graph-            let tag = nodeTag node-                deps = S.toList $ nodeNeighbours node-                unsatisfied = mapMaybe (`M.lookup` graph') deps-            in case unsatisfied of-                -                -- All dependencies for node are satisfied, we can return it and-                -- remove it from the graph-                [] -> order (tag : temp) stackTail (S.delete tag set)-                            (DirectedGraph $ M.delete tag graph')--                -- There is at least one dependency left. We need to solve that-                -- one first...-                (dep : _) -> if nodeTag dep `S.member` set-                    -- The dependency is already in our stack - cycle detected!-                    then cycleError-                    -- Continue with the dependency-                    else order temp (dep : node : stackTail)-                                    (S.insert (nodeTag dep) set)-                                    graph-  where-    cycleError = error $  "Hakyll.Core.DirectedGraph.DependencySolver.order: "-                       ++ "Cycle detected!"  -- TODO: Dump cycle
src/Hakyll/Core/DirectedGraph/Dot.hs view
@@ -16,11 +16,13 @@       -> String           -- ^ Resulting string toDot showTag graph = unlines $ concat     [ return "digraph dependencies {"-    , concatMap showNode (S.toList $ nodes graph)+    , map showNode (S.toList $ nodes graph)+    , concatMap showEdges (S.toList $ nodes graph)     , return "}"     ]   where-    showNode node = map (showEdge node) $ S.toList $ neighbours node graph+    showNode node = "    \"" ++ showTag node ++ "\";"+    showEdges node = map (showEdge node) $ S.toList $ neighbours node graph     showEdge x y = "    \"" ++ showTag x ++ "\" -> \"" ++ showTag y ++ "\";"  -- | Write out the @.dot@ file to a given file path. See 'toDot' for more
src/Hakyll/Core/Identifier.hs view
@@ -25,23 +25,38 @@     ( Identifier (..)     , parseIdentifier     , toFilePath+    , setGroup     ) where  import Control.Arrow (second)-import Data.Monoid (Monoid)+import Control.Applicative ((<$>), (<*>))+import Control.Monad (mplus)+import Data.Monoid (Monoid, mempty, mappend) import Data.List (intercalate) -import Data.Binary (Binary)+import Data.Binary (Binary, get, put) import GHC.Exts (IsString, fromString) import Data.Typeable (Typeable)  -- | An identifier used to uniquely identify a value ---newtype Identifier = Identifier {unIdentifier :: String}-                   deriving (Eq, Ord, Monoid, Binary, Typeable)+data Identifier = Identifier+    { identifierGroup :: Maybe String+    , identifierPath  :: String+    } deriving (Eq, Ord, Typeable) +instance Monoid Identifier where+    mempty = Identifier Nothing ""+    Identifier g1 p1 `mappend` Identifier g2 p2 =+        Identifier (g1 `mplus` g2) (p1 `mappend` p2)++instance Binary Identifier where+    put (Identifier g p) = put g >> put p+    get = Identifier <$> get <*> get+ instance Show Identifier where-    show = toFilePath+    show i@(Identifier Nothing _)  = toFilePath i+    show i@(Identifier (Just g) _) = toFilePath i ++ " (" ++ g ++ ")"  instance IsString Identifier where     fromString = parseIdentifier@@ -49,7 +64,8 @@ -- | Parse an identifier from a string -- parseIdentifier :: String -> Identifier-parseIdentifier = Identifier . intercalate "/" . filter (not . null) . split'+parseIdentifier = Identifier Nothing+                . intercalate "/" . filter (not . null) . split'   where     split' [] = [[]]     split' str = let (pre, post) = second (drop 1) $ break (== '/') str@@ -58,4 +74,9 @@ -- | Convert an identifier to a relative 'FilePath' -- toFilePath :: Identifier -> FilePath-toFilePath = unIdentifier+toFilePath = identifierPath++-- | Set the identifier group for some identifier+--+setGroup :: Maybe String -> Identifier -> Identifier+setGroup g (Identifier _ p) = Identifier g p
src/Hakyll/Core/Identifier/Pattern.hs view
@@ -36,11 +36,11 @@     , parseGlob     , predicate     , regex+    , inGroup     , matches     , filterMatches     , capture     , fromCapture-    , fromCaptureString     , fromCaptures     ) where @@ -107,6 +107,12 @@ regex :: String -> Pattern regex str = predicate $ fromMaybe False . (=~~ str) . toFilePath +-- | Create a 'Pattern' which matches if the identifier is in a certain group+-- (or in no group)+--+inGroup :: Maybe String -> Pattern+inGroup group = predicate $ (== group) . identifierGroup+ -- | Check if an identifier matches a pattern -- matches :: Pattern -> Identifier -> Bool@@ -126,9 +132,9 @@  -- | Match a glob against a pattern, generating a list of captures ---capture :: Pattern -> Identifier -> Maybe [Identifier]-capture (Glob p) (Identifier i) = fmap (map Identifier) $ capture' p i-capture (Predicate _) _         = Nothing+capture :: Pattern -> Identifier -> Maybe [String]+capture (Glob p) (Identifier _ i) = capture' p i+capture (Predicate _) _           = Nothing  -- | Internal verion of 'capture' --@@ -152,44 +158,32 @@ -- -- Example: ----- > fromCapture (parseGlob "tags/*") (parseIdentifier "foo")+-- > fromCapture (parseGlob "tags/*") "foo" -- -- Result: -- -- > "tags/foo" ---fromCapture :: Pattern -> Identifier -> Identifier+fromCapture :: Pattern -> String -> Identifier fromCapture pattern = fromCaptures pattern . repeat --- | Simplified version of 'fromCapture' which takes a 'String' instead of an--- 'Identifier'------ > fromCaptureString (parseGlob "tags/*") "foo"------ Result:------ > "tags/foo"----fromCaptureString :: Pattern -> String -> Identifier-fromCaptureString pattern = fromCapture pattern . parseIdentifier- -- | Create an identifier from a pattern by filling in the captures with the -- given list of strings ---fromCaptures :: Pattern -> [Identifier] -> Identifier-fromCaptures (Glob p)      = fromCaptures' p+fromCaptures :: Pattern -> [String] -> Identifier+fromCaptures (Glob p)      = Identifier Nothing . fromCaptures' p fromCaptures (Predicate _) = error $     "Hakyll.Core.Identifier.Pattern.fromCaptures: fromCaptures called on a " ++     "predicate instead of a glob"  -- | Internally used version of 'fromCaptures' ---fromCaptures' :: [GlobComponent] -> [Identifier] -> Identifier+fromCaptures' :: [GlobComponent] -> [String] -> String fromCaptures' []        _ = mempty fromCaptures' (m : ms) [] = case m of-    Literal l -> Identifier l `mappend` fromCaptures' ms []+    Literal l -> l `mappend` fromCaptures' ms []     _         -> error $  "Hakyll.Core.Identifier.Pattern.fromCaptures': "                        ++ "identifier list exhausted" fromCaptures' (m : ms) ids@(i : is) = case m of-    Literal l -> Identifier l `mappend` fromCaptures' ms ids+    Literal l -> l `mappend` fromCaptures' ms ids     _         -> i `mappend` fromCaptures' ms is
src/Hakyll/Core/Resource.hs view
@@ -2,13 +2,23 @@ -- module Hakyll.Core.Resource     ( Resource (..)+    , fromIdentifier+    , toIdentifier     ) where  import Hakyll.Core.Identifier  -- | A resource ----- Invariant: the resource specified by the given identifier must exist----newtype Resource = Resource {unResource :: Identifier}+newtype Resource = Resource {unResource :: String}                  deriving (Eq, Show, Ord)++-- | Create a resource from an identifier+--+fromIdentifier :: Identifier -> Resource+fromIdentifier = Resource . toFilePath++-- | Map the resource to an identifier. Note that the group will not be set!+--+toIdentifier :: Resource -> Identifier+toIdentifier = parseIdentifier . unResource
src/Hakyll/Core/Resource/Provider.hs view
@@ -12,12 +12,14 @@ -- module Hakyll.Core.Resource.Provider     ( ResourceProvider (..)+    , makeResourceProvider     , resourceExists     , resourceDigest     , resourceModified     ) where -import Control.Concurrent (MVar, readMVar, modifyMVar_)+import Control.Applicative ((<$>))+import Control.Concurrent (MVar, readMVar, modifyMVar_, newMVar) import Control.Monad ((<=<)) import Data.Word (Word8) import Data.Map (Map)@@ -27,7 +29,6 @@ import OpenSSL.Digest.ByteString.Lazy (digest) import OpenSSL.Digest (MessageDigest (MD5)) -import Hakyll.Core.Identifier import Hakyll.Core.Store import Hakyll.Core.Resource @@ -44,10 +45,18 @@       resourceModifiedCache  :: MVar (Map Resource Bool)     } +-- | Create a resource provider+--+makeResourceProvider :: [Resource]                      -- ^ Resource list+                     -> (Resource -> IO String)         -- ^ String reader+                     -> (Resource -> IO LB.ByteString)  -- ^ ByteString reader+                     -> IO ResourceProvider             -- ^ Resulting provider+makeResourceProvider l s b = ResourceProvider l s b <$> newMVar M.empty+ -- | Check if a given identifier has a resource ---resourceExists :: ResourceProvider -> Identifier -> Bool-resourceExists provider = flip elem $ map unResource $ resourceList provider+resourceExists :: ResourceProvider -> Resource -> Bool+resourceExists provider = flip elem $ resourceList provider  -- | Retrieve a digest for a given resource --@@ -56,16 +65,16 @@  -- | Check if a resource was modified ---resourceModified :: ResourceProvider -> Resource -> Store -> IO Bool-resourceModified provider resource store = do+resourceModified :: ResourceProvider -> Store -> Resource -> IO Bool+resourceModified provider store resource = do     cache <- readMVar mvar     case M.lookup resource cache of         -- Already in the cache         Just m  -> return m         -- Not yet in the cache, check digests (if it exists)         Nothing -> do-            m <- if resourceExists provider (unResource resource)-                        then digestModified provider resource store+            m <- if resourceExists provider resource+                        then digestModified provider store resource                         else return False             modifyMVar_ mvar (return . M.insert resource m)             return m@@ -74,10 +83,10 @@  -- | Check if a resource digest was modified ---digestModified :: ResourceProvider -> Resource -> Store -> IO Bool-digestModified provider resource store = do+digestModified :: ResourceProvider -> Store -> Resource -> IO Bool+digestModified provider store resource = do     -- Get the latest seen digest from the store-    lastDigest <- storeGet store itemName $ unResource resource+    lastDigest <- storeGet store itemName identifier     -- Calculate the digest for the resource     newDigest <- resourceDigest provider resource     -- Check digests@@ -85,7 +94,8 @@         -- All is fine, not modified         then return False         -- Resource modified; store new digest-        else do storeSet store itemName (unResource resource) newDigest+        else do storeSet store itemName identifier newDigest                 return True   where+    identifier = toIdentifier resource     itemName = "Hakyll.Core.ResourceProvider.digestModified"
src/Hakyll/Core/Resource/Provider/File.hs view
@@ -5,14 +5,11 @@     ) where  import Control.Applicative ((<$>))-import Control.Concurrent (newMVar)-import qualified Data.Map as M  import qualified Data.ByteString.Lazy as LB  import Hakyll.Core.Resource import Hakyll.Core.Resource.Provider-import Hakyll.Core.Identifier import Hakyll.Core.Util.File import Hakyll.Core.Configuration @@ -20,17 +17,8 @@ -- fileResourceProvider :: HakyllConfiguration -> IO ResourceProvider fileResourceProvider configuration = do-    -- Retrieve a list of identifiers-    list <- map parseIdentifier . filter (not . ignoreFile configuration) <$>+    -- Retrieve a list of paths+    list <- map Resource . filter (not . ignoreFile configuration) <$>         getRecursiveContents False "."--    -- MVar for the cache-    mvar <- newMVar M.empty--    -- Construct a resource provider-    return ResourceProvider-        { resourceList           = map Resource list-        , resourceString         = readFile . toFilePath . unResource-        , resourceLazyByteString = LB.readFile . toFilePath . unResource-        , resourceModifiedCache  = mvar-        }+    makeResourceProvider list (readFile . unResource)+                              (LB.readFile . unResource)
src/Hakyll/Core/Rules.hs view
@@ -19,6 +19,7 @@     ( RulesM     , Rules     , match+    , group     , compile     , create     , route@@ -29,7 +30,7 @@ import Control.Applicative ((<$>)) import Control.Monad.Writer (tell) import Control.Monad.Reader (ask, local)-import Control.Arrow (second, (>>>), arr, (>>^))+import Control.Arrow (second, (>>>), arr, (>>^), (***)) import Control.Monad.State (get, put) import Data.Monoid (mempty, mappend) import qualified Data.Set as S@@ -58,16 +59,21 @@ tellCompilers :: (Binary a, Typeable a, Writable a)              => [(Identifier, Compiler () a)]              -> Rules-tellCompilers compilers = RulesM $ tell $ RuleSet mempty compilers' mempty+tellCompilers compilers = RulesM $ do+    -- We box the compilers so they have a more simple type, and we apply the+    -- current group to the corresponding identifiers+    group' <- rulesGroup <$> ask+    let compilers' = map (setGroup group' *** boxCompiler) compilers+    tell $ RuleSet mempty compilers' mempty   where-    compilers' = map (second boxCompiler) compilers     boxCompiler = (>>> arr compiledItem >>> arr CompileRule)  -- | Add resources -- tellResources :: [Resource]               -> Rules-tellResources resources = RulesM $ tell $ RuleSet mempty mempty $ S.fromList resources+tellResources resources = RulesM $ tell $+    RuleSet mempty mempty $ S.fromList resources  -- | Only compile/route items satisfying the given predicate --@@ -78,6 +84,42 @@         { rulesPattern = rulesPattern env `mappend` pattern         } +-- | Greate a group of compilers+--+-- Imagine you have a page that you want to render, but you also want the raw+-- content available on your site.+--+-- > match "test.markdown" $ do+-- >     route $ setExtension "html"+-- >     compile pageCompiler+-- >+-- > match "test.markdown" $ do+-- >     route idRoute+-- >     compile copyPageCompiler+--+-- Will of course conflict! In this case, Hakyll will pick the first matching+-- compiler (@pageCompiler@ in this case).+--+-- In case you want to have them both, you can use the 'group' function to+-- create a new group. For example,+--+-- > match "test.markdown" $ do+-- >     route $ setExtension "html"+-- >     compile pageCompiler+-- >+-- > group "raw" $ do+-- >     match "test.markdown" $ do+-- >         route idRoute+-- >         compile copyPageCompiler+--+-- This will put the compiler for the raw content in a separate group+-- (@\"raw\"@), which causes it to be compiled as well.+--+group :: String -> Rules -> Rules+group g = RulesM . local setGroup' . unRulesM+  where+    setGroup' env = env { rulesGroup = Just g }+ -- | Add a compilation rule to the rules. -- -- This instructs all resources to be compiled using the given compiler. When@@ -89,11 +131,11 @@ compile compiler = RulesM $ do     pattern <- rulesPattern <$> ask     provider <- rulesResourceProvider <$> ask-    let ids = filterMatches pattern $ map unResource $ resourceList provider+    let ids = filterMatches pattern $ map toIdentifier $ resourceList provider     unRulesM $ do         tellCompilers $ flip map ids $ \identifier ->-            (identifier, constA (Resource identifier) >>> compiler)-        tellResources $ map Resource ids+            (identifier, constA (fromIdentifier identifier) >>> compiler)+        tellResources $ map fromIdentifier ids                     -- | Add a compilation rule --@@ -112,8 +154,11 @@ -- route :: Routes -> Rules route route' = RulesM $ do+    -- We want the route only to be applied if we match the current pattern and+    -- group     pattern <- rulesPattern <$> ask-    unRulesM $ tellRoute $ matchRoute pattern route'+    group' <- rulesGroup <$> ask+    unRulesM $ tellRoute $ matchRoute (pattern `mappend` inGroup group') route'  -- | Apart from regular compilers, one is also able to specify metacompilers. -- Metacompilers are a special class of compilers: they are compilers which@@ -154,7 +199,7 @@     -- Create an identifier from the state     state <- get     let index = rulesMetaCompilerIndex state-        id' = fromCaptureString "Hakyll.Core.Rules.metaCompile/*" (show index)+        id' = fromCapture "Hakyll.Core.Rules.metaCompile/*" (show index)      -- Update the state with a new identifier     put $ state {rulesMetaCompilerIndex = index + 1}@@ -172,9 +217,16 @@                 -- ^ Compiler generating the other compilers                 -> Rules                 -- ^ Resulting rules-metaCompileWith identifier compiler = RulesM $ tell $-    RuleSet mempty compilers mempty-  where-    makeRule = MetaCompileRule . map (second box)-    compilers = [(identifier, compiler >>> arr makeRule )]-    box = (>>> fromDependency identifier >>^ CompileRule . compiledItem)+metaCompileWith identifier compiler = RulesM $ do+    group' <- rulesGroup <$> ask++    let -- Set the correct group on the identifier+        id' = setGroup group' identifier+        -- Function to box an item into a rule+        makeRule = MetaCompileRule . map (second box)+        -- Entire boxing function+        box = (>>> fromDependency id' >>^ CompileRule . compiledItem)+        -- Resulting compiler list+        compilers = [(id', compiler >>> arr makeRule )]++    tell $ RuleSet mempty compilers mempty
src/Hakyll/Core/Rules/Internal.hs view
@@ -17,6 +17,7 @@ import Control.Monad.State (State, evalState) import Data.Monoid (Monoid, mempty, mappend) import Data.Set (Set)+import qualified Data.Map as M  import Hakyll.Core.Resource import Hakyll.Core.Resource.Provider@@ -63,6 +64,7 @@ data RuleEnvironment = RuleEnvironment     { rulesResourceProvider :: ResourceProvider     , rulesPattern          :: Pattern+    , rulesGroup            :: Maybe String     }  -- | The monad used to compose rules@@ -79,10 +81,19 @@ -- | Run a Rules monad, resulting in a 'RuleSet' -- runRules :: Rules -> ResourceProvider -> RuleSet-runRules rules provider =+runRules rules provider = nubCompilers $     evalState (execWriterT $ runReaderT (unRulesM rules) env) state   where     state = RuleState {rulesMetaCompilerIndex = 0}     env = RuleEnvironment { rulesResourceProvider = provider                           , rulesPattern          = mempty+                          , rulesGroup            = Nothing                           }++-- | Remove duplicate compilers from the 'RuleSet'. When two compilers match an+-- item, we prefer the first one+--+nubCompilers :: RuleSet -> RuleSet+nubCompilers set = set { rulesCompilers = nubCompilers' (rulesCompilers set) }+  where+    nubCompilers' = M.toList . M.fromListWith (flip const)
src/Hakyll/Core/Run.hs view
@@ -10,12 +10,12 @@ import Control.Monad.Trans (liftIO) import Control.Applicative (Applicative, (<$>)) import Control.Monad.Reader (ReaderT, runReaderT, ask)-import Control.Monad.State.Strict (StateT, runStateT, get, modify)-import Control.Arrow ((&&&))+import Control.Monad.State.Strict (StateT, runStateT, get, put)+import Data.Map (Map) import qualified Data.Map as M import Data.Monoid (mempty, mappend)+import Data.Maybe (fromMaybe) import System.FilePath ((</>))-import Data.Set (Set) import qualified Data.Set as S  import Hakyll.Core.Routes@@ -28,7 +28,7 @@ import Hakyll.Core.Resource.Provider.File import Hakyll.Core.Rules.Internal import Hakyll.Core.DirectedGraph-import Hakyll.Core.DirectedGraph.DependencySolver+import Hakyll.Core.DependencyAnalyzer import Hakyll.Core.Writable import Hakyll.Core.Store import Hakyll.Core.Configuration@@ -46,36 +46,37 @@     provider <- timed logger "Creating provider" $         fileResourceProvider configuration +    -- Fetch the old graph from the store+    oldGraph <- fromMaybe mempty <$>+        storeGet store "Hakyll.Core.Run.run" "dependencies"+     let ruleSet = runRules rules provider         compilers = rulesCompilers ruleSet          -- Extract the reader/state-        reader = unRuntime $ addNewCompilers [] compilers-        stateT = runReaderT reader $ env logger ruleSet provider store+        reader = unRuntime $ addNewCompilers compilers+        stateT = runReaderT reader $ RuntimeEnvironment+                    { hakyllLogger           = logger+                    , hakyllConfiguration    = configuration+                    , hakyllRoutes           = rulesRoutes ruleSet+                    , hakyllResourceProvider = provider+                    , hakyllStore            = store+                    }      -- Run the program and fetch the resulting state-    ((), state') <- runStateT stateT state+    ((), state') <- runStateT stateT $ RuntimeState+        { hakyllAnalyzer  = makeDependencyAnalyzer mempty (const False) oldGraph+        , hakyllCompilers = M.empty+        }      -- We want to save the final dependency graph for the next run-    storeSet store "Hakyll.Core.Run.run" "dependencies" $ hakyllGraph state'+    storeSet store "Hakyll.Core.Run.run" "dependencies" $+        analyzerGraph $ hakyllAnalyzer state'      -- Flush and return     flushLogger logger     return ruleSet-  where-    env logger ruleSet provider store = RuntimeEnvironment-        { hakyllLogger           = logger-        , hakyllConfiguration    = configuration-        , hakyllRoutes           = rulesRoutes ruleSet-        , hakyllResourceProvider = provider-        , hakyllStore            = store-        } -    state = RuntimeState-        { hakyllModified = S.empty-        , hakyllGraph    = mempty-        }- data RuntimeEnvironment = RuntimeEnvironment     { hakyllLogger           :: Logger     , hakyllConfiguration    :: HakyllConfiguration@@ -85,109 +86,90 @@     }  data RuntimeState = RuntimeState-    { hakyllModified :: Set Identifier-    , hakyllGraph    :: DirectedGraph Identifier+    { hakyllAnalyzer  :: DependencyAnalyzer Identifier+    , hakyllCompilers :: Map Identifier (Compiler () CompileRule)     }  newtype Runtime a = Runtime     { unRuntime :: ReaderT RuntimeEnvironment (StateT RuntimeState IO) a     } deriving (Functor, Applicative, Monad) --- | Return a set of modified identifiers----modified :: ResourceProvider     -- ^ Resource provider-         -> Store                -- ^ Store-         -> [Identifier]         -- ^ Identifiers to check-         -> IO (Set Identifier)  -- ^ Modified resources-modified provider store ids = fmap S.fromList $ flip filterM ids $ \id' ->-    resourceModified provider (Resource id') store- -- | Add a number of compilers and continue using these compilers -- addNewCompilers :: [(Identifier, Compiler () CompileRule)]-                -- ^ Remaining compilers yet to be run-                -> [(Identifier, Compiler () CompileRule)]                 -- ^ Compilers to add                 -> Runtime ()-addNewCompilers oldCompilers newCompilers = Runtime $ do+addNewCompilers newCompilers = Runtime $ do     -- Get some information     logger <- hakyllLogger <$> ask     section logger "Adding new compilers"     provider <- hakyllResourceProvider <$> ask     store <- hakyllStore <$> ask -    let -- All compilers-        compilers = oldCompilers ++ newCompilers+    -- Old state information+    oldCompilers <- hakyllCompilers <$> get+    oldAnalyzer <- hakyllAnalyzer <$> get -        -- Get all dependencies for the compilers-        dependencies = flip map compilers $ \(id', compiler) ->-            let deps = runCompilerDependencies compiler id' provider-            in (id', deps)+    let -- All known compilers+        universe = M.keys oldCompilers ++ map fst newCompilers -        -- Create a compiler map (Id -> Compiler)-        compilerMap = M.fromList compilers+        -- Create a new partial dependency graph+        dependencies = flip map newCompilers $ \(id', compiler) ->+            let deps = runCompilerDependencies compiler id' universe+            in (id', deps)          -- Create the dependency graph-        currentGraph = fromList dependencies--    -- Find the old graph and append the new graph to it. This forms the-    -- complete graph-    completeGraph <- timed logger "Creating graph" $-        mappend currentGraph . hakyllGraph <$> get+        newGraph = fromList dependencies -    orderedCompilers <- timed logger "Solving dependencies" $ do-        -- Check which items are up-to-date. This only needs to happen for the new-        -- compilers-        oldModified <- hakyllModified <$> get -        newModified <- liftIO $ modified provider store $ map fst newCompilers+    -- Check which items have been modified+    modified <- fmap S.fromList $ flip filterM (map fst newCompilers) $+        liftIO . resourceModified provider store . fromIdentifier -        let modified' = oldModified `S.union` newModified-            -            -- Find obsolete items. Every item that is reachable from a modified-            -- item is considered obsolete. From these obsolete items, we are only-            -- interested in ones that are in the current subgraph.-            obsolete = S.filter (`member` currentGraph)-                     $ reachableNodes modified' $ reverse completeGraph+    -- Create a new analyzer and append it to the currect one+    let newAnalyzer = makeDependencyAnalyzer newGraph (`S.member` modified) $+            analyzerPreviousGraph oldAnalyzer+        analyzer = mappend oldAnalyzer newAnalyzer -            -- Solve the graph and retain only the obsolete items-            ordered = filter (`S.member` obsolete) $ solveDependencies currentGraph+    -- Update the state+    put $ RuntimeState+        { hakyllAnalyzer  = analyzer+        , hakyllCompilers = M.union oldCompilers (M.fromList newCompilers)+        } -        -- Update the state-        modify $ updateState modified' completeGraph+    -- Continue+    unRuntime stepAnalyzer -        -- Join the order with the compilers again-        return $ map (id &&& (compilerMap M.!)) ordered+stepAnalyzer :: Runtime ()+stepAnalyzer = Runtime $ do+    -- Step the analyzer+    state <- get+    let (signal, analyzer') = step $ hakyllAnalyzer state+    put $ state { hakyllAnalyzer = analyzer' } -    -- Now run the ordered list of compilers-    unRuntime $ runCompilers orderedCompilers-  where-    -- Add the modified information for the new compilers-    updateState modified' graph state = state-        { hakyllModified = modified'-        , hakyllGraph    = graph-        }+    case signal of Done      -> return ()+                   Cycle _   -> return ()+                   Build id' -> unRuntime $ build id' -runCompilers :: [(Identifier, Compiler () CompileRule)]-             -- ^ Ordered list of compilers-             -> Runtime ()-             -- ^ No result-runCompilers [] = return ()-runCompilers ((id', compiler) : compilers) = Runtime $ do-    -- Obtain information+build :: Identifier -> Runtime ()+build id' = Runtime $ do     logger <- hakyllLogger <$> ask     routes <- hakyllRoutes <$> ask     provider <- hakyllResourceProvider <$> ask     store <- hakyllStore <$> ask-    modified' <- hakyllModified <$> get+    compilers <- hakyllCompilers <$> get      section logger $ "Compiling " ++ show id' -    let -- Check if the resource was modified-        isModified = id' `S.member` modified'+    -- Fetch the right compiler from the map+    let compiler = compilers M.! id' +    -- Check if the resource was modified+    isModified <- liftIO $ resourceModified provider store $ fromIdentifier id'+     -- Run the compiler     result <- timed logger "Total compile time" $ liftIO $-        runCompiler compiler id' provider routes store isModified logger+        runCompiler compiler id' provider (M.keys compilers) routes+                    store isModified logger      case result of         -- Compile rule for one item, easy stuff@@ -202,14 +184,14 @@                     liftIO $ write path compiled              -- Continue for the remaining compilers-            unRuntime $ runCompilers compilers +            unRuntime stepAnalyzer          -- Metacompiler, slightly more complicated         Right (MetaCompileRule newCompilers) ->             -- Actually I was just kidding, it's not hard at all-            unRuntime $ addNewCompilers compilers newCompilers+            unRuntime $ addNewCompilers newCompilers          -- Some error happened, log and continue         Left err -> do             thrown logger err -            unRuntime $ runCompilers compilers+            unRuntime stepAnalyzer
src/Hakyll/Core/Store.hs view
@@ -11,6 +11,7 @@ import Control.Concurrent.MVar (MVar, newMVar, readMVar, modifyMVar_) import System.FilePath ((</>)) import System.Directory (doesFileExist)+import Data.Maybe (fromMaybe) import Data.Map (Map) import qualified Data.Map as M @@ -52,8 +53,10 @@ -- | Create a path -- makePath :: Store -> String -> Identifier -> FilePath-makePath store name identifier =-    storeDirectory store </> name </> toFilePath identifier </> "hakyllstore"+makePath store name identifier = storeDirectory store </> name+    </> group </> toFilePath identifier </> "hakyllstore"+  where+    group = fromMaybe "" $ identifierGroup identifier  -- | Store an item --