hakyll/src/Hakyll/Check.hs

164 lines
5.6 KiB
Haskell
Raw Normal View History

2012-12-29 09:41:05 +00:00
--------------------------------------------------------------------------------
module Hakyll.Check
( runCheck
) where
--------------------------------------------------------------------------------
import Control.Applicative ((<$>))
2012-12-29 16:49:11 +00:00
import Control.Exception (SomeException, handle)
2012-12-29 09:41:05 +00:00
import Control.Monad (forM_)
import Control.Monad.Reader (ReaderT, ask, runReaderT)
import Control.Monad.Trans (liftIO)
import Control.Monad.Writer (WriterT, runWriterT, tell)
import Data.List (isPrefixOf)
import Data.Monoid (Monoid (..))
2012-12-29 16:49:11 +00:00
import qualified Network.HTTP.Conduit as Http
import qualified Network.HTTP.Types as Http
2012-12-29 10:36:53 +00:00
import System.Directory (doesDirectoryExist, doesFileExist)
2012-12-29 09:41:05 +00:00
import System.FilePath (takeDirectory, takeExtension, (</>))
import qualified Text.HTML.TagSoup as TS
--------------------------------------------------------------------------------
import Hakyll.Core.Configuration
import Hakyll.Core.Logger (Logger)
import qualified Hakyll.Core.Logger as Logger
import Hakyll.Core.Util.File
import Hakyll.Web.Html
--------------------------------------------------------------------------------
data CheckerRead = CheckerRead
{ checkerConfig :: Configuration
, checkerLogger :: Logger
}
--------------------------------------------------------------------------------
data CheckerWrite = CheckerWrite
{ checkerFaulty :: Int
, checkerOk :: Int
2012-12-29 10:36:53 +00:00
} deriving (Show)
2012-12-29 09:41:05 +00:00
--------------------------------------------------------------------------------
instance Monoid CheckerWrite where
mempty = CheckerWrite 0 0
mappend (CheckerWrite f1 o1) (CheckerWrite f2 o2) =
CheckerWrite (f1 + f2) (o1 + o2)
--------------------------------------------------------------------------------
type Checker a = ReaderT CheckerRead (WriterT CheckerWrite IO) a
--------------------------------------------------------------------------------
runCheck :: Configuration -> IO ()
runCheck config = do
logger <- Logger.new (verbosity config)
let read' = CheckerRead config logger
2012-12-29 10:36:53 +00:00
((), write) <- runWriterT $ runReaderT check read'
Logger.header logger $ show write
2012-12-29 09:41:05 +00:00
Logger.flush logger
--------------------------------------------------------------------------------
check :: Checker ()
check = do
config <- checkerConfig <$> ask
files <- liftIO $ getRecursiveContents (destinationDirectory config)
let htmls =
[ destinationDirectory config </> file
| file <- files
, takeExtension file == ".html"
]
forM_ htmls checkFile
--------------------------------------------------------------------------------
checkFile :: FilePath -> Checker ()
checkFile filePath = do
logger <- checkerLogger <$> ask
contents <- liftIO $ readFile filePath
Logger.header logger $ "Checking " ++ filePath
2012-12-29 10:36:53 +00:00
2012-12-29 11:30:23 +00:00
let urls = getUrls $ TS.parseTags contents
forM_ urls $ \url ->
if isExternal url
then checkExternalUrl url
else checkInternalUrl filePath url
2012-12-29 10:36:53 +00:00
2012-12-29 11:30:23 +00:00
2012-12-29 16:49:11 +00:00
--------------------------------------------------------------------------------
ok :: String -> Checker ()
ok _ = tell $ mempty {checkerOk = 1}
--------------------------------------------------------------------------------
faulty :: String -> Checker ()
faulty url = do
logger <- checkerLogger <$> ask
Logger.error logger $ "Broken link to " ++ show url
tell $ mempty {checkerFaulty = 1}
2012-12-29 11:30:23 +00:00
--------------------------------------------------------------------------------
checkInternalUrl :: FilePath -> String -> Checker ()
checkInternalUrl base url = case url' of
2012-12-29 16:49:11 +00:00
"" -> ok url
2012-12-29 11:30:23 +00:00
_ -> do
config <- checkerConfig <$> ask
let dest = destinationDirectory config
dir = takeDirectory base
filePath
| "/" `isPrefixOf` url' = dest ++ url'
| otherwise = dir </> url'
exists <- checkFileExists filePath
2012-12-29 16:49:11 +00:00
if exists then ok url else faulty url
2012-12-29 11:30:23 +00:00
where
url' = stripFragments url
2012-12-29 09:41:05 +00:00
--------------------------------------------------------------------------------
2012-12-29 11:30:23 +00:00
checkExternalUrl :: String -> Checker ()
checkExternalUrl url = do
2012-12-29 09:41:05 +00:00
logger <- checkerLogger <$> ask
2012-12-29 11:30:23 +00:00
Logger.message logger $ "Not checking external url " ++ url
2012-12-29 16:49:11 +00:00
isOk <- liftIO $ handle failure $ Http.withManager $ \manager -> do
request <- Http.parseUrl url
response <- Http.http (settings request) manager
let code = Http.statusCode (Http.responseStatus response)
return $ code >= 200 && code < 300
if isOk then ok url else faulty url
where
settings r = r
{ Http.redirectCount = 10
, Http.responseTimeout = Just 10
}
failure :: SomeException -> IO Bool
failure e = print e >> return False
2012-12-29 10:36:53 +00:00
--------------------------------------------------------------------------------
-- | Wraps doesFileExist, also checks for index.html
checkFileExists :: FilePath -> Checker Bool
checkFileExists filePath = liftIO $ do
file <- doesFileExist filePath
dir <- doesDirectoryExist filePath
case (file, dir) of
(True, _) -> return True
(_, True) -> doesFileExist $ filePath </> "index.html"
_ -> return False
--------------------------------------------------------------------------------
stripFragments :: String -> String
stripFragments = takeWhile (not . flip elem ['?', '#'])