hakyll 1.4 → 2.0
raw patch · 27 files changed
+1001/−766 lines, 27 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Text.Hakyll.Context: changeExtension :: String -> ContextManipulation
- Text.Hakyll.Context: changeValue :: String -> (String -> String) -> ContextManipulation
- Text.Hakyll.Context: renderDate :: String -> String -> String -> ContextManipulation
- Text.Hakyll.Context: renderValue :: String -> String -> (String -> String) -> ContextManipulation
- Text.Hakyll.Context: type ContextManipulation = Context -> Context
- Text.Hakyll.Hakyll: HakyllConfiguration :: Context -> FilePath -> FilePath -> Bool -> Int -> HakyllConfiguration
- Text.Hakyll.Hakyll: additionalContext :: HakyllConfiguration -> Context
- Text.Hakyll.Hakyll: askHakyll :: (HakyllConfiguration -> a) -> Hakyll a
- Text.Hakyll.Hakyll: cacheDirectory :: HakyllConfiguration -> FilePath
- Text.Hakyll.Hakyll: data HakyllConfiguration
- Text.Hakyll.Hakyll: enableIndexUrl :: HakyllConfiguration -> Bool
- Text.Hakyll.Hakyll: previewPollDelay :: HakyllConfiguration -> Int
- Text.Hakyll.Hakyll: siteDirectory :: HakyllConfiguration -> FilePath
- Text.Hakyll.Hakyll: type Hakyll = ReaderT HakyllConfiguration IO
- Text.Hakyll.Internal.Cache: getFromCache :: (Binary a) => FilePath -> Hakyll a
- Text.Hakyll.Internal.Cache: isCacheMoreRecent :: FilePath -> [FilePath] -> Hakyll Bool
- Text.Hakyll.Internal.Cache: storeInCache :: (Binary a) => a -> FilePath -> Hakyll ()
- Text.Hakyll.Internal.CompressCss: compressCss :: String -> String
- Text.Hakyll.Internal.Render: finalSubstitute :: Template -> Context -> String
- Text.Hakyll.Internal.Render: pureRenderAndConcatWith :: ContextManipulation -> [Template] -> [Context] -> String
- Text.Hakyll.Internal.Render: pureRenderChainWith :: ContextManipulation -> [Template] -> Context -> Context
- Text.Hakyll.Internal.Render: pureRenderWith :: ContextManipulation -> Template -> Context -> Context
- Text.Hakyll.Internal.Render: regularSubstitute :: Template -> Context -> String
- Text.Hakyll.Internal.Render: substitute :: String -> Template -> Context -> String
- Text.Hakyll.Internal.Render: writePage :: Page -> Hakyll ()
- Text.Hakyll.Internal.Template: data Template
- Text.Hakyll.Internal.Template: finalSubstitute :: Template -> Context -> String
- Text.Hakyll.Internal.Template: fromString :: String -> Template
- Text.Hakyll.Internal.Template: instance Arbitrary Template
- Text.Hakyll.Internal.Template: instance Binary Template
- Text.Hakyll.Internal.Template: instance Eq Template
- Text.Hakyll.Internal.Template: instance Read Template
- Text.Hakyll.Internal.Template: instance Show Template
- Text.Hakyll.Internal.Template: readTemplate :: FilePath -> Hakyll Template
- Text.Hakyll.Internal.Template: regularSubstitute :: Template -> Context -> String
- Text.Hakyll.Internal.Template: substitute :: String -> Template -> Context -> String
- Text.Hakyll.Page: data Page
- Text.Hakyll.Page: fromContext :: Context -> Page
- Text.Hakyll.Page: getBody :: Page -> String
- Text.Hakyll.Page: getValue :: String -> Page -> String
- Text.Hakyll.Page: instance Arbitrary Page
- Text.Hakyll.Page: instance Binary Page
- Text.Hakyll.Page: instance Eq Page
- Text.Hakyll.Page: instance Ord Page
- Text.Hakyll.Page: instance Read Page
- Text.Hakyll.Page: instance Renderable Page
- Text.Hakyll.Page: instance Show Page
- Text.Hakyll.Page: readPage :: FilePath -> Hakyll Page
- Text.Hakyll.Render: depends :: FilePath -> [FilePath] -> Hakyll () -> Hakyll ()
- Text.Hakyll.Render: renderAndConcatWith :: (Renderable a) => ContextManipulation -> [FilePath] -> [a] -> Hakyll String
- Text.Hakyll.Render: renderChainWith :: (Renderable a) => ContextManipulation -> [FilePath] -> a -> Hakyll ()
- Text.Hakyll.Render: renderWith :: (Renderable a) => ContextManipulation -> FilePath -> a -> Hakyll Page
- Text.Hakyll.Renderable: class Renderable a
- Text.Hakyll.Renderable: getDependencies :: (Renderable a) => a -> [FilePath]
- Text.Hakyll.Renderable: getUrl :: (Renderable a) => a -> Hakyll FilePath
- Text.Hakyll.Renderable: toContext :: (Renderable a) => a -> Hakyll Context
- Text.Hakyll.Renderables: combine :: (Renderable a, Renderable b) => a -> b -> CombinedRenderable a b
- Text.Hakyll.Renderables: combineWithUrl :: (Renderable a, Renderable b) => FilePath -> a -> b -> CombinedRenderable a b
- Text.Hakyll.Renderables: createCustomPage :: String -> [FilePath] -> [(String, Either String (Hakyll String))] -> CustomPage
- Text.Hakyll.Renderables: createListing :: (Renderable a) => String -> FilePath -> [a] -> [(String, String)] -> CustomPage
- Text.Hakyll.Renderables: createListingWith :: (Renderable a) => ContextManipulation -> String -> FilePath -> [a] -> [(String, String)] -> CustomPage
- Text.Hakyll.Renderables: createPagePath :: FilePath -> PagePath
- Text.Hakyll.Renderables: data CombinedRenderable a b
- Text.Hakyll.Renderables: data CustomPage
- Text.Hakyll.Renderables: data PagePath
- Text.Hakyll.Renderables: instance (Eq a, Eq b) => Eq (CombinedRenderable a b)
- Text.Hakyll.Renderables: instance (Ord a, Ord b) => Ord (CombinedRenderable a b)
- Text.Hakyll.Renderables: instance (Read a, Read b) => Read (CombinedRenderable a b)
- Text.Hakyll.Renderables: instance (Renderable a, Renderable b) => Renderable (CombinedRenderable a b)
- Text.Hakyll.Renderables: instance (Show a, Show b) => Show (CombinedRenderable a b)
- Text.Hakyll.Renderables: instance Binary PagePath
- Text.Hakyll.Renderables: instance Eq PagePath
- Text.Hakyll.Renderables: instance Ord PagePath
- Text.Hakyll.Renderables: instance Read PagePath
- Text.Hakyll.Renderables: instance Renderable CustomPage
- Text.Hakyll.Renderables: instance Renderable PagePath
- Text.Hakyll.Renderables: instance Show PagePath
+ Text.Hakyll.ContextManipulations: changeExtension :: String -> HakyllAction Context Context
+ Text.Hakyll.ContextManipulations: changeValue :: String -> (String -> String) -> HakyllAction Context Context
+ Text.Hakyll.ContextManipulations: copyValue :: String -> String -> HakyllAction Context Context
+ Text.Hakyll.ContextManipulations: renderBody :: (String -> String) -> HakyllAction Context Context
+ Text.Hakyll.ContextManipulations: renderDate :: String -> String -> String -> HakyllAction Context Context
+ Text.Hakyll.ContextManipulations: renderValue :: String -> String -> (String -> String) -> HakyllAction Context Context
+ Text.Hakyll.CreateContext: combine :: HakyllAction () Context -> HakyllAction () Context -> HakyllAction () Context
+ Text.Hakyll.CreateContext: combineWithUrl :: FilePath -> HakyllAction () Context -> HakyllAction () Context -> HakyllAction () Context
+ Text.Hakyll.CreateContext: createCustomPage :: FilePath -> [(String, Either String (HakyllAction () String))] -> HakyllAction () Context
+ Text.Hakyll.CreateContext: createListing :: FilePath -> [FilePath] -> [HakyllAction () Context] -> [(String, Either String (HakyllAction () String))] -> HakyllAction () Context
+ Text.Hakyll.CreateContext: createPage :: FilePath -> HakyllAction () Context
+ Text.Hakyll.Feed: FeedConfiguration :: String -> String -> String -> String -> FeedConfiguration
+ Text.Hakyll.Feed: data FeedConfiguration
+ Text.Hakyll.Feed: feedAuthorName :: FeedConfiguration -> String
+ Text.Hakyll.Feed: feedDescription :: FeedConfiguration -> String
+ Text.Hakyll.Feed: feedTitle :: FeedConfiguration -> String
+ Text.Hakyll.Feed: feedUrl :: FeedConfiguration -> String
+ Text.Hakyll.Feed: renderAtom :: FeedConfiguration -> [HakyllAction () Context] -> Hakyll ()
+ Text.Hakyll.Feed: renderRss :: FeedConfiguration -> [HakyllAction () Context] -> Hakyll ()
+ Text.Hakyll.HakyllAction: HakyllAction :: [FilePath] -> Maybe (Hakyll FilePath) -> (a -> Hakyll b) -> HakyllAction a b
+ Text.Hakyll.HakyllAction: actionDependencies :: HakyllAction a b -> [FilePath]
+ Text.Hakyll.HakyllAction: actionFunction :: HakyllAction a b -> a -> Hakyll b
+ Text.Hakyll.HakyllAction: actionUrl :: HakyllAction a b -> Maybe (Hakyll FilePath)
+ Text.Hakyll.HakyllAction: chain :: [HakyllAction a a] -> HakyllAction a a
+ Text.Hakyll.HakyllAction: createFileHakyllAction :: FilePath -> Hakyll b -> HakyllAction () b
+ Text.Hakyll.HakyllAction: createHakyllAction :: (a -> Hakyll b) -> HakyllAction a b
+ Text.Hakyll.HakyllAction: createSimpleHakyllAction :: Hakyll b -> HakyllAction () b
+ Text.Hakyll.HakyllAction: data HakyllAction a b
+ Text.Hakyll.HakyllAction: instance Arrow HakyllAction
+ Text.Hakyll.HakyllAction: instance Category HakyllAction
+ Text.Hakyll.HakyllAction: runHakyllAction :: HakyllAction () a -> Hakyll a
+ Text.Hakyll.HakyllAction: runHakyllActionIfNeeded :: HakyllAction () () -> Hakyll ()
+ Text.Hakyll.HakyllMonad: HakyllConfiguration :: String -> Context -> FilePath -> FilePath -> Bool -> Int -> HakyllConfiguration
+ Text.Hakyll.HakyllMonad: absoluteUrl :: HakyllConfiguration -> String
+ Text.Hakyll.HakyllMonad: additionalContext :: HakyllConfiguration -> Context
+ Text.Hakyll.HakyllMonad: askHakyll :: (HakyllConfiguration -> a) -> Hakyll a
+ Text.Hakyll.HakyllMonad: cacheDirectory :: HakyllConfiguration -> FilePath
+ Text.Hakyll.HakyllMonad: data HakyllConfiguration
+ Text.Hakyll.HakyllMonad: enableIndexUrl :: HakyllConfiguration -> Bool
+ Text.Hakyll.HakyllMonad: getAdditionalContext :: HakyllConfiguration -> Context
+ Text.Hakyll.HakyllMonad: previewPollDelay :: HakyllConfiguration -> Int
+ Text.Hakyll.HakyllMonad: siteDirectory :: HakyllConfiguration -> FilePath
+ Text.Hakyll.HakyllMonad: type Hakyll = ReaderT HakyllConfiguration IO
+ Text.Hakyll.Paginate: PaginateConfiguration :: String -> String -> String -> String -> PaginateConfiguration
+ Text.Hakyll.Paginate: data PaginateConfiguration
+ Text.Hakyll.Paginate: defaultPaginateConfiguration :: PaginateConfiguration
+ Text.Hakyll.Paginate: firstLabel :: PaginateConfiguration -> String
+ Text.Hakyll.Paginate: lastLabel :: PaginateConfiguration -> String
+ Text.Hakyll.Paginate: nextLabel :: PaginateConfiguration -> String
+ Text.Hakyll.Paginate: paginate :: PaginateConfiguration -> [HakyllAction () Context] -> [HakyllAction () Context]
+ Text.Hakyll.Paginate: previousLabel :: PaginateConfiguration -> String
+ Text.Hakyll.Render: writePage :: HakyllAction Context ()
+ Text.Hakyll.Tags: withTagMap :: HakyllAction () TagMap -> (String -> [HakyllAction () Context] -> Hakyll ()) -> Hakyll ()
- Text.Hakyll: hakyll :: Hakyll () -> IO ()
+ Text.Hakyll: hakyll :: String -> Hakyll () -> IO ()
- Text.Hakyll.Render: render :: (Renderable a) => FilePath -> a -> Hakyll Page
+ Text.Hakyll.Render: render :: FilePath -> HakyllAction Context Context
- Text.Hakyll.Render: renderAndConcat :: (Renderable a) => [FilePath] -> [a] -> Hakyll String
+ Text.Hakyll.Render: renderAndConcat :: [FilePath] -> [HakyllAction () Context] -> HakyllAction () String
- Text.Hakyll.Render: renderChain :: (Renderable a) => [FilePath] -> a -> Hakyll ()
+ Text.Hakyll.Render: renderChain :: [FilePath] -> HakyllAction () Context -> Hakyll ()
- Text.Hakyll.Tags: readCategoryMap :: String -> [PagePath] -> Hakyll TagMap
+ Text.Hakyll.Tags: readCategoryMap :: String -> [FilePath] -> HakyllAction () TagMap
- Text.Hakyll.Tags: readTagMap :: String -> [PagePath] -> Hakyll TagMap
+ Text.Hakyll.Tags: readTagMap :: String -> [FilePath] -> HakyllAction () TagMap
- Text.Hakyll.Tags: renderTagCloud :: TagMap -> (String -> String) -> Float -> Float -> String
+ Text.Hakyll.Tags: renderTagCloud :: (String -> String) -> Float -> Float -> HakyllAction TagMap String
- Text.Hakyll.Tags: renderTagLinks :: (String -> String) -> ContextManipulation
+ Text.Hakyll.Tags: renderTagLinks :: (String -> String) -> HakyllAction Context Context
- Text.Hakyll.Tags: type TagMap = Map String [PagePath]
+ Text.Hakyll.Tags: type TagMap = Map String [HakyllAction () Context]
Files
- data/templates/atom-item.xml +7/−0
- data/templates/atom.xml +12/−0
- data/templates/rss-item.xml +7/−0
- data/templates/rss.xml +12/−0
- hakyll.cabal +57/−49
- src/Network/Hakyll/SimpleServer.hs +10/−10
- src/Text/Hakyll.hs +18/−9
- src/Text/Hakyll/Context.hs +4/−75
- src/Text/Hakyll/ContextManipulations.hs +93/−0
- src/Text/Hakyll/CreateContext.hs +97/−0
- src/Text/Hakyll/Feed.hs +111/−0
- src/Text/Hakyll/File.hs +7/−19
- src/Text/Hakyll/Hakyll.hs +0/−33
- src/Text/Hakyll/HakyllAction.hs +95/−0
- src/Text/Hakyll/HakyllMonad.hs +50/−0
- src/Text/Hakyll/Internal/Cache.hs +3/−2
- src/Text/Hakyll/Internal/FileType.hs +48/−0
- src/Text/Hakyll/Internal/Page.hs +123/−0
- src/Text/Hakyll/Internal/Render.hs +0/−67
- src/Text/Hakyll/Internal/Template.hs +1/−1
- src/Text/Hakyll/Page.hs +0/−185
- src/Text/Hakyll/Paginate.hs +89/−0
- src/Text/Hakyll/Regex.hs +3/−2
- src/Text/Hakyll/Render.hs +80/−86
- src/Text/Hakyll/Renderable.hs +0/−18
- src/Text/Hakyll/Renderables.hs +0/−158
- src/Text/Hakyll/Tags.hs +74/−52
+ data/templates/atom-item.xml view
@@ -0,0 +1,7 @@+<entry>+ <title>$title</title>+ <link href="$absolute/$url" />+ <id>$absolute/$url</id>+ <updated>$timestamp</updated>+ <summary>$description</summary>+</entry>
+ data/templates/atom.xml view
@@ -0,0 +1,12 @@+<?xml version="1.0" encoding="utf-8"?>+<feed xmlns="http://www.w3.org/2005/Atom">+ <title>$title</title>+ <link href="$absolute/$url" rel="self" />+ <link href="$absolute" />+ <id>$absolute/$url</id>+ <author>+ <name>$authorName</name>+ </author>+ <updated>$timestamp</updated>+ $body+</feed>
+ data/templates/rss-item.xml view
@@ -0,0 +1,7 @@+<item>+ <title>$title</title>+ <link>$absolute/$url</link>+ <description>$description</description>+ <pubDate>$timestamp</pubDate>+ <guid>$absolute/$url</guid>+</item>
+ data/templates/rss.xml view
@@ -0,0 +1,12 @@+<?xml version="1.0" encoding="utf8"?>+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">+ <channel>+ <title>$title</title>+ <link>$absolute</link>+ <description>$description</description>+ <atom:link href="$absolute/$url" rel="self"+ type="application/rss+xml" />+ <lastBuildDate>$timestamp</lastBuildDate>+ $body+ </channel> +</rss>
hakyll.cabal view
@@ -1,55 +1,63 @@-Name: hakyll-Version: 1.4+Name: hakyll+Version: 2.0 -Synopsis: A simple static site generator library.-Description:- A simple static site generator library, mainly aimed at- creating blogs and brochure sites.-Author: Jasper Van der Jeugt-Maintainer: jaspervdj@gmail.com-Homepage: http://jaspervdj.be/hakyll-Bug-Reports: http://github.com/jaspervdj/Hakyll/issues-License: BSD3-License-File: LICENSE-Category: Text-Cabal-Version: >= 1.6+Synopsis: A simple static site generator library.+Description: A simple static site generator library, mainly aimed at+ creating blogs and brochure sites.+Author: Jasper Van der Jeugt+Maintainer: jaspervdj@gmail.com+Homepage: http://jaspervdj.be/hakyll+Bug-Reports: http://github.com/jaspervdj/Hakyll/issues+License: BSD3+License-File: LICENSE+Category: Text+Cabal-Version: >= 1.6+Data-Dir: data+Data-Files: templates/atom.xml+ templates/atom-item.xml+ templates/rss.xml+ templates/rss-item.xml -build-type: Simple+build-type: Simple source-repository head- type: git- location: git://github.com/jaspervdj/Hakyll.git+ type: git+ location: git://github.com/jaspervdj/Hakyll.git library- ghc-options: -Wall- hs-source-dirs: src/- build-depends: base >= 4 && < 5,- filepath >= 1.1,- directory >= 1,- containers >= 0.1,- pandoc >= 1,- regex-base >= 0.83,- regex-tdfa >= 0.92,- network >= 2,- mtl >= 1.1,- old-locale >= 1,- old-time >= 1,- time >= 1,- binary >= 0.5,- QuickCheck >= 2- exposed-modules: Network.Hakyll.SimpleServer- Text.Hakyll- Text.Hakyll.Context- Text.Hakyll.File- Text.Hakyll.Hakyll- Text.Hakyll.Regex- Text.Hakyll.Render- Text.Hakyll.Renderable- Text.Hakyll.Renderables- Text.Hakyll.Page- Text.Hakyll.Util- Text.Hakyll.Tags- Text.Hakyll.Internal.Cache- Text.Hakyll.Internal.CompressCss- Text.Hakyll.Internal.Render- Text.Hakyll.Internal.Template+ ghc-options: -Wall+ hs-source-dirs: src+ build-depends: base >= 4 && < 5,+ filepath >= 1.1,+ directory >= 1,+ containers >= 0.1,+ pandoc >= 1,+ regex-base >= 0.83,+ regex-tdfa >= 0.92,+ network >= 2,+ mtl >= 1.1,+ old-locale >= 1,+ old-time >= 1,+ time >= 1,+ binary >= 0.5,+ QuickCheck >= 2+ exposed-modules: Network.Hakyll.SimpleServer+ Text.Hakyll+ Text.Hakyll.Context+ Text.Hakyll.ContextManipulations+ Text.Hakyll.CreateContext+ Text.Hakyll.File+ Text.Hakyll.HakyllMonad+ Text.Hakyll.Regex+ Text.Hakyll.Render+ Text.Hakyll.HakyllAction+ Text.Hakyll.Paginate+ Text.Hakyll.Util+ Text.Hakyll.Tags+ Text.Hakyll.Feed+ other-modules: Paths_hakyll+ Text.Hakyll.Internal.Cache+ Text.Hakyll.Internal.CompressCss+ Text.Hakyll.Internal.FileType+ Text.Hakyll.Internal.Page+ Text.Hakyll.Internal.Template
src/Network/Hakyll/SimpleServer.hs view
@@ -143,16 +143,16 @@ Nothing -> [] where result = lookup (takeExtension fileName) [ (".css", "text/css")- , (".gif", "image/gif")- , (".htm", "text/html")- , (".html", "text/html")- , (".jpeg", "image/jpeg")- , (".jpg", "image/jpeg")- , (".js", "text/javascript")- , (".png", "image/png")- , (".txt", "text/plain")- , (".xml", "text/xml")- ]+ , (".gif", "image/gif")+ , (".htm", "text/html")+ , (".html", "text/html")+ , (".jpeg", "image/jpeg")+ , (".jpg", "image/jpeg")+ , (".js", "text/javascript")+ , (".png", "image/png")+ , (".txt", "text/plain")+ , (".xml", "text/xml")+ ] -- | Respond to an incoming request. respond :: Handle -> Server ()
src/Text/Hakyll.hs view
@@ -22,22 +22,29 @@ import System.Time (getClockTime) import Network.Hakyll.SimpleServer (simpleServer)-import Text.Hakyll.Hakyll+import Text.Hakyll.HakyllMonad import Text.Hakyll.File -- | The default hakyll configuration. defaultHakyllConfiguration :: HakyllConfiguration defaultHakyllConfiguration = HakyllConfiguration- { additionalContext = M.empty- , siteDirectory = "_site"- , cacheDirectory = "_cache"- , enableIndexUrl = False- , previewPollDelay = 1000000+ { absoluteUrl = ""+ , additionalContext = M.empty+ , siteDirectory = "_site"+ , cacheDirectory = "_cache"+ , enableIndexUrl = False+ , previewPollDelay = 1000000 } --- | Main function to run Hakyll with the default configuration.-hakyll :: Hakyll () -> IO ()-hakyll = hakyllWithConfiguration defaultHakyllConfiguration+-- | Main function to run Hakyll with the default configuration. The+-- absolute URL is only used in certain cases, for example RSS feeds et+-- cetera.+hakyll :: String -- ^ Absolute URL of your site. Used in certain cases.+ -> Hakyll () -- ^ You code.+ -> IO ()+hakyll absolute = hakyllWithConfiguration configuration+ where+ configuration = defaultHakyllConfiguration { absoluteUrl = absolute } -- | Main function to run hakyll with a custom configuration. hakyllWithConfiguration :: HakyllConfiguration -> Hakyll () -> IO ()@@ -47,6 +54,7 @@ ["clean"] -> clean ["preview", p] -> preview buildFunction (read p) ["preview"] -> preview buildFunction 8000+ ["rebuild"] -> clean >> buildFunction ["server", p] -> server (read p) ["server"] -> server 8000 _ -> help@@ -90,6 +98,7 @@ ++ name ++ " clean Clean up and remove cache.\n" ++ name ++ " help Show this message.\n" ++ name ++ " preview [port] Run a server and autocompile.\n"+ ++ name ++ " rebuild Clean up and build again.\n" ++ name ++ " server [port] Run a local test server.\n" -- | Start a server at the given port number.
src/Text/Hakyll/Context.hs view
@@ -1,82 +1,11 @@--- | Module containing various functions to manipulate contexts.+-- | This (quite small) module exports the datatype used for contexts. A+-- @Context@ is a simple key-value mapping. You can render these @Context@s+-- with templates, and manipulate them in various ways. module Text.Hakyll.Context ( Context- , ContextManipulation- , renderValue- , changeValue- , renderDate- , changeExtension ) where -import qualified Data.Map as M import Data.Map (Map)-import System.Locale (defaultTimeLocale)-import System.FilePath (takeFileName, addExtension, dropExtension)-import Data.Time.Format (parseTime, formatTime)-import Data.Time.Clock (UTCTime)-import Data.Maybe (fromMaybe) -import Text.Hakyll.Regex (substituteRegex)---- | Type for a context.+-- | Datatype used for key-value mappings. type Context = Map String String---- | Type for context manipulating functions.-type ContextManipulation = Context -> Context---- | Do something with a value in a @Context@, but keep the old value as well.--- This is probably the most common function to construct a--- @ContextManipulation@.-renderValue :: String -- ^ Key of which the value should be copied.- -> String -- ^ Key the value should be copied to.- -> (String -> String) -- ^ Function to apply on the value.- -> ContextManipulation-renderValue src dst f context = case M.lookup src context of- Nothing -> context- (Just value) -> M.insert dst (f value) context---- | Change a value in a @Context@.------ > import Data.Char (toUpper)--- > changeValue "title" (map toUpper)------ Will put the title in UPPERCASE.-changeValue :: String -- ^ Key of which the value should be changed.- -> (String -> String) -- ^ Function to apply on the value.- -> ContextManipulation-changeValue key = renderValue key key---- | When the context has a key called @path@ in a @yyyy-mm-dd-title.extension@--- format (default for pages), this function can render the date.------ > renderDate "date" "%B %e, %Y" "Date unknown"------ Will render something like @January 32, 2010@.-renderDate :: String -- ^ Key in which the rendered date should be placed.- -> String -- ^ Format to use on the date.- -> String -- ^ Default value when the date cannot be parsed.- -> ContextManipulation-renderDate key format defaultValue context = M.insert key value context- where- value = fromMaybe defaultValue pretty- pretty = do filePath <- M.lookup "path" context- let dateString = substituteRegex "^([0-9]*-[0-9]*-[0-9]*).*" "\\1"- (takeFileName filePath)- time <- parseTime defaultTimeLocale- "%Y-%m-%d"- dateString :: Maybe UTCTime- return $ formatTime defaultTimeLocale format time---- | Change the extension of a file. This is only needed when you want to--- render, for example, mardown to @.php@ files instead of @.html@ files.------ > renderChainWith (changeExtension "php")--- > ["templates/default.html"]--- > (createPagePath "test.markdown")------ Will render to @test.php@ instead of @test.html@.-changeExtension :: String -- ^ Extension to change to.- -> ContextManipulation-changeExtension extension = changeValue "url" changeExtension'- where- changeExtension' = flip addExtension extension . dropExtension
+ src/Text/Hakyll/ContextManipulations.hs view
@@ -0,0 +1,93 @@+-- | This module exports a number of functions that produce @HakyllAction@s to+-- manipulate @Context@s.+module Text.Hakyll.ContextManipulations+ ( renderValue+ , changeValue+ , copyValue+ , renderDate+ , changeExtension+ , renderBody+ ) where++import Control.Arrow (arr)+import System.Locale (defaultTimeLocale)+import System.FilePath (takeFileName, addExtension, dropExtension)+import Data.Time.Format (parseTime, formatTime)+import Data.Time.Clock (UTCTime)+import Data.Maybe (fromMaybe)+import qualified Data.Map as M++import Text.Hakyll.Regex (substituteRegex)+import Text.Hakyll.HakyllAction (HakyllAction)+import Text.Hakyll.Context (Context)++-- | Do something with a value in a @Context@, but keep the old value as well.+-- If the key given is not present in the @Context@, nothing will happen.+renderValue :: String -- ^ Key of which the value should be copied.+ -> String -- ^ Key the value should be copied to.+ -> (String -> String) -- ^ Function to apply on the value.+ -> HakyllAction Context Context+renderValue source destination f = arr $ \context ->+ case M.lookup source context of+ Nothing -> context+ (Just value) -> M.insert destination (f value) context++-- | Change a value in a @Context@.+--+-- > import Data.Char (toUpper)+-- > changeValue "title" (map toUpper)+--+-- Will put the title in UPPERCASE.+changeValue :: String -- ^ Key to change.+ -> (String -> String) -- ^ Function to apply on the value.+ -> HakyllAction Context Context+changeValue key = renderValue key key++-- | Copy a value from one key to another in a @Context@.+copyValue :: String -- ^ Source key.+ -> String -- ^ Destination key.+ -> HakyllAction Context Context+copyValue source destination = renderValue source destination id++-- | When the context has a key called @path@ in a+-- @folder/yyyy-mm-dd-title.extension@ format (the convention for pages),+-- this function can render the date.+--+-- > renderDate "date" "%B %e, %Y" "Date unknown"+--+-- Will render something like @January 32, 2010@.+renderDate :: String -- ^ Key in which the rendered date should be placed.+ -> String -- ^ Format to use on the date.+ -> String -- ^ Default key, in case the date cannot be parsed.+ -> HakyllAction Context Context+renderDate key format defaultValue = renderValue "path" key renderDate'+ where+ renderDate' filePath = fromMaybe defaultValue $ do+ let dateString = substituteRegex "^([0-9]*-[0-9]*-[0-9]*).*" "\\1"+ (takeFileName filePath)+ time <- parseTime defaultTimeLocale+ "%Y-%m-%d"+ dateString :: Maybe UTCTime+ return $ formatTime defaultTimeLocale format time++-- | Change the extension of a file. This is only needed when you want to+-- render, for example, mardown to @.php@ files instead of @.html@ files.+--+-- > changeExtension "php"+--+-- Will render @test.markdown@ to @test.php@ instead of @test.html@.+changeExtension :: String -- ^ Extension to change to.+ -> HakyllAction Context Context+changeExtension extension = changeValue "url" changeExtension'+ where+ changeExtension' = flip addExtension extension . dropExtension++-- | Change the body of a file using a certain manipulation.+--+-- > import Data.Char (toUpper)+-- > renderBody (map toUpper)+--+-- Will put the entire body of the page in UPPERCASE.+renderBody :: (String -> String)+ -> HakyllAction Context Context+renderBody = renderValue "body" "body"
+ src/Text/Hakyll/CreateContext.hs view
@@ -0,0 +1,97 @@+-- | A module that provides different ways to create a @Context@. These+-- functions all use the @HakyllAction@ arrow, so they produce values of the+-- type @HakyllAction () Context@.+module Text.Hakyll.CreateContext+ ( createPage+ , createCustomPage+ , createListing+ , combine+ , combineWithUrl+ ) where++import qualified Data.Map as M+import Control.Arrow (second)+import Control.Monad (liftM2, mplus)+import Control.Applicative ((<$>))++import Text.Hakyll.File+import Text.Hakyll.Context+import Text.Hakyll.HakyllAction+import Text.Hakyll.Render+import Text.Hakyll.Internal.Page++-- | Create a @Context@ from a page file stored on the disk. This is probably+-- the most common way to create a @Context@.+createPage :: FilePath -> HakyllAction () Context+createPage path = HakyllAction+ { actionDependencies = [path]+ , actionUrl = Just $ toUrl path+ , actionFunction = const (readPage path)+ }++-- | Create a "custom page" @Context@.+-- +-- The association list given maps keys to values for substitution. Note+-- that as value, you can either give a @String@ or a+-- @HakyllAction () String@. The latter is preferred for more complex data,+-- since it allows dependency checking. A @String@ is obviously more simple+-- to use in some cases.+createCustomPage :: FilePath+ -> [(String, Either String (HakyllAction () String))]+ -> HakyllAction () Context+createCustomPage url association = HakyllAction+ { actionDependencies = dataDependencies+ , actionUrl = Just $ return url+ , actionFunction = \_ -> M.fromList <$> assoc'+ }+ where+ mtuple (a, b) = b >>= \b' -> return (a, b')+ toHakyllString = second (either return runHakyllAction)+ assoc' = mapM (mtuple . toHakyllString) $ ("url", Left url) : association+ dataDependencies = map snd association >>= getDependencies+ getDependencies (Left _) = []+ getDependencies (Right x) = actionDependencies x++-- | A @createCustomPage@ function specialized in creating listings.+--+-- This function creates a listing of a certain list of @Context@s. Every+-- item in the list is created by applying the given template to every+-- renderable. You can also specify additional context to be included in the+-- @CustomPage@.+createListing :: FilePath -- ^ Destination of the page.+ -> [FilePath] -- ^ Templates to render items with.+ -> [HakyllAction () Context] -- ^ Renderables in the list.+ -> [(String, Either String (HakyllAction () String))]+ -> HakyllAction () Context+createListing url templates renderables additional =+ createCustomPage url context+ where+ context = ("body", Right concatenation) : additional+ concatenation = renderAndConcat templates renderables++-- | Combine two @Context@s. The url will always be taken from the first+-- @Renderable@. Also, if a `$key` is present in both renderables, the+-- value from the first @Context@ will be taken as well.+--+-- You can see this as a this as a @union@ between two mappings.+combine :: HakyllAction () Context -> HakyllAction () Context+ -> HakyllAction () Context+combine x y = HakyllAction+ { actionDependencies = actionDependencies x ++ actionDependencies y+ , actionUrl = actionUrl x `mplus` actionUrl y+ , actionFunction = \_ ->+ liftM2 M.union (runHakyllAction x) (runHakyllAction y)+ }++-- | Combine two @Context@s and set a custom URL. This behaves like @combine@,+-- except that for the @url@ field, the given URL is always chosen.+combineWithUrl :: FilePath+ -> HakyllAction () Context+ -> HakyllAction () Context+ -> HakyllAction () Context+combineWithUrl url x y = combine'+ { actionUrl = Just $ return url+ , actionFunction = \_ -> M.insert "url" url <$> runHakyllAction combine'+ }+ where+ combine' = combine x y
+ src/Text/Hakyll/Feed.hs view
@@ -0,0 +1,111 @@+-- | A Module that allows easy rendering of RSS feeds. If you use this module,+-- you must make sure you set the `absoluteUrl` field in the main Hakyll+-- configuration.+--+-- Apart from that, the main rendering functions (@renderRss@, @renderAtom@) +-- all assume that you pass the list of items so that the most recent entry+-- in the feed is the first item in the list.+--+-- Also note that the @Context@s should have (at least) the following+-- fields to produce a correct feed:+--+-- - @$title@: Title of the item.+--+-- - @$description@: Description to appear in the feed.+--+-- - @$url@: URL to the item - this is usually set automatically.+--+-- Furthermore, the feed will be generated, but will be incorrect (it won't+-- validate) if an empty list is passed.+module Text.Hakyll.Feed+ ( FeedConfiguration (..)+ , renderRss+ , renderAtom+ ) where++import Control.Arrow ((>>>), second)+import Control.Monad.Reader (liftIO)+import Data.Maybe (fromMaybe)+import qualified Data.Map as M++import Text.Hakyll.Context (Context)+import Text.Hakyll.CreateContext (createListing)+import Text.Hakyll.ContextManipulations (renderDate)+import Text.Hakyll.HakyllMonad (Hakyll)+import Text.Hakyll.Render (render, renderChain)+import Text.Hakyll.HakyllAction++import Paths_hakyll++-- | This is a data structure to keep the configuration of a feed.+data FeedConfiguration = FeedConfiguration+ { -- | Url of the feed (relative to site root). For example, @rss.xml@.+ feedUrl :: String+ , -- | Title of the feed.+ feedTitle :: String+ , -- | Description of the feed.+ feedDescription :: String+ , -- | Name of the feed author.+ feedAuthorName :: String+ }++-- | This is an auxiliary function to create a listing that is, in fact, a feed.+-- The items should be sorted on date.+createFeed :: FeedConfiguration -- ^ Feed configuration.+ -> [HakyllAction () Context] -- ^ Items to include.+ -> FilePath -- ^ Feed template.+ -> FilePath -- ^ Item template.+ -> HakyllAction () Context+createFeed configuration renderables template itemTemplate =+ listing >>> render template+ where+ listing = createListing (feedUrl configuration)+ [itemTemplate] renderables additional++ additional = map (second $ Left . ($ configuration))+ [ ("title", feedTitle)+ , ("description", feedDescription)+ , ("authorName", feedAuthorName)+ ] ++ updated++ -- Take the first timestamp, which should be the most recent.+ updated = let action = createHakyllAction $+ return . fromMaybe "foo" . M.lookup "timestamp"+ toTuple r = ("timestamp", Right $ r >>> action)+ in map toTuple $ take 1 renderables+ ++-- | Abstract function to render any feed.+renderFeed :: FeedConfiguration -- ^ Feed configuration.+ -> [HakyllAction () Context] -- ^ Items to include in the feed.+ -> FilePath -- ^ Feed template.+ -> FilePath -- ^ Item template.+ -> Hakyll ()+renderFeed configuration renderables template itemTemplate = do+ template' <- liftIO $ getDataFileName template+ itemTemplate' <- liftIO $ getDataFileName itemTemplate+ let renderFeed' = createFeed configuration renderables+ template' itemTemplate'+ renderChain [] renderFeed'++-- | Render an RSS feed with a number of items.+renderRss :: FeedConfiguration -- ^ Feed configuration.+ -> [HakyllAction () Context] -- ^ Items to include in the feed.+ -> Hakyll ()+renderRss configuration renderables =+ renderFeed configuration (map (>>> renderRssDate) renderables)+ "templates/rss.xml" "templates/rss-item.xml"+ where+ renderRssDate = renderDate "timestamp" "%a, %d %b %Y %H:%M:%S UT"+ "No date found."++-- | Render an Atom feed with a number of items.+renderAtom :: FeedConfiguration -- ^ Feed configuration.+ -> [HakyllAction () Context] -- ^ Items to include in the feed.+ -> Hakyll ()+renderAtom configuration renderables =+ renderFeed configuration (map (>>> renderAtomDate) renderables)+ "templates/atom.xml" "templates/atom-item.xml"+ where+ renderAtomDate = renderDate "timestamp" "%Y-%m-%dT%H:%M:%SZ"+ "No date found."
src/Text/Hakyll/File.hs view
@@ -20,9 +20,11 @@ import System.Time (ClockTime) import Control.Monad import Data.List (isPrefixOf, sortBy)+import Data.Ord (comparing) import Control.Monad.Reader (liftIO) -import Text.Hakyll.Hakyll+import Text.Hakyll.HakyllMonad+import Text.Hakyll.Internal.FileType (isRenderableFile) -- | Auxiliary function to remove pathSeparators form the start. We don't deal -- with absolute paths here. We also remove $root from the start.@@ -61,7 +63,7 @@ toUrl path = do enableIndexUrl' <- askHakyll enableIndexUrl -- If the file does not have a renderable extension, like for -- example favicon.ico, we don't have to change it at all.- return $ if not hasRenderableExtension+ return $ if not (isRenderableFile path) then path -- If index url's are enabled, we create pick it -- unless the page is an index already.@@ -69,23 +71,9 @@ then indexUrl else withSimpleHtmlExtension where- hasRenderableExtension = takeExtension path `elem` [ ".markdown"- , ".md"- , ".mdn"- , ".mdwn"- , ".mkd"- , ".mkdn"- , ".mkdwn"- , ".rst"- , ".text"- , ".tex"- , ".lhs"- , ".htm"- , ".html"- ]- isIndex = (dropExtension $ takeFileName path) == "index"+ isIndex = dropExtension (takeFileName path) == "index" withSimpleHtmlExtension = flip addExtension ".html" $ dropExtension path- indexUrl = (dropExtension path) ++ "/"+ indexUrl = dropExtension path ++ "/" -- | Get the relative url to the site root, for a given (absolute) url@@ -131,7 +119,7 @@ sortByBaseName :: [FilePath] -> [FilePath] sortByBaseName = sortBy compareBaseName where- compareBaseName f1 f2 = compare (takeFileName f1) (takeFileName f2)+ compareBaseName = comparing takeFileName -- | A filter that takes all file names with a given extension. Prefix the -- extension with a dot:
− src/Text/Hakyll/Hakyll.hs
@@ -1,33 +0,0 @@--- | Module describing the Hakyll monad stack.-module Text.Hakyll.Hakyll- ( HakyllConfiguration (..)- , Hakyll- , askHakyll- ) where--import Control.Monad.Reader (ReaderT, ask)-import Control.Monad (liftM)--import Text.Hakyll.Context (Context)---- | Hakyll global configuration type.-data HakyllConfiguration = HakyllConfiguration- { -- | An additional context to use when rendering. This additional context- -- is used globally.- additionalContext :: Context- , -- | Directory where the site is placed.- siteDirectory :: FilePath- , -- | Directory for cache files.- cacheDirectory :: FilePath- , -- | Enable index links.- enableIndexUrl :: Bool- , -- | Delay between polls in preview mode.- previewPollDelay :: Int- }---- | Our custom monad stack.-type Hakyll = ReaderT HakyllConfiguration IO---- | Simplified @ask@ function for the Hakyll monad stack.-askHakyll :: (HakyllConfiguration -> a) -> Hakyll a-askHakyll = flip liftM ask
+ src/Text/Hakyll/HakyllAction.hs view
@@ -0,0 +1,95 @@+-- | This is the module which exports @HakyllAction@.+module Text.Hakyll.HakyllAction+ ( HakyllAction (..)+ , createHakyllAction+ , createSimpleHakyllAction+ , createFileHakyllAction+ , chain+ , runHakyllAction+ , runHakyllActionIfNeeded+ ) where++import Control.Arrow+import Control.Category+import Control.Monad ((<=<), mplus, unless)+import Control.Monad.Reader (liftIO)+import Prelude hiding ((.), id)+import System.IO (hPutStrLn, stderr)++import Text.Hakyll.File (toDestination, isFileMoreRecent)+import Text.Hakyll.HakyllMonad++-- | Type used for rendering computations that carry along dependencies.+data HakyllAction a b = HakyllAction+ { -- | Dependencies of the @HakyllAction@.+ actionDependencies :: [FilePath]+ , -- | URL pointing to the result of this @HakyllAction@.+ actionUrl :: Maybe (Hakyll FilePath)+ , -- | The actual render function.+ actionFunction :: a -> Hakyll b+ }++-- | Create a @HakyllAction@ from a function.+createHakyllAction :: (a -> Hakyll b) -- ^ Function to execute.+ -> HakyllAction a b+createHakyllAction f = id { actionFunction = f }++-- | Create a @HakyllAction@ from a simple @Hakyll@ value.+createSimpleHakyllAction :: Hakyll b -- ^ Hakyll value to pass on.+ -> HakyllAction () b+createSimpleHakyllAction = createHakyllAction . const++-- | Create a @HakyllAction@ that operates on one file.+createFileHakyllAction :: FilePath -- ^ File to operate on.+ -> Hakyll b -- ^ Value to pass on.+ -> HakyllAction () b -- ^ The resulting action.+createFileHakyllAction path action = HakyllAction+ { actionDependencies = [path]+ , actionUrl = Just $ return path+ , actionFunction = const action+ }++-- | Run a @HakyllAction@ now.+runHakyllAction :: HakyllAction () a -- ^ Render action to run.+ -> Hakyll a -- ^ Result of the action.+runHakyllAction action = actionFunction action ()++-- | Run a @HakyllAction@, but only when it is out-of-date. At this point, the+-- @actionUrl@ field must be set.+runHakyllActionIfNeeded :: HakyllAction () () -- ^ Action to run.+ -> Hakyll () -- ^ Empty result.+runHakyllActionIfNeeded action = do+ url <- case actionUrl action of+ (Just u) -> u+ Nothing -> error "No url when checking dependencies."+ destination <- toDestination url+ valid <- isFileMoreRecent destination $ actionDependencies action+ unless valid $ do liftIO $ hPutStrLn stderr $ "Rendering " ++ destination+ runHakyllAction action++-- | Chain a number of @HakyllAction@ computations.+chain :: [HakyllAction a a] -- ^ Actions to chain.+ -> HakyllAction a a -- ^ Resulting action.+chain [] = id+chain list = foldl1 (>>>) list++instance Category HakyllAction where+ id = HakyllAction+ { actionDependencies = []+ , actionUrl = Nothing+ , actionFunction = return+ }++ x . y = HakyllAction+ { actionDependencies = actionDependencies x ++ actionDependencies y+ , actionUrl = actionUrl x `mplus` actionUrl y+ , actionFunction = actionFunction x <=< actionFunction y+ }++instance Arrow HakyllAction where+ arr f = id { actionFunction = return . f }++ first x = x+ { actionFunction = \(y, z) -> do y' <- actionFunction x y+ return (y', z)+ }
+ src/Text/Hakyll/HakyllMonad.hs view
@@ -0,0 +1,50 @@+-- | Module describing the Hakyll monad stack.+module Text.Hakyll.HakyllMonad+ ( HakyllConfiguration (..)+ , Hakyll+ , askHakyll+ , getAdditionalContext+ ) where++import Control.Monad.Reader (ReaderT, ask)+import Control.Monad (liftM)+import qualified Data.Map as M++import Text.Hakyll.Context (Context)++-- | Our custom monad stack.+type Hakyll = ReaderT HakyllConfiguration IO++-- | Hakyll global configuration type.+data HakyllConfiguration = HakyllConfiguration+ { -- | Absolute URL of the site.+ absoluteUrl :: String+ , -- | An additional context to use when rendering. This additional context+ -- is used globally.+ additionalContext :: Context+ , -- | Directory where the site is placed.+ siteDirectory :: FilePath+ , -- | Directory for cache files.+ cacheDirectory :: FilePath+ , -- | Enable index links.+ enableIndexUrl :: Bool+ , -- | Delay between polls in preview mode.+ previewPollDelay :: Int+ }++-- | Simplified @ask@ function for the Hakyll monad stack.+--+-- Usage would typically be something like:+--+-- > doSomething :: a -> b -> Hakyll c+-- > doSomething arg1 arg2 = do+-- > siteDirectory' <- askHakyll siteDirectory+-- > ...+--+askHakyll :: (HakyllConfiguration -> a) -> Hakyll a+askHakyll = flip liftM ask++getAdditionalContext :: HakyllConfiguration -> Context+getAdditionalContext configuration =+ M.insert "absolute" (absoluteUrl configuration)+ (additionalContext configuration)
src/Text/Hakyll/Internal/Cache.hs view
@@ -6,9 +6,10 @@ import Control.Monad ((<=<)) import Control.Monad.Reader (liftIO)-import Text.Hakyll.Hakyll (Hakyll)-import Text.Hakyll.File import Data.Binary++import Text.Hakyll.File+import Text.Hakyll.HakyllMonad (Hakyll) -- | We can store all datatypes instantiating @Binary@ to the cache. The cache -- directory is specified by the @HakyllConfiguration@, usually @_cache@.
+ src/Text/Hakyll/Internal/FileType.hs view
@@ -0,0 +1,48 @@+-- | A module dealing with file extensions and associated file types.+module Text.Hakyll.Internal.FileType+ ( FileType (..)+ , getFileType+ , isRenderable+ , isRenderableFile+ ) where++import System.FilePath (takeExtension)++-- | Datatype to represent the different file types Hakyll can deal with.+data FileType = Html+ | LaTeX+ | LiterateHaskellMarkdown+ | Markdown+ | ReStructuredText+ | Text+ | UnknownFileType+ deriving (Eq, Ord, Show, Read)++-- | Get the file type for a certain file. The type is determined by extension.+getFileType :: FilePath -> FileType+getFileType = getFileType' . takeExtension+ where+ getFileType' ".htm" = Html+ getFileType' ".html" = Html+ getFileType' ".lhs" = LiterateHaskellMarkdown+ getFileType' ".markdown" = Markdown+ getFileType' ".md" = Markdown+ getFileType' ".mdn" = Markdown+ getFileType' ".mdown" = Markdown+ getFileType' ".mdwn" = Markdown+ getFileType' ".mkd" = Markdown+ getFileType' ".mkdwn" = Markdown+ getFileType' ".rst" = ReStructuredText+ getFileType' ".tex" = LaTeX+ getFileType' ".text" = Text+ getFileType' ".txt" = Text+ getFileType' _ = UnknownFileType++-- | Check if a certain @FileType@ is renderable.+isRenderable :: FileType -> Bool+isRenderable UnknownFileType = False+isRenderable _ = True++-- | Check if a certain file is renderable.+isRenderableFile :: FilePath -> Bool+isRenderableFile = isRenderable . getFileType
+ src/Text/Hakyll/Internal/Page.hs view
@@ -0,0 +1,123 @@+-- | A module for dealing with @Page@s. This module is mostly internally used.+module Text.Hakyll.Internal.Page + ( readPage+ ) where++import qualified Data.Map as M+import Data.List (isPrefixOf)+import Data.Char (isSpace)+import Control.Monad.Reader (liftIO)+import System.FilePath++import Text.Pandoc++import Text.Hakyll.Context (Context)+import Text.Hakyll.File+import Text.Hakyll.HakyllMonad+import Text.Hakyll.Regex (substituteRegex, matchesRegex)+import Text.Hakyll.Util (trim)+import Text.Hakyll.Internal.Cache+import Text.Hakyll.Internal.FileType++-- | The default reader options for pandoc parsing.+readerOptions :: ParserState+readerOptions = defaultParserState+ { -- The following option causes pandoc to read smart typography, a nice+ -- and free bonus.+ stateSmart = True+ }++-- | The default writer options for pandoc rendering.+writerOptions :: WriterOptions+writerOptions = defaultWriterOptions+ { -- This option causes literate haskell to be written using '>' marks in+ -- html, which I think is a good default.+ writerLiterateHaskell = True+ }++-- | Get a render function for a given extension.+getRenderFunction :: FileType -> (String -> String)+getRenderFunction Html = id+getRenderFunction Text = id+getRenderFunction fileType = writeHtmlString writerOptions+ . readFunction fileType (readOptions fileType)+ where+ readFunction ReStructuredText = readRST+ readFunction LaTeX = readLaTeX+ readFunction Markdown = readMarkdown+ readFunction LiterateHaskellMarkdown = readMarkdown+ readFunction t = error $ "Cannot render " ++ show t++ readOptions LiterateHaskellMarkdown =+ readerOptions { stateLiterateHaskell = True }+ readOptions _ = readerOptions++-- | Split a page into sections.+splitAtDelimiters :: [String] -> [[String]]+splitAtDelimiters [] = []+splitAtDelimiters ls@(x:xs)+ | isDelimiter x = let (content, rest) = break isDelimiter xs+ in (x : content) : splitAtDelimiters rest+ | otherwise = [ls]++-- | Check if the given string is a metadata delimiter.+isDelimiter :: String -> Bool+isDelimiter = isPrefixOf "---"++-- | Read one section of a page.+readSection :: (String -> String) -- ^ Render function.+ -> Bool -- ^ If this section is the first section in the page.+ -> [String] -- ^ Lines in the section.+ -> [(String, String)] -- ^ Key-values extracted.+readSection _ _ [] = []+readSection renderFunction isFirst ls+ | not isDelimiter' = body ls+ | isNamedDelimiter = readSectionMetaData ls+ | isFirst = readSimpleMetaData (tail ls)+ | otherwise = body (tail ls)+ where+ isDelimiter' = isDelimiter (head ls)+ isNamedDelimiter = head ls `matchesRegex` "^----* *[a-zA-Z0-9][a-zA-Z0-9]*"+ body ls' = [("body", renderFunction $ unlines ls')]++ readSimpleMetaData = map readPair . filter (not . all isSpace)+ readPair = trimPair . break (== ':')+ trimPair (key, value) = (trim key, trim $ tail value)++ readSectionMetaData [] = []+ readSectionMetaData (header:value) =+ let key = substituteRegex "[^a-zA-Z0-9]" "" header+ in [(key, renderFunction $ unlines value)]++-- | Read a page from a file. Metadata is supported, and if the filename+-- has a @.markdown@ extension, it will be rendered using pandoc.+readPageFromFile :: FilePath -> Hakyll Context+readPageFromFile path = do+ let renderFunction = getRenderFunction $ getFileType path+ sectionFunctions = map (readSection renderFunction)+ (True : repeat False)++ -- Read file.+ contents <- liftIO $ readFile path+ url <- toUrl path+ let sections = splitAtDelimiters $ lines contents+ sectionsData = concat $ zipWith ($) sectionFunctions sections+ context = M.fromList $+ ("url", url) : ("path", path) : category ++ sectionsData++ return context+ where+ category = let dirs = splitDirectories $ takeDirectory path+ in [("category", last dirs) | not (null dirs)]++-- | Read a page. Might fetch it from the cache if available. Otherwise, it will+-- read it from the file given and store it in the cache.+readPage :: FilePath -> Hakyll Context+readPage path = do+ isCacheMoreRecent' <- isCacheMoreRecent fileName [path]+ if isCacheMoreRecent' then getFromCache fileName+ else do page <- readPageFromFile path+ storeInCache page fileName+ return page+ where+ fileName = "pages" </> path
− src/Text/Hakyll/Internal/Render.hs
@@ -1,67 +0,0 @@--- | Internal module do some low-level rendering.-module Text.Hakyll.Internal.Render- ( substitute- , regularSubstitute- , finalSubstitute- , pureRenderWith- , pureRenderAndConcatWith- , pureRenderChainWith- , writePage- ) where--import qualified Data.Map as M-import Control.Monad.Reader (liftIO)-import Data.List (foldl')-import Data.Maybe (fromMaybe)--import Text.Hakyll.Context (Context, ContextManipulation)-import Text.Hakyll.Renderable-import Text.Hakyll.Page-import Text.Hakyll.File-import Text.Hakyll.Hakyll-import Text.Hakyll.Internal.Template---- | A pure render function.-pureRenderWith :: ContextManipulation -- ^ Manipulation to apply on the context.- -> Template -- ^ Template to use for rendering.- -> Context -- ^ Renderable object to render with given template.- -> Context -- ^ The body of the result will contain the render.-pureRenderWith manipulation template context =- -- Ignore $root when substituting here. We will only replace that in the- -- final render (just before writing).- let contextIgnoringRoot = M.insert "root" "$root" (manipulation context)- body = regularSubstitute template contextIgnoringRoot- in M.insert "body" body context---- | A pure renderAndConcat function.-pureRenderAndConcatWith :: ContextManipulation -- ^ Manipulation to apply.- -> [Template] -- ^ Templates to use.- -> [Context] -- ^ Different renderables.- -> String-pureRenderAndConcatWith manipulation templates =- concatMap renderAndConcat- where- renderAndConcat = fromMaybe "" . M.lookup "body"- . pureRenderChainWith manipulation templates---- | A pure renderChain function.-pureRenderChainWith :: ContextManipulation- -> [Template]- -> Context- -> Context-pureRenderChainWith manipulation templates context =- let initial = manipulation context- in foldl' (flip $ pureRenderWith id) initial templates---- | Write a page to the site destination. Final action after render--- chains and such.-writePage :: Page -> Hakyll ()-writePage page = do- additionalContext' <- askHakyll additionalContext- url <- getUrl page- destination <- toDestination url- let context = additionalContext' `M.union` M.singleton "root" (toRoot url)- makeDirectories destination- -- Substitute $root here, just before writing.- liftIO $ writeFile destination $ finalSubstitute (fromString $ getBody page)- context
src/Text/Hakyll/Internal/Template.hs view
@@ -19,8 +19,8 @@ import Test.QuickCheck -import Text.Hakyll.Hakyll (Hakyll) import Text.Hakyll.Context (Context)+import Text.Hakyll.HakyllMonad (Hakyll) import Text.Hakyll.Internal.Cache -- | Datatype used for template substitutions.
− src/Text/Hakyll/Page.hs
@@ -1,185 +0,0 @@--- | A module for dealing with @Page@s. This module is mostly internally used.-module Text.Hakyll.Page - ( Page- , fromContext- , getValue- , getBody- , readPage- ) where--import qualified Data.Map as M-import Data.List (isPrefixOf)-import Data.Char (isSpace)-import Data.Maybe (fromMaybe)-import Control.Monad (liftM, replicateM)-import Control.Monad.Reader (liftIO)-import System.FilePath--import Test.QuickCheck-import Text.Pandoc-import Data.Binary--import Text.Hakyll.Internal.Cache-import Text.Hakyll.Hakyll-import Text.Hakyll.File-import Text.Hakyll.Util (trim)-import Text.Hakyll.Context (Context)-import Text.Hakyll.Renderable-import Text.Hakyll.Regex (substituteRegex, matchesRegex)---- | A Page is basically key-value mapping. Certain keys have special--- meanings, like for example url, body and title.-data Page = Page Context- deriving (Ord, Eq, Show, Read)---- | Create a Page from a key-value mapping.-fromContext :: Context -> Page-fromContext = Page---- | Obtain a value from a page. Will resturn an empty string when nothing is--- found.-getValue :: String -> Page -> String-getValue str (Page page) = fromMaybe [] $ M.lookup str page---- | Get the URL for a certain page. This should always be defined. If--- not, it will error.-getPageUrl :: Page -> String-getPageUrl (Page page) = fromMaybe (error "No page url") $ M.lookup "url" page---- | Get the original page path.-getPagePath :: Page -> String-getPagePath (Page page) =- fromMaybe (error "No page path") $ M.lookup "path" page---- | Get the body for a certain page. When not defined, the body will be--- empty.-getBody :: Page -> String-getBody (Page page) = fromMaybe [] $ M.lookup "body" page---- | The default reader options for pandoc parsing.-readerOptions :: ParserState-readerOptions = defaultParserState- { -- The following option causes pandoc to read smart typography, a nice- -- and free bonus.- stateSmart = True- }---- | The default writer options for pandoc rendering.-writerOptions :: WriterOptions-writerOptions = defaultWriterOptions- { -- This option causes literate haskell to be written using '>' marks in- -- html, which I think is a good default.- writerLiterateHaskell = True- }---- | Get a render function for a given extension.-getRenderFunction :: String -> (String -> String)-getRenderFunction ".html" = id-getRenderFunction ".htm" = id-getRenderFunction ext = writeHtmlString writerOptions- . readFunction ext (readOptions ext)- where- readFunction ".rst" = readRST- readFunction ".tex" = readLaTeX- readFunction _ = readMarkdown-- readOptions ".lhs" = readerOptions { stateLiterateHaskell = True }- readOptions _ = readerOptions---- | Split a page into sections.-splitAtDelimiters :: [String] -> [[String]]-splitAtDelimiters [] = []-splitAtDelimiters ls@(x:xs)- | isDelimiter x = let (content, rest) = break isDelimiter xs- in (x : content) : splitAtDelimiters rest- | otherwise = [ls]---- | Check if the given string is a metadata delimiter.-isDelimiter :: String -> Bool-isDelimiter = isPrefixOf "---"---- | Read one section of a page.-readSection :: (String -> String) -- ^ Render function.- -> Bool -- ^ If this section is the first section in the page.- -> [String] -- ^ Lines in the section.- -> [(String, String)] -- ^ Key-values extracted.-readSection _ _ [] = []-readSection renderFunction isFirst ls- | not isDelimiter' = body ls- | isNamedDelimiter = readSectionMetaData ls- | isFirst = readSimpleMetaData (tail ls)- | otherwise = body (tail ls)- where- isDelimiter' = isDelimiter (head ls)- isNamedDelimiter = head ls `matchesRegex` "^----* *[a-zA-Z0-9][a-zA-Z0-9]*"- body ls' = [("body", renderFunction $ unlines ls')]-- readSimpleMetaData = map readPair . filter (not . all isSpace)- readPair = trimPair . break (== ':')- trimPair (key, value) = (trim key, trim $ tail value)-- readSectionMetaData [] = []- readSectionMetaData (header:value) =- let key = substituteRegex "[^a-zA-Z0-9]" "" header- in [(key, renderFunction $ unlines value)]---- | Read a page from a file. Metadata is supported, and if the filename--- has a @.markdown@ extension, it will be rendered using pandoc.-readPageFromFile :: FilePath -> Hakyll Page-readPageFromFile path = do- let renderFunction = getRenderFunction $ takeExtension path- sectionFunctions = map (readSection renderFunction)- (True : repeat False)-- -- Read file.- contents <- liftIO $ readFile path- url <- toUrl path- let sections = splitAtDelimiters $ lines contents- context = concat $ zipWith ($) sectionFunctions sections- page = fromContext $ M.fromList $- category ++- [ ("url", url)- , ("path", path)- ] ++ context-- return page- where- category = let dirs = splitDirectories $ takeDirectory path- in [("category", last dirs) | not (null dirs)]---- | Read a page. Might fetch it from the cache if available. Otherwise, it will--- read it from the file given and store it in the cache.-readPage :: FilePath -> Hakyll Page-readPage path = do- isCacheMoreRecent' <- isCacheMoreRecent fileName [path]- if isCacheMoreRecent' then getFromCache fileName- else do page <- readPageFromFile path- storeInCache page fileName- return page- where- fileName = "pages" </> path---- Make pages renderable.-instance Renderable Page where- getDependencies = (:[]) . getPagePath- getUrl = return . getPageUrl- toContext (Page page) = return page---- Make pages serializable.-instance Binary Page where- put (Page context) = put $ M.toAscList context- get = liftM (Page . M.fromAscList) get---- | Generate an arbitrary page.-arbitraryPage :: Gen Page-arbitraryPage = do keys <- listOf key'- values <- arbitrary- return $ Page $ M.fromList $ zip keys values- where- key' = do l <- choose (5, 10)- replicateM l $ choose ('a', 'z')---- Make pages testable-instance Arbitrary Page where- arbitrary = arbitraryPage- shrink (Page context) = map (Page . flip M.delete context) $ M.keys context
+ src/Text/Hakyll/Paginate.hs view
@@ -0,0 +1,89 @@+-- | Module aimed to paginate web pages.+module Text.Hakyll.Paginate+ ( PaginateConfiguration (..)+ , defaultPaginateConfiguration+ , paginate+ ) where++import Control.Applicative ((<$>))++import Text.Hakyll.Context (Context)+import Text.Hakyll.CreateContext+import Text.Hakyll.HakyllAction+import Text.Hakyll.Util (link)++-- | A configuration for a pagination.+data PaginateConfiguration = PaginateConfiguration+ { -- | Label for the link to the previous page.+ previousLabel :: String+ , -- | Label for the link to the next page.+ nextLabel :: String+ , -- | Label for the link to the first page.+ firstLabel :: String+ , -- | Label for the link to the last page.+ lastLabel :: String+ }++-- | A simple default configuration for pagination.+defaultPaginateConfiguration :: PaginateConfiguration+defaultPaginateConfiguration = PaginateConfiguration+ { previousLabel = "Previous"+ , nextLabel = "Next"+ , firstLabel = "First"+ , lastLabel = "Last"+ }++-- | The most important function for pagination. This function operates on a+-- list of @Context@s (the pages), and basically just adds fields to them+-- by combining them with a custom page.+--+-- The following metadata fields will be added:+--+-- - @$previous@: A link to the previous page.+--+-- - @$next@: A link to the next page.+--+-- - @$first@: A link to the first page.+--+-- - @$last@: A link to the last page.+--+-- - @$index@: 1-based index of the current page.+--+-- - @$length@: Total number of pages.+--+-- When @$previous@ or @$next@ are not available, they will be just a label+-- without a link. The same goes for when we are on the first or last page for+-- @$first@ and @$last@.+paginate :: PaginateConfiguration+ -> [HakyllAction () Context]+ -> [HakyllAction () Context]+paginate configuration renderables = paginate' Nothing renderables (1 :: Int)+ where+ -- Create a link with a given label, taken from the configuration.+ linkWithLabel f r = Right $ case actionUrl r of+ Just l -> createSimpleHakyllAction $ link (f configuration) <$> l+ Nothing -> error "No link found for pagination."++ -- The main function that creates combined renderables by recursing over+ -- the list of items.+ paginate' _ [] _ = []+ paginate' maybePrev (x:xs) index = + let (previous, first) = case maybePrev of+ (Just r) -> ( linkWithLabel previousLabel r+ , linkWithLabel firstLabel (head renderables) )+ Nothing -> ( Left $ previousLabel configuration+ , Left $ firstLabel configuration )+ (next, last') = case xs of+ (n:_) -> ( linkWithLabel nextLabel n+ , linkWithLabel lastLabel (last renderables) )+ [] -> ( Left $ nextLabel configuration+ , Left $ lastLabel configuration )+ customPage = createCustomPage ""+ [ ("previous", previous)+ , ("next", next)+ , ("first", first)+ , ("last", last')+ , ("index", Left $ show index)+ , ("length", Left $ show $ length renderables)+ ]+ in (x `combine` customPage) : paginate' (Just x) xs (index + 1)
src/Text/Hakyll/Regex.hs view
@@ -15,7 +15,7 @@ matchRegexAll = matchM -- | Replaces every occurance of the given regexp with the replacement string.-subRegex :: Regex -- ^ Search pattern+subRegex :: Regex -- ^ Search pattern -> String -- ^ Input string -> String -- ^ Replacement text -> String -- ^ Output string@@ -41,7 +41,8 @@ in case matchRegexAll regexp inp of Nothing -> inp Just (lead, match', trail, groups) ->- lead ++ lookup' match' replacement groups ++ subRegex regexp trail replacement+ lead ++ lookup' match' replacement groups+ ++ subRegex regexp trail replacement -- | Splits a string based on a regular expression. The regular expression -- should identify one delimiter.
src/Text/Hakyll/Render.hs view
@@ -1,87 +1,74 @@ -- | Module containing rendering functions. All these functions are used to -- render files to the @_site@ directory. module Text.Hakyll.Render - ( depends- , render- , renderWith+ ( render , renderAndConcat- , renderAndConcatWith , renderChain- , renderChainWith , static , css+ , writePage ) where -import Control.Monad (unless)+import Control.Arrow ((>>>))+import Control.Applicative ((<$>)) import Control.Monad.Reader (liftIO) import System.Directory (copyFile)+import Data.Maybe (fromMaybe)+import qualified Data.Map as M -import Text.Hakyll.Hakyll (Hakyll)-import Text.Hakyll.Context (ContextManipulation)-import Text.Hakyll.Page-import Text.Hakyll.Renderable+import Text.Hakyll.Context (Context)+import Text.Hakyll.HakyllMonad (Hakyll, askHakyll, getAdditionalContext) import Text.Hakyll.File-import Text.Hakyll.Internal.Template (readTemplate)+import Text.Hakyll.HakyllAction import Text.Hakyll.Internal.CompressCss-import Text.Hakyll.Internal.Render---- | Execute an IO action only when the cache is invalid.-depends :: FilePath -- ^ File to be rendered or created.- -> [FilePath] -- ^ Files the render depends on.- -> Hakyll () -- ^ IO action to execute when the file is out of date.- -> Hakyll ()-depends file dependencies action = do- destination <- toDestination file- valid <- isFileMoreRecent destination dependencies- unless valid action+import Text.Hakyll.Internal.Template --- | Render to a Page.-render :: Renderable a- => FilePath -- ^ Template to use for rendering.- -> a -- ^ Renderable object to render with given template.- -> Hakyll Page -- ^ The body of the result will contain the render.-render = renderWith id+-- | A pure render function - used internally.+pureRender :: Template -- ^ Template to use for rendering.+ -> Context -- ^ Renderable object to render with given template.+ -> Context -- ^ The body of the result will contain the render.+pureRender template context =+ -- Ignore $root when substituting here. We will only replace that in the+ -- final render (just before writing).+ let contextIgnoringRoot = M.insert "root" "$root" context+ body = regularSubstitute template contextIgnoringRoot+ in M.insert "body" body context --- | Render to a Page. This function allows you to manipulate the context--- first.-renderWith :: Renderable a- => ContextManipulation -- ^ Manipulation to apply on the context.- -> FilePath -- ^ Template to use for rendering.- -> a -- ^ Renderable object to render with given template.- -> Hakyll Page -- ^ The body of the result will contain the render.-renderWith manipulation templatePath renderable = do- template <- readTemplate templatePath- context <- toContext renderable- return $ fromContext $ pureRenderWith manipulation template context+-- | This is the most simple render action. You render a @Context@ with a+-- template, and get back the result.+render :: FilePath -- ^ Template to use for rendering.+ -> HakyllAction Context Context -- ^ The render computation.+render templatePath = HakyllAction+ { actionDependencies = [templatePath]+ , actionUrl = Nothing+ , actionFunction = \context ->+ flip pureRender context <$> readTemplate templatePath+ } --- | Render each renderable with the given templates, then concatenate the+-- | Render each @Context@ with the given templates, then concatenate the -- result. So, basically this function: ----- * Takes every renderable.+-- - Takes every @Context@. ----- * Renders every renderable with all given templates. This is comparable+-- - Renders every @Context@ with all given templates. This is comparable -- with a renderChain action. ----- * Concatenates the result.----renderAndConcat :: Renderable a- => [FilePath] -- ^ Templates to apply on every renderable.- -> [a] -- ^ Renderables to render.- -> Hakyll String-renderAndConcat = renderAndConcatWith id+-- - Concatenates the result and returns that as a @String@.+renderAndConcat :: [FilePath]+ -> [HakyllAction () Context]+ -> HakyllAction () String+renderAndConcat templatePaths renderables = HakyllAction+ { actionDependencies = renders >>= actionDependencies+ , actionUrl = Nothing+ , actionFunction = actionFunction'+ }+ where+ render' = chain (map render templatePaths)+ renders = map (>>> render') renderables --- | Render each renderable with the given templates, then concatenate the--- result. This function allows you to specify a @ContextManipulation@ to--- apply on every @Renderable@.-renderAndConcatWith :: Renderable a- => ContextManipulation- -> [FilePath]- -> [a]- -> Hakyll String-renderAndConcatWith manipulation templatePaths renderables = do- templates <- mapM readTemplate templatePaths- contexts <- mapM toContext renderables- return $ pureRenderAndConcatWith manipulation templates contexts+ actionFunction' _ = do+ contexts <- mapM runHakyllAction renders+ return $ concatMap (fromMaybe "" . M.lookup "body") contexts -- | Chain a render action for a page with a number of templates. This will -- also write the result to the site destination. This is the preferred way@@ -93,38 +80,45 @@ -- -- This code will first render @warning.html@ using @templates/notice.html@, -- and will then render the result with @templates/default.html@.-renderChain :: Renderable a => [FilePath] -> a -> Hakyll ()-renderChain = renderChainWith id---- | A more custom render chain that allows you to specify a--- @ContextManipulation@ which to apply on the context when it is read first.-renderChainWith :: Renderable a- => ContextManipulation -> [FilePath] -> a -> Hakyll ()-renderChainWith manipulation templatePaths renderable = do- url <- getUrl renderable- depends url dependencies render'+renderChain :: [FilePath]+ -> HakyllAction () Context+ -> Hakyll ()+renderChain templatePaths initial =+ runHakyllActionIfNeeded renderChainWith' where- dependencies = getDependencies renderable ++ templatePaths- render' = do- templates <- mapM readTemplate templatePaths- context <- toContext renderable- let result = pureRenderChainWith manipulation templates context- writePage $ fromContext result+ renderChainWith' = initial >>> chain' >>> writePage+ chain' = chain $ map render templatePaths -- | Mark a certain file as static, so it will just be copied when the site is -- generated. static :: FilePath -> Hakyll ()-static source = do destination <- toDestination source- depends destination [source] (action destination)+static source = runHakyllActionIfNeeded static' where- action destination = do makeDirectories destination- liftIO $ copyFile source destination+ static' = createFileHakyllAction source $ do+ destination <- toDestination source+ makeDirectories destination+ liftIO $ copyFile source destination -- | Render a css file, compressing it. css :: FilePath -> Hakyll ()-css source = do destination <- toDestination source- depends destination [source] (css' destination)+css source = runHakyllActionIfNeeded css' where- css' destination = do contents <- liftIO $ readFile source- makeDirectories destination- liftIO $ writeFile destination (compressCss contents)+ css' = createFileHakyllAction source $ do+ contents <- liftIO $ readFile source+ destination <- toDestination source+ makeDirectories destination+ liftIO $ writeFile destination (compressCss contents)++-- | Write a page to the site destination. Final action after render+-- chains and such.+writePage :: HakyllAction Context ()+writePage = createHakyllAction $ \initialContext -> do+ additionalContext' <- askHakyll getAdditionalContext+ let url = fromMaybe (error "No url defined at write time.")+ (M.lookup "url" initialContext)+ body = fromMaybe "" (M.lookup "body" initialContext)+ let context = additionalContext' `M.union` M.singleton "root" (toRoot url)+ destination <- toDestination url+ makeDirectories destination+ -- Substitute $root here, just before writing.+ liftIO $ writeFile destination $ finalSubstitute (fromString body) context
− src/Text/Hakyll/Renderable.hs
@@ -1,18 +0,0 @@-module Text.Hakyll.Renderable- ( Renderable(toContext, getDependencies, getUrl)- ) where--import Text.Hakyll.Hakyll (Hakyll)-import Text.Hakyll.Context (Context)---- | A class for datatypes that can be rendered to pages.-class Renderable a where- -- | Get a context to do substitutions with.- toContext :: a -> Hakyll Context-- -- | Get the dependencies for the renderable. This is used for cache- -- invalidation.- getDependencies :: a -> [FilePath]-- -- | Get the destination for the renderable.- getUrl :: a -> Hakyll FilePath
− src/Text/Hakyll/Renderables.hs
@@ -1,158 +0,0 @@-module Text.Hakyll.Renderables- ( CustomPage- , createCustomPage- , createListing- , createListingWith- , PagePath- , createPagePath- , CombinedRenderable- , combine- , combineWithUrl- ) where--import qualified Data.Map as M-import Control.Arrow (second)-import Control.Monad (liftM)--import Data.Binary--import Text.Hakyll.Hakyll (Hakyll)-import Text.Hakyll.Page-import Text.Hakyll.Renderable-import Text.Hakyll.File-import Text.Hakyll.Context-import Text.Hakyll.Render---- | A custom page.-data CustomPage = CustomPage - { customPageUrl :: String,- customPageDependencies :: [FilePath],- customPageContext :: [(String, Either String (Hakyll String))]- }---- | Create a custom page.--- --- The association list given maps keys to values for substitution. Note--- that as value, you can either give a @String@ or a @Hakyll String@.--- A @Hakyll String@ is preferred for more complex data, since it allows--- dependency checking. A @String@ is obviously more simple to use in some--- cases.-createCustomPage :: String -- ^ Destination of the page, relative to _site.- -> [FilePath] -- ^ Dependencies of the page.- -> [(String, Either String (Hakyll String))] -- ^ Mapping.- -> CustomPage-createCustomPage = CustomPage---- | A @createCustomPage@ function specialized in creating listings.------ This function creates a listing of a certain list of @Renderable@s. Every--- item in the list is created by applying the given template to every--- renderable. You can also specify additional context to be included in the--- @CustomPage@.------ > let customPage = createListingWith --- > "index.html" -- Destination of the page.--- > "templates/postitem.html" -- Path to template to--- > -- render the items with.--- > posts -- ^ Renderables to create the list with.--- > [("title", "Home")] -- ^ Additional context-createListing :: (Renderable a)- => String -- ^ Destination of the page.- -> FilePath -- ^ Template to render all items with.- -> [a] -- ^ Renderables in the list.- -> [(String, String)] -- ^ Additional context.- -> CustomPage-createListing = createListingWith id---- | A @createCustomPage@ function specialized in creating listings.------ In addition to @createListing@, this function allows you to specify an--- extra @ContextManipulation@ for all @Renderable@s given.-createListingWith :: (Renderable a)- => ContextManipulation -- ^ Manipulation for the renderables.- -> String -- ^ Destination of the page.- -> FilePath -- ^ Template to render all items with.- -> [a] -- ^ Renderables in the list.- -> [(String, String)] -- ^ Additional context.- -> CustomPage-createListingWith manipulation url template renderables additional =- createCustomPage url dependencies context- where- dependencies = template : concatMap getDependencies renderables- context = ("body", Right concatenation) : additional'- concatenation = renderAndConcatWith manipulation [template] renderables- additional' = map (second Left) additional--instance Renderable CustomPage where- getDependencies = customPageDependencies- getUrl = return . customPageUrl- toContext page = do- values <- mapM (either return id . snd) (customPageContext page)- let pairs = zip (map fst $ customPageContext page) values- return $ M.fromList $ ("url", customPageUrl page) : pairs---- | PagePath is a class that wraps a FilePath. This is used to render Pages--- without reading them first through use of caching.-data PagePath = PagePath FilePath- deriving (Ord, Eq, Read, Show)---- | Create a PagePath from a FilePath.-createPagePath :: FilePath -> PagePath-createPagePath = PagePath---- We can render filepaths-instance Renderable PagePath where- getDependencies (PagePath path) = return path- getUrl (PagePath path) = toUrl path- toContext (PagePath path) = readPage path >>= toContext---- We can serialize filepaths-instance Binary PagePath where- put (PagePath path) = put path- get = liftM PagePath get---- | A combination of two other renderables.-data CombinedRenderable a b = CombinedRenderable a b- | CombinedRenderableWithUrl FilePath a b- deriving (Ord, Eq, Read, Show)---- | Combine two renderables. The url will always be taken from the first--- @Renderable@. Also, if a `$key` is present in both renderables, the--- value from the first @Renderable@ will be taken as well.------ Since renderables are always more or less key-value maps, you can see--- this as a @union@ between two maps.-combine :: (Renderable a, Renderable b) => a -> b -> CombinedRenderable a b-combine = CombinedRenderable---- | Combine two renderables and set a custom URL. This behaves like @combine@,--- except that for the @url@ field, the given URL is always chosen.-combineWithUrl :: (Renderable a, Renderable b)- => FilePath- -> a- -> b- -> CombinedRenderable a b-combineWithUrl = CombinedRenderableWithUrl---- Render combinations.-instance (Renderable a, Renderable b)- => Renderable (CombinedRenderable a b) where-- -- Add the dependencies.- getDependencies (CombinedRenderable a b) =- getDependencies a ++ getDependencies b- getDependencies (CombinedRenderableWithUrl _ a b) =- getDependencies a ++ getDependencies b-- -- Take the url from the first renderable, or the specified URL.- getUrl (CombinedRenderable a _) = getUrl a- getUrl (CombinedRenderableWithUrl url _ _) = return url-- -- Take a union of the contexts.- toContext (CombinedRenderable a b) = do- c1 <- toContext a- c2 <- toContext b- return $ c1 `M.union` c2- toContext (CombinedRenderableWithUrl url a b) = do- c <- toContext (CombinedRenderable a b)- return $ M.singleton "url" url `M.union` c
src/Text/Hakyll/Tags.hs view
@@ -18,8 +18,6 @@ -- is to place pages in subdirectories. -- -- An example, the page @posts\/coding\/2010-01-28-hakyll-categories.markdown@--- would be placed under the `coding` category.--- -- Tags or categories are read using the @readTagMap@ and @readCategoryMap@ -- functions. Because categories are implemented using tags - categories can -- be seen as tags, with the restriction that a page can only have one@@ -33,6 +31,7 @@ ( TagMap , readTagMap , readCategoryMap+ , withTagMap , renderTagCloud , renderTagLinks ) where@@ -40,16 +39,16 @@ import qualified Data.Map as M import Data.List (intercalate) import Data.Maybe (fromMaybe, maybeToList)-import Control.Monad (foldM)-import Control.Arrow (second)+import Control.Arrow (second, (>>>)) import Control.Applicative ((<$>)) import System.FilePath -import Text.Hakyll.Hakyll-import Text.Hakyll.Context+import Text.Hakyll.Context (Context)+import Text.Hakyll.ContextManipulations (changeValue)+import Text.Hakyll.CreateContext (createPage)+import Text.Hakyll.HakyllMonad (Hakyll) import Text.Hakyll.Regex-import Text.Hakyll.Renderable-import Text.Hakyll.Renderables+import Text.Hakyll.HakyllAction import Text.Hakyll.Util import Text.Hakyll.Internal.Cache import Text.Hakyll.Internal.Template@@ -59,75 +58,98 @@ -- This is a map associating tags or categories to the appropriate pages -- using that tag or category. In the case of categories, each path will only -- appear under one category - this is not the case with tags.-type TagMap = M.Map String [PagePath]+type TagMap = M.Map String [HakyllAction () Context] -- | Read a tag map. This is a internally used function that can be used for -- tags as well as for categories. readMap :: (Context -> [String]) -- ^ Function to get tags from a context. -> String -- ^ Unique identifier for the tagmap.- -> [PagePath]- -> Hakyll TagMap-readMap getTagsFunction identifier paths = do- isCacheMoreRecent' <- isCacheMoreRecent fileName (getDependencies =<< paths)- if isCacheMoreRecent' then M.fromAscList <$> getFromCache fileName- else do tagMap <- readTagMap'- storeInCache (M.toAscList tagMap) fileName- return tagMap+ -> [FilePath]+ -> HakyllAction () TagMap+readMap getTagsFunction identifier paths = HakyllAction+ { actionDependencies = paths+ , actionUrl = Nothing+ , actionFunction = actionFunction'+ } where fileName = "tagmaps" </> identifier - readTagMap' = foldM addPaths M.empty paths- addPaths current path = do- context <- toContext path+ actionFunction' _ = do+ isCacheMoreRecent' <- isCacheMoreRecent fileName paths+ assocMap <- if isCacheMoreRecent'+ then M.fromAscList <$> getFromCache fileName+ else do assocMap' <- readTagMap'+ storeInCache (M.toAscList assocMap') fileName+ return assocMap'+ return $ M.map (map createPage) assocMap++ -- TODO: preserve order+ readTagMap' :: Hakyll (M.Map String [FilePath])+ readTagMap' = do+ pairs' <- concat <$> mapM pairs paths+ return $ M.fromListWith (flip (++)) pairs'++ -- | Read a page, and return an association list where every tag is+ -- associated with some paths. Of course, this will always be just one+ -- @FilePath@ here.+ pairs :: FilePath -> Hakyll [(String, [FilePath])]+ pairs path = do+ context <- runHakyllAction $ createPage path let tags = getTagsFunction context- addPaths' = flip (M.insertWith (++)) [path]- return $ foldr addPaths' current tags+ return $ map (\tag -> (tag, [path])) tags -- | Read a @TagMap@, using the @tags@ metadata field.-readTagMap :: String -- ^ Unique identifier for the map.- -> [PagePath] -- ^ Paths to get tags from.- -> Hakyll TagMap-readTagMap = readMap getTagsFunction+readTagMap :: String -- ^ Unique identifier for the map.+ -> [FilePath] -- ^ Paths to get tags from.+ -> HakyllAction () TagMap+readTagMap = readMap getTagsFunction where getTagsFunction = map trim . splitRegex "," . fromMaybe [] . M.lookup "tags" -- | Read a @TagMap@, using the subdirectories the pages are placed in.-readCategoryMap :: String -- ^ Unique identifier for the map.- -> [PagePath] -- ^ Paths to get tags from.- -> Hakyll TagMap+readCategoryMap :: String -- ^ Unique identifier for the map.+ -> [FilePath] -- ^ Paths to get tags from.+ -> HakyllAction () TagMap readCategoryMap = readMap $ maybeToList . M.lookup "category" +withTagMap :: HakyllAction () TagMap+ -> (String -> [HakyllAction () Context] -> Hakyll ())+ -> Hakyll ()+withTagMap tagMap function = runHakyllAction (tagMap >>> action)+ where+ action = createHakyllAction (mapM_ (uncurry function) . M.toList)+ -- | Render a tag cloud.-renderTagCloud :: TagMap -- ^ Map as produced by @readTagMap@.- -> (String -> String) -- ^ Function to produce an url for a tag.- -> Float -- ^ Smallest font size, in percent.- -> Float -- ^ Biggest font size, in percent.- -> String -- ^ Result of the render.-renderTagCloud tagMap urlFunction minSize maxSize =- intercalate " " $ map renderTag tagCount+renderTagCloud :: (String -> String) -- ^ Function to produce an url for a tag.+ -> Float -- ^ Smallest font size, in percent.+ -> Float -- ^ Biggest font size, in percent.+ -> HakyllAction TagMap String+renderTagCloud urlFunction minSize maxSize = createHakyllAction renderTagCloud' where- renderTag :: (String, Float) -> String- renderTag (tag, count) = - finalSubstitute linkTemplate $ M.fromList [ ("size", sizeTag count)- , ("url", urlFunction tag)- , ("tag", tag)- ]+ renderTagCloud' tagMap =+ return $ intercalate " " $ map (renderTag tagMap) (tagCount tagMap)++ renderTag tagMap (tag, count) = + finalSubstitute linkTemplate $ M.fromList+ [ ("size", sizeTag tagMap count)+ , ("url", urlFunction tag)+ , ("tag", tag)+ ]+ linkTemplate = fromString "<a style=\"font-size: $size\" href=\"$url\">$tag</a>" - sizeTag :: Float -> String- sizeTag count = show size' ++ "%"+ sizeTag tagMap count = show (size' :: Int) ++ "%" where- size' :: Int- size' = floor $ minSize + relative count * (maxSize - minSize)+ size' = floor $ minSize + relative tagMap count * (maxSize - minSize) - minCount = minimum $ map snd tagCount- maxCount = maximum $ map snd tagCount- relative count = (count - minCount) / (maxCount - minCount)+ minCount = minimum . map snd . tagCount+ maxCount = maximum . map snd . tagCount+ relative tagMap count = (count - minCount tagMap) /+ (maxCount tagMap - minCount tagMap) - tagCount :: [(String, Float)]- tagCount = map (second $ fromIntegral . length) $ M.toList tagMap+ tagCount = map (second $ fromIntegral . length) . M.toList -- | Render all tags to links. -- @@ -140,7 +162,7 @@ -- Note that it is your own responsibility to ensure a page with such an url -- exists. renderTagLinks :: (String -> String) -- ^ Function to produce an url for a tag.- -> ContextManipulation+ -> HakyllAction Context Context renderTagLinks urlFunction = changeValue "tags" renderTagLinks' where renderTagLinks' = intercalate ", "