module Hakyll.FileStore.Context
    ( fsGetItemUTC
    , fsGetItemUTC'
    , fsDateField
    , fsDateFieldWith
    , fsGetItemModificationTime
    , fsGetItemModificationTime'
    , fsModificationTimeField
    , fsModificationTimeFieldWith
    , fsGetRevisions
    , fsGetAuthors
    , fsGetAuthorNames
    , fsAuthorNamesField
    , fsAuthorNamesFieldWith
    , fsGetAuthorEmails
    , fsAuthorEmailsField
    , fsAuthorEmailsFieldWith
    ) where
--------------------------------------------------------------------------------

import           Control.Monad               (liftM)
import           Data.Time.Clock             (UTCTime)
import           Data.FileStore              (Author,
                                              FileStore,
                                              Revision,
                                              TimeRange (TimeRange),
                                              authorEmail,
                                              authorName,
                                              history,
                                              revAuthor,
                                              revDateTime)
import           Data.List                   (intercalate, nub)
import           Data.Maybe                  (listToMaybe)
import           Data.Time.Format            (formatTime)
import           Data.Time.Locale.Compat     (TimeLocale, defaultTimeLocale)
import           Hakyll.Core.Compiler        (Compiler, unsafeCompiler)
import           Hakyll.Core.Identifier      (Identifier, toFilePath)
import           Hakyll.Core.Item            (itemIdentifier)
import           Hakyll.Web.Template.Context (Context,
                                              field,
                                              getItemModificationTime,
                                              getItemUTC)

--------------------------------------------------------------------------------

fsGetWithFallback :: Monad m
                  => (FileStore -> Identifier -> m (Maybe a))
                  -> (Identifier -> m a)
                  -> FileStore
                  -> Identifier
                  -> m a
fsGetWithFallback :: (FileStore -> Identifier -> m (Maybe a))
-> (Identifier -> m a) -> FileStore -> Identifier -> m a
fsGetWithFallback FileStore -> Identifier -> m (Maybe a)
fsF Identifier -> m a
f FileStore
fs Identifier
i = do
    Maybe a
maybeIt <- FileStore -> Identifier -> m (Maybe a)
fsF FileStore
fs Identifier
i
    m a -> (a -> m a) -> Maybe a -> m a
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Identifier -> m a
f Identifier
i) a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe a
maybeIt

extractFromRevisions :: ([Revision] -> a)
                     -> FileStore
                     -> Identifier
                     -> Compiler a
extractFromRevisions :: ([Revision] -> a) -> FileStore -> Identifier -> Compiler a
extractFromRevisions [Revision] -> a
f FileStore
fs Identifier
i = IO a -> Compiler a
forall a. IO a -> Compiler a
unsafeCompiler (IO a -> Compiler a) -> IO a -> Compiler a
forall a b. (a -> b) -> a -> b
$ do
    let path :: FilePath
path = Identifier -> FilePath
toFilePath Identifier
i
    [Revision]
revisions <- FileStore -> [FilePath] -> TimeRange -> Maybe Int -> IO [Revision]
history FileStore
fs [FilePath
path] (Maybe UTCTime -> Maybe UTCTime -> TimeRange
TimeRange Maybe UTCTime
forall a. Maybe a
Nothing Maybe UTCTime
forall a. Maybe a
Nothing) Maybe Int
forall a. Maybe a
Nothing
    a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (a -> IO a) -> a -> IO a
forall a b. (a -> b) -> a -> b
$ [Revision] -> a
f [Revision]
revisions

generateFsField :: (FileStore -> Identifier -> Compiler String)
                -> FileStore
                -> String
                -> Context a
generateFsField :: (FileStore -> Identifier -> Compiler FilePath)
-> FileStore -> FilePath -> Context a
generateFsField FileStore -> Identifier -> Compiler FilePath
fsF FileStore
fs FilePath
key =
    FilePath -> (Item a -> Compiler FilePath) -> Context a
forall a. FilePath -> (Item a -> Compiler FilePath) -> Context a
field FilePath
key (FileStore -> Identifier -> Compiler FilePath
fsF FileStore
fs (Identifier -> Compiler FilePath)
-> (Item a -> Identifier) -> Item a -> Compiler FilePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Item a -> Identifier
forall a. Item a -> Identifier
itemIdentifier)

fsIntercalateToContext :: (FileStore -> Identifier -> Compiler [String])
                       -> FileStore
                       -> String
                       -> String
                       -> Context a
fsIntercalateToContext :: (FileStore -> Identifier -> Compiler [FilePath])
-> FileStore -> FilePath -> FilePath -> Context a
fsIntercalateToContext FileStore -> Identifier -> Compiler [FilePath]
fsF FileStore
fs FilePath
delimeter =
    (FileStore -> Identifier -> Compiler FilePath)
-> FileStore -> FilePath -> Context a
forall a.
(FileStore -> Identifier -> Compiler FilePath)
-> FileStore -> FilePath -> Context a
generateFsField (\ FileStore
fs' -> ([FilePath] -> FilePath)
-> Compiler [FilePath] -> Compiler FilePath
forall (m :: * -> *) a1 r. Monad m => (a1 -> r) -> m a1 -> m r
liftM (FilePath -> [FilePath] -> FilePath
forall a. [a] -> [[a]] -> [a]
intercalate FilePath
delimeter) (Compiler [FilePath] -> Compiler FilePath)
-> (Identifier -> Compiler [FilePath])
-> Identifier
-> Compiler FilePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FileStore -> Identifier -> Compiler [FilePath]
fsF FileStore
fs') FileStore
fs

generateFsTimeFieldWith :: (FileStore -> Identifier -> Compiler UTCTime)
                        -> FileStore
                        -> TimeLocale
                        -> String
                        -> String
                        -> Context a
generateFsTimeFieldWith :: (FileStore -> Identifier -> Compiler UTCTime)
-> FileStore -> TimeLocale -> FilePath -> FilePath -> Context a
generateFsTimeFieldWith FileStore -> Identifier -> Compiler UTCTime
fsF FileStore
fs TimeLocale
locale FilePath
key FilePath
fmt = FilePath -> (Item a -> Compiler FilePath) -> Context a
forall a. FilePath -> (Item a -> Compiler FilePath) -> Context a
field FilePath
key ((Item a -> Compiler FilePath) -> Context a)
-> (Item a -> Compiler FilePath) -> Context a
forall a b. (a -> b) -> a -> b
$ \ Item a
i -> do
    UTCTime
time <- FileStore -> Identifier -> Compiler UTCTime
fsF FileStore
fs (Identifier -> Compiler UTCTime) -> Identifier -> Compiler UTCTime
forall a b. (a -> b) -> a -> b
$ Item a -> Identifier
forall a. Item a -> Identifier
itemIdentifier Item a
i
    FilePath -> Compiler FilePath
forall (m :: * -> *) a. Monad m => a -> m a
return (FilePath -> Compiler FilePath) -> FilePath -> Compiler FilePath
forall a b. (a -> b) -> a -> b
$ TimeLocale -> FilePath -> UTCTime -> FilePath
forall t. FormatTime t => TimeLocale -> FilePath -> t -> FilePath
formatTime TimeLocale
locale FilePath
fmt UTCTime
time

--------------------------------------------------------------------------------

fsGetItemUTC :: FileStore        -- ^ 'FileStore' to work in
             -> TimeLocale       -- ^ Output time locale
             -> Identifier       -- ^ Input page
             -> Compiler UTCTime -- ^ Returns the time of the first revision if
                                 -- revisions are available, date of the
                                 -- identifier otherwise
fsGetItemUTC :: FileStore -> TimeLocale -> Identifier -> Compiler UTCTime
fsGetItemUTC FileStore
fs TimeLocale
locale =
    (FileStore -> Identifier -> Compiler (Maybe UTCTime))
-> (Identifier -> Compiler UTCTime)
-> FileStore
-> Identifier
-> Compiler UTCTime
forall (m :: * -> *) a.
Monad m =>
(FileStore -> Identifier -> m (Maybe a))
-> (Identifier -> m a) -> FileStore -> Identifier -> m a
fsGetWithFallback FileStore -> Identifier -> Compiler (Maybe UTCTime)
fsGetItemUTC' (TimeLocale -> Identifier -> Compiler UTCTime
forall (m :: * -> *).
(MonadMetadata m, MonadFail m) =>
TimeLocale -> Identifier -> m UTCTime
getItemUTC TimeLocale
locale) FileStore
fs

