packages feed

rss-conduit 0.4.1.0 → 0.4.2.0

raw patch · 10 files changed

+259/−66 lines, 10 filesdep +blaze-builderdep ~atom-conduitPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: blaze-builder

Dependency ranges changed: atom-conduit

API changes (from Hackage documentation)

+ Text.RSS.Extensions: class RenderRssExtension e
+ Text.RSS.Extensions: renderRssChannelExtension :: (RenderRssExtension e, Monad m) => RssChannelExtension e -> Source m Event
+ Text.RSS.Extensions: renderRssChannelExtensions :: Monad m => RenderRssExtensions e => RssChannelExtensions e -> Source m Event
+ Text.RSS.Extensions: renderRssItemExtension :: (RenderRssExtension e, Monad m) => RssItemExtension e -> Source m Event
+ Text.RSS.Extensions: renderRssItemExtensions :: Monad m => RenderRssExtensions e => RssItemExtensions e -> Source m Event
+ Text.RSS.Extensions: type RenderRssExtensions (e :: [*]) = AllConstrained RenderRssExtension e
+ Text.RSS.Extensions.Atom: instance Text.RSS.Extensions.RenderRssExtension Text.RSS.Extensions.Atom.AtomModule
+ Text.RSS.Extensions.Content: instance Text.RSS.Extensions.RenderRssExtension Text.RSS.Extensions.Content.ContentModule
+ Text.RSS.Extensions.Content: renderContentEncoded :: Monad m => Text -> Source m Event
+ Text.RSS.Extensions.DublinCore: instance Text.RSS.Extensions.RenderRssExtension Text.RSS.Extensions.DublinCore.DublinCoreModule
+ Text.RSS.Extensions.Syndication: instance GHC.Enum.Bounded Text.RSS.Extensions.Syndication.SyndicationPeriod
+ Text.RSS.Extensions.Syndication: instance GHC.Enum.Enum Text.RSS.Extensions.Syndication.SyndicationPeriod
+ Text.RSS.Extensions.Syndication: instance GHC.Read.Read (Text.RSS.Types.RssChannelExtension Text.RSS.Extensions.Syndication.SyndicationModule)
+ Text.RSS.Extensions.Syndication: instance GHC.Read.Read (Text.RSS.Types.RssItemExtension Text.RSS.Extensions.Syndication.SyndicationModule)
+ Text.RSS.Extensions.Syndication: instance GHC.Read.Read Text.RSS.Extensions.Syndication.SyndicationInfo
+ Text.RSS.Extensions.Syndication: instance GHC.Read.Read Text.RSS.Extensions.Syndication.SyndicationPeriod
+ Text.RSS.Extensions.Syndication: instance Text.RSS.Extensions.RenderRssExtension Text.RSS.Extensions.Syndication.SyndicationModule
+ Text.RSS.Extensions.Syndication: renderSyndicationBase :: Monad m => UTCTime -> Source m Event
+ Text.RSS.Extensions.Syndication: renderSyndicationFrequency :: Monad m => Int -> Source m Event
+ Text.RSS.Extensions.Syndication: renderSyndicationInfo :: Monad m => SyndicationInfo -> Source m Event
+ Text.RSS.Extensions.Syndication: renderSyndicationPeriod :: Monad m => SyndicationPeriod -> Source m Event
+ Text.RSS.Extensions.Syndication: syndicationInfo :: MonadThrow m => ConduitM Event o m SyndicationInfo
+ Text.RSS.Types: instance GHC.Read.Read (Data.Vinyl.Core.Rec Text.RSS.Types.RssChannelExtension a) => GHC.Read.Read (Text.RSS.Types.RssChannelExtensions a)
+ Text.RSS.Types: instance GHC.Read.Read (Data.Vinyl.Core.Rec Text.RSS.Types.RssItemExtension a) => GHC.Read.Read (Text.RSS.Types.RssItemExtensions a)
+ Text.RSS.Types: instance GHC.Read.Read Text.RSS.Types.CloudProtocol
+ Text.RSS.Types: instance GHC.Read.Read Text.RSS.Types.RssException
- Text.RSS.Conduit.Render: renderRssDocument :: Monad m => RssDocument a -> Source m Event
+ Text.RSS.Conduit.Render: renderRssDocument :: Monad m => RenderRssExtensions e => RssDocument e -> Source m Event
- Text.RSS.Conduit.Render: renderRssItem :: Monad m => RssItem e -> Source m Event
+ Text.RSS.Conduit.Render: renderRssItem :: Monad m => RenderRssExtensions e => RssItem e -> Source m Event

Files

