on de01fdf help sign in
hakyll/src/Hakyll/Core/Compiler.hs
373 lines 13 kB history raw
1-- | A Compiler manages targets and dependencies between targets
2--
3-- The most distinguishing property of a 'Compiler' is that it is an Arrow. A
4-- compiler of the type @Compiler a b@ is simply a compilation phase which takes
5-- an @a@ as input, and produces a @b@ as output.
6--
7-- Compilers are chained using the '>>>' arrow operation. If we have a compiler
8--
9-- > getResourceString :: Compiler Resource String
10--
11-- which reads the resource, and a compiler
12--
13-- > readPage :: Compiler String (Page String)
14--
15-- we can chain these two compilers to get a
16--
17-- > (getResourceString >>> readPage) :: Compiler Resource (Page String)
18--
19-- Most compilers can be created by combining smaller compilers using '>>>'.
20--
21-- More advanced constructions are also possible using arrow, and sometimes
22-- these are needed. For a good introduction to arrow, you can refer to
23--
24-- <http://en.wikibooks.org/wiki/Haskell/Understanding_arrows>
25--
26-- A construction worth writing a few paragraphs about here are the 'require'
27-- functions. Different variants of this function are exported here, but they
28-- all serve more or less the same goal.
29--
30-- When you use only '>>>' to chain your compilers, you get a linear pipeline --
31-- it is not possible to add extra items from other compilers along the way.
32-- This is where the 'require' functions come in.
33--
34-- This function allows you to reference other items, which are then added to
35-- the pipeline. Let's look at this crappy ASCII illustration which represents
36-- a pretty common scenario:
37--
38-- > read resource >>> pandoc render >>> layout >>> relativize URL's
39-- >
40-- > @templates/fancy.html@
41--
42-- We want to construct a pipeline of compilers to go from our resource to a
43-- proper webpage. However, the @layout@ compiler takes more than just the
44-- rendered page as input: it needs the @templates/fancy.html@ template as well.
45--
46-- This is an example of where we need the @require@ function. We can solve
47-- this using a construction that looks like:
48--
49-- > ... >>> pandoc render >>> require >>> layout >>> ...
50-- > |
51-- > @templates/fancy.html@ ------/
52--
53-- This illustration can help us understand the type signature of 'require'.
54--
55-- > require :: (Binary a, Typeable a, Writable a)
56-- > => Identifier a
57-- > -> (b -> a -> c)
58-- > -> Compiler b c
59--
60-- Let's look at it in detail:
61--
62-- > (Binary a, Typeable a, Writable a)
63--
64-- These are constraints for the @a@ type. @a@ (the template) needs to have
65-- certain properties for it to be required.
66--
67-- > Identifier a
68--
69-- This is simply @templates/fancy.html@: the 'Identifier' of the item we want
70-- to 'require', in other words, the name of the item we want to add to the
71-- pipeline somehow.
72--
73-- > (b -> a -> c)
74--
75-- This is a function given by the user, specifying /how/ the two items shall be
76-- merged. @b@ is the output of the previous compiler, and @a@ is the item we
77-- just required -- the template. This means @c@ will be the final output of the
78-- 'require' combinator.
79--
80-- > Compiler b c
81--
82-- Indeed, we have now constructed a compiler which takes a @b@ and produces a
83-- @c@. This means that we have a linear pipeline again, thanks to the 'require'
84-- function. So, the 'require' function actually helps to reduce to complexity
85-- of Hakyll applications!
86--
87-- Note that require will fetch a previously compiled item: in our example of
88-- the type @a@. It is /very/ important that the compiler which produced this
89-- value, produced the right type as well!
90--
91{-# LANGUAGE GeneralizedNewtypeDeriving #-}
92module Hakyll.Core.Compiler
93 ( Compiler
94 , runCompiler
95 , getIdentifier
96 , getResource
97 , getRoute
98 , getRouteFor
99 , getResourceString
100 , getResourceLBS
101 , fromDependency
102 , require_
103 , require
104 , requireA
105 , requireAll_
106 , requireAll
107 , requireAllA
108 , cached
109 , unsafeCompiler
110 , traceShowCompiler
111 , mapCompiler
112 , timedCompiler
113 , byExtension
114 ) where
116import Prelude hiding ((.), id)
117import Control.Arrow ((>>>), (&&&), arr)
118import Control.Applicative ((<$>))
119import Control.Monad.Reader (ask)
120import Control.Monad.Trans (liftIO)
121import Control.Monad.Error (throwError)
122import Control.Category (Category, (.), id)
123import Data.Maybe (fromMaybe)
124import System.FilePath (takeExtension)
126import Data.Binary (Binary)
127import Data.Typeable (Typeable)
128import Data.ByteString.Lazy (ByteString)
130import Hakyll.Core.Identifier
131import Hakyll.Core.Identifier.Pattern
132import Hakyll.Core.CompiledItem
133import Hakyll.Core.Writable
134import Hakyll.Core.Resource
135import Hakyll.Core.Resource.Provider
136import Hakyll.Core.Compiler.Internal
137import Hakyll.Core.Store
138import Hakyll.Core.Rules.Internal
139import Hakyll.Core.Routes
140import Hakyll.Core.Logger
142-- | Run a compiler, yielding the resulting target and it's dependencies. This
143-- version of 'runCompilerJob' also stores the result
144--
145runCompiler :: Compiler () CompileRule -- ^ Compiler to run
146 -> Identifier () -- ^ Target identifier
147 -> ResourceProvider -- ^ Resource provider
148 -> [Identifier ()] -- ^ Universe
149 -> Routes -- ^ Route
150 -> Store -- ^ Store
151 -> Bool -- ^ Was the resource modified?
152 -> Logger -- ^ Logger
153 -> IO (Throwing CompileRule) -- ^ Resulting item
154runCompiler compiler id' provider universe routes store modified logger = do
155 -- Run the compiler job
156 result <- runCompilerJob compiler id' provider universe
157 routes store modified logger
159 -- Inspect the result
160 case result of
161 -- In case we compiled an item, we will store a copy in the cache first,
162 -- before we return control. This makes sure the compiled item can later
163 -- be accessed by e.g. require.
164 Right (CompileRule (CompiledItem x)) ->
165 storeSet store "Hakyll.Core.Compiler.runCompiler"
166 (castIdentifier id') x
168 -- Otherwise, we do nothing here
169 _ -> return ()
171 return result
173-- | Get the identifier of the item that is currently being compiled
174--
175getIdentifier :: Compiler a (Identifier b)
176getIdentifier = fromJob $ const $ CompilerM $
177 castIdentifier . compilerIdentifier <$> ask
179-- | Get the resource that is currently being compiled
180--
181getResource :: Compiler a Resource
182getResource = getIdentifier >>> arr fromIdentifier
184-- | Get the route we are using for this item
185--
186getRoute :: Compiler a (Maybe FilePath)
187getRoute = getIdentifier >>> getRouteFor
189-- | Get the route for a specified item
190--
191getRouteFor :: Compiler (Identifier a) (Maybe FilePath)
192getRouteFor = fromJob $ \identifier -> CompilerM $ do
193 routes <- compilerRoutes <$> ask
194 return $ runRoutes routes identifier
196-- | Get the resource we are compiling as a string
197--
198getResourceString :: Compiler Resource String
199getResourceString = getResourceWith resourceString
201-- | Get the resource we are compiling as a lazy bytestring
202--
203getResourceLBS :: Compiler Resource ByteString
204getResourceLBS = getResourceWith resourceLBS
206-- | Overloadable function for 'getResourceString' and 'getResourceLBS'
207--
208getResourceWith :: (ResourceProvider -> Resource -> IO a)
209 -> Compiler Resource a
210getResourceWith reader = fromJob $ \resource -> CompilerM $ do
211 let identifier = unResource resource
212 provider <- compilerResourceProvider <$> ask
213 if resourceExists provider resource
214 then liftIO $ reader provider resource
215 else throwError $ error' identifier
216 where
217 error' id' = "Hakyll.Core.Compiler.getResourceWith: resource "
218 ++ show id' ++ " not found"
220-- | Auxiliary: get a dependency
221--
222getDependency :: (Binary a, Writable a, Typeable a)
223 => Identifier a -> CompilerM a
224getDependency id' = CompilerM $ do
225 store <- compilerStore <$> ask
226 result <- liftIO $ storeGet store "Hakyll.Core.Compiler.runCompiler" id'
227 case result of
228 NotFound -> throwError notFound
229 WrongType e r -> throwError $ wrongType e r
230 Found x -> return x
231 where
232 notFound =
233 "Hakyll.Core.Compiler.getDependency: " ++ show id' ++ " was " ++
234 "not found in the cache, the cache might be corrupted or " ++
235 "the item you are referring to might not exist"
236 wrongType e r =
237 "Hakyll.Core.Compiler.getDependency: " ++ show id' ++ " was found " ++
238 "in the cache, but does not have the right type: expected " ++ show e ++
239 " but got " ++ show r
241-- | Variant of 'require' which drops the current value
242--
243require_ :: (Binary a, Typeable a, Writable a)
244 => Identifier a
245 -> Compiler b a
246require_ identifier =
247 fromDependency identifier >>> fromJob (const $ getDependency identifier)
249-- | Require another target. Using this function ensures automatic handling of
250-- dependencies
251--
252require :: (Binary a, Typeable a, Writable a)
253 => Identifier a
254 -> (b -> a -> c)
255 -> Compiler b c
256require identifier = requireA identifier . arr . uncurry
258-- | Arrow-based variant of 'require'
259--
260requireA :: (Binary a, Typeable a, Writable a)
261 => Identifier a
262 -> Compiler (b, a) c
263 -> Compiler b c
264requireA identifier = (id &&& require_ identifier >>>)
266-- | Variant of 'requireAll' which drops the current value
267--
268requireAll_ :: (Binary a, Typeable a, Writable a)
269 => Pattern a
270 -> Compiler b [a]
271requireAll_ pattern = fromDependencies (const getDeps) >>> fromJob requireAll_'
272 where
273 getDeps = map castIdentifier . filterMatches pattern . map castIdentifier
274 requireAll_' = const $ CompilerM $ do
275 deps <- getDeps . compilerUniverse <$> ask
276 mapM (unCompilerM . getDependency) deps
278-- | Require a number of targets. Using this function ensures automatic handling
279-- of dependencies
280--
281requireAll :: (Binary a, Typeable a, Writable a)
282 => Pattern a
283 -> (b -> [a] -> c)
284 -> Compiler b c
285requireAll pattern = requireAllA pattern . arr . uncurry
287-- | Arrow-based variant of 'requireAll'
288--
289requireAllA :: (Binary a, Typeable a, Writable a)
290 => Pattern a
291 -> Compiler (b, [a]) c
292 -> Compiler b c
293requireAllA pattern = (id &&& requireAll_ pattern >>>)
295cached :: (Binary a, Typeable a, Writable a)
296 => String
297 -> Compiler Resource a
298 -> Compiler Resource a
299cached name (Compiler d j) = Compiler d $ const $ CompilerM $ do
300 logger <- compilerLogger <$> ask
301 identifier <- castIdentifier . compilerIdentifier <$> ask
302 store <- compilerStore <$> ask
303 modified <- compilerResourceModified <$> ask
304 report logger $ "Checking cache: " ++ if modified then "modified" else "OK"
305 if modified
306 then do v <- unCompilerM $ j $ fromIdentifier identifier
307 liftIO $ storeSet store name identifier v
308 return v
309 else do v <- liftIO $ storeGet store name identifier
310 case v of Found v' -> return v'
311 _ -> throwError error'
312 where
313 error' = "Hakyll.Core.Compiler.cached: Cache corrupt!"
315-- | Create an unsafe compiler from a function in IO
316--
317unsafeCompiler :: (a -> IO b) -- ^ Function to lift
318 -> Compiler a b -- ^ Resulting compiler
319unsafeCompiler f = fromJob $ CompilerM . liftIO . f
321-- | Compiler for debugging purposes
322--
323traceShowCompiler :: Show a => Compiler a a
324traceShowCompiler = fromJob $ \x -> CompilerM $ do
325 logger <- compilerLogger <$> ask
326 report logger $ show x
327 return x
329-- | Map over a compiler
330--
331mapCompiler :: Compiler a b
332 -> Compiler [a] [b]
333mapCompiler (Compiler d j) = Compiler d $ mapM j
335-- | Log and time a compiler
336--
337timedCompiler :: String -- ^ Message
338 -> Compiler a b -- ^ Compiler to time
339 -> Compiler a b -- ^ Resulting compiler
340timedCompiler msg (Compiler d j) = Compiler d $ \x -> CompilerM $ do
341 logger <- compilerLogger <$> ask
342 timed logger msg $ unCompilerM $ j x
344-- | Choose a compiler by extension
345--
346-- Example:
347--
348-- > route "css/*" $ setExtension "css"
349-- > compile "css/*" $ byExtension (error "Not a (S)CSS file")
350-- > [ (".css", compressCssCompiler)
351-- > , (".scss", sass)
352-- > ]
353--
354-- This piece of code will select the @compressCssCompiler@ for @.css@ files,
355-- and the @sass@ compiler (defined elsewhere) for @.scss@ files.
356--
357byExtension :: Compiler a b -- ^ Default compiler
358 -> [(String, Compiler a b)] -- ^ Choices
359 -> Compiler a b -- ^ Resulting compiler
360byExtension defaultCompiler choices = Compiler deps job
361 where
362 -- Lookup the compiler, give an error when it is not found
363 lookup' identifier =
364 let extension = takeExtension $ toFilePath identifier
365 in fromMaybe defaultCompiler $ lookup extension choices
366 -- Collect the dependencies of the choice
367 deps = do
368 identifier <- dependencyIdentifier <$> ask
369 compilerDependencies $ lookup' identifier
370 -- Collect the job of the choice
371 job x = CompilerM $ do
372 identifier <- compilerIdentifier <$> ask
373 unCompilerM $ compilerJob (lookup' identifier) x