summaryrefslogtreecommitdiff
path: root/src/Hakyll/Web
diff options
context:
space:
mode:
authorJasper Van der Jeugt <m@jaspervdj.be>2012-11-22 14:37:04 +0100
committerJasper Van der Jeugt <m@jaspervdj.be>2012-11-22 14:37:04 +0100
commitc54c7a05b1b5a949dc50c37b05300ef21ce22042 (patch)
treebe412ba114e6af65d33b39a7ac6e0845beecd245 /src/Hakyll/Web
parent82d5210f25d560e9488ab14f243a2cad9ad906a9 (diff)
downloadhakyll-c54c7a05b1b5a949dc50c37b05300ef21ce22042.tar.gz
Port tags module to hakyll4 (WIP)
Diffstat (limited to 'src/Hakyll/Web')
-rw-r--r--src/Hakyll/Web/Tags.hs260
-rw-r--r--src/Hakyll/Web/Template/Context.hs7
2 files changed, 142 insertions, 125 deletions
diff --git a/src/Hakyll/Web/Tags.hs b/src/Hakyll/Web/Tags.hs
index af3c3ba..e10af56 100644
--- a/src/Hakyll/Web/Tags.hs
+++ b/src/Hakyll/Web/Tags.hs
@@ -1,4 +1,4 @@
--- TODO: port
+--------------------------------------------------------------------------------
-- | Module containing some specialized functions to deal with tags.
-- This Module follows certain conventions. My advice is to stick with them if
-- possible.
@@ -20,117 +20,131 @@
-- is to place pages in subdirectories.
--
-- An example, the page @posts\/coding\/2010-01-28-hakyll-categories.markdown@
--- Tags or categories are read using the @readTags@ and @readCategory@
+-- Tags or categories are read using the @buildTags@ and @buildCategory@
-- functions. This module only provides functions to work with tags:
-- categories are represented as tags. This is perfectly possible: categories
-- only have an additional restriction that a page can only have one category
-- (instead of multiple tags).
---
-{-# LANGUAGE DeriveDataTypeable, OverloadedStrings, Arrows #-}
+{-# LANGUAGE Arrows #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings #-}
module Hakyll.Web.Tags
( Tags (..)
, getTags
- , readTagsWith
- , readTags
- , readCategory
+ , buildTagsWith
+ , buildTags
+ , buildCategory
, renderTagCloud
, renderTagList
- , renderTagsField
- , renderTagsFieldWith
- , renderCategoryField
+ , tagsField
+ , categoryField
, sortTagsBy
, caseInsensitiveTags
) where
-import Prelude hiding (id)
-import Control.Category (id)
-import Control.Applicative ((<$>))
-import Data.Char (toLower)
-import Data.Ord (comparing)
-import qualified Data.Map as M
-import Data.List (intersperse, intercalate, sortBy)
-import Control.Arrow (arr, (&&&), (>>>), (***), (<<^), returnA)
-import Data.Maybe (catMaybes, fromMaybe)
-import Data.Monoid (mconcat)
-
-import Data.Typeable (Typeable)
-import Data.Binary (Binary, get, put)
-import Text.Blaze.Html.Renderer.String (renderHtml)
-import Text.Blaze.Html ((!), toHtml, toValue)
-import qualified Text.Blaze.Html5 as H
-import qualified Text.Blaze.Html5.Attributes as A
-
-import Hakyll.Web.Page
-import Hakyll.Web.Page.Metadata
-import Hakyll.Web.Urls
-import Hakyll.Core.Writable
-import Hakyll.Core.Identifier
-import Hakyll.Core.Compiler
-import Hakyll.Core.Util.Arrow
-import Hakyll.Core.Util.String
+--------------------------------------------------------------------------------
+import Control.Arrow ((&&&))
+import Control.Monad (foldM, forM)
+import Data.Binary (Binary)
+import Data.Char (toLower)
+import Data.List (intercalate, intersperse,
+ sortBy)
+import qualified Data.Map as M
+import Data.Maybe (catMaybes, fromMaybe)
+import Data.Monoid (mconcat)
+import Data.Ord (comparing)
+import Data.Typeable (Typeable)
+import System.FilePath (takeBaseName, takeDirectory)
+import Text.Blaze.Html (toHtml, toValue, (!))
+import Text.Blaze.Html.Renderer.String (renderHtml)
+import qualified Text.Blaze.Html5 as H
+import qualified Text.Blaze.Html5.Attributes as A
+
+
+--------------------------------------------------------------------------------
+import Hakyll.Core.Compiler
+import Hakyll.Core.Identifier
+import Hakyll.Core.Item
+import Hakyll.Core.Metadata
+import Hakyll.Core.Util.String
+import Hakyll.Core.Writable
+import Hakyll.Web.Template.Context
+import Hakyll.Web.Urls
+
+
+--------------------------------------------------------------------------------
-- | Data about tags
---
-data Tags a = Tags
- { tagsMap :: [(String, [Page a])]
- } deriving (Show, Typeable)
+-- TODO Make this a map instead of a list?
+newtype Tags = Tags
+ { unTags :: [(String, [Identifier])]
+ } deriving (Binary, Show, Typeable)
-instance Binary a => Binary (Tags a) where
- get = Tags <$> get
- put (Tags m) = put m
-instance Writable (Tags a) where
+--------------------------------------------------------------------------------
+instance Writable Tags where
write _ _ = return ()
+
+--------------------------------------------------------------------------------
-- | Obtain tags from a page in the default way: parse them from the @tags@
-- metadata field.
---
-getTags :: Page a -> [String]
-getTags = map trim . splitAll "," . getField "tags"
+getTags :: MonadMetadata m => Identifier -> m [String]
+getTags identifier = do
+ metadata <- getMetadata identifier
+ return $ maybe [] (map trim . splitAll ",") $ M.lookup "tags" metadata
--- | Obtain categories from a page
---
-getCategory :: Page a -> [String]
-getCategory = return . getField "category"
+--------------------------------------------------------------------------------
+-- | Obtain categories from a page.
+getCategory :: MonadMetadata m => Identifier -> m [String]
+getCategory = return . return . takeBaseName . takeDirectory . toFilePath
+
+--------------------------------------------------------------------------------
-- | Higher-level function to read tags
---
-readTagsWith :: (Page a -> [String]) -- ^ Function extracting tags from a page
- -> [Page a] -- ^ Pages
- -> Tags a -- ^ Resulting tags
-readTagsWith f pages = Tags
- { tagsMap = M.toList $
- foldl (M.unionWith (++)) M.empty (map readTagsWith' pages)
- }
+buildTagsWith :: MonadMetadata m
+ => (Identifier -> m [String]) -> [Identifier] -> m Tags
+buildTagsWith f ids = do
+ tagMap <- foldM addTags M.empty ids
+ return $ Tags $ M.toList tagMap
where
-- Create a tag map for one page
- readTagsWith' page =
- let tags = f page
- in M.fromList $ zip tags $ repeat [page]
+ addTags tagMap id' = do
+ tags <- f id'
+ let tagMap' = M.fromList $ zip tags $ repeat [id']
+ return $ M.unionWith (++) tagMap tagMap'
+--------------------------------------------------------------------------------
-- | Read a tagmap using the @tags@ metadata field
---
-readTags :: [Page a] -> Tags a
-readTags = readTagsWith getTags
+-- TODO: Should use pattern
+buildTags :: MonadMetadata m => [Identifier] -> m Tags
+buildTags = buildTagsWith getTags
+
+--------------------------------------------------------------------------------
-- | Read a tagmap using the @category@ metadata field
---
-readCategory :: [Page a] -> Tags a
-readCategory = readTagsWith getCategory
+-- TODO: Should use pattern
+buildCategory :: MonadMetadata m => [Identifier] -> m Tags
+buildCategory = buildTagsWith getCategory
+
+--------------------------------------------------------------------------------
-- | Render tags in HTML
---
-renderTags :: (String -> Identifier (Page a))
- -- ^ Produce a link
+renderTags :: (String -> Identifier)
+ -- ^ Produce a tag page id
-> (String -> String -> Int -> Int -> Int -> String)
-- ^ Produce a tag item: tag, url, count, min count, max count
-> ([String] -> String)
-- ^ Join items
- -> Compiler (Tags a) String
+ -> Tags
-- ^ Tag cloud renderer
-renderTags makeUrl makeItem concatItems = proc (Tags tags) -> do
+ -> Compiler String
+renderTags makeTagId makeHtml concatHtml (Tags tags) = do
-- In tags' we create a list: [((tag, route), count)]
- tags' <- mapA ((id &&& (getRouteFor <<^ makeUrl)) *** arr length) -< tags
+ tags' <- forM tags $ \(tag, ids) -> do
+ route <- getRoute $ makeTagId tag
+ return ((tag, route), length ids)
let -- Absolute frequencies of the pages
freqs = map snd tags'
@@ -138,27 +152,31 @@ renderTags makeUrl makeItem concatItems = proc (Tags tags) -> do
-- The minimum and maximum count found
(min', max')
| null freqs = (0, 1)
- | otherwise = (minimum &&& maximum) freqs
+ | otherwise = (minimum &&& maximum) freqs
-- Create a link for one item
- makeItem' ((tag, url), count) =
- makeItem tag (toUrl $ fromMaybe "/" url) count min' max'
+ makeHtml' ((tag, url), count) =
+ makeHtml tag (toUrl $ fromMaybe "/" url) count min' max'
-- Render and return the HTML
- returnA -< concatItems $ map makeItem' tags'
+ return $ concatHtml $ map makeHtml' tags'
+
+--------------------------------------------------------------------------------
-- | Render a tag cloud in HTML
---
-renderTagCloud :: (String -> Identifier (Page a))
+-- TODO: Maybe produce a Context here
+renderTagCloud :: (String -> Identifier)
-- ^ Produce a link for a tag
-> Double
-- ^ Smallest font size, in percent
-> Double
-- ^ Biggest font size, in percent
- -> Compiler (Tags a) String
- -- ^ Tag cloud renderer
-renderTagCloud makeUrl minSize maxSize =
- renderTags makeUrl makeLink (intercalate " ")
+ -> Tags
+ -- ^ Input tags
+ -> Compiler String
+ -- ^ Rendered cloud
+renderTagCloud makeTagId minSize maxSize =
+ renderTags makeTagId makeLink (intercalate " ")
where
makeLink tag url count min' max' = renderHtml $
H.a ! A.style (toValue $ "font-size: " ++ size count min' max')
@@ -172,61 +190,67 @@ renderTagCloud makeUrl minSize maxSize =
size' = floor $ minSize + relative * (maxSize - minSize)
in show (size' :: Int) ++ "%"
+
+--------------------------------------------------------------------------------
-- | Render a simple tag list in HTML, with the tag count next to the item
---
-renderTagList :: (String -> Identifier (Page a)) -> Compiler (Tags a) (String)
-renderTagList makeUrl = renderTags makeUrl makeLink (intercalate ", ")
+-- TODO: Maybe produce a Context here
+renderTagList :: (String -> Identifier) -> Tags -> Compiler (String)
+renderTagList makeTagId = renderTags makeTagId makeLink (intercalate ", ")
where
makeLink tag url count _ _ = renderHtml $
H.a ! A.href (toValue url) $ toHtml (tag ++ " (" ++ show count ++ ")")
+
+--------------------------------------------------------------------------------
-- | Render tags with links with custom function to get tags. It is typically
-- together with 'getTags' like this:
---
+--
-- > renderTagsFieldWith (customFunction . getTags)
-- > "tags" (fromCapture "tags/*")
---
-renderTagsFieldWith :: (Page a -> [String]) -- ^ Function to get the tags
- -> String -- ^ Destination key
- -> (String -> Identifier a) -- ^ Create a link for a tag
- -> Compiler (Page a) (Page a) -- ^ Resulting compiler
-renderTagsFieldWith tags destination makeUrl =
- id &&& arr tags >>> setFieldA destination renderTags'
- where
- -- Compiler creating a comma-separated HTML string for a list of tags
- renderTags' :: Compiler [String] String
- renderTags' = arr (map $ id &&& makeUrl)
- >>> mapA (id *** getRouteFor)
- >>> arr (map $ uncurry renderLink)
- >>> arr (renderHtml . mconcat . intersperse ", " . catMaybes)
+tagsFieldWith :: (Identifier -> Compiler [String]) -- ^ Get the tags
+ -> String -- ^ Destination key
+ -> (String -> Identifier) -- ^ Create a link for a tag
+ -> Context a -- ^ Resulting context
+tagsFieldWith getTags' key makeTagId = field key $ \item -> do
+ tags <- getTags' $ itemIdentifier item
+ links <- forM tags $ \tag -> do
+ route <- getRoute $ makeTagId tag
+ return $ renderLink tag route
+ return $ renderHtml $ mconcat $ intersperse ", " $ catMaybes $ links
+ where
-- Render one tag link
renderLink _ Nothing = Nothing
renderLink tag (Just filePath) = Just $
H.a ! A.href (toValue $ toUrl filePath) $ toHtml tag
+
+--------------------------------------------------------------------------------
-- | Render tags with links
---
-renderTagsField :: String -- ^ Destination key
- -> (String -> Identifier a) -- ^ Create a link for a tag
- -> Compiler (Page a) (Page a) -- ^ Resulting compiler
-renderTagsField = renderTagsFieldWith getTags
+tagsField :: String -- ^ Destination key
+ -> (String -> Identifier) -- ^ Create a link for a tag
+ -> Context a -- ^ Context
+tagsField = tagsFieldWith getTags
+
+--------------------------------------------------------------------------------
-- | Render the category in a link
---
-renderCategoryField :: String -- ^ Destination key
- -> (String -> Identifier a) -- ^ Create a category link
- -> Compiler (Page a) (Page a) -- ^ Resulting compiler
-renderCategoryField = renderTagsFieldWith getCategory
+categoryField :: String -- ^ Destination key
+ -> (String -> Identifier) -- ^ Create a category link
+ -> Context a -- ^ Context
+categoryField = tagsFieldWith getCategory
+
+--------------------------------------------------------------------------------
-- | Sort tags using supplied function. First element of the tuple passed to
-- the comparing function is the actual tag name.
---
-sortTagsBy :: ((String, [Page a]) -> (String, [Page a]) -> Ordering)
- -> Compiler (Tags a) (Tags a)
-sortTagsBy f = arr $ Tags . sortBy f . tagsMap
+sortTagsBy :: ((String, [Identifier]) -> (String, [Identifier]) -> Ordering)
+ -> Tags -> Tags
+sortTagsBy f = Tags . sortBy f . unTags
+
+--------------------------------------------------------------------------------
-- | Sample sorting function that compares tags case insensitively.
---
-caseInsensitiveTags :: (String, [Page a]) -> (String, [Page a]) -> Ordering
+caseInsensitiveTags :: (String, [Identifier]) -> (String, [Identifier])
+ -> Ordering
caseInsensitiveTags = comparing $ map toLower . fst
diff --git a/src/Hakyll/Web/Template/Context.hs b/src/Hakyll/Web/Template/Context.hs
index 11491bf..3b98ea3 100644
--- a/src/Hakyll/Web/Template/Context.hs
+++ b/src/Hakyll/Web/Template/Context.hs
@@ -11,7 +11,6 @@ module Hakyll.Web.Template.Context
, metadataField
, urlField
, pathField
- , categoryField
, titleField
, dateField
, dateFieldWith
@@ -88,7 +87,6 @@ defaultContext =
metadataField `mappend`
urlField "url" `mappend`
pathField "path" `mappend`
- categoryField "category" `mappend`
titleField "title" `mappend`
missingField
@@ -117,11 +115,6 @@ pathField key = field key $ return . toFilePath . itemIdentifier
--------------------------------------------------------------------------------
-categoryField :: String -> Context a
-categoryField key = mapContext (takeBaseName . takeDirectory) $ pathField key
-
-
---------------------------------------------------------------------------------
titleField :: String -> Context a
titleField key = mapContext takeBaseName $ pathField key