Text/RSS/Conduit/Render.hs view
@@ -17,6 +17,7 @@   ) where  -- {{{ Imports+import           Text.RSS.Extensions import           Text.RSS.Lens import           Text.RSS.Types @@ -39,9 +40,7 @@ -- }}}  -- | Render the top-level @\<rss\>@ element.------ __Note__: RSS extensions are NOT rendered.-renderRssDocument :: Monad m => RssDocument a -> Source m Event+renderRssDocument :: Monad m => RenderRssExtensions e => RssDocument e -> Source m Event renderRssDocument d = tag "rss" (attr "version" . pack . showVersion $ d^.documentVersionL) $   tag "channel" mempty $ do     textTag "title" $ d^.channelTitleL@@ -64,11 +63,10 @@     renderRssSkipHours $ d^.channelSkipHoursL     renderRssSkipDays $ d^.channelSkipDaysL     forM_ (d^..channelItemsL) renderRssItem+    renderRssChannelExtensions $ d^.channelExtensionsL  -- | Render an @\<item\>@ element.------ __Note__: RSS extensions are NOT rendered.-renderRssItem :: Monad m => RssItem e -> Source m Event+renderRssItem :: Monad m => RenderRssExtensions e => RssItem e -> Source m Event renderRssItem i = tag "item" mempty $ do   optionalTextTag "title" $ i^.itemTitleL   forM_ (i^.itemLinkL) $ textTag "link" . renderRssURI@@ -80,6 +78,7 @@   forM_ (i^.itemGuidL) renderRssGuid   forM_ (i^.itemPubDateL) $ dateTag "pubDate"   forM_ (i^.itemSourceL) renderRssSource+  renderRssItemExtensions $ i^.itemExtensionsL  -- | Render a @\<source\>@ element. renderRssSource :: (Monad m) => RssSource -> Source m Event@@ -156,7 +155,7 @@   -- {{{ Utils-tshow :: (Show a) => a -> Text+tshow :: Show a => a -> Text tshow = pack . show  textTag :: (Monad m) => Name -> Text -> Source m Event
Text/RSS/Extensions.hs view
@@ -47,6 +47,16 @@ -- | Requirement on a list of extension tags to be able to parse and combine them. type ParseRssExtensions (e :: [*]) = (AllConstrained ParseRssExtension e, SingI e) +-- | Class of RSS extensions that can be rendered.+class RenderRssExtension e where+  -- | Render extension for the @\<channel\>@ element.+  renderRssChannelExtension :: Monad m => RssChannelExtension e -> Source m Event+  -- | Render extension for the @\<item\>@ element.+  renderRssItemExtension :: Monad m => RssItemExtension e -> Source m Event++-- | Requirement on a list of extension tags to be able to render them.+type RenderRssExtensions (e :: [*]) = (AllConstrained RenderRssExtension e)+ -- | Parse a combination of RSS extensions at @\<channel\>@ level. parseRssChannelExtensions :: ParseRssExtensions e => MonadThrow m => ConduitM Event o m (RssChannelExtensions e) parseRssChannelExtensions = f sing where@@ -66,3 +76,17 @@   f (SCons _ es) = fmap RssItemExtensions $ getZipConduit $ (:&)     <$> ZipConduit parseRssItemExtension     <*> ZipConduit (rssItemExtension <$> f es)++-- | Render a set of @\<channel\>@ extensions.+renderRssChannelExtensions :: Monad m => RenderRssExtensions e => RssChannelExtensions e -> Source m Event+renderRssChannelExtensions (RssChannelExtensions RNil) = pure ()+renderRssChannelExtensions (RssChannelExtensions (a :& t)) = do+  renderRssChannelExtension a+  renderRssChannelExtensions (RssChannelExtensions t)++-- | Render a set of @\<item\>@ extensions.+renderRssItemExtensions :: Monad m => RenderRssExtensions e => RssItemExtensions e -> Source m Event+renderRssItemExtensions (RssItemExtensions RNil) = pure ()+renderRssItemExtensions (RssItemExtensions (a :& t)) = do+  renderRssItemExtension a+  renderRssItemExtensions (RssItemExtensions t)
Text/RSS/Extensions/Atom.hs view
@@ -11,10 +11,11 @@ import           Text.RSS.Extensions import           Text.RSS.Types -import           Conduit                 hiding (throwM)+import           Conduit                  hiding (throwM) import           Data.Singletons import           GHC.Generics import           Text.Atom.Conduit.Parse+import           Text.Atom.Conduit.Render import           Text.Atom.Types import           Text.XML.Stream.Parse -- }}}@@ -29,6 +30,10 @@ instance ParseRssExtension AtomModule where   parseRssChannelExtension = AtomChannel <$> (manyYield' atomLink =$= headC)   parseRssItemExtension    = AtomItem <$> (manyYield' atomLink =$= headC)++instance RenderRssExtension AtomModule where+  renderRssChannelExtension = mapM_ renderAtomLink . channelAtomLink+  renderRssItemExtension    = mapM_ renderAtomLink . itemAtomLink  data instance RssChannelExtension AtomModule = AtomChannel { channelAtomLink :: Maybe AtomLink }   deriving(Eq, Generic, Show)
Text/RSS/Extensions/Content.hs view
@@ -13,8 +13,10 @@     ContentModule(..)   , RssChannelExtension(ContentChannel)   , RssItemExtension(ContentItem)-    -- * Parsers+    -- * Parser   , contentEncoded+    -- * Renderer+  , renderContentEncoded     -- * Misc   , namespacePrefix   , namespaceURI@@ -26,12 +28,15 @@  import           Conduit                hiding (throwM) import           Control.Exception.Safe as Exception+import           Control.Monad import           Data.Maybe import           Data.Singletons-import           Data.Text+import           Data.Text              (Text)+import qualified Data.Text              as Text import           Data.XML.Types import           GHC.Generics import           Text.XML.Stream.Parse+import qualified Text.XML.Stream.Render as Render import           URI.ByteString -- }}} @@ -46,6 +51,9 @@   parseRssChannelExtension = pure ContentChannel   parseRssItemExtension    = ContentItem <$> (manyYield' contentEncoded =$= headDefC mempty) +instance RenderRssExtension ContentModule where+  renderRssChannelExtension = const $ pure ()+  renderRssItemExtension (ContentItem e) = unless (Text.null e) $ renderContentEncoded e  data instance RssChannelExtension ContentModule = ContentChannel deriving(Eq, Generic, Ord, Show) data instance RssItemExtension ContentModule = ContentItem { itemContent :: Text }@@ -66,3 +74,7 @@ -- | Parse a @\<content:encoded\>@ element. contentEncoded :: MonadThrow m => ConduitM Event o m (Maybe Text) contentEncoded = tagIgnoreAttrs (matching (== contentName "encoded")) content++-- | Render a @\<content:encoded\>@ element.+renderContentEncoded :: Monad m => Text -> Source m Event+renderContentEncoded = Render.tag (contentName "encoded") mempty . Render.content
Text/RSS/Extensions/DublinCore.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE KindSignatures    #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes        #-}+{-# LANGUAGE RecordWildCards   #-} {-# LANGUAGE TemplateHaskell   #-} {-# LANGUAGE TypeFamilies      #-} -- | __Dublin Core__ extension for RSS.@@ -21,19 +22,22 @@ import           Text.RSS.Extensions import           Text.RSS.Types -import           Conduit                           hiding (throwM)-import           Control.Exception.Safe            as Exception+import           Conduit                            hiding (throwM)+import           Control.Exception.Safe             as Exception+import           Control.Monad import           Control.Monad.Fix import           Data.Maybe import           Data.Singletons-import           Data.Text+import           Data.Text                          (Text)+import qualified Data.Text                          as Text import           Data.Time.Clock import           Data.Time.LocalTime import           Data.Time.RFC3339 import           Data.XML.Types import           GHC.Generics import           Lens.Simple-import qualified Text.XML.DublinCore.Conduit.Parse as DC+import qualified Text.XML.DublinCore.Conduit.Parse  as DC+import           Text.XML.DublinCore.Conduit.Render import           Text.XML.Stream.Parse import           URI.ByteString -- }}}@@ -115,7 +119,26 @@           , fmap ElementType <$> DC.elementType           ] +-- | Render a set of Dublin Core metadata elements.+renderDcMetadata :: Monad m => DcMetaData -> Source m Event+renderDcMetadata DcMetaData{..} = do+  unless (Text.null elementContributor) $ renderElementContributor elementContributor+  unless (Text.null elementCoverage) $ renderElementCoverage elementCoverage+  unless (Text.null elementCreator) $ renderElementCreator elementCreator+  forM_ elementDate renderElementDate+  unless (Text.null elementDescription) $ renderElementDescription elementDescription+  unless (Text.null elementFormat) $ renderElementFormat elementFormat+  unless (Text.null elementIdentifier) $ renderElementIdentifier elementIdentifier+  unless (Text.null elementLanguage) $ renderElementLanguage elementLanguage+  unless (Text.null elementPublisher) $ renderElementPublisher elementPublisher+  unless (Text.null elementRelation) $ renderElementRelation elementRelation+  unless (Text.null elementRights) $ renderElementRights elementRights+  unless (Text.null elementSource) $ renderElementSource elementSource+  unless (Text.null elementSubject) $ renderElementSubject elementSubject+  unless (Text.null elementTitle) $ renderElementTitle elementTitle+  unless (Text.null elementType) $ renderElementType elementType + -- | __Dublin Core__ tag type. data DublinCoreModule :: * @@ -126,6 +149,10 @@ instance ParseRssExtension DublinCoreModule where   parseRssChannelExtension = DublinCoreChannel <$> dcMetadata   parseRssItemExtension    = DublinCoreItem <$> dcMetadata++instance RenderRssExtension DublinCoreModule where+  renderRssChannelExtension = renderDcMetadata . channelDcMetaData+  renderRssItemExtension    = renderDcMetadata . itemDcMetaData   data instance RssChannelExtension DublinCoreModule = DublinCoreChannel { channelDcMetaData :: DcMetaData }
Text/RSS/Extensions/Syndication.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE KindSignatures    #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes        #-}+{-# LANGUAGE RecordWildCards   #-} {-# LANGUAGE TemplateHaskell   #-} {-# LANGUAGE TypeFamilies      #-} -- | __Syndication__ module for RSS.@@ -17,9 +18,15 @@   , SyndicationPeriod(..)   , asSyndicationPeriod     -- * Parsers+  , syndicationInfo   , syndicationPeriod   , syndicationFrequency   , syndicationBase+    -- * Renderers+  , renderSyndicationInfo+  , renderSyndicationPeriod+  , renderSyndicationFrequency+  , renderSyndicationBase     -- * Misc   , namespacePrefix   , namespaceURI@@ -29,9 +36,9 @@ import           Text.RSS.Extensions import           Text.RSS.Types -import           Conduit                           hiding (throwM)+import           Conduit                hiding (throwM) import           Control.Applicative-import           Control.Exception.Safe            as Exception+import           Control.Exception.Safe as Exception import           Control.Monad import           Control.Monad.Fix import           Data.Maybe@@ -46,12 +53,15 @@ import           GHC.Generics import           Lens.Simple import           Text.Read-import qualified Text.XML.DublinCore.Conduit.Parse as DC import           Text.XML.Stream.Parse+import qualified Text.XML.Stream.Render as Render import           URI.ByteString -- }}}  -- {{{ Utils+tshow :: Show a => a -> Text+tshow = pack . show+ asDate :: MonadThrow m => Text -> m UTCTime asDate text = maybe (throw $ InvalidTime text) (return . zonedTimeToUTC) $   parseTimeRFC3339 text <|> parseTimeRFC2822 text <|> parseTimeRFC822 text@@ -87,9 +97,13 @@ syndicationTag :: MonadThrow m => Text -> ConduitM Event o m a -> ConduitM Event o m (Maybe a) syndicationTag name = tagIgnoreAttrs (matching (== syndicationName name)) +renderSyndicationTag :: Monad m => Text -> Text -> Source m Event+renderSyndicationTag name = Render.tag (syndicationName name) mempty . Render.content -data SyndicationPeriod = Hourly | Daily | Weekly | Monthly | Yearly deriving(Eq, Generic, Ord, Show) +data SyndicationPeriod = Hourly | Daily | Weekly | Monthly | Yearly+  deriving (Bounded, Enum, Eq, Generic, Ord, Read, Show)+ asSyndicationPeriod :: MonadThrow m => Text -> m SyndicationPeriod asSyndicationPeriod "hourly"  = pure Hourly asSyndicationPeriod "daily"   = pure Daily@@ -98,12 +112,20 @@ asSyndicationPeriod "yearly"  = pure Yearly asSyndicationPeriod t         = throw $ InvalidSyndicationPeriod t +fromSyndicationPeriod :: SyndicationPeriod -> Text+fromSyndicationPeriod Hourly  = "hourly"+fromSyndicationPeriod Daily   = "daily"+fromSyndicationPeriod Weekly  = "weekly"+fromSyndicationPeriod Monthly = "monthly"+fromSyndicationPeriod Yearly  = "yearly"++ -- | __Syndication__ extension model. data SyndicationInfo = SyndicationInfo   { updatePeriod    :: Maybe SyndicationPeriod   , updateFrequency :: Maybe Int   , updateBase      :: Maybe UTCTime-  } deriving(Eq, Generic, Ord, Show)+  } deriving (Eq, Generic, Ord, Read, Show)  -- | Construct an empty 'SyndicationInfo'. mkSyndicationInfo :: SyndicationInfo@@ -114,7 +136,7 @@  makeTraversals ''ElementPiece --- | Parse __Syndication__ elements.+-- | Parse all __Syndication__ elements. syndicationInfo :: MonadThrow m => ConduitM Event o m SyndicationInfo syndicationInfo = manyYield' (choose piece) =$= parser where   parser = getZipConduit $ SyndicationInfo@@ -138,6 +160,26 @@ syndicationBase :: MonadThrow m => ConduitM Event o m (Maybe UTCTime) syndicationBase = syndicationTag "updateBase" (content >>= asDate) +-- | Render all __Syndication__ elements.+renderSyndicationInfo :: Monad m => SyndicationInfo -> Source m Event+renderSyndicationInfo SyndicationInfo{..} = do+  forM_ updatePeriod renderSyndicationPeriod+  forM_ updateFrequency renderSyndicationFrequency+  forM_ updateBase renderSyndicationBase++-- | Render a @\<sy:updatePeriod\>@ element.+renderSyndicationPeriod :: Monad m => SyndicationPeriod -> Source m Event+renderSyndicationPeriod = renderSyndicationTag "updatePeriod" . fromSyndicationPeriod++-- | Render a @\<sy:updateFrequency\>@ element.+renderSyndicationFrequency :: Monad m => Int -> Source m Event+renderSyndicationFrequency = renderSyndicationTag "updateFrequency" . tshow++-- | Render a @\<sy:updateBase\>@ element.+renderSyndicationBase :: Monad m => UTCTime -> Source m Event+renderSyndicationBase = renderSyndicationTag "updateBase" . formatTimeRFC822 . utcToZonedTime utc++ -- | __Syndication__ tag type. data SyndicationModule :: * @@ -149,7 +191,11 @@   parseRssChannelExtension = SyndicationChannel <$> syndicationInfo   parseRssItemExtension    = pure SyndicationItem +instance RenderRssExtension SyndicationModule where+  renderRssChannelExtension = renderSyndicationInfo . channelSyndicationInfo+  renderRssItemExtension    = const $ pure () + data instance RssChannelExtension SyndicationModule = SyndicationChannel { channelSyndicationInfo :: SyndicationInfo}-  deriving(Eq, Generic, Ord, Show)-data instance RssItemExtension SyndicationModule = SyndicationItem deriving(Eq, Generic, Ord, Show)+  deriving (Eq, Generic, Ord, Read, Show)+data instance RssItemExtension SyndicationModule = SyndicationItem deriving (Eq, Generic, Ord, Read, Show)
Text/RSS/Types.hs view
@@ -67,6 +67,7 @@  deriving instance Eq RssException deriving instance Generic RssException+deriving instance Read RssException deriving instance Show RssException  instance Exception RssException where@@ -183,7 +184,7 @@ deriving instance Show RssTextInput  data CloudProtocol = ProtocolXmlRpc | ProtocolSoap | ProtocolHttpPost-  deriving(Eq, Generic, Ord, Show)+  deriving(Eq, Generic, Ord, Read, Show)  -- | The @\<cloud\>@ element. data RssCloud = RssCloud@@ -303,6 +304,7 @@ deriving instance (Eq (Rec RssChannelExtension a)) => Eq (RssChannelExtensions a) deriving instance (Generic (Rec RssChannelExtension a)) => Generic (RssChannelExtensions a) deriving instance (Ord (Rec RssChannelExtension a)) => Ord (RssChannelExtensions a)+deriving instance (Read (Rec RssChannelExtension a)) => Read (RssChannelExtensions a) deriving instance (Show (Rec RssChannelExtension a)) => Show (RssChannelExtensions a)  -- | Combination of multiple @\<item\>@ extensions.@@ -312,4 +314,5 @@ deriving instance (Eq (Rec RssItemExtension a)) => Eq (RssItemExtensions a) deriving instance (Generic (Rec RssItemExtension a)) => Generic (RssItemExtensions a) deriving instance (Ord (Rec RssItemExtension a)) => Ord (RssItemExtensions a)+deriving instance (Read (Rec RssItemExtension a)) => Read (RssItemExtensions a) deriving instance (Show (Rec RssItemExtension a)) => Show (RssItemExtensions a)
rss-conduit.cabal view
@@ -1,5 +1,5 @@ name: rss-conduit-version: 0.4.1.0+version: 0.4.2.0 cabal-version: >=1.10 build-type: Simple license: PublicDomain@@ -35,7 +35,7 @@         Text.RSS.Lens         Text.RSS.Types     build-depends:-        atom-conduit -any,+        atom-conduit >=0.5,         base >=4.8 && <5,         conduit -any,         conduit-combinators -any,@@ -59,8 +59,9 @@     main-is: Main.hs     build-depends:         rss-conduit -any,-        atom-conduit -any,+        atom-conduit >=0.5,         base >=4.8 && <5,+        blaze-builder -any,         bytestring -any,         conduit -any,         conduit-combinators -any,
test/Arbitrary.hs view
@@ -5,25 +5,27 @@ module Arbitrary (module Arbitrary) where  -- {{{ Imports-import           Data.ByteString           (ByteString)+import           Text.RSS.Extensions.Atom+import           Text.RSS.Extensions.Content+import           Text.RSS.Extensions.DublinCore+import           Text.RSS.Extensions.Syndication+import           Text.RSS.Types++import           Data.ByteString                 (ByteString) import           Data.Char import           Data.Maybe-import           Data.MonoTraversable      (Element)+import           Data.MonoTraversable            (Element) import           Data.NonNull-import           Data.Sequences            (SemiSequence)-import           Data.Text                 (Text, find, pack)+import           Data.Sequences                  (SemiSequence)+import           Data.Text                       (Text, find, pack) import           Data.Text.Encoding import           Data.Time.Clock import           Data.Version import           Data.Vinyl.Core- import           GHC.Generics- import           Test.QuickCheck-import           Test.QuickCheck.Instances ()--import           Text.RSS.Types-+import           Test.QuickCheck.Instances       ()+import           Text.Atom.Types import           URI.ByteString -- }}} @@ -163,3 +165,49 @@   arbitrary = oneof [RssURI <$> (arbitrary :: Gen (URIRef Absolute)), RssURI <$> (arbitrary :: Gen (URIRef Relative))]   shrink (RssURI a@URI{})         = RssURI <$> shrink a   shrink (RssURI a@RelativeRef{}) = RssURI <$> shrink a++instance Arbitrary DcMetaData where+  arbitrary = DcMetaData+    <$> arbitrary+    <*> arbitrary+    <*> arbitrary+    <*> oneof [Just <$> genTime, pure Nothing]+    <*> arbitrary+    <*> arbitrary+    <*> arbitrary+    <*> arbitrary+    <*> arbitrary+    <*> arbitrary+    <*> arbitrary+    <*> arbitrary+    <*> arbitrary+    <*> arbitrary+    <*> arbitrary++instance Arbitrary (RssChannelExtension DublinCoreModule) where+  arbitrary = DublinCoreChannel <$> arbitrary++instance Arbitrary SyndicationPeriod where+  arbitrary = arbitraryBoundedEnum+  shrink = genericShrink++instance Arbitrary SyndicationInfo where+  arbitrary = SyndicationInfo+    <$> arbitrary+    <*> arbitrary+    <*> oneof [Just <$> genTime, pure Nothing]++instance Arbitrary (RssChannelExtension SyndicationModule) where+  arbitrary = SyndicationChannel <$> arbitrary++instance Arbitrary AtomURI where+  arbitrary = oneof [AtomURI <$> (arbitrary :: Gen (URIRef Absolute)), AtomURI <$> (arbitrary :: Gen (URIRef Relative))]++instance Arbitrary AtomLink where+  arbitrary = AtomLink <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary++instance Arbitrary (RssChannelExtension AtomModule) where+  arbitrary = AtomChannel <$> arbitrary++instance Arbitrary (RssItemExtension ContentModule) where+  arbitrary = ContentItem <$> arbitrary
test/Main.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE FlexibleInstances   #-} {-# LANGUAGE OverloadedLists     #-} {-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE QuasiQuotes         #-} {-# LANGUAGE ScopedTypeVariables #-} -- {{{ Imports import           Text.RSS.Conduit.Parse          as Parser@@ -16,6 +17,7 @@ import           Text.RSS1.Conduit.Parse         as Parser  import           Arbitrary+import           Blaze.ByteString.Builder        (toByteString) import           Conduit import           Control.Exception.Safe          as Exception import           Control.Monad@@ -26,10 +28,12 @@ import           Data.Default import           Data.Singletons.Prelude.List import           Data.Text                       (Text)+import           Data.Text.Encoding import           Data.Time.Calendar import           Data.Time.LocalTime import           Data.Version import           Data.Vinyl.Core+import           Data.Void import           Data.XML.Types import qualified Language.Haskell.HLint          as HLint (hlint) import           Lens.Simple@@ -43,6 +47,7 @@ import           Text.XML.Stream.Parse           as XML hiding (choose) import           Text.XML.Stream.Render import           URI.ByteString+import           URI.ByteString.QQ -- }}}  main :: IO ()@@ -80,16 +85,39 @@  properties :: TestTree properties = testGroup "Properties"-  [ roundtripTextInputProperty-  , roundtripImageProperty-  , roundtripCategoryProperty-  , roundtripEnclosureProperty-  , roundtripSourceProperty-  , roundtripGuidProperty-  , roundtripItemProperty+  [ roundtripProperty "RssTextInput" renderRssTextInput rssTextInput+  , roundtripProperty "RssImage" renderRssImage rssImage+  , roundtripProperty "RssCategory" renderRssCategory rssCategory+  , roundtripProperty "RssEnclosure" renderRssEnclosure rssEnclosure+  , roundtripProperty "RssSource" renderRssSource rssSource+  , roundtripProperty "RssGuid" renderRssGuid rssGuid+  , roundtripProperty "RssItem"+      (renderRssItem :: RssItem '[] -> Source Maybe Event)+      rssItem+  , roundtripProperty "DublinCore"+      (renderRssChannelExtension :: RssChannelExtension DublinCoreModule -> Source Maybe Event)+      (Just <$> parseRssChannelExtension)+  , roundtripProperty "Syndication"+      (renderRssChannelExtension :: RssChannelExtension SyndicationModule -> Source Maybe Event)+      (Just <$> parseRssChannelExtension)+  , roundtripProperty "Atom"+      (renderRssChannelExtension :: RssChannelExtension AtomModule -> Source Maybe Event)+      (Just <$> parseRssChannelExtension)+  , roundtripProperty "Content"+      (renderRssItemExtension :: RssItemExtension ContentModule -> Source Maybe Event)+      (Just <$> parseRssItemExtension)   ]  +roundtripProperty :: Eq a => Arbitrary a => Show a+                  => TestName -> (a -> Source Maybe Event) -> ConduitM Event Void Maybe (Maybe a) -> TestTree+roundtripProperty name render parse = testProperty ("parse . render = id (" <> name <> ")") $ do+  input <- arbitrary+  let intermediate = fmap (decodeUtf8 . toByteString) $ runConduit $ render input =$= renderBuilder def =$= foldC+      output = join $ runConduit $ render input =$= parse+  return $ counterexample (show input <> " | " <> show intermediate <> " | " <> show output) $ Just input == output++ skipHoursCase :: TestTree skipHoursCase = testCase "<skipHours> element" $ do   result <- runResourceT . runConduit $ sourceList input =$= XML.parseText' def =$= force "ERROR" rssSkipHours@@ -121,7 +149,7 @@   result^.textInputTitleL @?= "Search XML.com"   result^.textInputDescriptionL @?= "Search XML.com's XML collection"   result^.textInputNameL @?= "s"-  result^.textInputLinkL @=? RssURI (URI (Scheme "http") (Just (Authority Nothing (Host "search.xml.com") Nothing)) "" (Query []) Nothing)+  result^.textInputLinkL @=? RssURI [uri|http://search.xml.com|]   where input = [ "<textinput xmlns=\"http://purl.org/rss/1.0/\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" rdf:about=\"http://search.xml.com\">"                 , "<title>Search XML.com</title>"                 , "<description>Search XML.com's XML collection</description>"@@ -136,7 +164,7 @@   result^.textInputTitleL @?= "Title"   result^.textInputDescriptionL @?= "Description"   result^.textInputNameL @?= "Name"-  result^.textInputLinkL @=? RssURI (URI (Scheme "http") (Just (Authority Nothing (Host "link.ext") Nothing)) "" (Query []) Nothing)+  result^.textInputLinkL @=? RssURI [uri|http://link.ext|]   where input = [ "<textInput>"                 , "<title>Title</title>"                 , "<description>Description</description>"@@ -148,9 +176,9 @@ rss1ImageCase :: TestTree rss1ImageCase = testCase "RSS1 <image> element" $ do   result <- runResourceT . runConduit $ sourceList input =$= XML.parseText' def =$= force "ERROR" rss1Image-  result^.imageUriL @?= RssURI (URI (Scheme "http") (Just (Authority Nothing (Host "xml.com") Nothing)) "/universal/images/xml_tiny.gif" (Query []) Nothing)+  result^.imageUriL @?= RssURI [uri|http://xml.com/universal/images/xml_tiny.gif|]   result^.imageTitleL @?= "XML.com"-  result^.imageLinkL @?= RssURI (URI (Scheme "http") (Just (Authority Nothing (Host "www.xml.com") Nothing)) "" (Query []) Nothing)+  result^.imageLinkL @?= RssURI [uri|http://www.xml.com|]   where input = [ "<image xmlns=\"http://purl.org/rss/1.0/\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" rdf:about=\"http://xml.com/universal/images/xml_tiny.gif\">"                 , "<url>http://xml.com/universal/images/xml_tiny.gif</url>"                 , "<title>XML.com</title>"@@ -163,9 +191,9 @@ rss2ImageCase :: TestTree rss2ImageCase = testCase "RSS2 <image> element" $ do   result <- runResourceT . runConduit $ sourceList input =$= XML.parseText' def =$= force "ERROR" rssImage-  result^.imageUriL @?= RssURI (URI (Scheme "http") (Just (Authority Nothing (Host "image.ext") Nothing)) "" (Query []) Nothing)+  result^.imageUriL @?= RssURI [uri|http://image.ext|]   result^.imageTitleL @?= "Title"-  result^.imageLinkL @?= RssURI (URI (Scheme "http") (Just (Authority Nothing (Host "link.ext") Nothing)) "" (Query []) Nothing)+  result^.imageLinkL @?= RssURI [uri|http://link.ext|]   result^.imageWidthL @?= Just 100   result^.imageHeightL @?= Just 200   result^.imageDescriptionL @?= "Description"@@ -198,7 +226,7 @@   where input = [ "<cloud domain=\"rpc.sys.com\" port=\"80\" path=\"/RPC2\" registerProcedure=\"pingMe\" protocol=\"soap\"/>"                 , "<cloud domain=\"rpc.sys.com\" port=\"80\" path=\"/RPC2\" registerProcedure=\"myCloud.rssPleaseNotify\" protocol=\"xml-rpc\" />"                 ]-        uri = RssURI (RelativeRef (Just (Authority Nothing (Host "rpc.sys.com") (Just $ Port 80))) "/RPC2" (Query []) Nothing)+        uri = RssURI [relativeRef|//rpc.sys.com:80/RPC2|]  guidCase :: TestTree guidCase = testCase "<guid> element" $ do@@ -208,23 +236,23 @@                 , "<guid isPermaLink=\"false\">1</guid>"                 , "<guid>2</guid>"                 ]-        uri = RssURI (RelativeRef (Just (Authority Nothing (Host "guid.ext") Nothing)) "" (Query []) Nothing)+        uri = RssURI [relativeRef|//guid.ext|]  enclosureCase :: TestTree enclosureCase = testCase "<enclosure> element" $ do   result <- runResourceT . runConduit $ sourceList input =$= XML.parseText' def =$= force "ERROR" rssEnclosure-  result @?= RssEnclosure uri 12216320 "audio/mpeg"+  result @?= RssEnclosure url 12216320 "audio/mpeg"   where input = [ "<enclosure url=\"http://www.scripting.com/mp3s/weatherReportSuite.mp3\" length=\"12216320\" type=\"audio/mpeg\" />"                 ]-        uri = RssURI (URI (Scheme "http") (Just (Authority Nothing (Host "www.scripting.com") Nothing)) "/mp3s/weatherReportSuite.mp3" (Query []) Nothing)+        url = RssURI [uri|http://www.scripting.com/mp3s/weatherReportSuite.mp3|]  sourceCase :: TestTree sourceCase = testCase "<source> element" $ do   result <- runResourceT . runConduit $ sourceList input =$= XML.parseText' def =$= force "ERROR" rssSource-  result @?= RssSource uri "Tomalak's Realm"+  result @?= RssSource url "Tomalak's Realm"   where input = [ "<source url=\"http://www.tomalak.org/links2.xml\">Tomalak's Realm</source>"                 ]-        uri = RssURI (URI (Scheme "http") (Just (Authority Nothing (Host "www.tomalak.org") Nothing)) "/links2.xml" (Query []) Nothing)+        url = RssURI [uri|http://www.tomalak.org/links2.xml|]  rss1ItemCase :: TestTree rss1ItemCase = testCase "RSS1 <item> element" $ do@@ -246,7 +274,7 @@                 , "<sometag>Some content in unknown tag, should be ignored.</sometag>"                 , "</item>"                 ]-        link = RssURI (URI (Scheme "http") (Just (Authority Nothing (Host "xml.com") Nothing)) "/pub/2000/08/09/xslt/xslt.html" (Query []) Nothing)+        link = RssURI [uri|http://xml.com/pub/2000/08/09/xslt/xslt.html|]  rss2ItemCase :: TestTree rss2ItemCase = testCase "RSS2 <item> element" $ do@@ -266,7 +294,7 @@                 , "<sometag>Some content in unknown tag, should be ignored.</sometag>"                 , "</item>"                 ]-        link = RssURI (URI (Scheme "http") (Just (Authority Nothing (Host "www.example.com") Nothing)) "/blog/post/1" (Query []) Nothing)+        link = RssURI [uri|http://www.example.com/blog/post/1|]   rss1ChannelItemsCase :: TestTree@@ -336,10 +364,10 @@                 , "</textinput>"                 , "</rdf:RDF>"                 ]-        link = RssURI (URI (Scheme "http") (Just (Authority Nothing (Host "xml.com") Nothing)) "/pub" (Query []) Nothing)-        imageLink = RssURI (URI (Scheme "http") (Just (Authority Nothing (Host "www.xml.com") Nothing)) "" (Query []) Nothing)-        imageUri = RssURI (URI (Scheme "http") (Just (Authority Nothing (Host "xml.com") Nothing)) "/universal/images/xml_tiny.gif" (Query []) Nothing)-        textInputLink = RssURI (URI (Scheme "http") (Just (Authority Nothing (Host "search.xml.com") Nothing)) "" (Query []) Nothing)+        link = RssURI [uri|http://xml.com/pub|]+        imageLink = RssURI [uri|http://www.xml.com|]+        imageUri = RssURI [uri|http://xml.com/universal/images/xml_tiny.gif|]+        textInputLink = RssURI [uri|http://search.xml.com|]   rss2DocumentCase :: TestTree@@ -371,7 +399,7 @@                 , "</channel>"                 , "</rss>"                 ]-        link = RssURI (URI (Scheme "http") (Just (Authority Nothing (Host "www.example.com") Nothing)) "/main.html" (Query []) Nothing)+        link = RssURI [uri|http://www.example.com/main.html|]   dublinCoreChannelCase :: TestTree@@ -474,8 +502,8 @@                 , "</channel>"                 , "</rss>"                 ]-        uri = AtomURI (URI (Scheme "http") (Just (Authority Nothing (Host "dallas.example.com") Nothing)) "/rss.xml" (Query []) Nothing)-        link = AtomLink uri "self" "application/rss+xml" mempty mempty mempty+        url = AtomURI [uri|http://dallas.example.com/rss.xml|]+        link = AtomLink url "self" "application/rss+xml" mempty mempty mempty  multipleExtensionsCase :: TestTree multipleExtensionsCase = testCase "Multiple extensions" $ do@@ -488,8 +516,8 @@                 , "<content:encoded><![CDATA[<p>What a <em>beautiful</em> day!</p>]]></content:encoded>"                 , "</item>"                 ]-        uri = AtomURI (URI (Scheme "http") (Just (Authority Nothing (Host "dallas.example.com") Nothing)) "/rss.xml" (Query []) Nothing)-        link = AtomLink uri "self" "application/rss+xml" mempty mempty mempty+        url = AtomURI [uri|http://dallas.example.com/rss.xml|]+        link = AtomLink url "self" "application/rss+xml" mempty mempty mempty   hlint :: TestTree