{-# LANGUAGE Arrows #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
module Hakyll.Web.Tags
( Tags (..)
, getTags
, getTagsByField
, getCategory
, buildTagsWith
, buildTags
, buildCategories
, tagsRules
, renderTags
, renderTagCloud
, renderTagCloudWith
, tagCloudField
, tagCloudFieldWith
, renderTagList
, tagsField
, tagsFieldWith
, categoryField
, simpleRenderLink
, sortTagsBy
, caseInsensitiveTags
) where
import Control.Arrow ((&&&))
import Control.Monad (foldM, forM, forM_, mplus)
import Data.Char (toLower)
import Data.List (intercalate, intersperse,
sortBy)
import qualified Data.Map as M
import Data.Maybe (catMaybes, fromMaybe)
import Data.Ord (comparing)
import qualified Data.Set as S
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.Dependencies
import Hakyll.Core.Identifier
import Hakyll.Core.Identifier.Pattern
import Hakyll.Core.Item
import Hakyll.Core.Metadata
import Hakyll.Core.Rules
import Hakyll.Core.Util.String
import Hakyll.Web.Html
import Hakyll.Web.Template.Context
data Tags = Tags
{ Tags -> [([Char], [Identifier])]
tagsMap :: [(String, [Identifier])]
, Tags -> [Char] -> Identifier
tagsMakeId :: String -> Identifier
, Tags -> Dependency
tagsDependency :: Dependency
}
getTags :: MonadMetadata m => Identifier -> m [String]
getTags :: forall (m :: * -> *). MonadMetadata m => Identifier -> m [[Char]]
getTags = forall (m :: * -> *).
MonadMetadata m =>
[Char] -> Identifier -> m [[Char]]
getTagsByField [Char]
"tags"
getTagsByField :: MonadMetadata m => String -> Identifier -> m [String]
getTagsByField :: forall (m :: * -> *).
MonadMetadata m =>
[Char] -> Identifier -> m [[Char]]
getTagsByField [Char]
fieldName Identifier
identifier = do
Metadata
metadata <- forall (m :: * -> *). MonadMetadata m => Identifier -> m Metadata
getMetadata Identifier
identifier
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a. a -> Maybe a -> a
fromMaybe [] forall a b. (a -> b) -> a -> b
$
([Char] -> Metadata -> Maybe [[Char]]
lookupStringList [Char]
fieldName Metadata
metadata) forall (m :: * -> *) a. MonadPlus m => m a -> m a -> m a
`mplus`
(forall a b. (a -> b) -> [a] -> [b]
map [Char] -> [Char]
trim forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> [Char] -> [[Char]]
splitAll [Char]
"," forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Char] -> Metadata -> Maybe [Char]
lookupString [Char]
fieldName Metadata
metadata)
getCategory :: MonadMetadata m => Identifier -> m [String]
getCategory :: forall (m :: * -> *). MonadMetadata m => Identifier -> m [[Char]]
getCategory = forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> [Char]
takeBaseName forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> [Char]
takeDirectory forall b c a. (b -> c) -> (a -> b) -> a -> c
. Identifier -> [Char]
toFilePath
buildTagsWith :: MonadMetadata m
=> (Identifier -> m [String])
-> Pattern
-> (String -> Identifier)
-> m Tags
buildTagsWith :: forall (m :: * -> *).
MonadMetadata m =>
(Identifier -> m [[Char]])
-> Pattern -> ([Char] -> Identifier) -> m Tags
buildTagsWith Identifier -> m [[Char]]
f Pattern
pattern [Char] -> Identifier
makeId = do
[Identifier]
ids <- forall (m :: * -> *). MonadMetadata m => Pattern -> m [Identifier]
getMatches Pattern
pattern
Map [Char] [Identifier]
tagMap <- forall (t :: * -> *) (m :: * -> *) b a.
(Foldable t, Monad m) =>
(b -> a -> m b) -> b -> t a -> m b
foldM Map [Char] [Identifier]
-> Identifier -> m (Map [Char] [Identifier])
addTags forall k a. Map k a
M.empty [Identifier]
ids
let set' :: Set Identifier
set' = forall a. Ord a => [a] -> Set a
S.fromList [Identifier]
ids
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ [([Char], [Identifier])]
-> ([Char] -> Identifier) -> Dependency -> Tags
Tags (forall k a. Map k a -> [(k, a)]
M.toList Map [Char] [Identifier]
tagMap) [Char] -> Identifier
makeId (Pattern -> Set Identifier -> Dependency
PatternDependency Pattern
pattern Set Identifier
set')
where
addTags :: Map [Char] [Identifier]
-> Identifier -> m (Map [Char] [Identifier])
addTags Map [Char] [Identifier]
tagMap Identifier
id' = do
[[Char]]
tags <- Identifier -> m [[Char]]
f Identifier
id'
let tagMap' :: Map [Char] [Identifier]
tagMap' = forall k a. Ord k => [(k, a)] -> Map k a
M.fromList forall a b. (a -> b) -> a -> b
$ forall a b. [a] -> [b] -> [(a, b)]
zip [[Char]]
tags forall a b. (a -> b) -> a -> b
$ forall a. a -> [a]
repeat [Identifier
id']
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall k a. Ord k => (a -> a -> a) -> Map k a -> Map k a -> Map k a
M.unionWith forall a. [a] -> [a] -> [a]
(++) Map [Char] [Identifier]
tagMap Map [Char] [Identifier]
tagMap'
buildTags :: MonadMetadata m => Pattern -> (String -> Identifier) -> m Tags
buildTags :: forall (m :: * -> *).
MonadMetadata m =>
Pattern -> ([Char] -> Identifier) -> m Tags
buildTags = forall (m :: * -> *).
MonadMetadata m =>
(Identifier -> m [[Char]])
-> Pattern -> ([Char] -> Identifier) -> m Tags
buildTagsWith forall (m :: * -> *). MonadMetadata m => Identifier -> m [[Char]]
getTags
buildCategories :: MonadMetadata m => Pattern -> (String -> Identifier)
-> m Tags
buildCategories :: forall (m :: * -> *).
MonadMetadata m =>
Pattern -> ([Char] -> Identifier) -> m Tags
buildCategories = forall (m :: * -> *).
MonadMetadata m =>
(Identifier -> m [[Char]])
-> Pattern -> ([Char] -> Identifier) -> m Tags
buildTagsWith forall (m :: * -> *). MonadMetadata m => Identifier -> m [[Char]]
getCategory
tagsRules :: Tags -> (String -> Pattern -> Rules ()) -> Rules ()
tagsRules :: Tags -> ([Char] -> Pattern -> Rules ()) -> Rules ()
tagsRules Tags
tags [Char] -> Pattern -> Rules ()
rules =
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (Tags -> [([Char], [Identifier])]
tagsMap Tags
tags) forall a b. (a -> b) -> a -> b
$ \([Char]
tag, [Identifier]
identifiers) ->
forall a. [Dependency] -> Rules a -> Rules a
rulesExtraDependencies [Tags -> Dependency
tagsDependency Tags
tags] forall a b. (a -> b) -> a -> b
$
[Identifier] -> Rules () -> Rules ()
create [Tags -> [Char] -> Identifier
tagsMakeId Tags
tags [Char]
tag] forall a b. (a -> b) -> a -> b
$
[Char] -> Pattern -> Rules ()
rules [Char]
tag forall a b. (a -> b) -> a -> b
$ [Identifier] -> Pattern
fromList [Identifier]
identifiers
renderTags :: (String -> String -> Int -> Int -> Int -> String)
-> ([String] -> String)
-> Tags
-> Compiler String
renderTags :: ([Char] -> [Char] -> Int -> Int -> Int -> [Char])
-> ([[Char]] -> [Char]) -> Tags -> Compiler [Char]
renderTags [Char] -> [Char] -> Int -> Int -> Int -> [Char]
makeHtml [[Char]] -> [Char]
concatHtml Tags
tags = do
[(([Char], Maybe [Char]), Int)]
tags' <- forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
t a -> (a -> m b) -> m (t b)
forM (Tags -> [([Char], [Identifier])]
tagsMap Tags
tags) forall a b. (a -> b) -> a -> b
$ \([Char]
tag, [Identifier]
ids) -> do
Maybe [Char]
route' <- Identifier -> Compiler (Maybe [Char])
getRoute forall a b. (a -> b) -> a -> b
$ Tags -> [Char] -> Identifier
tagsMakeId Tags
tags [Char]
tag
forall (m :: * -> *) a. Monad m => a -> m a
return (([Char]
tag, Maybe [Char]
route'), forall (t :: * -> *) a. Foldable t => t a -> Int
length [Identifier]
ids)
let
freqs :: [Int]
freqs = forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd [(([Char], Maybe [Char]), Int)]
tags'
(Int
min', Int
max')
| forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Int]
freqs = (Int
0, Int
1)
| Bool
otherwise = (forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
minimum forall (a :: * -> * -> *) b c c'.
Arrow a =>
a b c -> a b c' -> a b (c, c')
&&& forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum) [Int]
freqs
makeHtml' :: (([Char], Maybe [Char]), Int) -> [Char]
makeHtml' (([Char]
tag, Maybe [Char]
url), Int
count) =
[Char] -> [Char] -> Int -> Int -> Int -> [Char]
makeHtml [Char]
tag ([Char] -> [Char]
toUrl forall a b. (a -> b) -> a -> b
$ forall a. a -> Maybe a -> a
fromMaybe [Char]
"/" Maybe [Char]
url) Int
count Int
min' Int
max'
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ [[Char]] -> [Char]
concatHtml forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (([Char], Maybe [Char]), Int) -> [Char]
makeHtml' [(([Char], Maybe [Char]), Int)]
tags'
renderTagCloud :: Double
-> Double
-> Tags
-> Compiler String
renderTagCloud :: Double -> Double -> Tags -> Compiler [Char]
renderTagCloud = (Double
-> Double -> [Char] -> [Char] -> Int -> Int -> Int -> [Char])
-> ([[Char]] -> [Char])
-> Double
-> Double
-> Tags
-> Compiler [Char]
renderTagCloudWith forall {a} {a} {p} {p} {a} {p}.
(ToMarkup a, ToValue a, Integral p, Integral p, Integral p,
RealFrac a) =>
a -> a -> a -> a -> p -> p -> p -> [Char]
makeLink (forall a. [a] -> [[a]] -> [a]
intercalate [Char]
" ")
where
makeLink :: a -> a -> a -> a -> p -> p -> p -> [Char]
makeLink a
minSize a
maxSize a
tag a
url p
count p
min' p
max' =
let diff :: a
diff = a
1 forall a. Num a => a -> a -> a
+ forall a b. (Integral a, Num b) => a -> b
fromIntegral p
max' forall a. Num a => a -> a -> a
- forall a b. (Integral a, Num b) => a -> b
fromIntegral p
min'
relative :: a
relative = (forall a b. (Integral a, Num b) => a -> b
fromIntegral p
count forall a. Num a => a -> a -> a
- forall a b. (Integral a, Num b) => a -> b
fromIntegral p
min') forall a. Fractional a => a -> a -> a
/ a
diff
size :: Int
size = forall a b. (RealFrac a, Integral b) => a -> b
floor forall a b. (a -> b) -> a -> b
$ a
minSize forall a. Num a => a -> a -> a
+ a
relative forall a. Num a => a -> a -> a
* (a
maxSize forall a. Num a => a -> a -> a
- a
minSize) :: Int
in Html -> [Char]
renderHtml forall a b. (a -> b) -> a -> b
$
Html -> Html
H.a forall h. Attributable h => h -> Attribute -> h
! AttributeValue -> Attribute
A.style (forall a. ToValue a => a -> AttributeValue
toValue forall a b. (a -> b) -> a -> b
$ [Char]
"font-size: " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> [Char]
show Int
size forall a. [a] -> [a] -> [a]
++ [Char]
"%")
forall h. Attributable h => h -> Attribute -> h
! AttributeValue -> Attribute
A.href (forall a. ToValue a => a -> AttributeValue
toValue a
url)
forall a b. (a -> b) -> a -> b
$ forall a. ToMarkup a => a -> Html
toHtml a
tag
renderTagCloudWith :: (Double -> Double ->
String -> String -> Int -> Int -> Int -> String)
-> ([String] -> String)
-> Double
-> Double
-> Tags
-> Compiler String
renderTagCloudWith :: (Double
-> Double -> [Char] -> [Char] -> Int -> Int -> Int -> [Char])
-> ([[Char]] -> [Char])
-> Double
-> Double
-> Tags
-> Compiler [Char]
renderTagCloudWith Double -> Double -> [Char] -> [Char] -> Int -> Int -> Int -> [Char]
makeLink [[Char]] -> [Char]
cat Double
minSize Double
maxSize =
([Char] -> [Char] -> Int -> Int -> Int -> [Char])
-> ([[Char]] -> [Char]) -> Tags -> Compiler [Char]
renderTags (Double -> Double -> [Char] -> [Char] -> Int -> Int -> Int -> [Char]
makeLink Double
minSize Double
maxSize) [[Char]] -> [Char]
cat
tagCloudField :: String
-> Double
-> Double
-> Tags
-> Context a
tagCloudField :: forall a. [Char] -> Double -> Double -> Tags -> Context a
tagCloudField [Char]
key Double
minSize Double
maxSize Tags
tags =
forall a. [Char] -> (Item a -> Compiler [Char]) -> Context a
field [Char]
key forall a b. (a -> b) -> a -> b
$ \Item a
_ -> Double -> Double -> Tags -> Compiler [Char]
renderTagCloud Double
minSize Double
maxSize Tags
tags
tagCloudFieldWith :: String
-> (Double -> Double ->
String -> String -> Int -> Int -> Int -> String)
-> ([String] -> String)
-> Double
-> Double
-> Tags
-> Context a
tagCloudFieldWith :: forall a.
[Char]
-> (Double
-> Double -> [Char] -> [Char] -> Int -> Int -> Int -> [Char])
-> ([[Char]] -> [Char])
-> Double
-> Double
-> Tags
-> Context a
tagCloudFieldWith [Char]
key Double -> Double -> [Char] -> [Char] -> Int -> Int -> Int -> [Char]
makeLink [[Char]] -> [Char]
cat Double
minSize Double
maxSize Tags
tags =
forall a. [Char] -> (Item a -> Compiler [Char]) -> Context a
field [Char]
key forall a b. (a -> b) -> a -> b
$ \Item a
_ -> (Double
-> Double -> [Char] -> [Char] -> Int -> Int -> Int -> [Char])
-> ([[Char]] -> [Char])
-> Double
-> Double
-> Tags
-> Compiler [Char]
renderTagCloudWith Double -> Double -> [Char] -> [Char] -> Int -> Int -> Int -> [Char]
makeLink [[Char]] -> [Char]
cat Double
minSize Double
maxSize Tags
tags
renderTagList :: Tags -> Compiler (String)
renderTagList :: Tags -> Compiler [Char]
renderTagList = ([Char] -> [Char] -> Int -> Int -> Int -> [Char])
-> ([[Char]] -> [Char]) -> Tags -> Compiler [Char]
renderTags forall {a} {a} {p} {p}.
(ToValue a, Show a) =>
[Char] -> a -> a -> p -> p -> [Char]
makeLink (forall a. [a] -> [[a]] -> [a]
intercalate [Char]
", ")
where
makeLink :: [Char] -> a -> a -> p -> p -> [Char]
makeLink [Char]
tag a
url a
count p
_ p
_ = Html -> [Char]
renderHtml forall a b. (a -> b) -> a -> b
$
Html -> Html
H.a forall h. Attributable h => h -> Attribute -> h
! AttributeValue -> Attribute
A.href (forall a. ToValue a => a -> AttributeValue
toValue a
url) forall h. Attributable h => h -> Attribute -> h
! AttributeValue -> Attribute
A.rel AttributeValue
"tag" forall a b. (a -> b) -> a -> b
$ forall a. ToMarkup a => a -> Html
toHtml ([Char]
tag forall a. [a] -> [a] -> [a]
++ [Char]
" (" forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> [Char]
show a
count forall a. [a] -> [a] -> [a]
++ [Char]
")")
tagsFieldWith :: (Identifier -> Compiler [String])
-> (String -> (Maybe FilePath) -> Maybe H.Html)
-> ([H.Html] -> H.Html)
-> String
-> Tags
-> Context a
tagsFieldWith :: forall a.
(Identifier -> Compiler [[Char]])
-> ([Char] -> Maybe [Char] -> Maybe Html)
-> ([Html] -> Html)
-> [Char]
-> Tags
-> Context a
tagsFieldWith Identifier -> Compiler [[Char]]
getTags' [Char] -> Maybe [Char] -> Maybe Html
renderLink [Html] -> Html
cat [Char]
key Tags
tags = forall a. [Char] -> (Item a -> Compiler [Char]) -> Context a
field [Char]
key forall a b. (a -> b) -> a -> b
$ \Item a
item -> do
[[Char]]
tags' <- Identifier -> Compiler [[Char]]
getTags' forall a b. (a -> b) -> a -> b
$ forall a. Item a -> Identifier
itemIdentifier Item a
item
[Maybe Html]
links <- forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
t a -> (a -> m b) -> m (t b)
forM [[Char]]
tags' forall a b. (a -> b) -> a -> b
$ \[Char]
tag -> do
Maybe [Char]
route' <- Identifier -> Compiler (Maybe [Char])
getRoute forall a b. (a -> b) -> a -> b
$ Tags -> [Char] -> Identifier
tagsMakeId Tags
tags [Char]
tag
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ [Char] -> Maybe [Char] -> Maybe Html
renderLink [Char]
tag Maybe [Char]
route'
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Html -> [Char]
renderHtml forall a b. (a -> b) -> a -> b
$ [Html] -> Html
cat forall a b. (a -> b) -> a -> b
$ forall a. [Maybe a] -> [a]
catMaybes forall a b. (a -> b) -> a -> b
$ [Maybe Html]
links
tagsField :: String
-> Tags
-> Context a
tagsField :: forall a. [Char] -> Tags -> Context a
tagsField =
forall a.
(Identifier -> Compiler [[Char]])
-> ([Char] -> Maybe [Char] -> Maybe Html)
-> ([Html] -> Html)
-> [Char]
-> Tags
-> Context a
tagsFieldWith forall (m :: * -> *). MonadMetadata m => Identifier -> m [[Char]]
getTags [Char] -> Maybe [Char] -> Maybe Html
simpleRenderLink (forall a. Monoid a => [a] -> a
mconcat forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. a -> [a] -> [a]
intersperse Html
", ")
categoryField :: String
-> Tags
-> Context a
categoryField :: forall a. [Char] -> Tags -> Context a
categoryField =
forall a.
(Identifier -> Compiler [[Char]])
-> ([Char] -> Maybe [Char] -> Maybe Html)
-> ([Html] -> Html)
-> [Char]
-> Tags
-> Context a
tagsFieldWith forall (m :: * -> *). MonadMetadata m => Identifier -> m [[Char]]
getCategory [Char] -> Maybe [Char] -> Maybe Html
simpleRenderLink (forall a. Monoid a => [a] -> a
mconcat forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. a -> [a] -> [a]
intersperse Html
", ")
simpleRenderLink :: String -> (Maybe FilePath) -> Maybe H.Html
simpleRenderLink :: [Char] -> Maybe [Char] -> Maybe Html
simpleRenderLink [Char]
_ Maybe [Char]
Nothing = forall a. Maybe a
Nothing
simpleRenderLink [Char]
tag (Just [Char]
filePath) = forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$
Html -> Html
H.a forall h. Attributable h => h -> Attribute -> h
! AttributeValue -> Attribute
A.title ([Char] -> AttributeValue
H.stringValue ([Char]
"All pages tagged '"forall a. [a] -> [a] -> [a]
++[Char]
tagforall a. [a] -> [a] -> [a]
++[Char]
"'."))
forall h. Attributable h => h -> Attribute -> h
! AttributeValue -> Attribute
A.href (forall a. ToValue a => a -> AttributeValue
toValue forall a b. (a -> b) -> a -> b
$ [Char] -> [Char]
toUrl [Char]
filePath)
forall h. Attributable h => h -> Attribute -> h
! (AttributeValue -> Attribute
A.rel AttributeValue
"tag")
forall a b. (a -> b) -> a -> b
$ forall a. ToMarkup a => a -> Html
toHtml [Char]
tag
sortTagsBy :: ((String, [Identifier]) -> (String, [Identifier]) -> Ordering)
-> Tags -> Tags
sortTagsBy :: (([Char], [Identifier]) -> ([Char], [Identifier]) -> Ordering)
-> Tags -> Tags
sortTagsBy ([Char], [Identifier]) -> ([Char], [Identifier]) -> Ordering
f Tags
t = Tags
t {tagsMap :: [([Char], [Identifier])]
tagsMap = forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy ([Char], [Identifier]) -> ([Char], [Identifier]) -> Ordering
f (Tags -> [([Char], [Identifier])]
tagsMap Tags
t)}
caseInsensitiveTags :: (String, [Identifier]) -> (String, [Identifier])
-> Ordering
caseInsensitiveTags :: ([Char], [Identifier]) -> ([Char], [Identifier]) -> Ordering
caseInsensitiveTags = forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst