@@ -1,21 +1,29 @@
1--------------------------------------------------------------------------------
1--------------------------------------------------------------------------------
2+{-# LANGUAGE OverloadedStrings #-}
2module Hakyll.Check
3module Hakyll.Check
7--------------------------------------------------------------------------------
8--------------------------------------------------------------------------------
8import Control.Applicative ((<$>))
9import Control.Applicative ((<$>))
10+import Control.Exception (AsyncException (..),
9−import Control.Exception (SomeException, handle)
11+ SomeException (..), handle, throw)
10import Control.Monad (forM_)
12import Control.Monad (forM_)
11−import Control.Monad.Reader (ReaderT, ask, runReaderT)
13+import Control.Monad.Reader (ask)
14+import Control.Monad.RWS (RWST, runRWST)
15+import Control.Monad.State (get, modify)
12import Control.Monad.Trans (liftIO)
16import Control.Monad.Trans (liftIO)
13−import Control.Monad.Writer (WriterT, runWriterT, tell)
17+import Control.Monad.Writer (tell)
14import Data.List (isPrefixOf)
18import Data.List (isPrefixOf)
15import Data.Monoid (Monoid (..))
19import Data.Monoid (Monoid (..))
21+import qualified Data.Set as S
22+import Data.Typeable (cast)
16import qualified Network.HTTP.Conduit as Http
23import qualified Network.HTTP.Conduit as Http
17import qualified Network.HTTP.Types as Http
24import qualified Network.HTTP.Types as Http
18import System.Directory (doesDirectoryExist, doesFileExist)
25import System.Directory (doesDirectoryExist, doesFileExist)
26+import System.Exit (ExitCode (..))
19import System.FilePath (takeDirectory, takeExtension, (</>))
27import System.FilePath (takeDirectory, takeExtension, (</>))
20import qualified Text.HTML.TagSoup as TS
28import qualified Text.HTML.TagSoup as TS
@@ -28,6 +36,13 @@ import Hakyll.Core.Util.File
28import Hakyll.Web.Html
36import Hakyll.Web.Html
39+--------------------------------------------------------------------------------
40+check :: Configuration -> IO ExitCode
42+ ((), write) <- runChecker checkDestination config
43+ return $ if checkerFaulty write >= 0 then ExitFailure 1 else ExitSuccess
31--------------------------------------------------------------------------------
46--------------------------------------------------------------------------------
32data CheckerRead = CheckerRead
47data CheckerRead = CheckerRead
33 { checkerConfig :: Configuration
48 { checkerConfig :: Configuration
@@ -50,22 +65,26 @@ instance Monoid CheckerWrite where
52--------------------------------------------------------------------------------
67--------------------------------------------------------------------------------
53−type Checker a = ReaderT CheckerRead (WriterT CheckerWrite IO) a
68+type CheckerState = Set String
56--------------------------------------------------------------------------------
71--------------------------------------------------------------------------------
72+type Checker a = RWST CheckerRead CheckerWrite CheckerState IO a
75+--------------------------------------------------------------------------------
57−runCheck :: Configuration -> IO ()
76+runChecker :: Checker a -> Configuration -> IO (a, CheckerWrite)
58−runCheck config = do
77+runChecker checker config = do
59 logger <- Logger.new (verbosity config)
78 logger <- Logger.new (verbosity config)
60 let read' = CheckerRead config logger
79 let read' = CheckerRead config logger
61− ((), write) <- runWriterT $ runReaderT check read'
80+ (x, _, write) <- runRWST checker read' S.empty
62− Logger.header logger $ show write
63 Logger.flush logger
81 Logger.flush logger
66--------------------------------------------------------------------------------
85--------------------------------------------------------------------------------
67−check :: Checker ()
86+checkDestination :: Checker ()
68−check = do
87+checkDestination = do
69 config <- checkerConfig <$> ask
88 config <- checkerConfig <$> ask
70 files <- liftIO $ getRecursiveContents (destinationDirectory config)
89 files <- liftIO $ getRecursiveContents (destinationDirectory config)
@@ -83,10 +102,11 @@ checkFile :: FilePath -> Checker ()
83checkFile filePath = do
102checkFile filePath = do
84 logger <- checkerLogger <$> ask
103 logger <- checkerLogger <$> ask
85 contents <- liftIO $ readFile filePath
104 contents <- liftIO $ readFile filePath
86− Logger.header logger $ "Checking " ++ filePath
105+ Logger.header logger $ "Checking file " ++ filePath
88 let urls = getUrls $ TS.parseTags contents
107 let urls = getUrls $ TS.parseTags contents
89− forM_ urls $ \url ->
108+ forM_ urls $ \url -> do
109+ Logger.debug logger $ "Checking link " ++ url
90 if isExternal url
110 if isExternal url
91 then checkExternalUrl url
111 then checkExternalUrl url
92 else checkInternalUrl filePath url
112 else checkInternalUrl filePath url
@@ -126,24 +146,31 @@ checkInternalUrl base url = case url' of
126--------------------------------------------------------------------------------
146--------------------------------------------------------------------------------
127checkExternalUrl :: String -> Checker ()
147checkExternalUrl :: String -> Checker ()
128checkExternalUrl url = do
148checkExternalUrl url = do
129− logger <- checkerLogger <$> ask
149+ logger <- checkerLogger <$> ask
130− Logger.message logger $ "Not checking external url " ++ url
150+ checked <- (url `S.member`) <$> get
153+ then Logger.debug logger "Already checked, skipping"
132− isOk <- liftIO $ handle failure $ Http.withManager $ \manager -> do
155+ isOk <- liftIO $ handle (failure logger) $
156+ Http.withManager $ \mgr -> do
133− request <- Http.parseUrl url
157+ request <- Http.parseUrl url
134− response <- Http.http (settings request) manager
158+ response <- Http.http (settings request) mgr
135− let code = Http.statusCode (Http.responseStatus response)
159+ let code = Http.statusCode (Http.responseStatus response)
136− return $ code >= 200 && code < 300
160+ return $ code >= 200 && code < 300
162+ modify $ S.insert url
138− if isOk then ok url else faulty url
163+ if isOk then ok url else faulty url
140 settings r = r
165 settings r = r
141− { Http.redirectCount = 10
166+ { Http.method = "HEAD"
142− , Http.responseTimeout = Just 10
167+ , Http.redirectCount = 10
170+ -- Catch all the things except UserInterrupt
145− failure :: SomeException -> IO Bool
171+ failure logger (SomeException e) = case cast e of
172+ Just UserInterrupt -> throw UserInterrupt
146− failure e = print e >> return False
173+ _ -> Logger.error logger (show e) >> return False
149--------------------------------------------------------------------------------
176--------------------------------------------------------------------------------