{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
-- | __Syndication__ module for RSS.
-- Cf specification at .
module Text.RSS.Extensions.Syndication
( -- * Types
SyndicationModule(..)
, RssChannelExtension(SyndicationChannel)
, RssItemExtension(SyndicationItem)
, SyndicationInfo(..)
, mkSyndicationInfo
, SyndicationPeriod(..)
, asSyndicationPeriod
-- * Parsers
, syndicationPeriod
, syndicationFrequency
, syndicationBase
-- * Misc
, namespacePrefix
, namespaceURI
) where
-- {{{ Imports
import Text.RSS.Extensions
import Text.RSS.Types
import Conduit hiding (throwM)
import Control.Applicative
import Control.Exception.Safe as Exception
import Control.Monad
import Control.Monad.Fix
import Data.Maybe
import Data.Singletons
import Data.Text
import Data.Time.Clock
import Data.Time.LocalTime
import Data.Time.RFC2822
import Data.Time.RFC3339
import Data.Time.RFC822
import Data.XML.Types
import GHC.Generics
import Lens.Simple
import Text.Read
import qualified Text.XML.DublinCore.Conduit.Parse as DC
import Text.XML.Stream.Parse
import URI.ByteString
-- }}}
-- {{{ Utils
asDate :: MonadThrow m => Text -> m UTCTime
asDate text = maybe (throw $ InvalidTime text) (return . zonedTimeToUTC) $
parseTimeRFC3339 text <|> parseTimeRFC2822 text <|> parseTimeRFC822 text
asInt :: MonadThrow m => Text -> m Int
asInt t = maybe (throwM $ InvalidInt t) return . readMaybe $ unpack t
projectC :: Monad m => Fold a a' b b' -> Conduit a m b
projectC prism = fix $ \recurse -> do
item <- await
case (item, item ^? (_Just . prism)) of
(_, Just a) -> yield a >> recurse
(Just _, _) -> recurse
_ -> return ()
-- }}}
newtype SyndicationException = InvalidSyndicationPeriod Text deriving(Eq, Generic, Ord, Show)
instance Exception SyndicationException where
displayException (InvalidSyndicationPeriod t) = "Invalid syndication period: " ++ unpack t
-- | XML prefix is @sy@.
namespacePrefix :: Text
namespacePrefix = "sy"
-- | XML namespace is .
namespaceURI :: URIRef Absolute
namespaceURI = uri where Right uri = parseURI laxURIParserOptions "http://purl.org/rss/1.0/modules/syndication/"
syndicationName :: Text -> Name
syndicationName string = Name string (Just "http://purl.org/rss/1.0/modules/syndication/") (Just namespacePrefix)
syndicationTag :: MonadThrow m => Text -> ConduitM Event o m a -> ConduitM Event o m (Maybe a)
syndicationTag name = tagIgnoreAttrs (matching (== syndicationName name))
data SyndicationPeriod = Hourly | Daily | Weekly | Monthly | Yearly deriving(Eq, Generic, Ord, Show)
asSyndicationPeriod :: MonadThrow m => Text -> m SyndicationPeriod
asSyndicationPeriod "hourly" = pure Hourly
asSyndicationPeriod "daily" = pure Daily
asSyndicationPeriod "weekly" = pure Weekly
asSyndicationPeriod "monthly" = pure Monthly
asSyndicationPeriod "yearly" = pure Yearly
asSyndicationPeriod t = throw $ InvalidSyndicationPeriod t
-- | __Syndication__ extension model.
data SyndicationInfo = SyndicationInfo
{ updatePeriod :: Maybe SyndicationPeriod
, updateFrequency :: Maybe Int
, updateBase :: Maybe UTCTime
} deriving(Eq, Generic, Ord, Show)
-- | Construct an empty 'SyndicationInfo'.
mkSyndicationInfo :: SyndicationInfo
mkSyndicationInfo = SyndicationInfo mzero mzero mzero
data ElementPiece = ElementPeriod SyndicationPeriod | ElementFrequency Int | ElementBase UTCTime
makeTraversals ''ElementPiece
-- | Parse __Syndication__ elements.
syndicationInfo :: MonadThrow m => ConduitM Event o m SyndicationInfo
syndicationInfo = manyYield' (choose piece) =$= parser where
parser = getZipConduit $ SyndicationInfo
<$> ZipConduit (projectC _ElementPeriod =$= headC)
<*> ZipConduit (projectC _ElementFrequency =$= headC)
<*> ZipConduit (projectC _ElementBase =$= headC)
piece = [ fmap ElementPeriod <$> syndicationPeriod
, fmap ElementFrequency <$> syndicationFrequency
, fmap ElementBase <$> syndicationBase
]
-- | Parse a @\@ element.
syndicationPeriod :: MonadThrow m => ConduitM Event o m (Maybe SyndicationPeriod)
syndicationPeriod = syndicationTag "updatePeriod" (content >>= asSyndicationPeriod)
-- | Parse a @\@ element.
syndicationFrequency :: MonadThrow m => ConduitM Event o m (Maybe Int)
syndicationFrequency = syndicationTag "updateFrequency" (content >>= asInt)
-- | Parse a @\@ element.
syndicationBase :: MonadThrow m => ConduitM Event o m (Maybe UTCTime)
syndicationBase = syndicationTag "updateBase" (content >>= asDate)
-- | __Syndication__ tag type.
data SyndicationModule :: *
data instance Sing SyndicationModule = SSyndicationModule
instance SingI SyndicationModule where sing = SSyndicationModule
instance ParseRssExtension SyndicationModule where
parseRssChannelExtension = SyndicationChannel <$> syndicationInfo
parseRssItemExtension = pure SyndicationItem
data instance RssChannelExtension SyndicationModule = SyndicationChannel { channelSyndicationInfo :: SyndicationInfo}
deriving(Eq, Generic, Ord, Show)
data instance RssItemExtension SyndicationModule = SyndicationItem deriving(Eq, Generic, Ord, Show)