hakyll/src/Hakyll/Core/Runtime.hs

238 lines
8.8 KiB
Haskell
Raw Normal View History

2012-11-13 14:10:01 +00:00
--------------------------------------------------------------------------------
module Hakyll.Core.Runtime
( run
) where
--------------------------------------------------------------------------------
import Control.Applicative ((<$>))
import Control.Monad (filterM)
import Control.Monad.Error (ErrorT, runErrorT, throwError)
import Control.Monad.Reader (ask)
import Control.Monad.RWS (RWST, runRWST)
import Control.Monad.State (get, modify)
import Control.Monad.Trans (liftIO)
import Data.Map (Map)
import qualified Data.Map as M
import Data.Monoid (mempty)
import Data.Set (Set)
import qualified Data.Set as S
import System.Exit (ExitCode (..), exitWith)
2012-11-13 14:10:01 +00:00
import System.FilePath ((</>))
--------------------------------------------------------------------------------
import Hakyll.Core.Compiler.Internal
2012-11-13 16:31:03 +00:00
import Hakyll.Core.Compiler.Require
2012-11-13 14:10:01 +00:00
import Hakyll.Core.Configuration
import Hakyll.Core.Dependencies
import Hakyll.Core.Identifier
2012-11-18 20:56:52 +00:00
import Hakyll.Core.Item
import Hakyll.Core.Item.SomeItem
2012-11-14 10:17:28 +00:00
import Hakyll.Core.Logger (Logger)
import qualified Hakyll.Core.Logger as Logger
2012-11-18 20:56:52 +00:00
import Hakyll.Core.Provider
2012-11-13 14:10:01 +00:00
import Hakyll.Core.Routes
import Hakyll.Core.Rules.Internal
import Hakyll.Core.Store (Store)
import qualified Hakyll.Core.Store as Store
import Hakyll.Core.Util.File
import Hakyll.Core.Writable
--------------------------------------------------------------------------------
run :: Configuration -> Rules a -> IO RuleSet
2012-11-21 19:38:13 +00:00
run config rules = do
2012-11-13 14:10:01 +00:00
-- Initialization
2012-11-14 10:17:28 +00:00
logger <- Logger.new Logger.Debug putStrLn
2012-11-14 12:32:31 +00:00
Logger.header logger "Initialising..."
Logger.message logger "Creating store..."
2012-11-21 19:38:13 +00:00
store <- Store.new (inMemoryCache config) $ storeDirectory config
2012-11-14 12:32:31 +00:00
Logger.message logger "Creating provider..."
2012-11-21 19:38:13 +00:00
provider <- newProvider store (ignoreFile config) $ providerDirectory config
2012-11-14 12:32:31 +00:00
Logger.message logger "Running rules..."
2012-11-14 10:17:28 +00:00
ruleSet <- runRules rules provider
2012-11-13 14:10:01 +00:00
-- Get old facts
mOldFacts <- Store.get store factsKey
let (oldFacts) = case mOldFacts of Store.Found f -> f
_ -> mempty
-- Build runtime read/state
let compilers = rulesCompilers ruleSet
read' = RuntimeRead
2012-11-21 19:38:13 +00:00
{ runtimeConfiguration = config
2012-11-13 14:10:01 +00:00
, runtimeLogger = logger
, runtimeProvider = provider
, runtimeStore = store
, runtimeRoutes = rulesRoutes ruleSet
, runtimeUniverse = compilers
}
state = RuntimeState
{ runtimeDone = S.empty
, runtimeTodo = M.empty
, runtimeFacts = oldFacts
}
-- Run the program and fetch the resulting state
result <- runErrorT $ runRWST build read' state
case result of
Left e -> do
Logger.error logger e
Logger.flush logger
exitWith $ ExitFailure 1
2012-11-13 14:10:01 +00:00
Right (_, s, _) -> do
Store.set store factsKey $ runtimeFacts s
Logger.flush logger
return ruleSet
2012-11-13 14:10:01 +00:00
where
factsKey = ["Hakyll.Core.Runtime.run", "facts"]
--------------------------------------------------------------------------------
data RuntimeRead = RuntimeRead
{ runtimeConfiguration :: Configuration
, runtimeLogger :: Logger
2012-11-18 20:56:52 +00:00
, runtimeProvider :: Provider
2012-11-13 14:10:01 +00:00
, runtimeStore :: Store
, runtimeRoutes :: Routes
2012-11-18 20:56:52 +00:00
, runtimeUniverse :: [(Identifier, Compiler SomeItem)]
2012-11-13 14:10:01 +00:00
}
--------------------------------------------------------------------------------
data RuntimeState = RuntimeState
{ runtimeDone :: Set Identifier
2012-11-18 20:56:52 +00:00
, runtimeTodo :: Map Identifier (Compiler SomeItem)
2012-11-13 14:10:01 +00:00
, runtimeFacts :: DependencyFacts
}
--------------------------------------------------------------------------------
type Runtime a = RWST RuntimeRead () RuntimeState (ErrorT String IO) a
--------------------------------------------------------------------------------
build :: Runtime ()
build = do
2012-11-14 10:17:28 +00:00
logger <- runtimeLogger <$> ask
Logger.header logger "Checking for out-of-date items"
2012-11-13 14:10:01 +00:00
scheduleOutOfDate
2012-11-14 10:17:28 +00:00
Logger.header logger "Compiling"
2012-11-13 14:10:01 +00:00
pickAndChase
2012-11-14 10:17:28 +00:00
Logger.header logger "Success"
2012-11-13 14:10:01 +00:00
--------------------------------------------------------------------------------
scheduleOutOfDate :: Runtime ()
scheduleOutOfDate = do
2012-11-14 12:32:31 +00:00
logger <- runtimeLogger <$> ask
2012-11-13 14:10:01 +00:00
provider <- runtimeProvider <$> ask
universe <- runtimeUniverse <$> ask
facts <- runtimeFacts <$> get
todo <- runtimeTodo <$> get
let identifiers = map fst universe
2012-11-14 10:17:28 +00:00
modified <- fmap S.fromList $ flip filterM identifiers $
liftIO . resourceModified provider
2012-11-14 12:32:31 +00:00
let (ood, facts', msgs) = outOfDate identifiers modified facts
todo' = M.fromList
2012-11-13 14:10:01 +00:00
[(id', c) | (id', c) <- universe, id' `S.member` ood]
2012-11-14 12:32:31 +00:00
-- Print messages
mapM_ (Logger.debug logger) msgs
2012-11-13 14:10:01 +00:00
-- Update facts and todo items
modify $ \s -> s
2012-11-13 18:03:58 +00:00
{ runtimeDone = runtimeDone s `S.union`
(S.fromList identifiers `S.difference` ood)
, runtimeTodo = todo `M.union` todo'
2012-11-13 14:10:01 +00:00
, runtimeFacts = facts'
}
--------------------------------------------------------------------------------
pickAndChase :: Runtime ()
pickAndChase = do
todo <- runtimeTodo <$> get
case M.minViewWithKey todo of
Nothing -> return ()
2012-11-13 18:03:58 +00:00
Just ((id', _), _) -> do
chase [] id'
pickAndChase
2012-11-13 14:10:01 +00:00
--------------------------------------------------------------------------------
chase :: [Identifier] -> Identifier -> Runtime ()
chase trail id'
| id' `elem` trail = return () -- Cycle detected!
| otherwise = do
logger <- runtimeLogger <$> ask
todo <- runtimeTodo <$> get
provider <- runtimeProvider <$> ask
universe <- runtimeUniverse <$> ask
routes <- runtimeRoutes <$> ask
store <- runtimeStore <$> ask
config <- runtimeConfiguration <$> ask
let compiler = todo M.! id'
read' = CompilerRead
2012-11-18 20:56:52 +00:00
{ compilerUnderlying = id'
2012-11-13 16:31:03 +00:00
, compilerProvider = provider
, compilerUniverse = map fst universe
, compilerRoutes = routes
, compilerStore = store
, compilerLogger = logger
2012-11-13 14:10:01 +00:00
}
2012-11-14 10:17:28 +00:00
result <- liftIO $ runCompiler compiler read'
2012-11-13 14:10:01 +00:00
case result of
-- Rethrow error
CompilerError e -> throwError e
-- Huge success
2012-11-18 20:56:52 +00:00
CompilerDone (SomeItem item) cwrite -> do
-- TODO: Sanity check on itemIdentifier?
let body = itemBody item
facts = compilerDependencies cwrite
2012-11-14 10:17:28 +00:00
cacheHits
2012-11-14 12:32:31 +00:00
| compilerCacheHits cwrite <= 0 = "updated"
| otherwise = "cached "
-- Print some info
Logger.message logger $ cacheHits ++ " " ++ show id'
2012-11-14 10:17:28 +00:00
2012-11-13 14:10:01 +00:00
-- Write if necessary
case runRoutes routes id' of
Nothing -> return ()
2012-11-14 10:17:28 +00:00
Just url -> do
2012-11-13 14:10:01 +00:00
let path = destinationDirectory config </> url
liftIO $ makeDirectories path
2012-11-18 20:56:52 +00:00
liftIO $ write path item
2012-11-14 12:32:31 +00:00
Logger.debug logger $ "Routed to " ++ path
2012-11-13 14:10:01 +00:00
2012-11-13 16:31:03 +00:00
-- Save! (For require)
2012-11-18 20:56:52 +00:00
liftIO $ save store id' body
2012-11-13 16:31:03 +00:00
2012-11-13 14:10:01 +00:00
-- Update state
modify $ \s -> s
{ runtimeDone = S.insert id' (runtimeDone s)
, runtimeTodo = M.delete id' (runtimeTodo s)
, runtimeFacts = M.insert id' facts (runtimeFacts s)
}
-- Try something else first
CompilerRequire dep c -> do
-- Update the compiler so we don't execute it twice
depDone <- (dep `S.member`) . runtimeDone <$> get
modify $ \s -> s
{ runtimeTodo = M.insert id'
(if depDone then c else compilerResult result)
(runtimeTodo s)
}
-- If the required item is already compiled, continue, or, start
-- chasing that
if depDone then chase trail id' else chase (id' : trail) dep