on master help sign in
Draft tool to check internal links
commit5b1a675b94deef7741d2fa3f4c619ce3634bfa4d authorJasper Van der Jeugt <m@jaspervdj.be> dateSat 29 Dec 2012, 10:41 (14 years ago) parent720c92a
7 files changed+140−17
hakyll.cabal+1 src/Hakyll/Check.hs+106 src/Hakyll/Core/Logger.hs+3−3 src/Hakyll/Core/Runtime.hs+1−1 src/Hakyll/Main.hs+8 src/Hakyll/Web/Html.hs+20−12 tests/TestSuite/Util.hs+1−1
hakyll.cabal+1
@@ -138,6 +138,7 @@ Library
138138 Hakyll.Web.Template.Read
139139
140140 Other-Modules:
141+ Hakyll.Check
141142 Hakyll.Core.Compiler.Internal
142143 Hakyll.Core.Compiler.Require
143144 Hakyll.Core.Item.SomeItem
src/Hakyll/Check.hsnew file+106
@@ -0,0 +1,106 @@
1+--------------------------------------------------------------------------------
2+module Hakyll.Check
3+ ( runCheck
4+ ) where
5+
6+
7+--------------------------------------------------------------------------------
8+import Control.Applicative ((<$>))
9+import Control.Monad (forM_)
10+import Control.Monad.Reader (ReaderT, ask, runReaderT)
11+import Control.Monad.Trans (liftIO)
12+import Control.Monad.Writer (WriterT, runWriterT, tell)
13+import Data.List (isPrefixOf)
14+import Data.Monoid (Monoid (..))
15+import System.Directory (doesFileExist)
16+import System.FilePath (takeDirectory, takeExtension, (</>))
17+import qualified Text.HTML.TagSoup as TS
18+
19+
20+--------------------------------------------------------------------------------
21+import Hakyll.Core.Configuration
22+import Hakyll.Core.Logger (Logger)
23+import qualified Hakyll.Core.Logger as Logger
24+import Hakyll.Core.Util.File
25+import Hakyll.Web.Html
26+
27+
28+--------------------------------------------------------------------------------
29+data CheckerRead = CheckerRead
30+ { checkerConfig :: Configuration
31+ , checkerLogger :: Logger
32+ }
33+
34+
35+--------------------------------------------------------------------------------
36+data CheckerWrite = CheckerWrite
37+ { checkerFaulty :: Int
38+ , checkerOk :: Int
39+ }
40+
41+
42+--------------------------------------------------------------------------------
43+instance Monoid CheckerWrite where
44+ mempty = CheckerWrite 0 0
45+ mappend (CheckerWrite f1 o1) (CheckerWrite f2 o2) =
46+ CheckerWrite (f1 + f2) (o1 + o2)
47+
48+
49+--------------------------------------------------------------------------------
50+type Checker a = ReaderT CheckerRead (WriterT CheckerWrite IO) a
51+
52+
53+--------------------------------------------------------------------------------
54+runCheck :: Configuration -> IO ()
55+runCheck config = do
56+ logger <- Logger.new (verbosity config)
57+ let read' = CheckerRead config logger
58+ ((), _write) <- runWriterT $ runReaderT check read'
59+ Logger.flush logger
60+
61+
62+--------------------------------------------------------------------------------
63+check :: Checker ()
64+check = do
65+ config <- checkerConfig <$> ask
66+ files <- liftIO $ getRecursiveContents (destinationDirectory config)
67+
68+ let htmls =
69+ [ destinationDirectory config </> file
70+ | file <- files
71+ , takeExtension file == ".html"
72+ ]
73+
74+ forM_ htmls checkFile
75+
76+
77+--------------------------------------------------------------------------------
78+checkFile :: FilePath -> Checker ()
79+checkFile filePath = do
80+ logger <- checkerLogger <$> ask
81+ contents <- liftIO $ readFile filePath
82+
83+ Logger.header logger $ "Checking " ++ filePath
84+ let tags = TS.parseTags contents
85+ urls = filter (not . isExternal) $ getUrls tags
86+ mapM_ (checkUrl filePath) urls
87+
88+
89+--------------------------------------------------------------------------------
90+checkUrl :: FilePath -> String -> Checker ()
91+checkUrl base url = do
92+ logger <- checkerLogger <$> ask
93+ config <- checkerConfig <$> ask
94+
95+ let dest = destinationDirectory config
96+ dir = takeDirectory base
97+ filePath
98+ | "/" `isPrefixOf` url = dest ++ url
99+ | otherwise = dir </> url
100+
101+ exists <- liftIO $ doesFileExist filePath
102+ if exists
103+ then tell $ mempty {checkerOk = 1}
104+ else do
105+ tell $ mempty {checkerFaulty = 1}
106+ Logger.error logger $ base ++ ": broken reference to " ++ url
src/Hakyll/Core/Logger.hs+3−3
@@ -42,10 +42,10 @@ data Logger = Logger
4242
4343--------------------------------------------------------------------------------
4444-- | Create a new logger
45−new :: Verbosity -> (String -> IO ()) -> IO Logger
46−new vbty sink = do
45+new :: Verbosity -> IO Logger
46+new vbty = do
4747 logger <- Logger <$>
48− newChan <*> newEmptyMVar <*> pure sink <*> pure vbty
48+ newChan <*> newEmptyMVar <*> pure putStrLn <*> pure vbty
4949 _ <- forkIO $ loggerThread logger
5050 return logger
5151 where
src/Hakyll/Core/Runtime.hs+1−1
@@ -43,7 +43,7 @@ import Hakyll.Core.Writable
4343run :: Configuration -> Rules a -> IO RuleSet
4444run config rules = do
4545 -- Initialization
46− logger <- Logger.new (verbosity config) putStrLn
46+ logger <- Logger.new (verbosity config)
4747 Logger.header logger "Initialising..."
4848 Logger.message logger "Creating store..."
4949 store <- Store.new (inMemoryCache config) $ storeDirectory config
src/Hakyll/Main.hs+8
@@ -16,6 +16,7 @@ import System.Process (system)
1616
1717
1818--------------------------------------------------------------------------------
19+import Hakyll.Check
1920import Hakyll.Core.Configuration
2021import Hakyll.Core.Rules
2122import Hakyll.Core.Runtime
@@ -47,6 +48,7 @@ hakyllWith conf rules = do
4748 args <- getArgs
4849 case args of
4950 ["build"] -> build conf rules
51+ ["check"] -> check conf
5052 ["clean"] -> clean conf
5153 ["help"] -> help
5254 ["preview"] -> preview conf rules 8000
@@ -66,6 +68,12 @@ build conf rules = do
6668 return ()
6769
6870
71+--------------------------------------------------------------------------------
72+-- | Run the checker
73+check :: Configuration -> IO ()
74+check = runCheck
75+
76+
6977--------------------------------------------------------------------------------
7078-- | Remove the output directories
7179clean :: Configuration -> IO ()
src/Hakyll/Web/Html.hs+20−12
@@ -8,6 +8,7 @@ module Hakyll.Web.Html
88 , demoteHeaders
99
1010 -- * Url manipulation
11+ , getUrls
1112 , withUrls
1213 , toUrl
1314 , toSiteRoot
@@ -20,16 +21,14 @@ module Hakyll.Web.Html
2021
2122
2223--------------------------------------------------------------------------------
23−import Data.Char (digitToInt, intToDigit, isDigit, toLower)
24−import Data.List (isPrefixOf)
25−import qualified Data.Set as S
26−import System.FilePath (joinPath, splitPath, takeDirectory)
27−import Text.Blaze.Html (toHtml)
28−import Text.Blaze.Html.Renderer.String (renderHtml)
29−
30−
31−--------------------------------------------------------------------------------
32−import qualified Text.HTML.TagSoup as TS
24+import Data.Char (digitToInt, intToDigit,
25+ isDigit, toLower)
26+import Data.List (isPrefixOf)
27+import System.FilePath (joinPath, splitPath,
28+ takeDirectory)
29+import Text.Blaze.Html (toHtml)
30+import Text.Blaze.Html.Renderer.String (renderHtml)
31+import qualified Text.HTML.TagSoup as TS
3332
3433
3534--------------------------------------------------------------------------------
@@ -52,6 +51,16 @@ demoteHeaders = withTags $ \tag -> case tag of
5251 demote t = t
5352
5453
54+--------------------------------------------------------------------------------
55+isUrlAttribute :: String -> Bool
56+isUrlAttribute = (`elem` ["src", "href"])
57+
58+
59+--------------------------------------------------------------------------------
60+getUrls :: [TS.Tag String] -> [String]
61+getUrls tags = [v | TS.TagOpen _ as <- tags, (k, v) <- as, isUrlAttribute k]
62+
63+
5564--------------------------------------------------------------------------------
5665-- | Apply a function to each URL on a webpage
5766withUrls :: (String -> String) -> String -> String
@@ -59,8 +68,7 @@ withUrls f = withTags tag
5968 where
6069 tag (TS.TagOpen s a) = TS.TagOpen s $ map attr a
6170 tag x = x
62− attr (k, v) = (k, if k `S.member` refs then f v else v)
63− refs = S.fromList ["src", "href"]
71+ attr (k, v) = (k, if isUrlAttribute k then f v else v)
6472
6573
6674-- | Customized TagSoup renderer. (The default TagSoup renderer escape CSS
tests/TestSuite/Util.hs+1−1
@@ -68,7 +68,7 @@ newTestProvider store = newProvider store (const False) "tests/data"
6868testCompiler :: Store -> Provider -> Identifier -> Compiler a
6969 -> IO (CompilerResult a)
7070testCompiler store provider underlying compiler = do
71− logger <- Logger.new Logger.Debug (\_ -> return ())
71+ logger <- Logger.new Logger.Error
7272 let read' = CompilerRead
7373 { compilerUnderlying = underlying
7474 , compilerProvider = provider