-------------------------------------------------------------------------------- -- | This module provides a declarative DSL in which the user can specify the -- different rules used to run the compilers. -- -- The convention is to just list all items in the 'Rules' monad, routes and -- compilation rules. -- -- A typical usage example would be: -- -- > main = hakyll $ do -- > match "posts/*" $ do -- > route (setExtension "html") -- > compile someCompiler -- > match "css/*" $ do -- > route idRoute -- > compile compressCssCompiler {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} module Hakyll.Core.Rules ( Rules , match , matchMetadata , create , version , compile , route -- * Advanced usage , preprocess , Dependency (..) , rulesExtraDependencies ) where -------------------------------------------------------------------------------- import Control.Applicative ((<$>)) import Control.Monad.Reader (ask, local) import Control.Monad.State (get, modify, put) import Control.Monad.Trans (liftIO) import Control.Monad.Writer (censor, tell) import Data.Maybe (fromMaybe) import Data.Monoid (mempty) import qualified Data.Set as S -------------------------------------------------------------------------------- import Data.Binary (Binary) import Data.Typeable (Typeable) -------------------------------------------------------------------------------- import Hakyll.Core.Compiler.Internal import Hakyll.Core.Dependencies import Hakyll.Core.Identifier import Hakyll.Core.Identifier.Pattern import Hakyll.Core.Item import Hakyll.Core.Item.SomeItem import Hakyll.Core.Metadata import Hakyll.Core.Routes import Hakyll.Core.Rules.Internal import Hakyll.Core.Writable -------------------------------------------------------------------------------- -- | Add a route tellRoute :: Routes -> Rules () tellRoute route' = Rules $ tell $ RuleSet route' mempty mempty mempty -------------------------------------------------------------------------------- -- | Add a number of compilers tellCompilers :: [(Identifier, Compiler SomeItem)] -> Rules () tellCompilers compilers = Rules $ tell $ RuleSet mempty compilers mempty mempty -------------------------------------------------------------------------------- -- | Add resources tellResources :: [Identifier] -> Rules () tellResources resources' = Rules $ tell $ RuleSet mempty mempty (S.fromList resources') mempty -------------------------------------------------------------------------------- -- | Add a pattern tellPattern :: Pattern -> Rules () tellPattern pattern = Rules $ tell $ RuleSet mempty mempty mempty pattern -------------------------------------------------------------------------------- flush :: Rules () flush = Rules $ do mcompiler <- rulesCompiler <$> get case mcompiler of Nothing -> return () Just compiler -> do matches' <- rulesMatches <$> ask version' <- rulesVersion <$> ask route' <- fromMaybe mempty . rulesRoute <$> get -- The version is possibly not set correctly at this point (yet) let ids = map (setVersion version') matches' {- ids <- case fromLiteral pattern of Just id' -> return [setVersion version' id'] Nothing -> do ids <- unRules $ getMatches pattern unRules $ tellResources ids return $ map (setVersion version') ids -} -- Create a fast pattern for routing that matches exactly the -- compilers created in the block given to match let fastPattern = fromList ids -- Write out the compilers and routes unRules $ tellRoute $ matchRoute fastPattern route' unRules $ tellCompilers $ [(id', compiler) | id' <- ids] put $ emptyRulesState -------------------------------------------------------------------------------- matchInternal :: Pattern -> Rules [Identifier] -> Rules () -> Rules () matchInternal pattern getIDs rules = do tellPattern pattern flush ids <- getIDs tellResources ids Rules $ local (setMatches ids) $ unRules $ rules >> flush where setMatches ids env = env {rulesMatches = ids} -------------------------------------------------------------------------------- match :: Pattern -> Rules () -> Rules () match pattern = matchInternal pattern $ getMatches pattern -------------------------------------------------------------------------------- matchMetadata :: Pattern -> (Metadata -> Bool) -> Rules () -> Rules () matchMetadata pattern metadataPred = matchInternal pattern $ map fst . filter (metadataPred . snd) <$> getAllMetadata pattern -------------------------------------------------------------------------------- create :: [Identifier] -> Rules () -> Rules () create ids rules = do flush -- TODO Maybe check if the resources exist and call tellResources on that Rules $ local setMatches $ unRules $ rules >> flush where setMatches env = env {rulesMatches = ids} -------------------------------------------------------------------------------- version :: String -> Rules () -> Rules () version v rules = do flush Rules $ local setVersion' $ unRules $ rules >> flush where setVersion' env = env {rulesVersion = Just v} -------------------------------------------------------------------------------- -- | Add a compilation rule to the rules. -- -- This instructs all resources to be compiled using the given compiler. compile :: (Binary a, Typeable a, Writable a) => Compiler (Item a) -> Rules () compile compiler = Rules $ modify $ \s -> s {rulesCompiler = Just (fmap SomeItem compiler)} -------------------------------------------------------------------------------- -- | Add a route. -- -- This adds a route for all items matching the current pattern. route :: Routes -> Rules () route route' = Rules $ modify $ \s -> s {rulesRoute = Just route'} -------------------------------------------------------------------------------- -- | Execute an 'IO' action immediately while the rules are being evaluated. -- This should be avoided if possible, but occasionally comes in useful. preprocess :: IO a -> Rules a preprocess = Rules . liftIO -------------------------------------------------------------------------------- -- | Advanced usage: add extra dependencies to compilers. Basically this is -- needed when you're doing unsafe tricky stuff in the rules monad, but you -- still want correct builds. -- -- A useful utility for this purpose is 'makePatternDependency'. rulesExtraDependencies :: [Dependency] -> Rules a -> Rules a rulesExtraDependencies deps rules = -- Note that we add the dependencies seemingly twice here. However, this is -- done so that 'rulesExtraDependencies' works both if we have something -- like: -- -- > match "*.css" $ rulesExtraDependencies [foo] $ ... -- -- and something like: -- -- > rulesExtraDependencies [foo] $ match "*.css" $ ... -- -- (1) takes care of the latter and (2) of the former. Rules $ censor fixRuleSet $ do x <- unRules rules fixCompiler return x where -- (1) Adds the dependencies to the compilers we are yet to create fixCompiler = modify $ \s -> case rulesCompiler s of Nothing -> s Just c -> s { rulesCompiler = Just $ compilerTellDependencies deps >> c } -- (2) Adds the dependencies to the compilers that are already in the ruleset fixRuleSet ruleSet = ruleSet { rulesCompilers = [ (i, compilerTellDependencies deps >> c) | (i, c) <- rulesCompilers ruleSet ] }