fsGetItemUTC' :: FileStore                -- ^ 'FileStore' to work  in
              -> Identifier               -- ^ Input page
              -> Compiler (Maybe UTCTime) -- ^ Returns 'Just' the time of the
                                          -- first revision if the file has
                                          -- some revisions, 'Nothing'
                                          -- otherwise
fsGetItemUTC' :: FileStore -> Identifier -> Compiler (Maybe UTCTime)
fsGetItemUTC' =
    ([Revision] -> Maybe UTCTime)
-> FileStore -> Identifier -> Compiler (Maybe UTCTime)
forall a.
([Revision] -> a) -> FileStore -> Identifier -> Compiler a
extractFromRevisions ([UTCTime] -> Maybe UTCTime
forall a. [a] -> Maybe a
listToMaybe ([UTCTime] -> Maybe UTCTime)
-> ([Revision] -> [UTCTime]) -> [Revision] -> Maybe UTCTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Revision -> UTCTime) -> [Revision] -> [UTCTime]
forall a b. (a -> b) -> [a] -> [b]
map Revision -> UTCTime
revDateTime ([Revision] -> [UTCTime])
-> ([Revision] -> [Revision]) -> [Revision] -> [UTCTime]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Revision] -> [Revision]
forall a. [a] -> [a]
reverse)

fsDateField :: FileStore -- ^ 'FileStore' to work in
            -> String    -- ^ Destination key
            -> String    -- ^ Format to use on the date
            -> Context a -- ^ Resulting context
fsDateField :: FileStore -> FilePath -> FilePath -> Context a
fsDateField FileStore
fs = FileStore -> TimeLocale -> FilePath -> FilePath -> Context a
forall a.
FileStore -> TimeLocale -> FilePath -> FilePath -> Context a
fsDateFieldWith FileStore
fs TimeLocale
defaultTimeLocale

fsDateFieldWith :: FileStore  -- ^ 'FileStore' to work in
                -> TimeLocale -- ^ Output time locale
                -> String     -- ^ Destination key
                -> String     -- ^ Format to use on the date
                -> Context a  -- ^ Resulting context
fsDateFieldWith :: FileStore -> TimeLocale -> FilePath -> FilePath -> Context a
fsDateFieldWith FileStore
fs TimeLocale
locale =
    (FileStore -> Identifier -> Compiler UTCTime)
-> FileStore -> TimeLocale -> FilePath -> FilePath -> Context a
forall a.
(FileStore -> Identifier -> Compiler UTCTime)
-> FileStore -> TimeLocale -> FilePath -> FilePath -> Context a
generateFsTimeFieldWith (FileStore -> TimeLocale -> Identifier -> Compiler UTCTime
`fsGetItemUTC` TimeLocale
locale) FileStore
fs TimeLocale
locale

fsGetItemModificationTime :: FileStore        -- ^ 'FileStore' to work in
                          -> Identifier       -- ^ Input page
                          -> Compiler UTCTime -- ^ Returns the time of the last
                                              -- revision if revisions are
                                              -- available, modification time
                                              -- of the file otherwise.
fsGetItemModificationTime :: FileStore -> Identifier -> Compiler UTCTime
fsGetItemModificationTime =
    (FileStore -> Identifier -> Compiler (Maybe UTCTime))
-> (Identifier -> Compiler UTCTime)
-> FileStore
-> Identifier
-> Compiler UTCTime
forall (m :: * -> *) a.
Monad m =>
(FileStore -> Identifier -> m (Maybe a))
-> (Identifier -> m a) -> FileStore -> Identifier -> m a
fsGetWithFallback FileStore -> Identifier -> Compiler (Maybe UTCTime)
fsGetItemModificationTime' Identifier -> Compiler UTCTime
getItemModificationTime

fsGetItemModificationTime' :: FileStore                -- ^ 'FileStore' to
                                                       -- work in
                           -> Identifier               -- ^ Identifier in
                                                       -- question
                           -> Compiler (Maybe UTCTime) -- ^ Returns 'Just' the
                                                       -- time of last revision
                                                       -- if the file has some
                                                       -- revisions, 'Nothing'
                                                       -- otherwise.
fsGetItemModificationTime' :: FileStore -> Identifier -> Compiler (Maybe UTCTime)
fsGetItemModificationTime' =
    ([Revision] -> Maybe UTCTime)
-> FileStore -> Identifier -> Compiler (Maybe UTCTime)
forall a.
([Revision] -> a) -> FileStore -> Identifier -> Compiler a
extractFromRevisions ([UTCTime] -> Maybe UTCTime
forall a. [a] -> Maybe a
listToMaybe ([UTCTime] -> Maybe UTCTime)
-> ([Revision] -> [UTCTime]) -> [Revision] -> Maybe UTCTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Revision -> UTCTime) -> [Revision] -> [UTCTime]
forall a b. (a -> b) -> [a] -> [b]
map Revision -> UTCTime
revDateTime)

fsModificationTimeField :: FileStore -- ^ 'FileStore' to work in
                        -> String    -- ^ Destination key
                        -> String    -- ^ Format to use on the date
                        -> Context a -- ^ Resulting context
fsModificationTimeField :: FileStore -> FilePath -> FilePath -> Context a
fsModificationTimeField FileStore
fs = FileStore -> TimeLocale -> FilePath -> FilePath -> Context a
forall a.
FileStore -> TimeLocale -> FilePath -> FilePath -> Context a
fsModificationTimeFieldWith FileStore
fs TimeLocale
defaultTimeLocale

fsModificationTimeFieldWith :: FileStore  -- ^ 'FileStore' to work in
                            -> TimeLocale -- ^ Output time locale
                            -> String     -- ^ Destination key
                            -> String     -- ^ Format to use on the date
                            -> Context a  -- ^ Resulting context
fsModificationTimeFieldWith :: FileStore -> TimeLocale -> FilePath -> FilePath -> Context a
fsModificationTimeFieldWith = (FileStore -> Identifier -> Compiler UTCTime)
-> FileStore -> TimeLocale -> FilePath -> FilePath -> Context a
forall a.
(FileStore -> Identifier -> Compiler UTCTime)
-> FileStore -> TimeLocale -> FilePath -> FilePath -> Context a
generateFsTimeFieldWith FileStore -> Identifier -> Compiler UTCTime
fsGetItemModificationTime

--------------------------------------------------------------------------------

-- TODO: Add "getters" for other fields of "Revision"?

fsGetRevisions :: FileStore           -- ^ 'FileStore' to work  in
               -> Identifier          -- ^ Input Page
               -> Compiler [Revision] -- ^ Returns the revisions of the page
                                      -- in reverse chronological order
fsGetRevisions :: FileStore -> Identifier -> Compiler [Revision]
fsGetRevisions = ([Revision] -> [Revision])
-> FileStore -> Identifier -> Compiler [Revision]
forall a.
([Revision] -> a) -> FileStore -> Identifier -> Compiler a
extractFromRevisions [Revision] -> [Revision]
forall a. a -> a
id

fsGetAuthors :: FileStore         -- ^ 'FileStore' to work  in
             -> Identifier        -- ^ Input Page
             -> Compiler [Author] -- ^ Returns the authors of the page in
                                  -- chronological order of occurence in the
                                  -- revisions
fsGetAuthors :: FileStore -> Identifier -> Compiler [Author]
fsGetAuthors FileStore
fs Identifier
i = ([Author] -> [Author]
forall a. Eq a => [a] -> [a]
nub ([Author] -> [Author])
-> ([Revision] -> [Author]) -> [Revision] -> [Author]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Revision -> Author) -> [Revision] -> [Author]
forall a b. (a -> b) -> [a] -> [b]
map Revision -> Author
revAuthor ([Revision] -> [Author])
-> ([Revision] -> [Revision]) -> [Revision] -> [Author]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Revision] -> [Revision]
forall a. [a] -> [a]
reverse) ([Revision] -> [Author])
-> Compiler [Revision] -> Compiler [Author]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> FileStore -> Identifier -> Compiler [Revision]
fsGetRevisions FileStore
fs Identifier
i

fsGetAuthorNames :: FileStore         -- ^ 'FileStore' to work  in
                 -> Identifier        -- ^ Input Page
                 -> Compiler [String] -- ^ Returns the names of the authors of
                                      -- the page in chronological order of
                                      -- occurence in the revisions
