on master help sign in
Remove old code for now
commit9b63052148a140b8ad5fc04b996023d8b8e3796d authorJasper Van der Jeugt <jaspervdj@gmail.com> dateThu 23 Dec 2010, 14:31 (16 years ago) parentfef1172
24 files changed−2297
src/Network/Hakyll/SimpleServer.hs−215 src/Text/Hakyll.hs−185 src/Text/Hakyll/Configurations/Static.hs−59 src/Text/Hakyll/Context.hs−16 src/Text/Hakyll/CreateContext.hs−114 src/Text/Hakyll/Feed.hs−112 src/Text/Hakyll/File.hs−196 src/Text/Hakyll/Internal/Cache.hs−53 src/Text/Hakyll/Internal/CompressCss.hs−36 src/Text/Hakyll/Internal/FileType.hs−49 src/Text/Hakyll/Internal/Template.hs−86 src/Text/Hakyll/Internal/Template/Hamlet.hs−56 src/Text/Hakyll/Internal/Template/Template.hs−34 src/Text/Hakyll/Metadata.hs−108 src/Text/Hakyll/Monad.hs−115 src/Text/Hakyll/Page.hs−108 src/Text/Hakyll/Paginate.hs−94 src/Text/Hakyll/Pandoc.hs−88 src/Text/Hakyll/Regex.hs−77 src/Text/Hakyll/Render.hs−126 src/Text/Hakyll/Resource.hs−57 src/Text/Hakyll/Tags.hs−172 src/Text/Hakyll/Transformer.hs−107 src/Text/Hakyll/Util.hs−34
src/Network/Hakyll/SimpleServer.hsdeleted−215
@@ -1,215 +0,0 @@
1−-- | Module containing a small, simple http file server for testing and preview
2−-- purposes.
3−module Network.Hakyll.SimpleServer
4− ( simpleServer
5− ) where
6−
7−import Prelude hiding (log)
8−import Control.Monad (forever)
9−import Control.Monad.Reader (ReaderT, runReaderT, ask, liftIO)
10−import Network
11−import System.IO
12−import System.Directory (doesFileExist, doesDirectoryExist)
13−import Control.Concurrent (forkIO)
14−import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
15−import System.FilePath (takeExtension)
16−import qualified Data.Map as M
17−import Data.List (intercalate)
18−
19−import Text.Hakyll.Util
20−import Text.Hakyll.Regex
21−
22−-- | Function to log from a chan.
23−log :: Chan String -> IO ()
24−log logChan = forever (readChan logChan >>= hPutStrLn stderr)
25−
26−-- | General server configuration.
27−data ServerConfig = ServerConfig { documentRoot :: FilePath
28− , portNumber :: PortNumber
29− , logChannel :: Chan String
30− }
31−
32−-- | Custom monad stack.
33−type Server = ReaderT ServerConfig IO
34−
35−-- | Simple representation of a HTTP request.
36−data Request = Request { requestMethod :: String
37− , requestURI :: String
38− , requestVersion :: String
39− } deriving (Ord, Eq)
40−
41−instance Show Request where
42− show request = requestMethod request ++ " "
43− ++ requestURI request ++ " "
44− ++ requestVersion request
45−
46−-- | Read a HTTP request from a 'Handle'. For now, this will ignore the request
47−-- headers and body.
48−readRequest :: Handle -> Server Request
49−readRequest handle = do
50− requestLine <- liftIO $ hGetLine handle
51− let [method, uri, version] = map trim $ splitRegex " " requestLine
52− request = Request { requestMethod = method
53− , requestURI = uri
54− , requestVersion = version
55− }
56− return request
57−
58−-- | Simple representation of the HTTP response we send back.
59−data Response = Response { responseVersion :: String
60− , responseStatusCode :: Int
61− , responsePhrase :: String
62− , responseHeaders :: M.Map String String
63− , responseBody :: String
64− } deriving (Ord, Eq)
65−
66−instance Show Response where
67− show response = responseVersion response ++ " "
68− ++ show (responseStatusCode response) ++ " "
69− ++ responsePhrase response
70−
71−-- | A default response.
72−defaultResponse :: Response
73−defaultResponse = Response { responseVersion = "HTTP/1.1"
74− , responseStatusCode = 0
75− , responsePhrase = ""
76− , responseHeaders = M.empty
77− , responseBody = ""
78− }
79−
80−-- | Create a response for a given HTTP request.
81−createResponse :: Request -> Server Response
82−createResponse request
83− | requestMethod request == "GET" = createGetResponse request
84− | otherwise = return $ createErrorResponse 501 "Not Implemented"
85−
86−-- | Create a simple error response.
87−createErrorResponse :: Int -- ^ Error code.
88− -> String -- ^ Error phrase.
89− -> Response -- ^ Result.
90−createErrorResponse statusCode phrase = defaultResponse
91− { responseStatusCode = statusCode
92− , responsePhrase = phrase
93− , responseHeaders = M.singleton "Content-Type" "text/html"
94− , responseBody =
95− "<html> <head> <title>" ++ show statusCode ++ "</title> </head>"
96− ++ "<body> <h1>" ++ show statusCode ++ "</h1>\n"
97− ++ "<p>" ++ phrase ++ "</p> </body> </html>"
98− }
99−
100−-- | Create a simple get response.
101−createGetResponse :: Request -> Server Response
102−createGetResponse request = do
103− -- Construct the complete fileName of the requested resource.
104− config <- ask
105− let -- Drop everything after a '?'.
106− uri = takeWhile ((/=) '?') $ requestURI request
107− log' = writeChan (logChannel config)
108− isDirectory <- liftIO $ doesDirectoryExist $ documentRoot config ++ uri
109− let fileName =
110− documentRoot config ++ if isDirectory then uri ++ "/index.html"
111− else uri
112−
113− create200 = do
114− h <- openBinaryFile fileName ReadMode
115− contentLength <- hFileSize h
116− body <- hGetContents h
117− let mimeHeader = getMIMEHeader fileName
118− headers = ("Content-Length", show contentLength) : mimeHeader
119− return $ defaultResponse
120− { responseStatusCode = 200
121− , responsePhrase = "OK"
122− , responseHeaders = responseHeaders defaultResponse
123− `M.union` M.fromList headers
124− , responseBody = body
125− }
126−
127− -- Called when an error occurs during the creation of a 200 response.
128− create500 e = do
129− log' $ "Internal Error: " ++ show e
130− return $ createErrorResponse 500 "Internal Server Error"
131−
132− -- Send back the page if found.
133− exists <- liftIO $ doesFileExist fileName
134− if exists
135− then liftIO $ catch create200 create500
136− else do liftIO $ log' $ "Not Found: " ++ fileName
137− return $ createErrorResponse 404 "Not Found"
138−
139−-- | Get the mime header for a certain filename. This is based on the extension
140−-- of the given 'FilePath'.
141−getMIMEHeader :: FilePath -> [(String, String)]
142−getMIMEHeader fileName =
143− case result of (Just x) -> [("Content-Type", x)]
144− Nothing -> []
145− where
146− result = lookup (takeExtension fileName) [ (".css", "text/css")
147− , (".gif", "image/gif")
148− , (".htm", "text/html")
149− , (".html", "text/html")
150− , (".jpeg", "image/jpeg")
151− , (".jpg", "image/jpeg")
152− , (".js", "text/javascript")
153− , (".png", "image/png")
154− , (".txt", "text/plain")
155− , (".xml", "text/xml")
156− ]
157−
158−-- | Respond to an incoming request.
159−respond :: Handle -> Server ()
160−respond handle = do
161− -- Read the request and create a response.
162− request <- readRequest handle
163− response <- createResponse request
164−
165− -- Generate some output.
166− config <- ask
167− liftIO $ writeChan (logChannel config)
168− $ show request ++ " => " ++ show response
169−
170− -- Send the response back to the handle.
171− liftIO $ putResponse response
172− where
173− putResponse response = do hPutStr handle $ intercalate " "
174− [ responseVersion response
175− , show $ responseStatusCode response
176− , responsePhrase response
177− ]
178− hPutStr handle "\r\n"
179− mapM_ putHeader
180− (M.toList $ responseHeaders response)
181− hPutStr handle "\r\n"
182− hPutStr handle $ responseBody response
183− hPutStr handle "\r\n"
184− hClose handle
185−
186− putHeader (key, value) =
187− hPutStr handle $ key ++ ": " ++ value ++ "\r\n"
188−
189−-- | Start a simple http server on the given 'PortNumber', serving the given
190−-- directory.
191−--
192−simpleServer :: PortNumber -- ^ Port to listen on.
193− -> FilePath -- ^ Root directory to serve.
194− -> IO () -- ^ Optional pre-respond action.
195− -> IO ()
196−simpleServer port root preRespond = do
197− -- Channel to send logs to
198− logChan <- newChan
199−
200− let config = ServerConfig { documentRoot = root
201− , portNumber = port
202− , logChannel = logChan
203− }
204−
205− -- When a client connects, respond in a separate thread.
206− listen socket = do (handle, _, _) <- accept socket
207− preRespond
208− forkIO (runReaderT (respond handle) config)
209−
210− -- Handle logging in a separate thread
211− _ <- forkIO (log logChan)
212−
213− writeChan logChan $ "Starting hakyll server on port " ++ show port ++ "..."
214− socket <- listenOn (PortNumber port)
215− forever (listen socket)
src/Text/Hakyll.hsdeleted−185
@@ -1,185 +0,0 @@
1−-- | This is the main Hakyll module, exporting the important @hakyll@ function.
2−--
3−-- Most configurations would use this @hakyll@ function more or less as the
4−-- main function:
5−--
6−-- > main = hakyll $ do
7−-- > directory css "css"
8−-- > directory static "images"
9−--
10−module Text.Hakyll
11− ( defaultHakyllConfiguration
12− , hakyll
13− , hakyllWithConfiguration
14− , runDefaultHakyll
15−
16− , module Text.Hakyll.Context
17− , module Text.Hakyll.ContextManipulations
18− , module Text.Hakyll.CreateContext
19− , module Text.Hakyll.File
20− , module Text.Hakyll.HakyllMonad
21− , module Text.Hakyll.Regex
22− , module Text.Hakyll.Render
23− , module Text.Hakyll.HakyllAction
24− , module Text.Hakyll.Paginate
25− , module Text.Hakyll.Page
26− , module Text.Hakyll.Pandoc
27− , module Text.Hakyll.Util
28− , module Text.Hakyll.Tags
29− , module Text.Hakyll.Feed
30− , module Text.Hakyll.Configurations.Static
31− ) where
32−
33−import Control.Concurrent (forkIO, threadDelay)
34−import Control.Monad.Reader (runReaderT, liftIO, ask)
35−import Control.Monad (when)
36−import Data.Monoid (mempty)
37−import System.Environment (getArgs, getProgName)
38−import System.Directory (doesDirectoryExist, removeDirectoryRecursive)
39−import System.Time (getClockTime)
40−
41−import Text.Pandoc
42−import Text.Hamlet (defaultHamletSettings)
43−
44−import Network.Hakyll.SimpleServer (simpleServer)
45−import Text.Hakyll.Context
46−import Text.Hakyll.ContextManipulations
47−import Text.Hakyll.CreateContext
48−import Text.Hakyll.File
49−import Text.Hakyll.HakyllMonad
50−import Text.Hakyll.Regex
51−import Text.Hakyll.Render
52−import Text.Hakyll.HakyllAction
53−import Text.Hakyll.Paginate
54−import Text.Hakyll.Page
55−import Text.Hakyll.Pandoc
56−import Text.Hakyll.Util
57−import Text.Hakyll.Tags
58−import Text.Hakyll.Feed
59−import Text.Hakyll.Configurations.Static
60−
61−-- | The default reader options for pandoc parsing.
62−--
63−defaultPandocParserState :: ParserState
64−defaultPandocParserState = defaultParserState
65− { -- The following option causes pandoc to read smart typography, a nice
66− -- and free bonus.
67− stateSmart = True
68− }
69−
70−-- | The default writer options for pandoc rendering.
71−--
72−defaultPandocWriterOptions :: WriterOptions
73−defaultPandocWriterOptions = defaultWriterOptions
74− { -- This option causes literate haskell to be written using '>' marks in
75− -- html, which I think is a good default.
76− writerLiterateHaskell = True
77− }
78−
79−-- | The default hakyll configuration.
80−--
81−defaultHakyllConfiguration :: String -- ^ Absolute site URL.
82− -> HakyllConfiguration -- ^ Default config.
83−defaultHakyllConfiguration absoluteUrl' = HakyllConfiguration
84− { absoluteUrl = absoluteUrl'
85− , additionalContext = mempty
86− , siteDirectory = "_site"
87− , cacheDirectory = "_cache"
88− , enableIndexUrl = False
89− , previewMode = BuildOnRequest
90− , pandocParserState = defaultPandocParserState
91− , pandocWriterOptions = defaultPandocWriterOptions
92− , hamletSettings = defaultHamletSettings
93− }
94−
95−-- | Main function to run Hakyll with the default configuration. The
96−-- absolute URL is only used in certain cases, for example RSS feeds et
97−-- cetera.
98−--
99−hakyll :: String -- ^ Absolute URL of your site. Used in certain cases.
100− -> Hakyll () -- ^ You code.
101− -> IO ()
102−hakyll absolute = hakyllWithConfiguration configuration
103− where
104− configuration = defaultHakyllConfiguration absolute
105−
106−-- | Main function to run hakyll with a custom configuration.
107−--
108−hakyllWithConfiguration :: HakyllConfiguration -> Hakyll () -> IO ()
109−hakyllWithConfiguration configuration buildFunction = do
110− args <- getArgs
111− let f = case args of ["build"] -> buildFunction
112− ["clean"] -> clean
113− ["preview", p] -> preview (read p)
114− ["preview"] -> preview defaultPort
115− ["rebuild"] -> clean >> buildFunction
116− ["server", p] -> server (read p) (return ())
117− ["server"] -> server defaultPort (return ())
118− _ -> help
119− runReaderT f configuration
120− where
121− preview port = case previewMode configuration of
122− BuildOnRequest -> server port buildFunction
123− BuildOnInterval -> do
124− let pIO = runReaderT (previewThread buildFunction) configuration
125− _ <- liftIO $ forkIO pIO
126− server port (return ())
127−
128− defaultPort = 8000
129−
130−-- | A preview thread that periodically recompiles the site.
131−--
132−previewThread :: Hakyll () -- ^ Build function
133− -> Hakyll () -- ^ Result
134−previewThread buildFunction = run =<< liftIO getClockTime
135− where
136− delay = 1000000
137− run time = do liftIO $ threadDelay delay
138− contents <- getRecursiveContents "."
139− valid <- isMoreRecent time contents
140− when valid buildFunction
141− run =<< liftIO getClockTime
142−
143−-- | Clean up directories.
144−--
145−clean :: Hakyll ()
146−clean = do askHakyll siteDirectory >>= remove'
147− askHakyll cacheDirectory >>= remove'
148− where
149− remove' dir = liftIO $ do putStrLn $ "Removing " ++ dir ++ "..."
150− exists <- doesDirectoryExist dir
151− when exists $ removeDirectoryRecursive dir
152−
153−-- | Show usage information.
154−--
155−help :: Hakyll ()
156−help = liftIO $ do
157− name <- getProgName
158− putStrLn $ "This is a Hakyll site generator program. You should always\n"
159− ++ "run it from the project root directory.\n"
160− ++ "\n"
161− ++ "Usage:\n"
162− ++ name ++ " build Generate the site.\n"
163− ++ name ++ " clean Clean up and remove cache.\n"
164− ++ name ++ " help Show this message.\n"
165− ++ name ++ " preview [port] Run a server and autocompile.\n"
166− ++ name ++ " rebuild Clean up and build again.\n"
167− ++ name ++ " server [port] Run a local test server.\n"
168−
169−-- | Start a server at the given port number.
170−--
171−server :: Integer -- ^ Port number to serve on.
172− -> Hakyll () -- ^ Pre-respond action.
173− -> Hakyll ()
174−server port preRespond = do
175− configuration <- ask
176− root <- askHakyll siteDirectory
177− let preRespondIO = runReaderT preRespond configuration
178− liftIO $ simpleServer (fromIntegral port) root preRespondIO
179−
180−-- | Run a Hakyll action with default settings. This is mostly aimed at testing
181−-- code.
182−--
183−runDefaultHakyll :: Hakyll a -> IO a
184−runDefaultHakyll f =
185− runReaderT f $ defaultHakyllConfiguration "http://example.com"
src/Text/Hakyll/Configurations/Static.hsdeleted−59
@@ -1,59 +0,0 @@
1−-- | Module for a simple static configuration of a website.
2−--
3−-- The configuration works like this:
4−--
5−-- * The @templates/@ directory should contain one template.
6−--
7−-- * Renderable files in the directory tree are rendered using this template.
8−--
9−-- * The @static/@ directory is copied entirely (if it exists).
10−--
11−-- * All files in the @css/@ directory are compressed.
12−--
13−module Text.Hakyll.Configurations.Static
14− ( staticConfiguration
15− ) where
16−
17−import Control.Applicative ((<$>))
18−import Control.Monad (filterM, forM_)
19−
20−import Text.Hakyll.File ( getRecursiveContents, inDirectory, inHakyllDirectory
21− , directory )
22−import Text.Hakyll.Internal.FileType (isRenderableFile)
23−import Text.Hakyll.HakyllMonad (Hakyll, logHakyll)
24−import Text.Hakyll.Render (renderChain, css, static)
25−import Text.Hakyll.CreateContext (createPage)
26−
27−-- | A simple configuration for an entirely static website.
28−--
29−staticConfiguration :: Hakyll ()
30−staticConfiguration = do
31− -- Find all files not in _site or _cache.
32− files <- filterM isRenderableFile' =<< getRecursiveContents "."
33−
34− -- Find a main template to use
35− mainTemplate <- take 1 <$> getRecursiveContents templateDir
36− logHakyll $ case mainTemplate of [] -> "Using no template"
37− (x : _) -> "Using template " ++ x
38−
39− -- Render all files using this template
40− forM_ files $ renderChain mainTemplate . createPage
41−
42− -- Render a static directory
43− directory static staticDir
44−
45− -- Render a css directory
46− directory css cssDir
47− where
48− -- A file should have a renderable extension and not be in a hakyll
49− -- directory, and not in a special directory.
50− isRenderableFile' file = do
51− inHakyllDirectory' <- inHakyllDirectory file
52− return $ isRenderableFile file
53− && not (any (inDirectory file) [templateDir, cssDir, staticDir])
54− && not inHakyllDirectory'
55−
56− -- Directories
57− templateDir = "templates"
58− cssDir = "css"
59− staticDir = "static"
src/Text/Hakyll/Context.hsdeleted−16
@@ -1,16 +0,0 @@
1−-- | This (quite small) module exports the datatype used for contexts. A
2−-- @Context@ is a simple key-value mapping. You can render these @Context@s
3−-- with templates, and manipulate them in various ways.
4−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5−module Text.Hakyll.Context
6− ( Context (..)
7− ) where
8−
9−import Data.Monoid (Monoid)
10−import Data.Map (Map)
11−import Data.Binary (Binary)
12−
13−-- | Datatype used for key-value mappings.
14−newtype Context = Context { -- | Extract the context.
15− unContext :: Map String String
16− } deriving (Show, Monoid, Binary)
src/Text/Hakyll/CreateContext.hsdeleted−114
@@ -1,114 +0,0 @@
1−-- | A module that provides different ways to create a @Context@. These
2−-- functions all use the @HakyllAction@ arrow, so they produce values of the
3−-- type @HakyllAction () Context@.
4−module Text.Hakyll.CreateContext
5− ( createPage
6− , createCustomPage
7− , createListing
8− , addField
9− , combine
10− , combineWithUrl
11− ) where
12−
13−import Prelude hiding (id)
14−
15−import qualified Data.Map as M
16−import Control.Arrow (second, arr, (&&&), (***))
17−import Control.Monad (liftM2)
18−import Control.Applicative ((<$>))
19−import Control.Arrow ((>>>))
20−import Control.Category (id)
21−
22−import Text.Hakyll.Context
23−import Text.Hakyll.HakyllAction
24−import Text.Hakyll.Render
25−import Text.Hakyll.Page
26−import Text.Hakyll.Pandoc
27−import Text.Hakyll.Internal.Cache
28−
29−-- | Create a @Context@ from a page file stored on the disk. This is probably
30−-- the most common way to create a @Context@.
31−createPage :: FilePath -> HakyllAction () Context
32−createPage path = cacheAction "pages" $ readPageAction path >>> renderAction
33−
34−-- | Create a custom page @Context@.
35−--
36−-- The association list given maps keys to values for substitution. Note
37−-- that as value, you can either give a @String@ or a
38−-- @HakyllAction () String@. The latter is preferred for more complex data,
39−-- since it allows dependency checking. A @String@ is obviously more simple
40−-- to use in some cases.
41−--
42−createCustomPage :: FilePath
43− -> [(String, Either String (HakyllAction () String))]
44− -> HakyllAction () Context
45−createCustomPage url association = HakyllAction
46− { actionDependencies = dataDependencies
47− , actionUrl = Left $ return url
48− , actionFunction = \_ -> Context . M.fromList <$> assoc'
49− }
50− where
51− mtuple (a, b) = b >>= \b' -> return (a, b')
52− toHakyllString = second (either return runHakyllAction)
53− assoc' = mapM (mtuple . toHakyllString) $ ("url", Left url) : association
54− dataDependencies = map snd association >>= getDependencies
55− getDependencies (Left _) = []
56− getDependencies (Right x) = actionDependencies x
57−
58−-- | A @createCustomPage@ function specialized in creating listings.
59−--
60−-- This function creates a listing of a certain list of @Context@s. Every
61−-- item in the list is created by applying the given template to every
62−-- renderable. You can also specify additional context to be included in the
63−-- @CustomPage@.
64−createListing :: FilePath -- ^ Destination of the page.
65− -> [FilePath] -- ^ Templates to render items with.
66− -> [HakyllAction () Context] -- ^ Renderables in the list.
67− -> [(String, Either String (HakyllAction () String))]
68− -> HakyllAction () Context
69−createListing url templates renderables additional =
70− createCustomPage url context
71− where
72− context = ("body", Right concatenation) : additional
73− concatenation = renderAndConcat templates renderables
74−
75−-- | Add a field to a 'Context'.
76−--
77−addField :: String -- ^ Key
78− -> Either String (HakyllAction () String) -- ^ Value
79− -> HakyllAction Context Context -- ^ Result
80−addField key value = arr (const ()) &&& id
81− >>> value' *** id
82− >>> arr (uncurry insert)
83− where
84− value' = arr (const ()) >>> either (arr . const) id value
85− insert v = Context . M.insert key v . unContext
86−
87−-- | Combine two @Context@s. The url will always be taken from the first
88−-- @Renderable@. Also, if a `$key` is present in both renderables, the
89−-- value from the first @Context@ will be taken as well.
90−--
91−-- You can see this as a this as a @union@ between two mappings.
92−combine :: HakyllAction () Context -> HakyllAction () Context
93− -> HakyllAction () Context
94−combine x y = HakyllAction
95− { actionDependencies = actionDependencies x ++ actionDependencies y
96− , actionUrl = actionUrl x
97− , actionFunction = \_ ->
98− Context <$> liftM2 (M.union) (unContext <$> runHakyllAction x)
99− (unContext <$> runHakyllAction y)
100− }
101−
102−-- | Combine two @Context@s and set a custom URL. This behaves like @combine@,
103−-- except that for the @url@ field, the given URL is always chosen.
104−combineWithUrl :: FilePath
105− -> HakyllAction () Context
106− -> HakyllAction () Context
107− -> HakyllAction () Context
108−combineWithUrl url x y = combine'
109− { actionUrl = Left $ return url
110− , actionFunction = \_ ->
111− Context . M.insert "url" url . unContext <$> runHakyllAction combine'
112− }
113− where
114− combine' = combine x y
src/Text/Hakyll/Feed.hsdeleted−112
@@ -1,112 +0,0 @@
1−-- | A Module that allows easy rendering of RSS feeds. If you use this module,
2−-- you must make sure you set the `absoluteUrl` field in the main Hakyll
3−-- configuration.
4−--
5−-- Apart from that, the main rendering functions (@renderRss@, @renderAtom@)
6−-- all assume that you pass the list of items so that the most recent entry
7−-- in the feed is the first item in the list.
8−--
9−-- Also note that the @Context@s should have (at least) the following
10−-- fields to produce a correct feed:
11−--
12−-- - @$title@: Title of the item.
13−--
14−-- - @$description@: Description to appear in the feed.
15−--
16−-- - @$url@: URL to the item - this is usually set automatically.
17−--
18−-- Furthermore, the feed will be generated, but will be incorrect (it won't
19−-- validate) if an empty list is passed.
20−--
21−module Text.Hakyll.Feed
22− ( FeedConfiguration (..)
23− , renderRss
24− , renderAtom
25− ) where
26−
27−import Control.Arrow ((>>>), second)
28−import Control.Monad.Reader (liftIO)
29−import Data.Maybe (fromMaybe)
30−import qualified Data.Map as M
31−
32−import Text.Hakyll.Context (Context (..))
33−import Text.Hakyll.CreateContext (createListing)
34−import Text.Hakyll.ContextManipulations (renderDate)
35−import Text.Hakyll.HakyllMonad (Hakyll)
36−import Text.Hakyll.Render (render, renderChain)
37−import Text.Hakyll.HakyllAction
38−
39−import Paths_hakyll
40−
41−-- | This is a data structure to keep the configuration of a feed.
42−data FeedConfiguration = FeedConfiguration
43− { -- | Url of the feed (relative to site root). For example, @rss.xml@.
44− feedUrl :: String
45− , -- | Title of the feed.
46− feedTitle :: String
47− , -- | Description of the feed.
48− feedDescription :: String
49− , -- | Name of the feed author.
50− feedAuthorName :: String
51− }
52−
53−-- | This is an auxiliary function to create a listing that is, in fact, a feed.
54−-- The items should be sorted on date.
55−createFeed :: FeedConfiguration -- ^ Feed configuration.
56− -> [HakyllAction () Context] -- ^ Items to include.
57− -> FilePath -- ^ Feed template.
58− -> FilePath -- ^ Item template.
59− -> HakyllAction () Context
60−createFeed configuration renderables template itemTemplate =
61− listing >>> render template
62− where
63− listing = createListing (feedUrl configuration)
64− [itemTemplate] renderables additional
65−
66− additional = map (second $ Left . ($ configuration))
67− [ ("title", feedTitle)
68− , ("description", feedDescription)
69− , ("authorName", feedAuthorName)
70− ] ++ updated
71−
72− -- Take the first timestamp, which should be the most recent.
73− updated = let action = createHakyllAction $ return . fromMaybe "foo"
74− . M.lookup "timestamp" . unContext
75− toTuple r = ("timestamp", Right $ r >>> action)
76− in map toTuple $ take 1 renderables
77−
78−
79−-- | Abstract function to render any feed.
80−renderFeed :: FeedConfiguration -- ^ Feed configuration.
81− -> [HakyllAction () Context] -- ^ Items to include in the feed.
82− -> FilePath -- ^ Feed template.
83− -> FilePath -- ^ Item template.
84− -> Hakyll ()
85−renderFeed configuration renderables template itemTemplate = do
86− template' <- liftIO $ getDataFileName template
87− itemTemplate' <- liftIO $ getDataFileName itemTemplate
88− let renderFeed' = createFeed configuration renderables
89− template' itemTemplate'
90− renderChain [] renderFeed'
91−
92−-- | Render an RSS feed with a number of items.
93−renderRss :: FeedConfiguration -- ^ Feed configuration.
94− -> [HakyllAction () Context] -- ^ Items to include in the feed.
95− -> Hakyll ()
96−renderRss configuration renderables =
97− renderFeed configuration (map (>>> renderRssDate) renderables)
98− "templates/rss.xml" "templates/rss-item.xml"
99− where
100− renderRssDate = renderDate "timestamp" "%a, %d %b %Y %H:%M:%S UT"
101− "No date found."
102−
103−-- | Render an Atom feed with a number of items.
104−renderAtom :: FeedConfiguration -- ^ Feed configuration.
105− -> [HakyllAction () Context] -- ^ Items to include in the feed.
106− -> Hakyll ()
107−renderAtom configuration renderables =
108− renderFeed configuration (map (>>> renderAtomDate) renderables)
109− "templates/atom.xml" "templates/atom-item.xml"
110− where
111− renderAtomDate = renderDate "timestamp" "%Y-%m-%dT%H:%M:%SZ"
112− "No date found."
src/Text/Hakyll/File.hsdeleted−196
@@ -1,196 +0,0 @@
1−-- | A module containing various function for manipulating and examinating
2−-- files and directories.
3−module Text.Hakyll.File
4− ( toDestination
5− , toCache
6− , toUrl
7− , toRoot
8− , inDirectory
9− , inHakyllDirectory
10− , removeSpaces
11− , makeDirectories
12− , getRecursiveContents
13− , sortByBaseName
14− , havingExtension
15− , directory
16− , isMoreRecent
17− , isFileMoreRecent
18− ) where
19−
20−import System.Directory
21−import Control.Applicative ((<$>))
22−import System.FilePath
23−import System.Time (ClockTime)
24−import Control.Monad
25−import Data.List (isPrefixOf, sortBy)
26−import Data.Ord (comparing)
27−import Control.Monad.Reader (liftIO)
28−
29−import Text.Hakyll.Monad
30−import Text.Hakyll.Internal.FileType (isRenderableFile)
31−
32−-- | Auxiliary function to remove pathSeparators form the start. We don't deal
33−-- with absolute paths here. We also remove $root from the start.
34−removeLeadingSeparator :: FilePath -> FilePath
35−removeLeadingSeparator [] = []
36−removeLeadingSeparator path
37− | head path' `elem` pathSeparators = drop 1 path'
38− | otherwise = path'
39− where
40− path' = if "$root" `isPrefixOf` path then drop 5 path
41− else path
42−
43−-- | Convert a relative URL to a filepath in the destination
44−-- (default: @_site@).
45−toDestination :: FilePath -> Hakyll FilePath
46−toDestination url = do dir <- askHakyll siteDirectory
47− toFilePath dir url
48−
49−-- | Convert a relative URL to a filepath in the cache
50−-- (default: @_cache@).
51−toCache :: FilePath -> Hakyll FilePath
52−toCache path = do dir <- askHakyll cacheDirectory
53− toFilePath dir path
54−
55−-- | Implementation of toDestination/toCache
56−--
57−toFilePath :: String -- ^ Directory (site or cache)
58− -> String -- ^ URL
59− -> Hakyll FilePath -- ^ Resulting file path
60−toFilePath dir url = do
61− enableIndexUrl' <- askHakyll enableIndexUrl
62− let destination = if enableIndexUrl' && separatorEnd
63− then dir </> noSeparator </> "index.html"
64− else dir </> noSeparator
65− return destination
66− where
67− noSeparator = removeLeadingSeparator url
68− separatorEnd = not (null url) && last url == '/'
69−
70−-- | Get the url for a given page. For most extensions, this would be the path
71−-- itself. It's only for rendered extensions (@.markdown@, @.rst@, @.lhs@ this
72−-- function returns a path with a @.html@ extension instead.
73−toUrl :: FilePath -> Hakyll FilePath
74−toUrl path = do enableIndexUrl' <- askHakyll enableIndexUrl
75− -- If the file does not have a renderable extension, like for
76− -- example favicon.ico, we don't have to change it at all.
77− return $ if not (isRenderableFile path)
78− then path
79− -- If index url's are enabled, we create pick it
80− -- unless the page is an index already.
81− else if enableIndexUrl' && not isIndex
82− then indexUrl
83− else withSimpleHtmlExtension
84− where
85− isIndex = dropExtension (takeFileName path) == "index"
86− withSimpleHtmlExtension = flip addExtension ".html" $ dropExtension path
87− indexUrl = dropExtension path ++ "/"
88−
89−
90−-- | Get the relative url to the site root, for a given (absolute) url
91−toRoot :: FilePath -> FilePath
92−toRoot = emptyException . joinPath . map parent . splitPath
93− . takeDirectory . removeLeadingSeparator
94− where
95− parent = const ".."
96− emptyException [] = "."
97− emptyException x = x
98−
99−-- | Check if a file is in a given directory.
100−--
101−inDirectory :: FilePath -- ^ File path
102− -> FilePath -- ^ Directory
103− -> Bool -- ^ Result
104−inDirectory path dir = case splitDirectories path of
105− [] -> False
106− (x : _) -> x == dir
107−
108−-- | Check if a file is in a Hakyll directory. With a Hakyll directory, we mean
109−-- a directory that should be "ignored" such as the @_site@ or @_cache@
110−-- directory.
111−--
112−-- Example:
113−--
114−-- > inHakyllDirectory "_cache/pages/index.html"
115−--
116−-- Result:
117−--
118−-- > True
119−--
120−inHakyllDirectory :: FilePath -> Hakyll Bool
121−inHakyllDirectory path =
122− or <$> mapM (liftM (inDirectory path) . askHakyll)
123− [siteDirectory, cacheDirectory]
124−
125−-- | Swaps spaces for '-'.
126−removeSpaces :: FilePath -> FilePath
127−removeSpaces = map swap
128− where
129− swap ' ' = '-'
130− swap x = x
131−
132−-- | Given a path to a file, try to make the path writable by making
133−-- all directories on the path.
134−makeDirectories :: FilePath -> Hakyll ()
135−makeDirectories path = liftIO $ createDirectoryIfMissing True dir
136− where
137− dir = takeDirectory path
138−
139−-- | Get all contents of a directory. Note that files starting with a dot (.)
140−-- will be ignored.
141−--
142−getRecursiveContents :: FilePath -> Hakyll [FilePath]
143−getRecursiveContents topdir = do
144− topdirExists <- liftIO $ doesDirectoryExist topdir
145− if topdirExists
146− then do names <- liftIO $ getDirectoryContents topdir
147− let properNames = filter isProper names
148− paths <- forM properNames $ \name -> do
149− let path = topdir </> name
150− isDirectory <- liftIO $ doesDirectoryExist path
151− if isDirectory
152− then getRecursiveContents path
153− else return [normalise path]
154− return (concat paths)
155− else return []
156− where
157− isProper = not . (== '.') . head
158−
159−-- | Sort a list of filenames on the basename.
160−sortByBaseName :: [FilePath] -> [FilePath]
161−sortByBaseName = sortBy compareBaseName
162− where
163− compareBaseName = comparing takeFileName
164−
165−-- | A filter that takes all file names with a given extension. Prefix the
166−-- extension with a dot:
167−--
168−-- > havingExtension ".markdown" [ "index.markdown"
169−-- > , "style.css"
170−-- > ] == ["index.markdown"]
171−havingExtension :: String -> [FilePath] -> [FilePath]
172−havingExtension extension = filter ((==) extension . takeExtension)
173−
174−-- | Perform a Hakyll action on every file in a given directory.
175−directory :: (FilePath -> Hakyll ()) -> FilePath -> Hakyll ()
176−directory action dir = getRecursiveContents dir >>= mapM_ action
177−
178−-- | Check if a timestamp is newer then a number of given files.
179−isMoreRecent :: ClockTime -- ^ The time to check.
180− -> [FilePath] -- ^ Dependencies of the cached file.
181− -> Hakyll Bool
182−isMoreRecent _ [] = return True
183−isMoreRecent timeStamp depends = do
184− dependsModified <- liftIO $ mapM getModificationTime depends
185− return (timeStamp >= maximum dependsModified)
186−
187−-- | Check if a file is newer then a number of given files.
188−isFileMoreRecent :: FilePath -- ^ The cached file.
189− -> [FilePath] -- ^ Dependencies of the cached file.
190− -> Hakyll Bool
191−isFileMoreRecent file depends = do
192− exists <- liftIO $ doesFileExist file
193− if not exists
194− then return False
195− else do timeStamp <- liftIO $ getModificationTime file
196− isMoreRecent timeStamp depends
src/Text/Hakyll/Internal/Cache.hsdeleted−53
@@ -1,53 +0,0 @@
1−module Text.Hakyll.Internal.Cache
2− ( storeInCache
3− , getFromCache
4− , isCacheMoreRecent
5− , cacheAction
6− ) where
7−
8−import Control.Monad ((<=<))
9−import Control.Monad.Reader (liftIO)
10−import Data.Binary
11−import System.FilePath ((</>))
12−
13−import Text.Hakyll.File
14−import Text.Hakyll.HakyllMonad (Hakyll)
15−import Text.Hakyll.HakyllAction
16−
17−-- | We can store all datatypes instantiating @Binary@ to the cache. The cache
18−-- directory is specified by the @HakyllConfiguration@, usually @_cache@.
19−storeInCache :: (Binary a) => a -> FilePath -> Hakyll ()
20−storeInCache value path = do
21− cachePath <- toCache path
22− makeDirectories cachePath
23− liftIO $ encodeFile cachePath value
24−
25−-- | Get a value from the cache. The filepath given should not be located in the
26−-- cache. This function performs a timestamp check on the filepath and the
27−-- filepath in the cache, and only returns the cached value when it is still
28−-- up-to-date.
29−getFromCache :: (Binary a) => FilePath -> Hakyll a
30−getFromCache = liftIO . decodeFile <=< toCache
31−
32−-- | Check if a file in the cache is more recent than a number of other files.
33−isCacheMoreRecent :: FilePath -> [FilePath] -> Hakyll Bool
34−isCacheMoreRecent file depends = toCache file >>= flip isFileMoreRecent depends
35−
36−-- | Cache an entire arrow
37−--
38−cacheAction :: Binary a
39− => String
40− -> HakyllAction () a
41− -> HakyllAction () a
42−cacheAction key action = action { actionFunction = const cacheFunction }
43− where
44− cacheFunction = do
45− -- Construct a filename
46− fileName <- fmap (key </>) $ either id (const $ return "unknown")
47− $ actionUrl action
48− -- Check the cache
49− cacheOk <- isCacheMoreRecent fileName $ actionDependencies action
50− if cacheOk then getFromCache fileName
51− else do result <- actionFunction action ()
52− storeInCache result fileName
53− return result
src/Text/Hakyll/Internal/CompressCss.hsdeleted−36
@@ -1,36 +0,0 @@
1−-- | Module used for CSS compression. The compression is currently in a simple
2−-- state, but would typically reduce the number of bytes by about 25%.
3−module Text.Hakyll.Internal.CompressCss
4− ( compressCss
5− ) where
6−
7−import Data.List (isPrefixOf)
8−
9−import Text.Hakyll.Regex (substituteRegex)
10−
11−-- | Compress CSS to speed up your site.
12−compressCss :: String -> String
13−compressCss = compressSeparators
14− . stripComments
15− . compressWhitespace
16−
17−-- | Compresses certain forms of separators.
18−compressSeparators :: String -> String
19−compressSeparators = substituteRegex "; *}" "}"
20− . substituteRegex " *([{};:]) *" "\\1"
21− . substituteRegex ";;*" ";"
22−
23−-- | Compresses all whitespace.
24−compressWhitespace :: String -> String
25−compressWhitespace = substituteRegex "[ \t\n][ \t\n]*" " "
26−
27−-- | Function that strips CSS comments away.
28−stripComments :: String -> String
29−stripComments [] = []
30−stripComments str
31− | isPrefixOf "/*" str = stripComments $ eatComments $ drop 2 str
32− | otherwise = head str : stripComments (drop 1 str)
33− where
34− eatComments str' | null str' = []
35− | isPrefixOf "*/" str' = drop 2 str'
36− | otherwise = eatComments $ drop 1 str'
src/Text/Hakyll/Internal/FileType.hsdeleted−49
@@ -1,49 +0,0 @@
1−-- | A module dealing with file extensions and associated file types.
2−module Text.Hakyll.Internal.FileType
3− ( FileType (..)
4− , getFileType
5− , isRenderable
6− , isRenderableFile
7− ) where
8−
9−import System.FilePath (takeExtension)
10−
11−-- | Datatype to represent the different file types Hakyll can deal with.
12−data FileType = Html
13− | LaTeX
14− | LiterateHaskellMarkdown
15− | Markdown
16− | ReStructuredText
17− | Text
18− | UnknownFileType
19− deriving (Eq, Ord, Show, Read)
20−
21−-- | Get the file type for a certain file. The type is determined by extension.
22−getFileType :: FilePath -> FileType
23−getFileType = getFileType' . takeExtension
24− where
25− getFileType' ".htm" = Html
26− getFileType' ".html" = Html
27− getFileType' ".lhs" = LiterateHaskellMarkdown
28− getFileType' ".markdown" = Markdown
29− getFileType' ".md" = Markdown
30− getFileType' ".mdn" = Markdown
31− getFileType' ".mdown" = Markdown
32− getFileType' ".mdwn" = Markdown
33− getFileType' ".mkd" = Markdown
34− getFileType' ".mkdwn" = Markdown
35− getFileType' ".page" = Markdown
36− getFileType' ".rst" = ReStructuredText
37− getFileType' ".tex" = LaTeX
38− getFileType' ".text" = Text
39− getFileType' ".txt" = Text
40− getFileType' _ = UnknownFileType
41−
42−-- | Check if a certain @FileType@ is renderable.
43−isRenderable :: FileType -> Bool
44−isRenderable UnknownFileType = False
45−isRenderable _ = True
46−
47−-- | Check if a certain file is renderable.
48−isRenderableFile :: FilePath -> Bool
49−isRenderableFile = isRenderable . getFileType
src/Text/Hakyll/Internal/Template.hsdeleted−86
@@ -1,86 +0,0 @@
1−module Text.Hakyll.Internal.Template
2− ( Template (..)
3− , fromString
4− , readTemplate
5− , substitute
6− , regularSubstitute
7− , finalSubstitute
8− ) where
9−
10−import Control.Arrow ((>>>))
11−import Control.Applicative ((<$>))
12−import Data.List (isPrefixOf)
13−import Data.Char (isAlphaNum)
14−import Data.Maybe (fromMaybe)
15−import System.FilePath ((</>))
16−import qualified Data.Map as M
17−
18−import Text.Hakyll.Context (Context (..))
19−import Text.Hakyll.HakyllMonad (Hakyll)
20−import Text.Hakyll.HakyllAction
21−import Text.Hakyll.Pandoc
22−import Text.Hakyll.Internal.Cache
23−import Text.Hakyll.Page
24−import Text.Hakyll.ContextManipulations
25−import Text.Hakyll.Internal.Template.Template
26−import Text.Hakyll.Internal.Template.Hamlet
27−
28−-- | Construct a @Template@ from a string.
29−--
30−fromString :: String -> Template
31−fromString = Template . fromString'
32− where
33− fromString' [] = []
34− fromString' string
35− | "$$" `isPrefixOf` string =
36− EscapeCharacter : (fromString' $ drop 2 string)
37− | "$" `isPrefixOf` string =
38− let (key, rest) = span isAlphaNum $ drop 1 string
39− in Identifier key : fromString' rest
40− | otherwise =
41− let (chunk, rest) = break (== '$') string
42− in Chunk chunk : fromString' rest
43−
44−-- | Read a @Template@ from a file. This function might fetch the @Template@
45−-- from the cache, if available.
46−readTemplate :: FilePath -> Hakyll Template
47−readTemplate path = do
48− isCacheMoreRecent' <- isCacheMoreRecent fileName [path]
49− if isCacheMoreRecent'
50− then getFromCache fileName
51− else do
52− template <- if isHamletRTFile path
53− then readHamletTemplate
54− else readDefaultTemplate
55− storeInCache template fileName
56− return template
57− where
58− fileName = "templates" </> path
59− readDefaultTemplate = do
60− body <- runHakyllAction $ readPageAction path
61− >>> renderAction
62− >>> takeBody
63− return $ fromString body
64−
65− readHamletTemplate = fromHamletRT <$> readHamletRT path
66−
67−-- | Substitutes @$identifiers@ in the given @Template@ by values from the given
68−-- "Context". When a key is not found, it is left as it is. You can specify
69−-- the characters used to replace escaped dollars (@$$@) here.
70−substitute :: String -> Template -> Context -> String
71−substitute escaper template context = substitute' =<< unTemplate template
72− where
73− substitute' (Chunk chunk) = chunk
74− substitute' (Identifier key) =
75− fromMaybe ('$' : key) $ M.lookup key $ unContext context
76− substitute' (EscapeCharacter) = escaper
77−
78−-- | @substitute@ for use during a chain. This will leave escaped characters as
79−-- they are.
80−regularSubstitute :: Template -> Context -> String
81−regularSubstitute = substitute "$$"
82−
83−-- | @substitute@ for the end of a chain (just before writing). This renders
84−-- escaped characters.
85−finalSubstitute :: Template -> Context -> String
86−finalSubstitute = substitute "$"
src/Text/Hakyll/Internal/Template/Hamlet.hsdeleted−56
@@ -1,56 +0,0 @@
1−-- | Support for Hamlet templates in Hakyll.
2−--
3−module Text.Hakyll.Internal.Template.Hamlet
4− ( isHamletRTFile
5− , readHamletRT
6− , fromHamletRT
7− ) where
8−
9−import Control.Exception (try)
10−import Control.Monad.Trans (liftIO)
11−import System.FilePath (takeExtension)
12−
13−import Text.Hamlet.RT
14−
15−import Text.Hakyll.Internal.Template.Template
16−import Text.Hakyll.HakyllMonad (Hakyll, askHakyll, hamletSettings, logHakyll)
17−
18−-- | Determine if a file is a hamlet template by extension.
19−--
20−isHamletRTFile :: FilePath -> Bool
21−isHamletRTFile fileName = takeExtension fileName `elem` [".hamlet", ".hml"]
22−
23−-- | Read a 'HamletRT' by file name.
24−--
25−readHamletRT :: FilePath -- ^ Filename of the template
26− -> Hakyll HamletRT -- ^ Resulting hamlet template
27−readHamletRT fileName = do
28− settings <- askHakyll hamletSettings
29− string <- liftIO $ readFile fileName
30− result <- liftIO $ try $ parseHamletRT settings string
31− case result of
32− Left (HamletParseException s) -> error' s
33− Left (HamletUnsupportedDocException d) -> error' $ show d
34− Left (HamletRenderException s) -> error' s
35− Right x -> return x
36− where
37− error' s = do
38− logHakyll $ "Parse of hamlet file " ++ fileName ++ " failed."
39− logHakyll s
40− error "Parse failed."
41−
42−-- | Convert a 'HamletRT' to a 'Template'
43−--
44−fromHamletRT :: HamletRT -- ^ Hamlet runtime template
45− -> Template -- ^ Hakyll template
46−fromHamletRT (HamletRT sd) = Template $ map fromSimpleDoc sd
47− where
48− fromSimpleDoc :: SimpleDoc -> TemplateElement
49− fromSimpleDoc (SDRaw chunk) = Chunk chunk
50− fromSimpleDoc (SDVar [var]) = Identifier var
51− fromSimpleDoc (SDVar _) =
52− error "Hakyll does not support '.' in identifier names when using \
53− \hamlet templates."
54− fromSimpleDoc _ =
55− error "Only simple $key$ identifiers are allowed when using hamlet \
56− \templates."
src/Text/Hakyll/Internal/Template/Template.hsdeleted−34
@@ -1,34 +0,0 @@
1−-- | Module containing the template data structure.
2−--
3−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4−module Text.Hakyll.Internal.Template.Template
5− ( Template (..)
6− , TemplateElement (..)
7− ) where
8−
9−import Control.Applicative ((<$>))
10−
11−import Data.Binary (Binary, get, getWord8, put, putWord8)
12−
13−-- | Datatype used for template substitutions.
14−--
15−newtype Template = Template { unTemplate :: [TemplateElement] }
16− deriving (Show, Eq, Binary)
17−
18−-- | Elements of a template.
19−--
20−data TemplateElement = Chunk String
21− | Identifier String
22− | EscapeCharacter
23− deriving (Show, Eq)
24−
25−instance Binary TemplateElement where
26− put (Chunk string) = putWord8 0 >> put string
27− put (Identifier key) = putWord8 1 >> put key
28− put (EscapeCharacter) = putWord8 2
29−
30− get = getWord8 >>= \tag ->
31− case tag of 0 -> Chunk <$> get
32− 1 -> Identifier <$> get
33− 2 -> return EscapeCharacter
34− _ -> error "Error reading cached template"
src/Text/Hakyll/Metadata.hsdeleted−108
@@ -1,108 +0,0 @@
1−-- | This module exports a number of functions to manipulate metadata of
2−-- resources
3−--
4−module Text.Hakyll.ContextManipulations
5− ( renderValue
6− , changeValue
7− , changeUrl
8− , copyValue
9− , renderDate
10− , renderDateWithLocale
11− , changeExtension
12− ) where
13−
14−import System.Locale (TimeLocale, defaultTimeLocale)
15−import System.FilePath (takeFileName, addExtension, dropExtension)
16−import Data.Time.Format (parseTime, formatTime)
17−import Data.Time.Clock (UTCTime)
18−import Data.Maybe (fromMaybe)
19−import qualified Data.Map as M
20−
21−import Text.Hakyll.Regex (substituteRegex)
22−import Text.Hakyll.Transformer (Transformer (..), transformMetadata)
23−import Text.Hakyll.Resource
24−
25−-- | Do something with a value in a @Context@, but keep the old value as well.
26−-- If the key given is not present in the @Context@, nothing will happen.
27−--
28−renderValue :: String -- ^ Key of which the value should be copied.
29− -> String -- ^ Key the value should be copied to.
30− -> (String -> String) -- ^ Function to apply on the value.
31− -> Transformer a a -- ^ Resulting transformer
32−renderValue source destination f = transformMetadata $ \(Metadata m) ->
33− Metadata $ case M.lookup source m of
34− Nothing -> m
35− (Just value) -> M.insert destination (f value) m
36−
37−-- | Change a value in the metadata
38−--
39−-- > import Data.Char (toUpper)
40−-- > changeValue "title" (map toUpper)
41−--
42−-- Will put the title in UPPERCASE.
43−changeValue :: String -- ^ Key to change.
44− -> (String -> String) -- ^ Function to apply on the value.
45− -> Transformer a a
46−changeValue key = renderValue key key
47−
48−-- | Change the URL of a page. You should always use this function instead of
49−-- 'changeValue' for this, because using 'changeValue' might break dependency
50−-- handling when changing the @url@ field.
51−--
52−changeUrl :: (String -> String) -- ^ Function to change URL with.
53− -> Transformer a a -- ^ Resulting action.
54−changeUrl f = let t = changeValue "url" f
55− in t {transformerUrl = return . f}
56−
57−-- | Copy a metadata value from one key to another
58−--
59−copyValue :: String -- ^ Source key.
60− -> String -- ^ Destination key.
61− -> Transformer a a -- ^ Resulting transformer
62−copyValue source destination = renderValue source destination id
63−
64−-- | When the context has a key called @path@ in a
65−-- @folder/yyyy-mm-dd-title.extension@ format (the convention for pages),
66−-- this function can render the date.
67−--
68−-- > renderDate "date" "%B %e, %Y" "Date unknown"
69−--
70−-- Will render something like @January 32, 2010@.
71−--
72−renderDate :: String -- ^ Key in which the rendered date should be placed.
73− -> String -- ^ Format to use on the date.
74− -> String -- ^ Default key, in case the date cannot be parsed.
75− -> Transformer a a
76−renderDate = renderDateWithLocale defaultTimeLocale
77−
78−-- | This is an extended version of 'renderDate' that allows you to specify a
79−-- time locale that is used for outputting the date. For more details, see
80−-- 'renderDate'.
81−--
82−renderDateWithLocale :: TimeLocale -- ^ Output time locale.
83− -> String -- ^ Destination key.
84− -> String -- ^ Format to use on the date.
85− -> String -- ^ Default key.
86− -> Transformer a a
87−renderDateWithLocale locale key format defaultValue =
88− renderValue "path" key renderDate'
89− where
90− renderDate' filePath = fromMaybe defaultValue $ do
91− let dateString = substituteRegex "^([0-9]*-[0-9]*-[0-9]*).*" "\\1"
92− (takeFileName filePath)
93− time <- parseTime defaultTimeLocale
94− "%Y-%m-%d"
95− dateString :: Maybe UTCTime
96− return $ formatTime locale format time
97−
98−-- | Change the extension of a file. This is only needed when you want to
99−-- render, for example, mardown to @.php@ files instead of @.html@ files.
100−--
101−-- > changeExtension "php"
102−--
103−-- Will render @test.markdown@ to @test.php@ instead of @test.html@.
104−changeExtension :: String -- ^ Extension to change to.
105− -> Transformer a a -- ^ Resulting transformer
106−changeExtension extension = changeValue "url" changeExtension'
107− where
108− changeExtension' = flip addExtension extension . dropExtension
src/Text/Hakyll/Monad.hsdeleted−115
@@ -1,115 +0,0 @@
1−-- | Module describing the Hakyll monad stack.
2−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3−module Text.Hakyll.Monad
4− ( HakyllConfiguration (..)
5− , PreviewMode (..)
6− , Hakyll
7− , askHakyll
8− , getAdditionalContext
9− , logHakyll
10− , forkHakyllWait
11− , concurrentHakyll
12− ) where
13−
14−import Control.Monad.Trans (liftIO)
15−import Control.Monad.IO.Class (MonadIO, liftIO)
16−import Control.Concurrent.MVar (MVar, putMVar, newEmptyMVar, readMVar)
17−import Control.Monad.Reader (ReaderT, ask, runReaderT)
18−import Control.Monad (liftM, forM, forM_)
19−import qualified Data.Map as M
20−import System.IO (hPutStrLn, stderr)
21−
22−import Text.Pandoc (ParserState, WriterOptions)
23−import Text.Hamlet (HamletSettings)
24−
25−import Text.Hakyll.Context (Context (..))
26−
27−-- | Our custom monad stack.
28−--
29−newtype Hakyll a = Hakyll (ReaderT HakyllConfiguration IO a)
30− deriving (Monad, Functor)
31−
32−instance MonadIO Hakyll where
33− liftIO = Hakyll . liftIO
34−
35−-- | Run a hakyll stack
36−--
37−runHakyll :: Hakyll a -> HakyllConfiguration -> IO a
38−runHakyll (Hakyll h) = runReaderT h
39−
40−-- | Preview mode.
41−--
42−data PreviewMode = BuildOnRequest
43− | BuildOnInterval
44− deriving (Show, Eq, Ord)
45−
46−-- | Hakyll global configuration type.
47−--
48−data HakyllConfiguration = HakyllConfiguration
49− { -- | Absolute URL of the site.
50− absoluteUrl :: String
51− , -- | An additional context to use when rendering. This additional context
52− -- is used globally.
53− additionalContext :: Context
54− , -- | Directory where the site is placed.
55− siteDirectory :: FilePath
56− , -- | Directory for cache files.
57− cacheDirectory :: FilePath
58− , -- | Enable index links.
59− enableIndexUrl :: Bool
60− , -- | The preview mode used
61− previewMode :: PreviewMode
62− , -- | Pandoc parsing options
63− pandocParserState :: ParserState
64− , -- | Pandoc writer options
65− pandocWriterOptions :: WriterOptions
66− , -- | Hamlet settings (if you use hamlet for templates)
67− hamletSettings :: HamletSettings
68− }
69−
70−-- | Get the hakyll configuration
71−--
72−getHakyllConfiguration :: Hakyll HakyllConfiguration
73−getHakyllConfiguration = Hakyll ask
74−
75−-- | Simplified @ask@ function for the Hakyll monad stack.
76−--
77−-- Usage would typically be something like:
78−--
79−-- > doSomething :: a -> b -> Hakyll c
80−-- > doSomething arg1 arg2 = do
81−-- > siteDirectory' <- askHakyll siteDirectory
82−-- > ...
83−--
84−askHakyll :: (HakyllConfiguration -> a) -> Hakyll a
85−askHakyll = flip liftM getHakyllConfiguration
86−
87−-- | Obtain the globally available, additional context.
88−--
89−getAdditionalContext :: HakyllConfiguration -> Context
90−getAdditionalContext configuration =
91− let (Context c) = additionalContext configuration
92− in Context $ M.insert "absolute" (absoluteUrl configuration) c
93−
94−-- | Write some log information.
95−--
96−logHakyll :: String -> Hakyll ()
97−logHakyll = Hakyll . liftIO . hPutStrLn stderr
98−
99−-- | Perform a concurrent hakyll action. Returns an MVar you can wait on
100−--
101−forkHakyllWait :: Hakyll () -> Hakyll (MVar ())
102−forkHakyllWait action = do
103− mvar <- liftIO newEmptyMVar
104− config <- getHakyllConfiguration
105− liftIO $ do
106− runHakyll action config
107− putMVar mvar ()
108− return mvar
109−
110−-- | Perform a number of concurrent hakyll actions, and waits for them to finish
111−--
112−concurrentHakyll :: [Hakyll ()] -> Hakyll ()
113−concurrentHakyll actions = do
114− mvars <- forM actions forkHakyllWait
115− forM_ mvars (liftIO . readMVar)
src/Text/Hakyll/Page.hsdeleted−108
@@ -1,108 +0,0 @@
1−-- | A module for dealing with @Page@s. This module is mostly internally used.
2−module Text.Hakyll.Page
3− ( PageSection (..)
4− , readPage
5− , readPageAction
6− ) where
7−
8−import Data.List (isPrefixOf)
9−import Data.Char (isSpace)
10−import Control.Monad.Reader (liftIO)
11−import System.FilePath
12−import Control.Monad.State (State, evalState, get, put)
13−
14−import Text.Hakyll.File
15−import Text.Hakyll.HakyllMonad
16−import Text.Hakyll.HakyllAction
17−import Text.Hakyll.Regex (substituteRegex, matchesRegex)
18−import Text.Hakyll.Util (trim)
19−
20−-- | A page is first parsed into a number of page sections. A page section
21−-- consists of:
22−--
23−-- * A key
24−--
25−-- * A value
26−--
27−-- * A 'Bool' flag, indicating if the value is applicable for rendering
28−--
29−data PageSection = PageSection {unPageSection :: (String, String, Bool)}
30− deriving (Show)
31−
32−-- | Split a page into sections.
33−--
34−splitAtDelimiters :: [String] -> State (Maybe String) [[String]]
35−splitAtDelimiters [] = return []
36−splitAtDelimiters ls@(x:xs) = do
37− delimiter <- get
38− if not (isDelimiter delimiter x)
39− then return [ls]
40− else do let proper = takeWhile (== '-') x
41− (content, rest) = break (isDelimiter $ Just proper) xs
42− put $ Just proper
43− rest' <- splitAtDelimiters rest
44− return $ (x : content) : rest'
45− where
46− isDelimiter old = case old of
47− Nothing -> isPossibleDelimiter
48− (Just d) -> (== d) . takeWhile (== '-')
49−
50−-- | Check if the given string is a metadata delimiter.
51−isPossibleDelimiter :: String -> Bool
52−isPossibleDelimiter = isPrefixOf "---"
53−
54−-- | Read one section of a page.
55−--
56−readSection :: Bool -- ^ If this section is the first section in the page.
57− -> [String] -- ^ Lines in the section.
58− -> [PageSection] -- ^ Key-values extracted.
59−readSection _ [] = []
60−readSection isFirst ls
61− | not isDelimiter' = [body ls]
62− | isNamedDelimiter = readSectionMetaData ls
63− | isFirst = readSimpleMetaData (drop 1 ls)
64− | otherwise = [body (drop 1 ls)]
65− where
66− isDelimiter' = isPossibleDelimiter (head ls)
67− isNamedDelimiter = head ls `matchesRegex` "^----* *[a-zA-Z0-9][a-zA-Z0-9]*"
68− body ls' = PageSection ("body", unlines ls', True)
69−
70− readSimpleMetaData = map readPair . filter (not . all isSpace)
71− readPair = trimPair . break (== ':')
72− trimPair (key, value) = PageSection (trim key, trim (drop 1 value), False)
73−
74− readSectionMetaData [] = []
75− readSectionMetaData (header:value) =
76− let key = substituteRegex "[^a-zA-Z0-9]" "" header
77− in [PageSection (key, unlines value, True)]
78−
79−-- | Read a page from a file. Metadata is supported.
80−--
81−readPage :: FilePath -> Hakyll [PageSection]
82−readPage path = do
83− let sectionFunctions = map readSection $ True : repeat False
84−
85− -- Read file.
86− contents <- liftIO $ readFile path
87− url <- toUrl path
88− let sections = evalState (splitAtDelimiters $ lines contents) Nothing
89− sectionsData = concat $ zipWith ($) sectionFunctions sections
90−
91− -- Note that url, path etc. are listed first, which means can be overwritten
92− -- by section data
93− return $ PageSection ("url", url, False)
94− : PageSection ("path", path, False)
95− : PageSection ("title", takeBaseName path, False)
96− : (category ++ sectionsData)
97− where
98− category = let dirs = splitDirectories $ takeDirectory path
99− in [PageSection ("category", last dirs, False) | not (null dirs)]
100−
101−-- | Read a page from a file. Metadata is supported.
102−--
103−readPageAction :: FilePath -> HakyllAction () [PageSection]
104−readPageAction path = HakyllAction
105− { actionDependencies = [path]
106− , actionUrl = Left $ toUrl path
107− , actionFunction = const $ readPage path
108− }
src/Text/Hakyll/Paginate.hsdeleted−94
@@ -1,94 +0,0 @@
1−-- | Module aimed to paginate web pages.
2−--
3−module Text.Hakyll.Paginate
4− ( PaginateConfiguration (..)
5− , defaultPaginateConfiguration
6− , paginate
7− ) where
8−
9−import Control.Applicative ((<$>))
10−
11−import Text.Hakyll.Context (Context)
12−import Text.Hakyll.CreateContext
13−import Text.Hakyll.HakyllAction
14−import Text.Hakyll.Util (link)
15−
16−-- | A configuration for a pagination.
17−--
18−data PaginateConfiguration = PaginateConfiguration
19− { -- | Label for the link to the previous page.
20− previousLabel :: String
21− , -- | Label for the link to the next page.
22− nextLabel :: String
23− , -- | Label for the link to the first page.
24− firstLabel :: String
25− , -- | Label for the link to the last page.
26− lastLabel :: String
27− }
28−
29−-- | A simple default configuration for pagination.
30−--
31−defaultPaginateConfiguration :: PaginateConfiguration
32−defaultPaginateConfiguration = PaginateConfiguration
33− { previousLabel = "Previous"
34− , nextLabel = "Next"
35− , firstLabel = "First"
36− , lastLabel = "Last"
37− }
38−
39−-- | The most important function for pagination. This function operates on a
40−-- list of @Context@s (the pages), and basically just adds fields to them
41−-- by combining them with a custom page.
42−--
43−-- The following metadata fields will be added:
44−--
45−-- - @$previous@: A link to the previous page.
46−--
47−-- - @$next@: A link to the next page.
48−--
49−-- - @$first@: A link to the first page.
50−--
51−-- - @$last@: A link to the last page.
52−--
53−-- - @$index@: 1-based index of the current page.
54−--
55−-- - @$length@: Total number of pages.
56−--
57−-- When @$previous@ or @$next@ are not available, they will be just a label
58−-- without a link. The same goes for when we are on the first or last page for
59−-- @$first@ and @$last@.
60−--
61−paginate :: PaginateConfiguration
62− -> [HakyllAction () Context]
63− -> [HakyllAction () Context]
64−paginate configuration renderables = paginate' Nothing renderables (1 :: Int)
65− where
66− -- Create a link with a given label, taken from the configuration.
67− linkWithLabel f r = Right $ case actionUrl r of
68− Left l -> createSimpleHakyllAction $
69− link (f configuration) . ("$root/" ++) <$> l
70− Right _ -> error "No link found for pagination."
71−
72− -- The main function that creates combined renderables by recursing over
73− -- the list of items.
74− paginate' _ [] _ = []
75− paginate' maybePrev (x:xs) index =
76− let (previous, first) = case maybePrev of
77− (Just r) -> ( linkWithLabel previousLabel r
78− , linkWithLabel firstLabel (head renderables) )
79− Nothing -> ( Left $ previousLabel configuration
80− , Left $ firstLabel configuration )
81− (next, last') = case xs of
82− (n:_) -> ( linkWithLabel nextLabel n
83− , linkWithLabel lastLabel (last renderables) )
84− [] -> ( Left $ nextLabel configuration
85− , Left $ lastLabel configuration )
86− customPage = createCustomPage ""
87− [ ("previous", previous)
88− , ("next", next)
89− , ("first", first)
90− , ("last", last')
91− , ("index", Left $ show index)
92− , ("length", Left $ show $ length renderables)
93− ]
94− in (x `combine` customPage) : paginate' (Just x) xs (index + 1)
src/Text/Hakyll/Pandoc.hsdeleted−88
@@ -1,88 +0,0 @@
1−-- | Module exporting a pandoc arrow
2−--
3−module Text.Hakyll.Pandoc
4− ( renderAction
5− , renderActionWith
6− ) where
7−
8−import Data.Maybe (fromMaybe)
9−import qualified Data.Map as M
10−import Control.Arrow (second, (>>>), arr, (&&&))
11−
12−import Text.Pandoc
13−
14−import Text.Hakyll.Internal.FileType
15−import Text.Hakyll.Page
16−import Text.Hakyll.HakyllMonad
17−import Text.Hakyll.HakyllAction
18−import Text.Hakyll.Context
19−
20−-- | Reader function for plain text
21−--
22−readText :: ParserState -> String -> Pandoc
23−readText _ = Pandoc (Meta [] [] []) . return . Plain . return . Str
24−
25−-- | Get a read function for a given extension.
26−--
27−readPandoc :: HakyllAction (FileType, String) Pandoc
28−readPandoc = createHakyllAction $ \(fileType, inp) -> do
29− parserState <- askHakyll pandocParserState
30− return $ readFunction fileType (readOptions parserState fileType) inp
31− where
32− readFunction ReStructuredText = readRST
33− readFunction LaTeX = readLaTeX
34− readFunction Markdown = readMarkdown
35− readFunction LiterateHaskellMarkdown = readMarkdown
36− readFunction Html = readHtml
37− readFunction Text = readText
38− readFunction t = error $ "Cannot render " ++ show t
39−
40− readOptions options LiterateHaskellMarkdown = options
41− { stateLiterateHaskell = True }
42− readOptions options _ = options
43−
44−-- | Get a render function for a given extension.
45−--
46−getRenderFunction :: HakyllAction FileType (String -> String)
47−getRenderFunction = createHakyllAction $ \fileType -> case fileType of
48− Html -> return id
49− Text -> return id
50− UnknownFileType -> return id
51− _ -> do parserState <- askHakyll pandocParserState
52− writerOptions <- askHakyll pandocWriterOptions
53− return $ writeHtmlString writerOptions
54− . readFunction fileType (readOptions parserState fileType)
55− where
56− readFunction ReStructuredText = readRST
57− readFunction LaTeX = readLaTeX
58− readFunction Markdown = readMarkdown
59− readFunction LiterateHaskellMarkdown = readMarkdown
60− readFunction t = error $ "Cannot render " ++ show t
61−
62− readOptions options LiterateHaskellMarkdown = options
63− { stateLiterateHaskell = True }
64− readOptions options _ = options
65−
66−-- | Get a render action
67−--
68−renderPandoc :: HakyllAction Pandoc String
69−renderPandoc = createHakyllAction $ \p -> do
70− writerOptions <- askHakyll pandocWriterOptions
71− return $ writeHtmlString writerOptions p
72−
73−-- | An action that renders the list of page sections to a context using pandoc
74−--
75−renderAction :: HakyllAction [PageSection] Context
76−renderAction = (arr id &&& (getFileType' >>> getRenderFunction))
77− >>> renderActionWith
78− where
79− getFileType' = arr $ getFileType . fromMaybe "unknown" . lookup "path"
80− . map (\(x, y, _) -> (x, y)) . map unPageSection
81−
82−-- | An action to render pages, offering just a little more flexibility
83−--
84−renderActionWith :: HakyllAction ([PageSection], String -> String) Context
85−renderActionWith = createHakyllAction $ \(sections, render') -> return $
86− Context $ M.fromList $ map (renderTriple render' . unPageSection) sections
87− where
88− renderTriple render' (k, v, r) = second (if r then render' else id) (k, v)
src/Text/Hakyll/Regex.hsdeleted−77
@@ -1,77 +0,0 @@
1−-- | A module that exports a simple regex interface. This code is mostly copied
2−-- from the regex-compat package at hackage. I decided to write this module
3−-- because I want to abstract the regex package used.
4−module Text.Hakyll.Regex
5− ( splitRegex
6− , substituteRegex
7− , matchesRegex
8− ) where
9−
10−import Text.Regex.TDFA
11−
12−-- | Match a regular expression against a string, returning more information
13−-- about the match.
14−matchRegexAll :: Regex -> String -> Maybe (String, String, String, [String])
15−matchRegexAll = matchM
16−
17−-- | Replaces every occurance of the given regexp with the replacement string.
18−subRegex :: Regex -- ^ Search pattern
19− -> String -- ^ Input string
20− -> String -- ^ Replacement text
21− -> String -- ^ Output string
22−subRegex _ "" _ = ""
23−subRegex regexp inp replacement =
24− let -- bre matches a backslash then capture either a backslash or some digits
25− bre = makeRegex "\\\\(\\\\|[0-9]+)"
26− lookup' _ [] _ = []
27− lookup' [] _ _ = []
28− lookup' match' repl groups =
29− case matchRegexAll bre repl of
30− Nothing -> repl
31− Just (lead, _, trail, bgroups) ->
32− let newval =
33− if head bgroups == "\\"
34− then "\\"
35− else let index :: Int
36− index = read (head bgroups) - 1
37− in if index == -1
38− then match'
39− else groups !! index
40− in lead ++ newval ++ lookup' match' trail groups
41− in case matchRegexAll regexp inp of
42− Nothing -> inp
43− Just (lead, match', trail, groups) ->
44− lead ++ lookup' match' replacement groups
45− ++ subRegex regexp trail replacement
46−
47−-- | Splits a string based on a regular expression. The regular expression
48−-- should identify one delimiter.
49−splitRegex' :: Regex -> String -> [String]
50−splitRegex' _ [] = []
51−splitRegex' delim strIn = loop strIn where
52− loop str = case matchOnceText delim str of
53− Nothing -> [str]
54− Just (firstline, _, remainder) ->
55− if null remainder
56− then [firstline,""]
57− else firstline : loop remainder
58−
59−-- | Split a list at a certain element.
60−splitRegex :: String -> String -> [String]
61−splitRegex pattern = filter (not . null)
62− . splitRegex' (makeRegex pattern)
63−
64−-- | Substitute a regex. Simplified interface. This function performs a global
65−-- substitution.
66−substituteRegex :: String -- ^ Pattern to replace (regex).
67− -> String -- ^ Replacement string.
68− -> String -- ^ Input string.
69− -> String -- ^ Result.
70−substituteRegex pattern replacement string =
71− subRegex (makeRegex pattern) string replacement
72−
73−-- | Simple regex matching.
74−matchesRegex :: String -- ^ Input string.
75− -> String -- ^ Pattern to match.
76− -> Bool
77−matchesRegex = (=~)
src/Text/Hakyll/Render.hsdeleted−126
@@ -1,126 +0,0 @@
1−-- | Module containing rendering functions. All these functions are used to
2−-- render files to the @_site@ directory.
3−module Text.Hakyll.Render
4− ( render
5− , renderAndConcat
6− , renderChain
7− , static
8− , css
9− , writePage
10− ) where
11−
12−import Control.Arrow ((>>>))
13−import Control.Applicative ((<$>))
14−import Control.Monad.Reader (liftIO)
15−import System.Directory (copyFile)
16−import Data.Maybe (fromMaybe)
17−import qualified Data.Map as M
18−
19−import Text.Hakyll.Context (Context (..))
20−import Text.Hakyll.HakyllMonad (Hakyll, askHakyll, getAdditionalContext)
21−import Text.Hakyll.File
22−import Text.Hakyll.HakyllAction
23−import Text.Hakyll.ContextManipulations
24−import Text.Hakyll.Internal.CompressCss
25−import Text.Hakyll.Internal.Template
26−
27−-- | A pure render function - used internally.
28−pureRender :: Template -- ^ Template to use for rendering.
29− -> Context -- ^ Renderable object to render with given template.
30− -> Context -- ^ The body of the result will contain the render.
31−pureRender template (Context c) =
32− -- Ignore $root when substituting here. We will only replace that in the
33− -- final render (just before writing).
34− let contextIgnoringRoot = Context $ M.insert "root" "$root" c
35− body = regularSubstitute template $ contextIgnoringRoot
36− in Context $ M.insert "body" body c
37−
38−-- | This is the most simple render action. You render a @Context@ with a
39−-- template, and get back the result.
40−render :: FilePath -- ^ Template to use for rendering.
41− -> HakyllAction Context Context -- ^ The render computation.
42−render templatePath = HakyllAction
43− { actionDependencies = [templatePath]
44− , actionUrl = Right id
45− , actionFunction = \context ->
46− flip pureRender context <$> readTemplate templatePath
47− }
48−
49−-- | Render each @Context@ with the given templates, then concatenate the
50−-- result. So, basically this function:
51−--
52−-- - Takes every @Context@.
53−--
54−-- - Renders every @Context@ with all given templates. This is comparable
55−-- with a renderChain action.
56−--
57−-- - Concatenates the result and returns that as a @String@.
58−renderAndConcat :: [FilePath]
59− -> [HakyllAction () Context]
60− -> HakyllAction () String
61−renderAndConcat templatePaths renderables = HakyllAction
62− { actionDependencies = renders >>= actionDependencies
63− , actionUrl = Right id
64− , actionFunction = actionFunction'
65− }
66− where
67− render' = chain (map render templatePaths)
68− renders = map (>>> render') renderables
69−
70− actionFunction' _ = do
71− contexts <- mapM (runHakyllAction . (>>> takeBody)) renders
72− return $ concat contexts
73−
74−-- | Chain a render action for a page with a number of templates. This will
75−-- also write the result to the site destination. This is the preferred way
76−-- to do general rendering.
77−--
78−-- > renderChain [ "templates/notice.html"
79−-- > , "templates/default.html"
80−-- > ] $ createPagePath "warning.html"
81−--
82−-- This code will first render @warning.html@ using @templates/notice.html@,
83−-- and will then render the result with @templates/default.html@.
84−renderChain :: [FilePath]
85− -> HakyllAction () Context
86− -> Hakyll ()
87−renderChain templatePaths initial =
88− runHakyllActionIfNeeded renderChainWith'
89− where
90− renderChainWith' = initial >>> chain' >>> writePage
91− chain' = chain $ map render templatePaths
92−
93−-- | Mark a certain file as static, so it will just be copied when the site is
94−-- generated.
95−static :: FilePath -> Hakyll ()
96−static source = runHakyllActionIfNeeded static'
97− where
98− static' = createFileHakyllAction source $ do
99− destination <- toDestination source
100− makeDirectories destination
101− liftIO $ copyFile source destination
102−
103−-- | Render a css file, compressing it.
104−css :: FilePath -> Hakyll ()
105−css source = runHakyllActionIfNeeded css'
106− where
107− css' = createFileHakyllAction source $ do
108− contents <- liftIO $ readFile source
109− destination <- toDestination source
110− makeDirectories destination
111− liftIO $ writeFile destination (compressCss contents)
112−
113−-- | Write a page to the site destination. Final action after render
114−-- chains and such.
115−writePage :: HakyllAction Context ()
116−writePage = createHakyllAction $ \(Context initialContext) -> do
117− additionalContext' <- unContext <$> askHakyll getAdditionalContext
118− let url = fromMaybe (error "No url defined at write time.")
119− (M.lookup "url" initialContext)
120− body = fromMaybe "" (M.lookup "body" initialContext)
121− let context = additionalContext' `M.union` M.singleton "root" (toRoot url)
122− destination <- toDestination url
123− makeDirectories destination
124− -- Substitute $root here, just before writing.
125− liftIO $ writeFile destination $ finalSubstitute (fromString body)
126− (Context context)
src/Text/Hakyll/Resource.hsdeleted−57
@@ -1,57 +0,0 @@
1−-- | A resource represents data for a website
2−--
3−module Text.Hakyll.Resource
4− ( Metadata (..)
5− , Resource (..)
6− , getData
7− , getMetadata
8− ) where
9−
10−import Data.Monoid (Monoid, mempty, mappend)
11−import Control.Applicative (Applicative, (<*>), pure)
12−import Data.Map (Map)
13−import qualified Data.Map as M
14−
15−-- | Metadata for a resource
16−--
17−newtype Metadata = Metadata {unMetadata :: Map String String}
18− deriving (Show, Eq, Ord)
19−
20−instance Monoid Metadata where
21− mempty = Metadata M.empty
22− (Metadata m1) `mappend` (Metadata m2) = Metadata $ m1 `M.union` m2
23−
24−-- | A resource represents a data source for the website. It contains a value
25−-- and a number of metadata fields
26−--
27−data Resource a = Resource
28− { resourceMetadata :: Metadata
29− , resourceData :: a
30− } deriving (Show, Eq, Ord)
31−
32−instance Functor Resource where
33− fmap f (Resource m d) = Resource m $ f d
34−
35−instance Applicative Resource where
36− pure d = Resource mempty d
37− (Resource m1 f) <*> (Resource m2 d) = Resource (mappend m2 m1) (f d)
38−
39−instance Monad Resource where
40− return d = Resource mempty d
41− (Resource m1 d) >>= f = let Resource m2 d' = f d
42− in Resource (mappend m2 m1) d'
43−
44−instance Monoid a => Monoid (Resource a) where
45− mempty = Resource mempty mempty
46− mappend (Resource m1 d1) (Resource m2 d2) =
47− Resource (mappend m1 m2) (mappend d1 d2)
48−
49−-- | Get the data from a resource
50−--
51−getData :: Resource a -> a
52−getData = resourceData
53−
54−-- | Get a metadata field from a resource
55−--
56−getMetadata :: String -> Resource a -> Maybe String
57−getMetadata k (Resource m _) = M.lookup k $ unMetadata m
src/Text/Hakyll/Tags.hsdeleted−172
@@ -1,172 +0,0 @@
1−-- | Module containing some specialized functions to deal with tags.
2−-- This Module follows certain conventions. Stick with them.
3−--
4−-- More concrete: all functions in this module assume that the tags are
5−-- located in the @tags@ field, and separated by commas. An example file
6−-- @foo.markdown@ could look like:
7−--
8−-- > ---
9−-- > author: Philip K. Dick
10−-- > title: Do androids dream of electric sheep?
11−-- > tags: future, science fiction, humanoid
12−-- > ---
13−-- > The novel is set in a post-apocalyptic near future, where the Earth and
14−-- > its populations have been damaged greatly by Nuclear...
15−--
16−-- All the following functions would work with such a format. In addition to
17−-- tags, Hakyll also supports categories. The convention when using categories
18−-- is to place pages in subdirectories.
19−--
20−-- An example, the page @posts\/coding\/2010-01-28-hakyll-categories.markdown@
21−-- Tags or categories are read using the @readTagMap@ and @readCategoryMap@
22−-- functions. Because categories are implemented using tags - categories can
23−-- be seen as tags, with the restriction that a page can only have one
24−-- category - all functions for tags also work with categories.
25−--
26−-- When reading a @TagMap@ (which is also used for category maps) using the
27−-- @readTagMap@ or @readCategoryMap@ function, you also have to give a unique
28−-- identifier to it. This identifier is simply for caching reasons, so Hakyll
29−-- can tell different maps apart; it has no other use.
30−--
31−module Text.Hakyll.Tags
32− ( TagMap
33− , readTagMap
34− , readCategoryMap
35− , withTagMap
36− , renderTagCloud
37− , renderTagLinks
38− ) where
39−
40−import qualified Data.Map as M
41−import Data.List (intercalate)
42−import Data.Maybe (fromMaybe, maybeToList)
43−import Control.Arrow (second, (>>>))
44−import Control.Applicative ((<$>))
45−import System.FilePath
46−
47−import Text.Blaze.Renderer.String (renderHtml)
48−import Text.Blaze.Html5 ((!), string, stringValue)
49−import qualified Text.Blaze.Html5 as H
50−import qualified Text.Blaze.Html5.Attributes as A
51−
52−import Text.Hakyll.Context (Context (..))
53−import Text.Hakyll.ContextManipulations (changeValue)
54−import Text.Hakyll.CreateContext (createPage)
55−import Text.Hakyll.HakyllMonad (Hakyll)
56−import Text.Hakyll.Regex
57−import Text.Hakyll.HakyllAction
58−import Text.Hakyll.Util
59−import Text.Hakyll.Internal.Cache
60−
61−-- | Type for a tag map.
62−--
63−-- This is a map associating tags or categories to the appropriate pages
64−-- using that tag or category. In the case of categories, each path will only
65−-- appear under one category - this is not the case with tags.
66−type TagMap = M.Map String [HakyllAction () Context]
67−
68−-- | Read a tag map. This is a internally used function that can be used for
69−-- tags as well as for categories.
70−readMap :: (Context -> [String]) -- ^ Function to get tags from a context.
71− -> String -- ^ Unique identifier for the tagmap.
72− -> [FilePath]
73− -> HakyllAction () TagMap
74−readMap getTagsFunction identifier paths = HakyllAction
75− { actionDependencies = paths
76− , actionUrl = Right id
77− , actionFunction = actionFunction'
78− }
79− where
80− fileName = "tagmaps" </> identifier
81−
82− actionFunction' _ = do
83− isCacheMoreRecent' <- isCacheMoreRecent fileName paths
84− assocMap <- if isCacheMoreRecent'
85− then M.fromAscList <$> getFromCache fileName
86− else do assocMap' <- readTagMap'
87− storeInCache (M.toAscList assocMap') fileName
88− return assocMap'
89− return $ M.map (map createPage) assocMap
90−
91− -- TODO: preserve order
92− readTagMap' :: Hakyll (M.Map String [FilePath])
93− readTagMap' = do
94− pairs' <- concat <$> mapM pairs paths
95− return $ M.fromListWith (flip (++)) pairs'
96−
97− -- | Read a page, and return an association list where every tag is
98− -- associated with some paths. Of course, this will always be just one
99− -- @FilePath@ here.
100− pairs :: FilePath -> Hakyll [(String, [FilePath])]
101− pairs path = do
102− context <- runHakyllAction $ createPage path
103− let tags = getTagsFunction context
104− return $ map (\tag -> (tag, [path])) tags
105−
106−-- | Read a @TagMap@, using the @tags@ metadata field.
107−readTagMap :: String -- ^ Unique identifier for the map.
108− -> [FilePath] -- ^ Paths to get tags from.
109− -> HakyllAction () TagMap
110−readTagMap = readMap getTagsFunction
111− where
112− getTagsFunction = map trim . splitRegex ","
113− . fromMaybe [] . M.lookup "tags" . unContext
114−
115−-- | Read a @TagMap@, using the subdirectories the pages are placed in.
116−readCategoryMap :: String -- ^ Unique identifier for the map.
117− -> [FilePath] -- ^ Paths to get tags from.
118− -> HakyllAction () TagMap
119−readCategoryMap = readMap $ maybeToList . M.lookup "category" . unContext
120−
121−-- | Perform a @Hakyll@ action on every item in the tag
122−--
123−withTagMap :: HakyllAction () TagMap
124− -> (String -> [HakyllAction () Context] -> Hakyll ())
125− -> Hakyll ()
126−withTagMap tagMap function = runHakyllAction (tagMap >>> action)
127− where
128− action = createHakyllAction (mapM_ (uncurry function) . M.toList)
129−
130−-- | Render a tag cloud.
131−renderTagCloud :: (String -> String) -- ^ Function to produce an url for a tag.
132− -> Float -- ^ Smallest font size, in percent.
133− -> Float -- ^ Biggest font size, in percent.
134− -> HakyllAction TagMap String
135−renderTagCloud urlFunction minSize maxSize = createHakyllAction renderTagCloud'
136− where
137− renderTagCloud' tagMap =
138− return $ intercalate " " $ map (renderTag tagMap) (tagCount tagMap)
139−
140− renderTag tagMap (tag, count) = renderHtml $
141− H.a ! A.style (stringValue $ "font-size: " ++ sizeTag tagMap count)
142− ! A.href (stringValue $ urlFunction tag)
143− $ string tag
144−
145− sizeTag tagMap count = show (size' :: Int) ++ "%"
146− where
147− size' = floor $ minSize + relative tagMap count * (maxSize - minSize)
148−
149− minCount = minimum . map snd . tagCount
150− maxCount = maximum . map snd . tagCount
151− relative tagMap count = (count - minCount tagMap) /
152− (maxCount tagMap - minCount tagMap)
153−
154− tagCount = map (second $ fromIntegral . length) . M.toList
155−
156−-- | Render all tags to links.
157−--
158−-- On your site, it is nice if you can display the tags on a page, but
159−-- naturally, most people would expect these are clickable.
160−--
161−-- So, this function takes a function to produce an url for a given tag, and
162−-- applies it on all tags.
163−--
164−-- Note that it is your own responsibility to ensure a page with such an url
165−-- exists.
166−renderTagLinks :: (String -> String) -- ^ Function to produce an url for a tag.
167− -> HakyllAction Context Context
168−renderTagLinks urlFunction = changeValue "tags" renderTagLinks'
169− where
170− renderTagLinks' = intercalate ", "
171− . map ((\t -> link t $ urlFunction t) . trim)
172− . splitRegex ","
src/Text/Hakyll/Transformer.hsdeleted−107
@@ -1,107 +0,0 @@
1−-- | This is the module which exports @Transformer@.
2−module Text.Hakyll.Transformer
3− ( Transformer (..)
4− , transformResource
5− , transformResourceM
6− , transformData
7− , transformDataM
8− , transformMetadata
9− , transformMetadataM
10− , runTransformer
11− , runTransformerForced
12− ) where
13−
14−import Data.Monoid (Monoid, mappend, mempty)
15−import Control.Arrow
16−import Control.Category
17−import Control.Applicative ((<$>))
18−import Control.Monad ((<=<), unless, liftM2)
19−import Prelude hiding ((.), id)
20−
21−import Text.Hakyll.Resource
22−import Text.Hakyll.File (toDestination, isFileMoreRecent)
23−import Text.Hakyll.Monad
24−
25−-- | Type used for computations that transform resources, carrying along
26−-- dependencies.
27−--
28−data Transformer a b = Transformer
29− { -- | Dependencies of the @Transformer@.
30− transformerDependencies :: [FilePath]
31− , -- | URL pointing to the result of this @Transformer@.
32− transformerUrl :: FilePath -> Hakyll FilePath
33− , -- | The actual transforming function.
34− transformerFunction :: Resource a -> Hakyll (Resource b)
35− }
36−
37−instance Monoid b => Monoid (Transformer a b) where
38− mempty = arr (const mempty)
39− mappend x y = Transformer
40− { transformerDependencies =
41− transformerDependencies x ++ transformerDependencies y
42− , transformerUrl = transformerUrl x
43− , transformerFunction = \r ->
44− liftM2 mappend (transformerFunction x r) (transformerFunction y r)
45− }
46−
47−instance Category Transformer where
48− id = Transformer
49− { transformerDependencies = []
50− , transformerUrl = return
51− , transformerFunction = return
52− }
53−
54− x . y = Transformer
55− { transformerDependencies =
56− transformerDependencies x ++ transformerDependencies y
57− , transformerUrl = transformerUrl y <=< transformerUrl x
58− , transformerFunction = transformerFunction x <=< transformerFunction y
59− }
60−
61−instance Arrow Transformer where
62− arr = transformData
63−
64− first t = t
65− { transformerFunction = \(Resource m (x, y)) -> do
66− Resource m' x' <- transformerFunction t $ Resource m x
67− return $ Resource (mappend m' m) (x', y)
68− }
69−
70−transformResource :: (Resource a -> Resource b) -> Transformer a b
71−transformResource = transformResourceM . (return .)
72−
73−transformResourceM :: (Resource a -> Hakyll (Resource b)) -> Transformer a b
74−transformResourceM f = id {transformerFunction = f}
75−
76−transformData :: (a -> b) -> Transformer a b
77−transformData = transformResource . fmap
78−
79−transformDataM :: (a -> Hakyll b) -> Transformer a b
80−transformDataM f = transformResourceM $ \(Resource m x) ->
81− f x >>= return . Resource m
82−
83−transformMetadata :: (Metadata -> Metadata) -> Transformer a a
84−transformMetadata = transformMetadataM . (return .)
85−
86−transformMetadataM :: (Metadata -> Hakyll Metadata) -> Transformer a a
87−transformMetadataM f = transformResourceM $ \(Resource m x) -> do
88− m' <- f m
89− return $ Resource m' x
90−
91−-- | Run a transformer. This might not run it when the result is up-to-date
92−--
93−runTransformer :: Transformer () ()
94− -> Hakyll ()
95−runTransformer t = do
96− url <- transformerUrl t $
97− error "runTransformer: No url when checking dependencies."
98− destination <- toDestination url
99− valid <- isFileMoreRecent destination $ transformerDependencies t
100− unless valid $ do logHakyll $ "Rendering " ++ destination
101− runTransformerForced t
102−
103−-- | Always run the transformer, even when the target is up-to-date
104−--
105−runTransformerForced :: Transformer () ()
106− -> Hakyll ()
107−runTransformerForced t = getData <$> transformerFunction t mempty
src/Text/Hakyll/Util.hsdeleted−34
@@ -1,34 +0,0 @@
1−-- | Miscellaneous text manipulation functions.
2−module Text.Hakyll.Util
3− ( trim
4− , stripHtml
5− , link
6− ) where
7−
8−import Data.Char (isSpace)
9−
10−import Text.Blaze.Html5 ((!), string, stringValue, a)
11−import Text.Blaze.Html5.Attributes (href)
12−import Text.Blaze.Renderer.String (renderHtml)
13−
14−-- | Trim a string (drop spaces, tabs and newlines at both sides).
15−trim :: String -> String
16−trim = reverse . trim' . reverse . trim'
17− where
18− trim' = dropWhile isSpace
19−
20−-- | Strip html tags from the given string.
21−stripHtml :: String -> String
22−stripHtml [] = []
23−stripHtml str = let (beforeTag, rest) = break (== '<') str
24− (_, afterTag) = break (== '>') rest
25− in beforeTag ++ stripHtml (drop 1 afterTag)
26−
27−-- | Make a HTML link.
28−--
29−-- > link "foo" "bar.html" == "<a href='bar.html'>foo</a>"
30−link :: String -- ^ Link text.
31− -> String -- ^ Link destination.
32− -> String
33−link text destination = renderHtml $ a ! href (stringValue destination)
34− $ string text