@@ -1,6 +1,7 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2-- | Internally used compiler module
2-- | Internally used compiler module
3{-# LANGUAGE CPP #-}
3{-# LANGUAGE CPP #-}
4+{-# LANGUAGE DeriveFunctor #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE GADTs #-}
6{-# LANGUAGE GADTs #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7{-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -10,19 +11,26 @@ module Hakyll.Core.Compiler.Internal
11 , CompilerRead (..)
12 , CompilerRead (..)
12 , CompilerWrite (..)
13 , CompilerWrite (..)
14+ , CompilerErrors (..)
13 , CompilerResult (..)
15 , CompilerResult (..)
14 , Compiler (..)
16 , Compiler (..)
15 , runCompiler
17 , runCompiler
17 -- * Core operations
19 -- * Core operations
18 , compilerTell
21 , compilerTell
19 , compilerAsk
22 , compilerAsk
25+ -- * Error operations
20 , compilerThrow
26 , compilerThrow
21 , compilerCatch
28 , compilerCatch
22− , compilerResult
29+ , compilerTry
23− , compilerUnsafeIO
30+ , compilerErrorMessages
25 -- * Utilities
32 -- * Utilities
33+ , compilerDebugEntries
26 , compilerTellDependencies
34 , compilerTellDependencies
27 , compilerTellCacheHits
35 , compilerTellCacheHits
@@ -32,7 +40,9 @@ module Hakyll.Core.Compiler.Internal
32import Control.Applicative (Alternative (..))
40import Control.Applicative (Alternative (..))
33import Control.Exception (SomeException, handle)
41import Control.Exception (SomeException, handle)
34import Control.Monad (forM_)
42import Control.Monad (forM_)
35−import Control.Monad.Except (MonadError (..))
43+import Control.Monad.Except (MonadError (..))
44+import Data.List.NonEmpty (NonEmpty (..))
45+import qualified Data.List.NonEmpty as NonEmpty
36#if MIN_VERSION_base(4,9,0)
46#if MIN_VERSION_base(4,9,0)
37import Data.Semigroup (Semigroup (..))
47import Data.Semigroup (Semigroup (..))
@@ -45,7 +55,6 @@ import Hakyll.Core.Configuration
45import Hakyll.Core.Dependencies
55import Hakyll.Core.Dependencies
46import Hakyll.Core.Identifier
56import Hakyll.Core.Identifier
47import Hakyll.Core.Identifier.Pattern
57import Hakyll.Core.Identifier.Pattern
48−import Hakyll.Core.Logger (Logger)
49import qualified Hakyll.Core.Logger as Logger
58import qualified Hakyll.Core.Logger as Logger
50import Hakyll.Core.Metadata
59import Hakyll.Core.Metadata
51import Hakyll.Core.Provider
60import Hakyll.Core.Provider
@@ -75,7 +84,7 @@ data CompilerRead = CompilerRead
75 , -- | Compiler store
84 , -- | Compiler store
76 compilerStore :: Store
85 compilerStore :: Store
77 , -- | Logger
86 , -- | Logger
78− compilerLogger :: Logger
87+ compilerLogger :: Logger.Logger
@@ -104,11 +113,29 @@ instance Monoid CompilerWrite where
106--------------------------------------------------------------------------------
115--------------------------------------------------------------------------------
107−data CompilerResult a where
116+-- | Distinguishes reasons in a 'CompilerError'
117+data CompilerErrors a
118+ -- | One or more exceptions occured during compilation
119+ = CompilationFailure (NonEmpty a)
120+ -- | Absence of any result, most notably in template contexts. May still
121+ -- have error messages.
122+ | CompilationNoResult [a]
126+-- | Unwrap a `CompilerErrors`
127+compilerErrorMessages :: CompilerErrors a -> [a]
128+compilerErrorMessages (CompilationFailure x) = NonEmpty.toList x
129+compilerErrorMessages (CompilationNoResult x) = x
132+--------------------------------------------------------------------------------
133+-- | An intermediate result of a compilation step
134+data CompilerResult a
108− CompilerDone :: a -> CompilerWrite -> CompilerResult a
135+ = CompilerDone a CompilerWrite
109− CompilerSnapshot :: Snapshot -> Compiler a -> CompilerResult a
136+ | CompilerSnapshot Snapshot (Compiler a)
110− CompilerError :: [String] -> CompilerResult a
111− CompilerRequire :: (Identifier, Snapshot) -> Compiler a -> CompilerResult a
137+ | CompilerRequire (Identifier, Snapshot) (Compiler a)
138+ | CompilerError (CompilerErrors String)
114--------------------------------------------------------------------------------
141--------------------------------------------------------------------------------
@@ -126,14 +153,14 @@ instance Functor Compiler where
126 return $ case res of
153 return $ case res of
127 CompilerDone x w -> CompilerDone (f x) w
154 CompilerDone x w -> CompilerDone (f x) w
128 CompilerSnapshot s c' -> CompilerSnapshot s (fmap f c')
155 CompilerSnapshot s c' -> CompilerSnapshot s (fmap f c')
129− CompilerError e -> CompilerError e
130 CompilerRequire i c' -> CompilerRequire i (fmap f c')
156 CompilerRequire i c' -> CompilerRequire i (fmap f c')
157+ CompilerError e -> CompilerError e
131 {-# INLINE fmap #-}
158 {-# INLINE fmap #-}
134--------------------------------------------------------------------------------
161--------------------------------------------------------------------------------
135instance Monad Compiler where
162instance Monad Compiler where
136− return x = Compiler $ \_ -> return $ CompilerDone x mempty
163+ return x = compilerResult $ CompilerDone x mempty
137 {-# INLINE return #-}
164 {-# INLINE return #-}
139 Compiler c >>= f = Compiler $ \r -> do
166 Compiler c >>= f = Compiler $ \r -> do
@@ -146,14 +173,14 @@ instance Monad Compiler where
146 CompilerSnapshot s c' -> CompilerSnapshot s $ do
173 CompilerSnapshot s c' -> CompilerSnapshot s $ do
147 compilerTell w -- Save dependencies!
174 compilerTell w -- Save dependencies!
149− CompilerError e -> CompilerError e
150 CompilerRequire i c' -> CompilerRequire i $ do
176 CompilerRequire i c' -> CompilerRequire i $ do
151 compilerTell w -- Save dependencies!
177 compilerTell w -- Save dependencies!
179+ CompilerError e -> CompilerError e
154 CompilerSnapshot s c' -> return $ CompilerSnapshot s (c' >>= f)
181 CompilerSnapshot s c' -> return $ CompilerSnapshot s (c' >>= f)
155− CompilerError e -> return $ CompilerError e
156 CompilerRequire i c' -> return $ CompilerRequire i (c' >>= f)
182 CompilerRequire i c' -> return $ CompilerRequire i (c' >>= f)
183+ CompilerError e -> return $ CompilerError e
157 {-# INLINE (>>=) #-}
184 {-# INLINE (>>=) #-}
159 fail = compilerThrow . return
186 fail = compilerThrow . return
@@ -170,87 +197,145 @@ instance Applicative Compiler where
172--------------------------------------------------------------------------------
199--------------------------------------------------------------------------------
200+-- | Access provided metadata from anywhere
173instance MonadMetadata Compiler where
201instance MonadMetadata Compiler where
174 getMetadata = compilerGetMetadata
202 getMetadata = compilerGetMetadata
175 getMatches = compilerGetMatches
203 getMatches = compilerGetMatches
178--------------------------------------------------------------------------------
206--------------------------------------------------------------------------------
207+-- | Compilation may fail with multiple error messages.
208+-- 'catchError' handles errors from 'throwError', 'fail' and 'Hakyll.Core.Compiler.noResult'
179instance MonadError [String] Compiler where
209instance MonadError [String] Compiler where
180− throwError = compilerThrow
210+ throwError = compilerThrow
181− catchError = compilerCatch
211+ catchError c = compilerCatch c . (. compilerErrorMessages)
184--------------------------------------------------------------------------------
214--------------------------------------------------------------------------------
215+-- | Like 'unCompiler' but treating IO exceptions as 'CompilerError's
185runCompiler :: Compiler a -> CompilerRead -> IO (CompilerResult a)
216runCompiler :: Compiler a -> CompilerRead -> IO (CompilerResult a)
186runCompiler compiler read' = handle handler $ unCompiler compiler read'
217runCompiler compiler read' = handle handler $ unCompiler compiler read'
188 handler :: SomeException -> IO (CompilerResult a)
219 handler :: SomeException -> IO (CompilerResult a)
189− handler e = return $ CompilerError [show e]
220+ handler e = return $ CompilerError $ CompilationFailure $ show e :| []
192--------------------------------------------------------------------------------
223--------------------------------------------------------------------------------
224+-- | Trying alternative compilers if the first fails, regardless whether through
225+-- 'fail', 'throwError' or 'Hakyll.Core.Compiler.noResult'.
226+-- Aggregates error messages if all fail.
193instance Alternative Compiler where
227instance Alternative Compiler where
194− empty = compilerThrow []
228+ empty = compilerNoResult []
195− x <|> y = compilerCatch x $ \es -> do
229+ x <|> y = x `compilerCatch` (\rx -> y `compilerCatch` (\ry ->
196− logger <- compilerLogger <$> compilerAsk
230+ case (rx, ry) of
197− forM_ es $ \e -> compilerUnsafeIO $ Logger.debug logger $
231+ (CompilationFailure xs, CompilationFailure ys) ->
232+ compilerThrow $ NonEmpty.toList xs ++ NonEmpty.toList ys
233+ (CompilationFailure xs, CompilationNoResult ys) ->
234+ debug ys >> compilerThrow (NonEmpty.toList xs)
235+ (CompilationNoResult xs, CompilationFailure ys) ->
236+ debug xs >> compilerThrow (NonEmpty.toList ys)
237+ (CompilationNoResult xs, CompilationNoResult ys) -> compilerNoResult $ xs ++ ys
198− "Hakyll.Core.Compiler.Internal: Alternative failed: " ++ e
240+ debug = compilerDebugEntries "Hakyll.Core.Compiler.Internal: Alternative fail suppressed"
200 {-# INLINE (<|>) #-}
241 {-# INLINE (<|>) #-}
203--------------------------------------------------------------------------------
244--------------------------------------------------------------------------------
245+-- | Put the result back in a compiler
246+compilerResult :: CompilerResult a -> Compiler a
247+compilerResult x = Compiler $ \_ -> return x
248+{-# INLINE compilerResult #-}
251+--------------------------------------------------------------------------------
252+-- | Get the current environment
204compilerAsk :: Compiler CompilerRead
253compilerAsk :: Compiler CompilerRead
205compilerAsk = Compiler $ \r -> return $ CompilerDone r mempty
254compilerAsk = Compiler $ \r -> return $ CompilerDone r mempty
206{-# INLINE compilerAsk #-}
255{-# INLINE compilerAsk #-}
209--------------------------------------------------------------------------------
258--------------------------------------------------------------------------------
259+-- | Put a 'CompilerWrite'
210compilerTell :: CompilerWrite -> Compiler ()
260compilerTell :: CompilerWrite -> Compiler ()
211−compilerTell deps = Compiler $ \_ -> return $ CompilerDone () deps
261+compilerTell = compilerResult . CompilerDone ()
212{-# INLINE compilerTell #-}
262{-# INLINE compilerTell #-}
215--------------------------------------------------------------------------------
265--------------------------------------------------------------------------------
266+-- | Run an IO computation without dependencies in a Compiler
267+compilerUnsafeIO :: IO a -> Compiler a
268+compilerUnsafeIO io = Compiler $ \_ -> do
270+ return $ CompilerDone x mempty
271+{-# INLINE compilerUnsafeIO #-}
274+--------------------------------------------------------------------------------
275+-- | Throw errors in the 'Compiler'.
277+-- If no messages are given, this is considered a 'CompilationNoResult' error.
278+-- Otherwise, it is treated as a proper compilation failure.
216compilerThrow :: [String] -> Compiler a
279compilerThrow :: [String] -> Compiler a
217−compilerThrow es = Compiler $ \_ -> return $ CompilerError es
280+compilerThrow = compilerResult . CompilerError .
218−{-# INLINE compilerThrow #-}
281+ maybe (CompilationNoResult []) CompilationFailure .
284+-- | Put a 'CompilerError' with multiple messages as 'CompilationNoResult'
285+compilerNoResult :: [String] -> Compiler a
286+compilerNoResult = compilerResult . CompilerError . CompilationNoResult
221--------------------------------------------------------------------------------
289--------------------------------------------------------------------------------
222−compilerCatch :: Compiler a -> ([String] -> Compiler a) -> Compiler a
290+-- | Allows to distinguish 'CompilerError's and branch on them with 'Either'
292+-- prop> compilerTry = (`compilerCatch` return . Left) . fmap Right
293+compilerTry :: Compiler a -> Compiler (Either (CompilerErrors String) a)
294+compilerTry (Compiler x) = Compiler $ \r -> do
297+ CompilerDone res' w -> return (CompilerDone (Right res') w)
298+ CompilerSnapshot s c -> return (CompilerSnapshot s (compilerTry c))
299+ CompilerRequire i c -> return (CompilerRequire i (compilerTry c))
300+ CompilerError e -> return (CompilerDone (Left e) mempty)
301+{-# INLINE compilerTry #-}
304+--------------------------------------------------------------------------------
305+-- | Allows you to recover from 'CompilerError's.
306+-- Uses the same parameter order as 'catchError' so that it can be used infix.
308+-- prop> c `compilerCatch` f = compilerTry c >>= either f return
309+compilerCatch :: Compiler a -> (CompilerErrors String -> Compiler a) -> Compiler a
223compilerCatch (Compiler x) f = Compiler $ \r -> do
310compilerCatch (Compiler x) f = Compiler $ \r -> do
224 res <- x r
311 res <- x r
225 case res of
312 case res of
226 CompilerDone res' w -> return (CompilerDone res' w)
313 CompilerDone res' w -> return (CompilerDone res' w)
227 CompilerSnapshot s c -> return (CompilerSnapshot s (compilerCatch c f))
314 CompilerSnapshot s c -> return (CompilerSnapshot s (compilerCatch c f))
228− CompilerError e -> unCompiler (f e) r
229 CompilerRequire i c -> return (CompilerRequire i (compilerCatch c f))
315 CompilerRequire i c -> return (CompilerRequire i (compilerCatch c f))
316+ CompilerError e -> unCompiler (f e) r
230{-# INLINE compilerCatch #-}
317{-# INLINE compilerCatch #-}
233--------------------------------------------------------------------------------
320--------------------------------------------------------------------------------
234−-- | Put the result back in a compiler
321+compilerDebugLog :: [String] -> Compiler ()
235−compilerResult :: CompilerResult a -> Compiler a
322+compilerDebugLog ms = do
236−compilerResult x = Compiler $ \_ -> return x
323+ logger <- compilerLogger <$> compilerAsk
237−{-# INLINE compilerResult #-}
324+ compilerUnsafeIO $ forM_ ms $ Logger.debug logger
240--------------------------------------------------------------------------------
326--------------------------------------------------------------------------------
241−compilerUnsafeIO :: IO a -> Compiler a
327+-- | Pass a list of messages with a heading to the debug logger
242−compilerUnsafeIO io = Compiler $ \_ -> do
328+compilerDebugEntries :: String -> [String] -> Compiler ()
243− x <- io
329+compilerDebugEntries msg = compilerDebugLog . (msg:) . map indent
244− return $ CompilerDone x mempty
330+ where
245−{-# INLINE compilerUnsafeIO #-}
331+ indent = unlines . map (" "++) . lines
248--------------------------------------------------------------------------------
334--------------------------------------------------------------------------------
249compilerTellDependencies :: [Dependency] -> Compiler ()
335compilerTellDependencies :: [Dependency] -> Compiler ()
250compilerTellDependencies ds = do
336compilerTellDependencies ds = do
251− logger <- compilerLogger <$> compilerAsk
337+ compilerDebugLog $ map (\d ->
252− forM_ ds $ \d -> compilerUnsafeIO $ Logger.debug logger $
253− "Hakyll.Core.Compiler.Internal: Adding dependency: " ++ show d
338+ "Hakyll.Core.Compiler.Internal: Adding dependency: " ++ show d) ds
254 compilerTell mempty {compilerDependencies = ds}
339 compilerTell mempty {compilerDependencies = ds}
255{-# INLINE compilerTellDependencies #-}
340{-# INLINE compilerTellDependencies #-}