fsGetAuthorNames :: FileStore -> Identifier -> Compiler [FilePath]
fsGetAuthorNames FileStore
fs Identifier
i = (Author -> FilePath) -> [Author] -> [FilePath]
forall a b. (a -> b) -> [a] -> [b]
map Author -> FilePath
authorName ([Author] -> [FilePath])
-> Compiler [Author] -> Compiler [FilePath]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> FileStore -> Identifier -> Compiler [Author]
fsGetAuthors FileStore
fs Identifier
i

fsAuthorNamesField :: FileStore -- ^ 'FileStore' to work  in
                   -> String    -- ^ Destination key
                   -> Context a -- ^ Resulting context
fsAuthorNamesField :: FileStore -> FilePath -> Context a
fsAuthorNamesField = (FileStore -> FilePath -> FilePath -> Context a)
-> FilePath -> FileStore -> FilePath -> Context a
forall a b c. (a -> b -> c) -> b -> a -> c
flip FileStore -> FilePath -> FilePath -> Context a
forall a. FileStore -> FilePath -> FilePath -> Context a
fsAuthorNamesFieldWith FilePath
", "
-- ^ @fsAuthorNamesField = flip fsAuthorNamesFieldWith ", "@

fsAuthorNamesFieldWith :: FileStore -- ^ 'FileStore' to work  in
                       -> String    -- ^ Intercalation delimeter
                       -> String    -- ^ Destination key
                       -> Context a -- ^ Resulting context
fsAuthorNamesFieldWith :: FileStore -> FilePath -> FilePath -> Context a
fsAuthorNamesFieldWith = (FileStore -> Identifier -> Compiler [FilePath])
-> FileStore -> FilePath -> FilePath -> Context a
forall a.
(FileStore -> Identifier -> Compiler [FilePath])
-> FileStore -> FilePath -> FilePath -> Context a
fsIntercalateToContext FileStore -> Identifier -> Compiler [FilePath]
fsGetAuthorNames
-- ^ Field consisting of the names of the authors separated by the
-- given delimeter.

fsGetAuthorEmails :: FileStore         -- ^ 'FileStore' to work  in
                  -> Identifier        -- ^ Input Page
                  -> Compiler [String] -- ^ Returns the email addresses of the
                                       -- authors of the page in chronological
                                       -- chronological order of occurence
                                       -- in the revisions
fsGetAuthorEmails :: FileStore -> Identifier -> Compiler [FilePath]
fsGetAuthorEmails FileStore
fs Identifier
i = (Author -> FilePath) -> [Author] -> [FilePath]
forall a b. (a -> b) -> [a] -> [b]
map Author -> FilePath
authorEmail ([Author] -> [FilePath])
-> Compiler [Author] -> Compiler [FilePath]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> FileStore -> Identifier -> Compiler [Author]
fsGetAuthors FileStore
fs Identifier
i

fsAuthorEmailsField :: FileStore -- ^ 'FileStore' to work  in
                    -> String    -- ^ Destination key
                    -> Context a -- ^ Resulting context
fsAuthorEmailsField :: FileStore -> FilePath -> Context a
fsAuthorEmailsField = (FileStore -> FilePath -> FilePath -> Context a)
-> FilePath -> FileStore -> FilePath -> Context a
forall a b c. (a -> b -> c) -> b -> a -> c
flip FileStore -> FilePath -> FilePath -> Context a
forall a. FileStore -> FilePath -> FilePath -> Context a
fsAuthorEmailsFieldWith FilePath
", "
-- ^ @fsAuthorEmailsField = flip fsAuthorEmailsFieldWith ", "@

fsAuthorEmailsFieldWith :: FileStore -- ^ 'FileStore' to work  in
                       -> String    -- ^ Intercalation delimeter
                       -> String    -- ^ Destination key
                       -> Context a -- ^ Resulting context
fsAuthorEmailsFieldWith :: FileStore -> FilePath -> FilePath -> Context a
fsAuthorEmailsFieldWith = (FileStore -> Identifier -> Compiler [FilePath])
-> FileStore -> FilePath -> FilePath -> Context a
forall a.
(FileStore -> Identifier -> Compiler [FilePath])
-> FileStore -> FilePath -> FilePath -> Context a
fsIntercalateToContext FileStore -> Identifier -> Compiler [FilePath]
fsGetAuthorEmails
-- ^ Field consisting of the email addresses of the authors separated
-- by the given delimeter.

--------------------------------------------------------------------------------