pandoc-utils (empty) → 0.4.0
raw patch · 9 files changed
+839/−0 lines, 9 filesdep +basedep +containersdep +data-defaultsetup-changed
Dependencies added: base, containers, data-default, pandoc, pandoc-types, pandoc-utils, tasty, tasty-hspec, text, transformers
Files
- CHANGELOG.md +5/−0
- LICENSE +20/−0
- README.md +103/−0
- Setup.hs +2/−0
- pandoc-utils.cabal +61/−0
- src/Text/Pandoc/Filter/Utils.hs +180/−0
- src/Text/Pandoc/Filter/Utils/AttrBuilder.hs +52/−0
- src/Text/Pandoc/Utils.hs +52/−0
- test/Spec.hs +364/−0
+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Releases++## pandoc-utils 0.4.0 (2020-5-24)++- Initial public release.
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2020 Krasjet++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,103 @@+# pandoc-utils++This package contains some useful functions for writing+[Pandoc](https://pandoc.org/) filters and integrating Pandoc into Haskell+applications such as [Hakyll](https://jaspervdj.be/hakyll/). It provides a+composable wrapper for filters acting on nodes of the [Pandoc+AST](https://hackage.haskell.org/package/pandoc-types/docs/Text-Pandoc-Definition.html).++## Filter conversion/composition++As an example, let us look at the `behead` and `delink` filter from [Pandoc's+tutorial](https://pandoc.org/filters.html).+```haskell+behead :: Block -> Block+behead (Header n _ xs) | n >= 2 = Para [Emph xs]+behead x = x++delink :: Inline -> [Inline]+delink (Link _ txt _) = txt+delink x = [x]+```++Since `behead` has type `Block -> Block`, while `delink` has type `Inline ->+[Inline]`, they are not naturally composable. However, this package provides a+utility function `mkFilter` to convert them into a `PandocFilter`.+```haskell+import Text.Pandoc.Filter.Utils++beheadFilter :: PandocFilter+beheadFilter = mkFilter behead++delinkFilter :: PandocFilter+delinkFilter = mkFilter delink+```+`PandocFilter` is an alias for `PartialFilter Pandoc`, so you can also have+`PartialFilter Inline`, etc. There is also a monadic version called+`PartialFilterM` for encapsulating monadic filters.++The `PandocFilter` is a monoid so you can do something like,+```haskell+myFilter :: PandocFilter+myFilter = beheadFilter <> delinkFilter+```+where `myFilter` would apply `beheadFilter` first, then the `delinkFilter`. You+can apply the filter using `applyFilter`,+```haskell+import Text.Pandoc+import Data.Default (def)++mdToHtml+ :: Text -- ^ Input markdown string+ -> Either PandocError Text -- ^ Html string or error+mdToHtml md = runPure $ do+ doc <- readMarkdown def md+ let doc' = applyFilter myFilter doc+ writeHtml5String def doc'+```+or get an unwrapped `Pandoc -> Pandoc` filter using `getFilter` (this function+is also capable of doing implicit conversion from `PartialFilter a` to `b ->+b`).+```haskell+myPandocFilter :: Pandoc -> Pandoc+myPandocFilter = getFilter myFilter+```++For applying multiple filters, there is also a function called `applyFilters`,+which takes a list of filters and apply it to a `Pandoc` document (or subnode)+sequentially, from left to right.+```haskell+myFilters :: [PandocFilter]+myFilters =+ [ beheadFilter+ , delinkFilter+ ]++mdToHtml'+ :: Text -- ^ Input markdown string+ -> Either PandocError Text -- ^ Html string or error+mdToHtml' md = runPure $ do+ doc <- readMarkdown def md+ let doc' = applyFilters myFilters doc+ writeHtml5String def doc'+```++## Attribute builder++pandoc-utils also provides an attribute builder for handling attributes. You+can create a new attributes by+```haskell+ghci> import Text.Pandoc.Filter.Utils.AttrBuilder+ghci> import Text.Pandoc.Definition+ghci> nullAttr `setId` "id" `addClass` "class" `addKVPair` ("key","value")+("id",["class"],[("key","value")])+```+or modifying an existing attributes by+```haskell+ghci> attr = ("id",[],[])+ghci> attr `setId` "newId"+("newId",[],[])+```++For more examples, please read the+[spec](https://github.com/Krasjet/pandoc-utils/blob/master/test/Spec.hs).
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ pandoc-utils.cabal view
@@ -0,0 +1,61 @@+build-type: Simple+cabal-version: 2.0++name: pandoc-utils+version: 0.4.0+synopsis:+ Utility functions to work with Pandoc in Haskell applications.+description:+ This package contains some useful functions for writing [Pandoc](https://pandoc.org/)+ filters and integrating Pandoc into Haskell applications such+ as [Hakyll](https://jaspervdj.be/hakyll/). It provides a+ composable wrapper for filters acting on nodes of the [Pandoc+ AST](https://hackage.haskell.org/package/pandoc-types/docs/Text-Pandoc-Definition.html).+ .+ For more examples, please see the [README](https://github.com/Krasjet/pandoc-utils) on GitHub.+license: MIT+license-file: LICENSE+author: Krasjet+maintainer: Krasjet+copyright: Copyright (c) 2020 Krasjet+homepage: https://github.com/Krasjet/pandoc-utils+bug-reports: https://github.com/Krasjet/pandoc-utils/issues+category: Text+extra-source-files:+ CHANGELOG.md+ README.md++source-repository head+ type: git+ location: git://github.com/Krasjet/pandoc-utils.git++library+ hs-source-dirs: src+ default-language: Haskell2010+ ghc-options: -Wall+ exposed-modules:+ Text.Pandoc.Utils+ Text.Pandoc.Filter.Utils+ Text.Pandoc.Filter.Utils.AttrBuilder+ build-depends:+ base >=4.10 && <4.15+ , pandoc-types ^>=1.17.2 || ^>=1.20+ , text ^>=1.2++test-suite pandoc-utils-test+ hs-source-dirs: test+ default-language: Haskell2010+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ build-depends:+ base >=4.10 && <4.15+ , pandoc-utils+ , containers ^>=0.6+ , data-default ^>=0.7+ , pandoc ^>=2.9+ , pandoc-types ^>=1.20+ , transformers ^>=0.5+ , tasty ^>=1.2+ , tasty-hspec ^>=1.1+ , text ^>=1.2
+ src/Text/Pandoc/Filter/Utils.hs view
@@ -0,0 +1,180 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}++-- | This module contains some utility functions to work with different levels+-- of Pandoc filters. For example, for the conversion from @'Inline' ->+-- ['Inline']@ to @'Pandoc' -> 'Pandoc'@ filter.+module Text.Pandoc.Filter.Utils (+ -- * Definitions+ PartialFilterM,+ PartialFilter,+ PandocFilterM,+ PandocFilter,+ -- * Filter application+ applyFilterM,+ applyFilter,+ -- * Filter composition+ applyFiltersM,+ applyFilters,+ -- * Filter conversion+ getFilterM,+ getFilter,+ getConcatedFilterM,+ getConcatedFilter,+ ToPartialFilter (..),+ mkConcatedFilter,+ toFilterM,+ ) where++import Control.Monad ((>=>))+import Data.Foldable (fold)+import Data.Functor.Identity (Identity (..))+import Text.Pandoc.Definition+import Text.Pandoc.Walk++-- | @PartialFilterM m p@ is a wrapper for any monadic @p -> m p@ Pandoc+-- filters acting on a subnode (e.g. 'Inline' or 'Block') of the 'Pandoc'+-- abstract syntax tree.+--+-- * @m@: a monad.+-- * @p@: the type of a subnode of 'Pandoc' (e.g. 'Inline').+newtype PartialFilterM m p =+ PartialFilterM+ { -- | Apply a monadic filter to @p@.+ applyFilterM :: p -> m p+ }++-- | @PartialFilter p@ is a wrapper for ordinary @p -> p@ Pandoc filters acting+-- on a subnode (e.g. 'Inline' or 'Block') of the 'Pandoc' abstract syntax+-- tree.+--+-- * @p@: the type of a subnode of 'Pandoc' (e.g. 'Inline').+type PartialFilter = PartialFilterM Identity++-- | An alias for @PartialFilter Pandoc@. It encapsulates a monadic+-- filter @'Pandoc' -> m 'Pandoc'@ acting directly on 'Pandoc'.+--+-- * @m@: a monad.+type PandocFilter = PartialFilter Pandoc++-- | An alias for @PartialFilterM m Pandoc@, a monadic version of+-- 'PandocFilter'.+--+-- * @m@: a monad.+type PandocFilterM m = PartialFilterM m Pandoc++-- | Apply an ordinary filter to @p@, which returns @p@ directly.+applyFilter+ :: PartialFilter p -- ^ A wrapped partial filter.+ -> p -- ^ 'Pandoc' AST node.+ -> p -- ^ Transformed node.+applyFilter = (runIdentity .) . applyFilterM++-- | It is mostly the same as 'applyFilterM' and should be used when you don't+-- need to apply the filter immediately. The only difference is that it will+-- perform an implicit conversion if the requested filter function is of a+-- different type.+getFilterM+ :: (Monad m, Walkable a b)+ => PartialFilterM m a -- ^ A wrapped partial filter on @a@.+ -> (b -> m b) -- ^ Unwrapped filter function that can be directly applied to @b@.+getFilterM = applyFilterM . mkFilter++-- | It is mostly the same as 'applyFilter' and should be used when you don't+-- need to apply the filter immediately. The only difference is that it will+-- perform an implicit conversion if the requested filter function is of a+-- different type.+getFilter+ :: (Walkable a b)+ => PartialFilter a -- ^ A wrapped partial filter on @a@.+ -> (b -> b) -- ^ Unwrapped filter function that can be directly applied to @b@.+getFilter = applyFilter . mkFilter++-- | The 'Semigroup' instance of `PartialFilterM`. @f1 <> f2@ will apply @f1@+-- first followed by @f2@.+instance (Monad m) => Semigroup (PartialFilterM m p) where+ f1 <> f2 = PartialFilterM (applyFilterM f1 >=> applyFilterM f2)++-- | The 'Monoid' instance of `PartialFilterM`.+instance (Monad m) => Monoid (PartialFilterM m p) where+ mempty = PartialFilterM return++-- | A helper typeclass used as a polymorphic constructor of 'PartialFilterM'.+class ToPartialFilter m f p where+ -- | The actual constructor of 'PartialFilterM'. It takes an ordinary filter+ -- function @a -> b@ and wraps it as a 'PartialFilterM'. It can also be used+ -- to convert between different types of @'PartialFilterM' m@.+ mkFilter+ :: f -- ^ A partial filter function, usually @a -> a@ for some @'Walkable' a p@.+ -> PartialFilterM m p -- ^ Wrapped partial Pandoc filter.++instance (Monad m, Walkable a p) => ToPartialFilter m (a -> a) p where+ mkFilter = PartialFilterM . (return .) . walk++instance (Monad m, Walkable a p) => ToPartialFilter m (a -> m a) p where+ mkFilter = PartialFilterM . walkM++instance (Monad m, Walkable [a] p) => ToPartialFilter m (a -> [a]) p where+ mkFilter = PartialFilterM . (return .) . walk . concatMap++instance (Monad m, Walkable [a] p) => ToPartialFilter m (a -> m [a]) p where+ mkFilter = PartialFilterM . walkM . (fmap concat .) . mapM++-- | This instance can be used to convert @'PartialFilterM' m a@ to+-- @'PartialFilterM' m b@.+instance (Monad m, Walkable a b) => ToPartialFilter m (PartialFilterM m a) b where+ mkFilter = mkFilter . applyFilterM++-- | Construct a 'PartialFilterM' from a list of filter functions of the same+-- type. The final filter is concatenated from left to right such that the+-- first element in the list will be applied first and the last element will be+-- applied at the end.+mkConcatedFilter+ :: (Monad m, ToPartialFilter m f p, Foldable t)+ => t f -- ^ A list of filter functions of the same type.+ -> PartialFilterM m p -- ^ Concatenated filter.+mkConcatedFilter = foldMap mkFilter++-- | Convert an ordinary 'PartialFilter' to the monadic version+-- 'PartialFilterM'.+toFilterM+ :: (Monad m)+ => PartialFilter p -- ^ An ordinary filter.+ -> PartialFilterM m p -- ^ The monadic version.+toFilterM = PartialFilterM . (return .) . applyFilter++-- | Apply a list of monadic partial filters sequentially, from left to+-- right, i.e. the first element in the list will be applied first and the+-- last element will be applied at the end.+applyFiltersM+ :: (Foldable t, Monad m)+ => t (PartialFilterM m p) -- ^ A list of monadic partial filters.+ -> p -- ^ 'Pandoc' AST node.+ -> m p -- ^ Transformed node.+applyFiltersM = applyFilterM . fold++-- | Apply a list of partial filters sequentially, from left to right, i.e.+-- the first element in the list will be applied first and the last element+-- will be applied at the end.+applyFilters+ :: (Foldable t)+ => t (PartialFilter p) -- ^ A list of partial filter.+ -> p -- ^ 'Pandoc' AST node.+ -> p -- ^ Transformed node.+applyFilters = applyFilter . fold++-- | An alias for 'applyFiltersM', used when the filter is not used+-- immediately.+getConcatedFilterM+ :: (Foldable t, Monad m)+ => t (PartialFilterM m p) -- ^ A list of monadic partial filters.+ -> (p -> m p) -- ^ Unwrapped monadic filter applicable to @p@ directly.+getConcatedFilterM = applyFiltersM++-- | An alias for 'applyFilters', used when the filter is not used immediately.+getConcatedFilter+ :: (Foldable t)+ => t (PartialFilter p) -- ^ A list of partial filter.+ -> (p -> p) -- ^ Unwrapped filter applicable to @p@ directly.+getConcatedFilter = applyFilters
+ src/Text/Pandoc/Filter/Utils/AttrBuilder.hs view
@@ -0,0 +1,52 @@+-- | This module contains some utility functions for building+-- attributes.+module Text.Pandoc.Filter.Utils.AttrBuilder (+ -- * Attribute builders+ addClass,+ addClasses,+ addKVPair,+ addKVPairs,+ setId,+) where++import Text.Pandoc.Utils++import Data.Bifunctor (bimap, first, second)+import Data.Text (Text)+import Text.Pandoc.Definition++-- | Append a new class to attributes.+addClass+ :: Attr -- ^ Original attributes.+ -> Text -- ^ Name of the class.+ -> Attr -- ^ New attributes.+attr `addClass` cls = first (fromText cls:) attr+ -- (i, fromText newCls:cls, p)++-- | Append a list of classes to attributes.+addClasses+ :: Attr -- ^ Original attributes.+ -> [Text] -- ^ A list of class names.+ -> Attr -- ^ New attributes.+attr `addClasses` clses = first (map fromText clses<>) attr++-- | Append a new key-value pair to attributes.+addKVPair+ :: Attr -- ^ Original attributes.+ -> (Text, Text) -- ^ A key-value pair.+ -> Attr -- ^ New attributes.+attr `addKVPair` p = second (bimap fromText fromText p:) attr++-- | Append new key-value pairs to attributes.+addKVPairs+ :: Attr -- ^ Original attributes.+ -> [(Text, Text)] -- ^ A list of key-value pairs.+ -> Attr -- ^ New attributes.+attr `addKVPairs` ps = second (map (bimap fromText fromText) ps <>) attr++-- | Set the id of attributes.+setId+ :: Attr -- ^ Original attributes.+ -> Text -- ^ The id of the attrbute.+ -> Attr -- ^ New attributes.+(_, cls, p) `setId` i = (i, cls, p)
+ src/Text/Pandoc/Utils.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE TypeFamilies #-}++-- | Due to the switch from 'String' to 'Text' in pandoc-types 1.20, some+-- legacy filters might not work in newer versions. This module contains some+-- string conversion utility functions to make filters work in across different+-- versions.+module Text.Pandoc.Utils (+ -- * Conversions+ ToString (..),+ ToText (..),+ IsText (..),+ -- * Reexport from Data.String+ IsString (..),+ ) where++import qualified Data.Text as T++import Data.String (IsString (..))+import Data.Text (Text)++-- | A helper typeclass for converting 'String' and 'Text' to 'String'.+class IsString s => ToString s where+ -- | Convert strings to 'String'.+ toString :: s -> String++instance Char ~ c => ToString [c] where+ toString = id++instance ToString Text where+ toString = T.unpack++-- | A helper typeclass for converting 'String' and 'Text' to 'Text'.+class IsString s => ToText s where+ -- | Convert strings to 'Text'.+ toText :: s -> Text++instance Char ~ c => ToText [c] where+ toText = T.pack++instance ToText Text where+ toText = id++-- | A helper typeclass for converting 'Text' to either 'String' or 'Text'.+class IsText a where+ -- | Convert from 'Text' to strings+ fromText :: Text -> a++instance Char ~ c => IsText [c] where+ fromText = T.unpack++instance IsText Text where+ fromText = id
+ test/Spec.hs view
@@ -0,0 +1,364 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import qualified Data.Text as T+import qualified Text.Pandoc as P++import Control.Monad.Trans.Writer+import Data.Default (def)+import Data.Either (fromRight)+import Data.Text (Text)+import Test.Tasty+import Test.Tasty.Hspec+import Text.Pandoc.Definition+import Text.Pandoc.Filter.Utils+import Text.Pandoc.Filter.Utils.AttrBuilder++-- * Testing data++-- ** Partial level++blockPara :: Block+blockPara = Para [Str "abcd"]++expectedInlinePartial :: Block+expectedInlinePartial = Para [Str "ABCD"]++expectedInlinePartialL :: Block+expectedInlinePartialL = Para [Str "abcd", Str "abcd"]++-- ** Pandoc level++docPara :: Pandoc+docPara = Pandoc (Meta mempty) [blockPara]++expectedInline :: Pandoc+expectedInline = Pandoc (Meta mempty) [Para [Str "ABCD"]]++expectedBlock :: Pandoc+expectedBlock = Pandoc (Meta mempty) [Plain [Str "abcd"]]++expectedInlineL :: Pandoc+expectedInlineL = Pandoc (Meta mempty) [Para [Str "abcd", Str "abcd"]]++expectedBlockL :: Pandoc+expectedBlockL = Pandoc (Meta mempty) [blockPara, blockPara]++-- ** Composition results++compPara :: Block+compPara = Para [Str "abcd"]++compPara2 :: Block+compPara2 = Para [Str "abcd", Str "efgh"]++expectedDup :: Block+expectedDup = Para [Str "abcd", Str "abcd"]++expectedMerge :: Block+expectedMerge = Para [Str "abcdefgh"]++expectedDupMerge :: Block+expectedDupMerge = Para [Str "abcdabcd"]++expectedMergeDup :: Block+expectedMergeDup = Para [Str "abcd", Str "abcd"]++-- * Filters++-- ** Plain filters++capFilterInline :: Inline -> Inline+capFilterInline (Str str) = Str $ T.toUpper str+capFilterInline x = x++uncapFilterInline :: Inline -> Inline+uncapFilterInline (Str str) = Str $ T.toLower str+uncapFilterInline x = x++unParaFilterBlock :: Block -> Block+unParaFilterBlock (Para ils) = Plain ils+unParaFilterBlock x = x++dupFilterInline :: Inline -> [Inline]+dupFilterInline (Str str) = [Str str, Str str]+dupFilterInline x = [x]++mergeFilterInline :: [Inline] -> [Inline]+mergeFilterInline (Str str1 : Str str2 : xs) = Str (str1 <> str2) : xs+mergeFilterInline x = x++dupFilterBlock :: Block -> [Block]+dupFilterBlock (Para ils) = [Para ils, Para ils]+dupFilterBlock x = [x]++-- ** Monadic filters++extractFilterInlineM :: Inline -> Writer Text Inline+extractFilterInlineM il@(Str str) = tell str >> return (capFilterInline il)+extractFilterInlineM x = return x++extractFilterInlineML :: Inline -> Writer Text [Inline]+extractFilterInlineML il@(Str str) = tell str >> return (dupFilterInline il)+extractFilterInlineML x = return [x]++extractFilter :: Inline -> Writer Text Inline+extractFilter il@(Str str) = tell str >> return il+extractFilter x = return x++-- * Readme example++-- ** Filters++behead :: Block -> Block+behead (Header n _ xs) | n >= 2 = Para [Emph xs]+behead x = x++beheadFilter :: PandocFilter+beheadFilter = mkFilter behead++delink :: Inline -> [Inline]+delink (Link _ txt _) = txt+delink x = [x]++delinkFilter :: PandocFilter+delinkFilter = mkFilter delink++myFilter :: PandocFilter+myFilter = beheadFilter <> delinkFilter++-- ** Documents++readmeText :: Text+readmeText = T.strip $ T.unlines+ [ "## Heading"+ , "Hello, [Pandoc](https://pandoc.org)."+ ]++readmeDoc :: Pandoc+readmeDoc = Pandoc (Meta mempty)+ [ Header 2 ("heading", [], []) [Str "Heading"]+ , Para [ Str "Hello,"+ , Space+ , Link ("",[],[]) [Str "Pandoc"] ("https://pandoc.org","")+ , Str "."+ ]+ ]++expectedHtml :: Text+expectedHtml = T.strip $ T.unlines+ [ "<p><em>Heading</em></p>"+ , "<p>Hello, Pandoc.</p>"+ ]++expectedDoc :: Pandoc+expectedDoc = Pandoc (Meta mempty)+ [ Para [Emph [Str "Heading"]]+ , Para [ Str "Hello,"+ , Space+ , Str "Pandoc"+ , Str "."+ ]+ ]++-- ** The example++mdToHtml+ :: Text -- ^ Input markdown string+ -> Either P.PandocError Text -- ^ Html string or error+mdToHtml md = P.runPure $ do+ doc <- P.readMarkdown def md+ let doc' = applyFilters [beheadFilter, delinkFilter] doc+ P.writeHtml5String def doc'++mdToHtmlCompose+ :: Text -- ^ Input markdown string+ -> Either P.PandocError Text -- ^ Html string or error+mdToHtmlCompose md = P.runPure $ do+ doc <- P.readMarkdown def md+ let doc' = applyFilter myFilter doc+ P.writeHtml5String def doc'+++convertSpec :: Spec+convertSpec = parallel $ do+ describe "mkFilter" $ do+ it "converts a -> a filter to Pandoc -> Pandoc filter" $ do+ applyFilter (mkFilter capFilterInline) docPara `shouldBe` expectedInline+ applyFilter (mkFilter unParaFilterBlock) docPara `shouldBe` expectedBlock++ it "converts a -> a filter to b -> b partial filter" $+ applyFilter (mkFilter capFilterInline) blockPara `shouldBe` expectedInlinePartial++ it "converts a -> [a] filter to Pandoc -> Pandoc filter" $ do+ applyFilter (mkFilter dupFilterInline) docPara `shouldBe` expectedInlineL+ applyFilter (mkFilter dupFilterBlock) docPara `shouldBe` expectedBlockL++ it "converts a -> [a] filter to b -> b partial filter" $+ applyFilter (mkFilter dupFilterInline) blockPara `shouldBe` expectedInlinePartialL++ it "converts a -> m a filter to Pandoc -> m Pandoc filter" $ do+ let (doc, s) = runWriter $ applyFilterM (mkFilter extractFilterInlineM) docPara+ doc `shouldBe` expectedInline+ s `shouldBe` T.pack "abcd"++ it "converts a -> m a filter to b -> m b filter" $ do+ let (bl, s) = runWriter $ applyFilterM (mkFilter extractFilterInlineM) blockPara+ bl `shouldBe` expectedInlinePartial+ s `shouldBe` T.pack "abcd"++ it "converts a -> m [a] filter to Pandoc -> m Pandoc filter" $ do+ let (doc, s) = runWriter $ applyFilterM (mkFilter extractFilterInlineML) docPara+ doc `shouldBe` expectedInlineL+ s `shouldBe` T.pack "abcd"++ it "converts a -> m [a] filter to b -> m b filter" $ do+ let (bl, s) = runWriter $ applyFilterM (mkFilter extractFilterInlineML) blockPara+ bl `shouldBe` expectedInlinePartialL+ s `shouldBe` T.pack "abcd"++ it "converts PartialFilter a to PartialFilter b" $ do+ let fInline = mkFilter capFilterInline :: PartialFilter Inline+ fBlock = mkFilter fInline :: PartialFilter Block+ fPandoc = mkFilter fInline :: PandocFilter+ applyFilter fBlock blockPara `shouldBe` expectedInlinePartial+ applyFilter fPandoc docPara `shouldBe` expectedInline++ it "converts PartialFilterM m a to PartialFilterM m b" $ do+ let fInline :: PartialFilterM (Writer Text) Inline+ fInline = mkFilter extractFilterInlineM+ fBlock = mkFilter fInline :: PartialFilterM (Writer Text) Block+ fPandoc = mkFilter fInline :: PandocFilterM (Writer Text)+ let (doc, s) = runWriter $ applyFilterM fPandoc docPara+ doc `shouldBe` expectedInline+ s `shouldBe` T.pack "abcd"++ let (bl, s') = runWriter $ applyFilterM fBlock blockPara+ bl `shouldBe` expectedInlinePartial+ s' `shouldBe` T.pack "abcd"++ describe "getFilter" $ do+ it "converts PartialFilter a to a -> a" $ do+ let fInline = mkFilter capFilterInline :: PartialFilter Inline+ getFilter fInline (Str "abcd") `shouldBe` Str "ABCD"++ it "converts PartialFilterM m a to a -> m a" $ do+ let fInline :: PartialFilterM (Writer Text) Inline+ fInline = mkFilter extractFilterInlineM+ let (doc, s) = runWriter $ getFilterM fInline (Str "abcd")+ doc `shouldBe` Str "ABCD"+ s `shouldBe` T.pack "abcd"++ it "converts PartialFilter a to b -> b" $ do+ let fInline = mkFilter capFilterInline :: PartialFilter Inline+ fBlock = getFilter fInline :: Block -> Block+ fPandoc = getFilter fInline :: Pandoc -> Pandoc+ fBlock blockPara `shouldBe` expectedInlinePartial+ fPandoc docPara `shouldBe` expectedInline++ it "converts PartialFilterM m a to b -> m b" $ do+ let fInline :: PartialFilterM (Writer Text) Inline+ fInline = mkFilter extractFilterInlineM+ fBlock = getFilterM fInline :: Block -> Writer Text Block+ fPandoc = getFilterM fInline :: Pandoc -> Writer Text Pandoc+ let (doc, s) = runWriter $ fPandoc docPara+ doc `shouldBe` expectedInline+ s `shouldBe` T.pack "abcd"++ let (bl, s') = runWriter $ fBlock blockPara+ bl `shouldBe` expectedInlinePartial+ s' `shouldBe` T.pack "abcd"++ describe "mkConcatedFilter" $ do+ let cap = mkConcatedFilter [uncapFilterInline, capFilterInline]+ uncap = mkConcatedFilter [capFilterInline, uncapFilterInline]+ it "concats filter from left to right" $ do+ applyFilter cap docPara `shouldBe` expectedInline+ applyFilter uncap docPara `shouldBe` docPara++ describe "toFilterM" $+ it "converts a -> a filter to a -> m a filter" $ do+ let capFilterM = toFilterM $ mkFilter capFilterInline+ applyFilterM capFilterM (Str "abcd") `shouldBe` Just (Str "ABCD")++composeSpec :: Spec+composeSpec = parallel $ do+ let dup = mkFilter dupFilterInline+ merge = mkFilter mergeFilterInline+ extract = mkFilter extractFilter+ describe "applyFilter" $ do+ it "applys dup correctly" $+ applyFilter dup compPara `shouldBe` expectedDup++ it "applys merge correctly" $+ applyFilter merge compPara2 `shouldBe` expectedMerge++ describe "applyFilters and monoid" $ do+ it "applys PartialFilter composition from left to right" $ do+ applyFilter (dup <> merge) compPara `shouldBe` expectedDupMerge+ applyFilter (merge <> dup) compPara `shouldBe` expectedMergeDup+ applyFilters [dup, merge] compPara `shouldBe` expectedDupMerge+ applyFilters [merge, dup] compPara `shouldBe` expectedMergeDup++ it "applys PartialFilterM composition from left to right" $ do+ let (doc, s) = runWriter $ applyFilterM (extract <> toFilterM dup) compPara+ doc `shouldBe` expectedDup+ s `shouldBe` T.pack "abcd"++ let (doc', s') = runWriter $ applyFilterM (toFilterM dup <> extract) compPara+ doc' `shouldBe` expectedDup+ s' `shouldBe` T.pack "abcdabcd"++readmeSpec :: Spec+readmeSpec = parallel $+ describe "readme example" $ do+ it "processes filter examples correctly on AST level" $ do+ applyFilters [beheadFilter, delinkFilter] readmeDoc `shouldBe` expectedDoc+ applyFilter myFilter readmeDoc `shouldBe` expectedDoc++ it "processes filter examples correctly on Text level" $ do+ fromRight "" (mdToHtml readmeText) `shouldBe` expectedHtml+ fromRight "" (mdToHtmlCompose readmeText) `shouldBe` expectedHtml++attrBuilderSpec :: Spec+attrBuilderSpec = parallel $ do+ let testAttr = ("id", ["test"], [("k", "v")])+ describe "addClass" $+ it "adds a new class to attributes" $ do+ nullAttr `addClass` "test" `shouldBe` ("", ["test"], [])+ nullAttr `addClass` "test" `addClass` "test2" `shouldBe` ("", ["test2", "test"], [])+ testAttr `addClass` "test2" `shouldBe` ("id", ["test2", "test"], [("k", "v")])++ describe "addClasses" $+ it "adds new classes to attributes" $ do+ nullAttr `addClasses` ["test", "test2"] `shouldBe` ("", ["test", "test2"], [])+ testAttr `addClasses` ["test1", "test2"] `shouldBe` ("id", ["test1", "test2", "test"], [("k", "v")])++ describe "addKVPair" $+ it "adds a new kv pair to attributes" $ do+ nullAttr `addKVPair` ("k", "v") `shouldBe` ("", [], [("k", "v")])+ testAttr `addKVPair` ("k2", "v2") `shouldBe` ("id", ["test"], [("k2", "v2"), ("k", "v")])++ describe "addKVPairs" $+ it "adds new kv pairs to attributes" $ do+ nullAttr `addKVPairs` [("k", "v"), ("k2", "v2")] `shouldBe` ("", [], [("k", "v"), ("k2", "v2")])+ testAttr `addKVPairs` [("k1", "v1"), ("k2", "v2")] `shouldBe` ("id", ["test"], [("k1", "v1"), ("k2", "v2"), ("k", "v")])++ describe "setId" $+ it "sets id of attributes" $ do+ nullAttr `setId` "id" `shouldBe` ("id", [], [])+ testAttr `setId` "id2" `shouldBe` ("id2", ["test"], [("k", "v")])++main :: IO ()+main = do+ testConvert <- testSpec "Filter conversion" convertSpec+ testCompose <- testSpec "Filter composition" composeSpec+ testReadme <- testSpec "Readme examples" readmeSpec+ testAttrBuilder <- testSpec "Attr builder" attrBuilderSpec+ defaultMain $ testGroup "Tests"+ [ testConvert+ , testCompose+ , testReadme+ , testAttrBuilder+ ]