summaryrefslogtreecommitdiff
path: root/src/Hakyll/Core/DependencyAnalyzer.hs
blob: be470b311db805e1eac063227cbdc58c9e899cec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
--------------------------------------------------------------------------------
module Hakyll.Core.DependencyAnalyzer
    ( Analysis (..)
    , analyze
    ) where


--------------------------------------------------------------------------------
import           Control.Applicative       ((<$>))
import           Control.DeepSeq           (NFData (..))
import           Control.Monad             (filterM, forM_, msum, when)
import           Control.Monad.Reader      (ask)
import           Control.Monad.RWS         (RWS, runRWS)
import           Control.Monad.State       (evalState, get, modify)
import           Control.Monad.Writer      (tell)
import qualified Data.Map                  as M
import           Data.Set                  (Set)
import qualified Data.Set                  as S


--------------------------------------------------------------------------------
import           Hakyll.Core.DirectedGraph


--------------------------------------------------------------------------------
data Analysis a
    = Cycle [a]
    | Order [a]
    deriving (Show)


--------------------------------------------------------------------------------
instance NFData a => NFData (Analysis a) where
    rnf (Cycle c) = rnf c `seq` ()
    rnf (Order o) = rnf o `seq` ()


--------------------------------------------------------------------------------
analyze :: Ord a
        => DirectedGraph a  -- ^ Old graph
        -> DirectedGraph a  -- ^ New graph
        -> (a -> Bool)      -- ^ Out of date?
        -> Analysis a       -- ^ Result
analyze old new ood = case findCycle new of
    Just c  -> Cycle c
    Nothing -> Order $ findOrder old new ood


--------------------------------------------------------------------------------
-- | Simple algorithm do find a cycle in a graph, if any exists. This one can
-- still be optimised by a lot.
findCycle :: Ord a
          => DirectedGraph a
          -> Maybe [a]
findCycle dg = fmap reverse $ msum
    [ findCycle' [x] x n
    | x <- S.toList $ nodes dg
    , n <- neighbours x dg
    ]
  where
    findCycle' stack start x
        | x == start = Just (x : stack)
        | otherwise  = msum
            [ findCycle' (x : stack) start n
            | n <- neighbours x dg
            ]


--------------------------------------------------------------------------------
-- | Do not call this on graphs with cycles
findOrder :: Ord a
          => DirectedGraph a
          -> DirectedGraph a
          -> (a -> Bool)
          -> [a]
findOrder old new ood = ls
  where
    -- Make an extension of ood: an item is ood when it is actually ood OR if
    -- the list of its dependencies has changed. Based on that, create a set of
    -- dirty items.
    ood' x = ood x || neighbours x old /= neighbours x new
    dirty' = dirty ood' new

    -- Run all walks in our own little monad...
    (_, _, ls) = runRWS walks new dirty'


--------------------------------------------------------------------------------
type Analyzer i a = RWS (DirectedGraph i) [i] (Set i) a


--------------------------------------------------------------------------------
isDirty :: Ord a => a -> Analyzer a Bool
isDirty x = (x `S.member`) <$> get


--------------------------------------------------------------------------------
walks :: Ord a
      => Analyzer a ()
walks = do
    dirty' <- get
    if S.null dirty'
        then return ()
        else do
            walk $ S.findMin dirty'
            walks


--------------------------------------------------------------------------------
-- | Invariant: given node to walk /must/ be dirty
walk :: Ord a
     => a
     -> Analyzer a ()
walk x = do
    -- Determine dirty neighbours and walk them
    dg <- ask
    forM_ (neighbours x dg) $ \n -> do
        d <- isDirty n
        when d $ walk n

    -- Once all dirty neighbours are done, we're safe to go
    tell [x]
    modify $ S.delete x


--------------------------------------------------------------------------------
-- | This auxiliary function checks which nodes are dirty: a node is dirty if
-- it's out-of-date or if one of its dependencies is dirty.
dirty :: Ord a
      => (a -> Bool)      -- ^ Out of date?
      -> DirectedGraph a  -- ^ Graph
      -> Set a            -- ^ All dirty items
dirty ood dg = S.fromList $ flip evalState M.empty $
    filterM go $ S.toList $ nodes dg
  where
    go x = do
        m <- get
        case M.lookup x m of
            Just d  -> return d
            Nothing -> do
                nd <- mapM go $ neighbours x dg
                let d = ood x || or nd
                modify $ M.insert x d
                return d