module Hakyll.Core.DependencyAnalyzer
( DependencyAnalyzer (..)
, Signal (..)
, makeDependencyAnalyzer
, step
, stepAll
) where
import Prelude hiding (reverse)
import qualified Prelude as P (reverse)
import Control.Arrow (first)
import Data.Set (Set)
import qualified Data.Set as S
import Data.Monoid (Monoid, mappend, mempty)
import Hakyll.Core.DirectedGraph
data DependencyAnalyzer a = DependencyAnalyzer
{
analyzerGraph :: DirectedGraph a
,
analyzerRemains :: Set a
,
analyzerDone :: Set a
,
analyzerPreviousGraph :: DirectedGraph a
} deriving (Show)
data Signal a = Build a
| Cycle [a]
| Done
deriving (Show)
instance (Ord a, Show a) => Monoid (DependencyAnalyzer a) where
mempty = DependencyAnalyzer mempty mempty mempty mempty
mappend x y = growRemains $ DependencyAnalyzer
(analyzerGraph x `mappend` analyzerGraph y)
(analyzerRemains x `mappend` analyzerRemains y)
(analyzerDone x `mappend` analyzerDone y)
(analyzerPreviousGraph x `mappend` analyzerPreviousGraph y)
makeDependencyAnalyzer :: (Ord a, Show a)
=> DirectedGraph a
-> (a -> Bool)
-> DirectedGraph a
-> DependencyAnalyzer a
makeDependencyAnalyzer graph isOutOfDate prev =
growRemains $ DependencyAnalyzer graph remains S.empty prev
where
remains = S.fromList $ filter isOutOfDate $ map fst $ toList graph
growRemains :: (Ord a, Show a) => DependencyAnalyzer a -> DependencyAnalyzer a
growRemains (DependencyAnalyzer graph remains done prev) =
(DependencyAnalyzer graph remains' done prev)
where
remains' = S.filter (`S.notMember` done) indirect
indirect = reachableNodes (remains `S.union` changedDeps) $ reverse graph
changedDeps = S.fromList $ map fst $
filter (uncurry (/=) . first (`neighbours` prev)) $ toList graph
step :: (Ord a, Show a) => DependencyAnalyzer a -> (Signal a, DependencyAnalyzer a)
step analyzer@(DependencyAnalyzer graph remains done prev)
| S.null remains = (Done, analyzer)
| otherwise =
let item = S.findMin remains
in case findReady analyzer item of
Done -> (Done, analyzer)
Cycle c -> (Cycle c, analyzer)
Build build ->
let remains' = S.delete build remains
done' = S.insert build done
in (Build build, DependencyAnalyzer graph remains' done' prev)
stepAll :: (Ord a, Show a) => DependencyAnalyzer a -> Maybe (Set a)
stepAll = stepAll' S.empty
where
stepAll' xs analyzer = case step analyzer of
(Build x, analyzer') -> stepAll' (S.insert x xs) analyzer'
(Done, _) -> Just xs
(Cycle _, _) -> Nothing
findReady :: (Ord a, Show a) => DependencyAnalyzer a -> a -> Signal a
findReady analyzer = findReady' [] S.empty
where
graph = analyzerGraph analyzer
todo = analyzerRemains analyzer `S.difference` analyzerDone analyzer
findReady' stack visited item
| item `S.member` visited = Cycle $ P.reverse stack'
| otherwise = case filter (`S.member` todo) neighbours' of
[] -> Build item
(x : _) -> findReady' stack' visited' x
where
neighbours' = S.toList $ neighbours item graph
stack' = item : stack
visited' = S.insert item visited