1-- | A Compiler manages targets and dependencies between targets
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.
7-- Compilers are chained using the '>>>' arrow operation. If we have a compiler
9-- > getResourceString :: Compiler Resource String
11-- which reads the resource, and a compiler
13-- > readPage :: Compiler String (Page String)
15-- we can chain these two compilers to get a
17-- > (getResourceString >>> readPage) :: Compiler Resource (Page String)
19-- Most compilers can be created by combining smaller compilers using '>>>'.
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
24-- <http://en.wikibooks.org/wiki/Haskell/Understanding_arrows>
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.
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.
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:
38-- > read resource >>> pandoc render >>> layout >>> relativize URL's
40-- > @templates/fancy.html@
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.
46-- This is an example of where we need the @require@ function. We can solve
47-- this using a construction that looks like:
49-- > ... >>> pandoc render >>> require >>> layout >>> ...
51-- > @templates/fancy.html@ ------/
53-- This illustration can help us understand the type signature of 'require'.
55-- > require :: (Binary a, Typeable a, Writable a)
60-- Let's look at it in detail:
62-- > (Binary a, Typeable a, Writable a)
64-- These are constraints for the @a@ type. @a@ (the template) needs to have
65-- certain properties for it to be required.
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
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.
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!
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!
91{-# LANGUAGE GeneralizedNewtypeDeriving, ScopedTypeVariables #-}
92module Hakyll.Core.Compiler
117import Prelude hiding ((.), id)
118import Control.Arrow ((>>>), (&&&), arr, first)
119import Control.Applicative ((<$>))
120import Control.Exception (SomeException, handle)
121import Control.Monad.Reader (ask)
122import Control.Monad.Trans (liftIO)
123import Control.Monad.Error (throwError)
124import Control.Category (Category, (.), id)
125import Data.List (find)
126import System.Environment (getProgName)
127import System.FilePath (takeExtension)
129import Data.Binary (Binary)
130import Data.Typeable (Typeable)
131import Data.ByteString.Lazy (ByteString)
133import Hakyll.Core.Identifier
134import Hakyll.Core.Identifier.Pattern
135import Hakyll.Core.CompiledItem
136import Hakyll.Core.Writable
137import Hakyll.Core.ResourceProvider
138import Hakyll.Core.Compiler.Internal
139import Hakyll.Core.Store (Store)
140import Hakyll.Core.Rules.Internal
141import Hakyll.Core.Routes
142import Hakyll.Core.Logger
143import qualified Hakyll.Core.Store as Store
145-- | Run a compiler, yielding the resulting target and it's dependencies. This
146-- version of 'runCompilerJob' also stores the result
148runCompiler :: Compiler () CompileRule -- ^ Compiler to run
149 -> Identifier () -- ^ Target identifier
150 -> ResourceProvider -- ^ Resource provider
151 -> [Identifier ()] -- ^ Universe
154 -> Bool -- ^ Was the resource modified?
155 -> Logger -- ^ Logger
156 -> IO (Throwing CompileRule) -- ^ Resulting item
157runCompiler compiler id' provider universe routes store modified logger = do
158 -- Run the compiler job
159 result <- handle (\(e :: SomeException) -> return $ Left $ show e) $
160 runCompilerJob compiler id' provider universe routes store modified
163 -- Inspect the result
165 -- In case we compiled an item, we will store a copy in the cache first,
166 -- before we return control. This makes sure the compiled item can later
167 -- be accessed by e.g. require.
168 Right (CompileRule (CompiledItem x)) ->
169 Store.set store ["Hakyll.Core.Compiler.runCompiler", show id'] x
171 -- Otherwise, we do nothing here
176-- | Get the identifier of the item that is currently being compiled
178getIdentifier :: Compiler a (Identifier b)
179getIdentifier = fromJob $ const $ CompilerM $
180 castIdentifier . compilerIdentifier <$> ask
182-- | Get the route we are using for this item
184getRoute :: Compiler a (Maybe FilePath)
185getRoute = getIdentifier >>> getRouteFor
187-- | Get the route for a specified item
189getRouteFor :: Compiler (Identifier a) (Maybe FilePath)
190getRouteFor = fromJob $ \identifier -> CompilerM $ do
191 routes <- compilerRoutes <$> ask
192 return $ runRoutes routes identifier
194-- | Get the resource we are compiling as a string
196getResourceString :: Compiler a String
197getResourceString = getResourceWith resourceString
199-- | Get the resource we are compiling as a lazy bytestring
201getResourceLBS :: Compiler a ByteString
202getResourceLBS = getResourceWith resourceLBS
204-- | Overloadable function for 'getResourceString' and 'getResourceLBS'
206getResourceWith :: (Identifier a -> IO b) -> Compiler c b
207getResourceWith reader = fromJob $ \_ -> CompilerM $ do
208 provider <- compilerResourceProvider <$> ask
209 r <- compilerIdentifier <$> ask
210 let filePath = toFilePath r
211 if resourceExists provider r
212 then liftIO $ reader $ castIdentifier r
213 else throwError $ error' filePath
215 error' id' = "Hakyll.Core.Compiler.getResourceWith: resource "
216 ++ show id' ++ " not found"
218-- | Auxiliary: get a dependency
220getDependency :: (Binary a, Writable a, Typeable a)
221 => Identifier a -> CompilerM a
222getDependency id' = CompilerM $ do
223 store <- compilerStore <$> ask
225 Store.get store ["Hakyll.Core.Compiler.runCompiler", show id']
227 Store.NotFound -> throwError notFound
228 Store.WrongType e r -> throwError $ wrongType e r
229 Store.Found x -> return x
232 "Hakyll.Core.Compiler.getDependency: " ++ show id' ++ " was " ++
233 "not found in the cache, the cache might be corrupted or " ++
234 "the item you are referring to might not exist"
236 "Hakyll.Core.Compiler.getDependency: " ++ show id' ++ " was found " ++
237 "in the cache, but does not have the right type: expected " ++ show e ++
238 " but got " ++ show r
240-- | Variant of 'require' which drops the current value
242require_ :: (Binary a, Typeable a, Writable a)
246 fromDependency identifier >>> fromJob (const $ getDependency identifier)
248-- | Require another target. Using this function ensures automatic handling of
251require :: (Binary a, Typeable a, Writable a)
255require identifier = requireA identifier . arr . uncurry
257-- | Arrow-based variant of 'require'
259requireA :: (Binary a, Typeable a, Writable a)
263requireA identifier = (id &&& require_ identifier >>>)
265-- | Variant of 'requireAll' which drops the current value
267requireAll_ :: (Binary a, Typeable a, Writable a)
270requireAll_ pattern = fromDependencies (const getDeps) >>> fromJob requireAll_'
272 getDeps = map castIdentifier . filterMatches pattern . map castIdentifier
273 requireAll_' = const $ CompilerM $ do
274 deps <- getDeps . compilerUniverse <$> ask
275 mapM (unCompilerM . getDependency) deps
277-- | Require a number of targets. Using this function ensures automatic handling
280requireAll :: (Binary a, Typeable a, Writable a)
284requireAll pattern = requireAllA pattern . arr . uncurry
286-- | Arrow-based variant of 'requireAll'
288requireAllA :: (Binary a, Typeable a, Writable a)
290 -> Compiler (b, [a]) c
292requireAllA pattern = (id &&& requireAll_ pattern >>>)
294cached :: (Binary a, Typeable a, Writable a)
298cached name (Compiler d j) = Compiler d $ const $ CompilerM $ do
299 logger <- compilerLogger <$> ask
300 identifier <- castIdentifier . compilerIdentifier <$> ask
301 store <- compilerStore <$> ask
302 modified <- compilerResourceModified <$> ask
303 progName <- liftIO getProgName
304 report logger $ "Checking cache: " ++ if modified then "modified" else "OK"
306 then do v <- unCompilerM $ j ()
307 liftIO $ Store.set store [name, show identifier] v
309 else do v <- liftIO $ Store.get store [name, show identifier]
310 case v of Store.Found v' -> return v'
311 _ -> throwError (error' progName)
314 "Hakyll.Core.Compiler.cached: Cache corrupt! " ++
315 "Try running: " ++ progName ++ " clean"
317-- | Create an unsafe compiler from a function in IO
319unsafeCompiler :: (a -> IO b) -- ^ Function to lift
320 -> Compiler a b -- ^ Resulting compiler
321unsafeCompiler f = fromJob $ CompilerM . liftIO . f
323-- | Compiler for debugging purposes
325traceShowCompiler :: Show a => Compiler a a
326traceShowCompiler = fromJob $ \x -> CompilerM $ do
327 logger <- compilerLogger <$> ask
328 report logger $ show x
331-- | Map over a compiler
333mapCompiler :: Compiler a b
335mapCompiler (Compiler d j) = Compiler d $ mapM j
337-- | Log and time a compiler
339timedCompiler :: String -- ^ Message
340 -> Compiler a b -- ^ Compiler to time
341 -> Compiler a b -- ^ Resulting compiler
342timedCompiler msg (Compiler d j) = Compiler d $ \x -> CompilerM $ do
343 logger <- compilerLogger <$> ask
344 timed logger msg $ unCompilerM $ j x
346-- | Choose a compiler by identifier
348-- For example, assume that most content files need to be compiled
349-- normally, but a select few need an extra step in the pipeline:
351-- > compile $ pageCompiler >>> byPattern id
352-- > [ ("projects.md", addProjectListCompiler)
353-- > , ("sitemap.md", addSiteMapCompiler)
356byPattern :: Compiler a b -- ^ Default compiler
357 -> [(Pattern (), Compiler a b)] -- ^ Choices
358 -> Compiler a b -- ^ Resulting compiler
359byPattern defaultCompiler choices = Compiler deps job
361 -- Lookup the compiler, give an error when it is not found
362 lookup' identifier = maybe defaultCompiler snd $
363 find (\(p, _) -> matches p identifier) choices
364 -- Collect the dependencies of the choice
366 identifier <- castIdentifier . dependencyIdentifier <$> ask
367 compilerDependencies $ lookup' identifier
368 -- Collect the job of the choice
369 job x = CompilerM $ do
370 identifier <- castIdentifier . compilerIdentifier <$> ask
371 unCompilerM $ compilerJob (lookup' identifier) x
373-- | Choose a compiler by extension
377-- > match "css/*" $ do
378-- > route $ setExtension "css"
379-- > compile $ byExtension (error "Not a (S)CSS file")
380-- > [ (".css", compressCssCompiler)
381-- > , (".scss", sass)
384-- This piece of code will select the @compressCssCompiler@ for @.css@ files,
385-- and the @sass@ compiler (defined elsewhere) for @.scss@ files.
387byExtension :: Compiler a b -- ^ Default compiler
388 -> [(String, Compiler a b)] -- ^ Choices
389 -> Compiler a b -- ^ Resulting compiler
390byExtension defaultCompiler = byPattern defaultCompiler . map (first extPattern)
392 extPattern c = predicate $ (== c) . takeExtension . toFilePath
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list