packages feed

yesod-paginator 0.11.0 → 1.1.0.0

raw patch · 11 files changed

+751/−336 lines, 11 filesdep +blaze-markupdep +doctestdep +path-piecesdep −data-defaultdep −resourcetdep −wai-extradep ~basedep ~persistentdep ~yesodPVP ok

version bump matches the API change (PVP)

Dependencies added: blaze-markup, doctest, path-pieces, safe, uri-encode, yesod-persistent

Dependencies removed: data-default, resourcet, wai-extra

Dependency ranges changed: base, persistent, yesod, yesod-core

API changes (from Hackage documentation)

- Yesod.Paginator: paginateWith :: Yesod m => PageWidget m -> Int -> [a] -> HandlerT m IO ([a], WidgetT m IO ())
- Yesod.Paginator: selectPaginatedWith :: (PersistEntity val, PersistEntityBackend val ~ BaseBackend (YesodPersistBackend m), PersistQuery (YesodPersistBackend m), Yesod m) => PageWidget m -> Int -> [Filter val] -> [SelectOpt val] -> YesodDB m ([Entity val], WidgetT m IO ())
- Yesod.Paginator.Widget: Disabled :: Text -> Text -> PageLink
- Yesod.Paginator.Widget: Enabled :: Int -> Text -> Text -> PageLink
- Yesod.Paginator.Widget: PageWidgetConfig :: Text -> Text -> Int -> Bool -> Bool -> [Text] -> PageWidgetConfig
- Yesod.Paginator.Widget: [ascending] :: PageWidgetConfig -> Bool
- Yesod.Paginator.Widget: [listClasses] :: PageWidgetConfig -> [Text]
- Yesod.Paginator.Widget: [nextText] :: PageWidgetConfig -> Text
- Yesod.Paginator.Widget: [pageCount] :: PageWidgetConfig -> Int
- Yesod.Paginator.Widget: [prevText] :: PageWidgetConfig -> Text
- Yesod.Paginator.Widget: [showEllipsis] :: PageWidgetConfig -> Bool
- Yesod.Paginator.Widget: data PageLink
- Yesod.Paginator.Widget: data PageWidgetConfig
- Yesod.Paginator.Widget: defaultPageWidgetConfig :: PageWidgetConfig
- Yesod.Paginator.Widget: defaultWidget :: Yesod m => PageWidget m
- Yesod.Paginator.Widget: getCurrentPage :: Yesod m => HandlerT m IO Int
- Yesod.Paginator.Widget: paginationWidget :: Yesod m => PageWidgetConfig -> PageWidget m
- Yesod.Paginator.Widget: showLink :: [(Text, Text)] -> PageLink -> WidgetT m IO ()
- Yesod.Paginator.Widget: simplePaginationWidget :: Yesod m => PageWidgetConfig -> PageWidget m
- Yesod.Paginator.Widget: type PageWidget m = Int -> Int -> Int -> WidgetT m IO ()
+ Yesod.Paginator: data ItemsCount
+ Yesod.Paginator: data Page a
+ Yesod.Paginator: data PageNumber
+ Yesod.Paginator: data Pages a
+ Yesod.Paginator: data PerPage
+ Yesod.Paginator: ellipsed :: Natural -> PaginationWidget m a
+ Yesod.Paginator: pageItems :: Page a -> [a]
+ Yesod.Paginator: pagesCurrent :: Pages a -> Page a
+ Yesod.Paginator: simple :: Natural -> PaginationWidget m a
+ Yesod.Paginator.Pages: data ItemsCount
+ Yesod.Paginator.Pages: data Page a
+ Yesod.Paginator.Pages: data PageNumber
+ Yesod.Paginator.Pages: data Pages a
+ Yesod.Paginator.Pages: data PerPage
+ Yesod.Paginator.Pages: getNextPage :: Pages a -> Maybe PageNumber
+ Yesod.Paginator.Pages: getPreviousPage :: Pages a -> Maybe PageNumber
+ Yesod.Paginator.Pages: instance GHC.Classes.Eq Yesod.Paginator.Pages.ItemsCount
+ Yesod.Paginator.Pages: instance GHC.Classes.Eq Yesod.Paginator.Pages.PageNumber
+ Yesod.Paginator.Pages: instance GHC.Classes.Eq Yesod.Paginator.Pages.PerPage
+ Yesod.Paginator.Pages: instance GHC.Classes.Eq a => GHC.Classes.Eq (Yesod.Paginator.Pages.Page a)
+ Yesod.Paginator.Pages: instance GHC.Classes.Eq a => GHC.Classes.Eq (Yesod.Paginator.Pages.Pages a)
+ Yesod.Paginator.Pages: instance GHC.Classes.Ord Yesod.Paginator.Pages.ItemsCount
+ Yesod.Paginator.Pages: instance GHC.Classes.Ord Yesod.Paginator.Pages.PageNumber
+ Yesod.Paginator.Pages: instance GHC.Classes.Ord Yesod.Paginator.Pages.PerPage
+ Yesod.Paginator.Pages: instance GHC.Enum.Enum Yesod.Paginator.Pages.ItemsCount
+ Yesod.Paginator.Pages: instance GHC.Enum.Enum Yesod.Paginator.Pages.PageNumber
+ Yesod.Paginator.Pages: instance GHC.Enum.Enum Yesod.Paginator.Pages.PerPage
+ Yesod.Paginator.Pages: instance GHC.Num.Num Yesod.Paginator.Pages.ItemsCount
+ Yesod.Paginator.Pages: instance GHC.Num.Num Yesod.Paginator.Pages.PageNumber
+ Yesod.Paginator.Pages: instance GHC.Num.Num Yesod.Paginator.Pages.PerPage
+ Yesod.Paginator.Pages: instance GHC.Read.Read Yesod.Paginator.Pages.ItemsCount
+ Yesod.Paginator.Pages: instance GHC.Read.Read Yesod.Paginator.Pages.PerPage
+ Yesod.Paginator.Pages: instance GHC.Real.Integral Yesod.Paginator.Pages.ItemsCount
+ Yesod.Paginator.Pages: instance GHC.Real.Integral Yesod.Paginator.Pages.PageNumber
+ Yesod.Paginator.Pages: instance GHC.Real.Integral Yesod.Paginator.Pages.PerPage
+ Yesod.Paginator.Pages: instance GHC.Real.Real Yesod.Paginator.Pages.ItemsCount
+ Yesod.Paginator.Pages: instance GHC.Real.Real Yesod.Paginator.Pages.PageNumber
+ Yesod.Paginator.Pages: instance GHC.Real.Real Yesod.Paginator.Pages.PerPage
+ Yesod.Paginator.Pages: instance GHC.Show.Show Yesod.Paginator.Pages.ItemsCount
+ Yesod.Paginator.Pages: instance GHC.Show.Show Yesod.Paginator.Pages.PageNumber
+ Yesod.Paginator.Pages: instance GHC.Show.Show Yesod.Paginator.Pages.PerPage
+ Yesod.Paginator.Pages: instance GHC.Show.Show a => GHC.Show.Show (Yesod.Paginator.Pages.Page a)
+ Yesod.Paginator.Pages: instance GHC.Show.Show a => GHC.Show.Show (Yesod.Paginator.Pages.Pages a)
+ Yesod.Paginator.Pages: instance Text.Blaze.ToMarkup Yesod.Paginator.Pages.PageNumber
+ Yesod.Paginator.Pages: instance Web.PathPieces.PathPiece Yesod.Paginator.Pages.ItemsCount
+ Yesod.Paginator.Pages: instance Web.PathPieces.PathPiece Yesod.Paginator.Pages.PerPage
+ Yesod.Paginator.Pages: pageItems :: Page a -> [a]
+ Yesod.Paginator.Pages: pageNumber :: Page a -> PageNumber
+ Yesod.Paginator.Pages: pageOffset :: PageNumber -> PerPage -> ItemsCount
+ Yesod.Paginator.Pages: pagesCurrent :: Pages a -> Page a
+ Yesod.Paginator.Pages: pagesLast :: Pages a -> PageNumber
+ Yesod.Paginator.Pages: takeNextPages :: Natural -> Pages a -> [PageNumber]
+ Yesod.Paginator.Pages: takePreviousPages :: Natural -> Pages a -> [PageNumber]
+ Yesod.Paginator.Pages: toPage :: [a] -> PageNumber -> Page a
+ Yesod.Paginator.Pages: toPages :: PageNumber -> PerPage -> ItemsCount -> [a] -> Pages a
+ Yesod.Paginator.Paginate: paginate :: Yesod site => PerPage -> [a] -> HandlerFor site (Pages a)
+ Yesod.Paginator.Paginate: paginate' :: PerPage -> [a] -> PageNumber -> Pages a
+ Yesod.Paginator.Paginate: selectPaginated :: (PersistEntity val, PersistEntityBackend val ~ BaseBackend (YesodPersistBackend site), PersistQuery (YesodPersistBackend site), Yesod site) => PerPage -> [Filter val] -> [SelectOpt val] -> YesodDB site (Pages (Entity val))
+ Yesod.Paginator.Paginate: selectPaginated' :: (MonadIO m, PersistEntity record, PersistEntityBackend record ~ BaseBackend backend, PersistQueryRead backend) => PerPage -> [Filter record] -> [SelectOpt record] -> PageNumber -> ReaderT backend m (Pages (Entity record))
+ Yesod.Paginator.Prelude: instance Web.PathPieces.PathPiece GHC.Natural.Natural
+ Yesod.Paginator.Prelude: nubOn :: Eq b => (a -> b) -> [a] -> [a]
+ Yesod.Paginator.Prelude: tshow :: Show a => a -> Text
+ Yesod.Paginator.Widgets: ellipsed :: Natural -> PaginationWidget m a
+ Yesod.Paginator.Widgets: simple :: Natural -> PaginationWidget m a
+ Yesod.Paginator.Widgets: type PaginationWidget site a = Pages a -> WidgetFor site ()
- Yesod.Paginator: paginate :: Yesod m => Int -> [a] -> HandlerT m IO ([a], WidgetT m IO ())
+ Yesod.Paginator: paginate :: Yesod site => PerPage -> [a] -> HandlerFor site (Pages a)
- Yesod.Paginator: selectPaginated :: (PersistEntity val, PersistEntityBackend val ~ BaseBackend (YesodPersistBackend m), PersistQuery (YesodPersistBackend m), Yesod m) => Int -> [Filter val] -> [SelectOpt val] -> YesodDB m ([Entity val], WidgetT m IO ())
+ Yesod.Paginator: selectPaginated :: (PersistEntity val, PersistEntityBackend val ~ BaseBackend (YesodPersistBackend site), PersistQuery (YesodPersistBackend site), Yesod site) => PerPage -> [Filter val] -> [SelectOpt val] -> YesodDB site (Pages (Entity val))

Files

+ doctest/Main.hs view
@@ -0,0 +1,6 @@+module Main (main) where++import Test.DocTest++main :: IO ()+main = doctest ["-isrc", "src/"]
example/Main.hs view
@@ -1,13 +1,15 @@-{-# LANGUAGE QuasiQuotes           #-}-{-# LANGUAGE TypeFamilies          #-}-{-# LANGUAGE TemplateHaskell       #-}-{-# LANGUAGE OverloadedStrings     #-} {-# LANGUAGE MultiParamTypeClasses #-}-module Main where+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -fno-warn-unused-top-binds #-} +module Main (main) where++import Network.Wai.Handler.Warp (run) import Yesod import Yesod.Paginator-import Network.Wai.Handler.Warp (run)  data App = App @@ -29,27 +31,32 @@                     <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha256-7s5uDGW3AHqw6xtJmNNtr+OBRJUlgkNJEo78P4b0yRw= sha512-nNo+yCHEyn0smMxSswnf/OnX6/KwJuZTlNZBjauKhTK0c+zT+q5JOCx0UFhXQ6rJR9jg6Es8gPuD2uZcYDLqSw==" crossorigin="anonymous">                     ^{pageHead pc}                 <body>-                    ^{pageBody pc}+                    <div .container>+                        ^{pageBody pc}             |]  getRootR :: Handler Html getRootR = do-    -- unneeded return here to match README-    things' <- return [1..1142] :: Handler [Int]+    let things' = [1..1142] :: [Int] -    (things, widget) <- paginate 3 things'+    pages <- paginate 3 things'      defaultLayout $ do         setTitle "My title"         [whamlet|$newline never-            <h1>Pagination-            <p>The things:+            <h1>Pagination Examples+            <h2>The things:             <ul>-                $forall thing <- things+                $forall thing <- pageItems $ pagesCurrent pages                     <li>Thing #{show thing} +            <h2>Simple navigation             <div .pagination>-                ^{widget}+                ^{simple 10 pages}++            <h2>Ellipsed navigation+            <div .pagination>+                ^{ellipsed 10 pages}             |]  main :: IO ()
src/Yesod/Paginator.hs view
@@ -1,113 +1,64 @@-{-# LANGUAGE CPP               #-}-{-# LANGUAGE QuasiQuotes       #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE FlexibleContexts  #-}-{-# LANGUAGE TypeFamilies      #-}-------------------------------------------------------------------------------- -- | ----- Inspiration from a concept by ajdunlap:---      <http://hackage.haskell.org/package/yesod-paginate>+-- There are two pagination functions. One for arbitrary items where you provide+-- the list of things to be paginated: ----- But uses an entirely different approach.+-- @+-- getSomeRoute = do+--     -- 10 items per page+--     pages <- 'paginate' 10 =<< getAllThings ----- There are two pagination functions. One for arbitrary items where you--- provide the list of things to be paginated:+--     defaultLayout $ do+--         [whamlet|+--             $forall thing <- 'pageItems' $ 'pagesCurrent' pages+--                 ^{showThing thing} ----- > getSomeRoute = do--- >     things' <- getAllThings--- >--- >     (things, widget) <- paginate 10 things'--- >--- >     defaultLayout $ do--- >         [whamlet|--- >             $forall thing <- things--- >                 ^{showThing thing}--- >--- >             <div .pagination>--- >                  ^{widget}--- >             |]+--             $# display at most 5 page elements, with current page middle-ish+--             ^{'simple' 5 pages}+--             |]+-- @ ----- And another for paginating directly out of the database, you provide--- the same filters as you would to @selectList@.+-- And another for paginating directly out of the database, you provide the same+-- arguments as you would for @'selectList'@: ----- > getSomeRoute something = do--- >     -- note: things is [Entity val] just like selectList returns--- >     (things, widget) <- runDB $ selectPaginated 10 [SomeThing ==. something] []--- >--- >     defaultLayout $ do--- >         [whamlet|--- >             $forall thing <- things--- >                 ^{showThing $ entityVal thing}--- >--- >             <div .pagination>--- >                  ^{widget}--- >             |]+-- @+-- getSomeRoute something = do+--     pages <- runDB $ 'selectPaginated' 10 [SomeThing ==. something] [] ----- Both functions return a tuple: the first element being the list of--- items (or Entities) to display on this page and the second being a--- widget showing the pagination navagation links.+--     defaultLayout $ do+--         [whamlet|+--             $forall thing <- 'pageItems' $ 'pagesCurrent' pages+--                 ^{showThing $ entityVal thing} ----------------------------------------------------------------------------------+--             ^{'simple' 5 pages}+--             |]+-- @+-- module Yesod.Paginator-    ( paginate-    , paginateWith-    , selectPaginated-    , selectPaginatedWith-    , module Yesod.Paginator.Widget-    ) where--import Yesod-import Yesod.Paginator.Widget--paginate :: Yesod m => Int -> [a] -> HandlerT m IO ([a], WidgetT m IO ())-paginate = paginateWith defaultWidget--paginateWith :: Yesod m-             => PageWidget m-             -> Int-             -> [a]-             -> HandlerT m IO ([a], WidgetT m IO ())-paginateWith widget per items = do-    p <- getCurrentPage+    (+    -- * Type-safe numerics+      PageNumber+    , PerPage+    , ItemsCount -    let tot = length items-    let  xs = take per $ drop ((p - 1) * per) items+    -- * Paginated data+    , Pages+    , pagesCurrent -    return (xs, widget p per tot)+    -- * The current page+    , Page+    , pageItems -selectPaginated :: ( PersistEntity val-#if MIN_VERSION_persistent(2, 5, 0)-                   , PersistEntityBackend val ~ BaseBackend (YesodPersistBackend m)-#else-                   , PersistEntityBackend val ~ YesodPersistBackend m-#endif-                   , PersistQuery (YesodPersistBackend m)-                   , Yesod m-                   )-                => Int-                -> [Filter val]-                -> [SelectOpt val]-                -> YesodDB m ([Entity val], WidgetT m IO ())-selectPaginated = selectPaginatedWith defaultWidget+    -- * Paginators+    , paginate+    , selectPaginated -selectPaginatedWith :: ( PersistEntity val-#if MIN_VERSION_persistent(2, 5, 0)-                       , PersistEntityBackend val ~ BaseBackend (YesodPersistBackend m)-#else-                       , PersistEntityBackend val ~ YesodPersistBackend m-#endif-                       , PersistQuery (YesodPersistBackend m)-                       , Yesod m-                       )-                    => PageWidget m-                    -> Int-                    -> [Filter val]-                    -> [SelectOpt val]-                    -> YesodDB m ([Entity val], WidgetT m IO ())-selectPaginatedWith widget per filters selectOpts = do-    p   <- lift getCurrentPage-    tot <- count filters-    xs  <- selectList filters (selectOpts ++ [OffsetBy ((p-1)*per), LimitTo per])+    -- * Widgets+    , simple+    , ellipsed+    )+where -    return (xs, widget p per tot)+import Yesod.Paginator.Pages+import Yesod.Paginator.Paginate+import Yesod.Paginator.Widgets
+ src/Yesod/Paginator/Pages.hs view
@@ -0,0 +1,150 @@+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Yesod.Paginator.Pages+    (+    -- * Type safe @'Natural'@s+    -- |+    --+    -- N.B. @'PageNumber'@ and @'PerPage'@ will currently allow @0@, but it's+    -- unclear if that's correct and may not be the case in the future.+    --+      PageNumber+    , PerPage+    , ItemsCount+    , pageOffset++    -- * Page+    , Page+    , pageItems+    , pageNumber+    , toPage++    -- * Pages+    , Pages+    , pagesCurrent+    , pagesLast+    , toPages++    -- * Safely accessing Pages data+    , takePreviousPages+    , takeNextPages+    , getPreviousPage+    , getNextPage+    )+where++import Yesod.Paginator.Prelude++import Text.Blaze (ToMarkup)+import Web.PathPieces++newtype PageNumber = PageNumber Natural+    deriving (Enum, Eq, Integral, Num, Ord, Real)+    deriving newtype (Show, ToMarkup)++newtype PerPage = PerPage Natural+    deriving (Enum, Eq, Integral, Num, Ord, Real)+    deriving newtype (Read, Show, PathPiece)++newtype ItemsCount = ItemsCount Natural+    deriving (Enum, Eq, Integral, Num, Ord, Real)+    deriving newtype (Read, Show, PathPiece)++data Page a = Page+    { pageItems :: [a]+    , pageNumber :: PageNumber+    }+    deriving (Eq, Show)++-- | @'Page'@ constructor+toPage :: [a] -> PageNumber -> Page a+toPage = Page++data Pages a = Pages+    { pagesCurrent :: Page a+    , pagesPrevious :: [PageNumber]+    , pagesNext :: [PageNumber]+    , pagesLast :: PageNumber+    }+    deriving (Eq, Show)++-- | Take previous pages, going back from current+--+-- >>> takePreviousPages 3 $ Pages (Page [] 5) [1,2,3,4] [6] 6+-- [2,3,4]+--+takePreviousPages :: Natural -> Pages a -> [PageNumber]+takePreviousPages n = reverse . genericTake n . reverse . pagesPrevious++-- | Take next pages, going forward from current+--+-- >>> takeNextPages 3 $ Pages (Page [] 2) [1] [3,4,5,6] 6+-- [3,4,5]+--+takeNextPages :: Natural -> Pages a -> [PageNumber]+takeNextPages n = genericTake n . pagesNext++-- | The previous page number, if it exists+--+-- >>> getPreviousPage $ Pages (Page [] 1) [] [2,3,4] 4+-- Nothing+--+-- >>> getPreviousPage $ Pages (Page [] 2) [1] [3,4] 4+-- Just 1+--+getPreviousPage :: Pages a -> Maybe PageNumber+getPreviousPage pages = do+    let prevPage = pageNumber (pagesCurrent pages) - 1+    firstPage <- headMay $ pagesPrevious pages+    prevPage <$ guard (prevPage >= firstPage)++-- | The next page number, if it exists+--+-- >>> getNextPage $ Pages (Page [] 4) [1,2,3] [] 4+-- Nothing+--+-- >>> getNextPage $ Pages (Page [] 3) [1,2] [4] 4+-- Just 4+--+getNextPage :: Pages a -> Maybe PageNumber+getNextPage pages = do+    let nextPage = pageNumber (pagesCurrent pages) + 1+    lastPage <- lastMay $ pagesNext pages+    nextPage <$ guard (nextPage <= lastPage)++-- | Construct a @'Pages' a@ from paginated data+--+-- >>> toPages 4 3 10 []+-- Pages {pagesCurrent = Page {pageItems = [], pageNumber = 4}, pagesPrevious = [1,2,3], pagesNext = [], pagesLast = 4}+--+toPages :: PageNumber -> PerPage -> ItemsCount -> [a] -> Pages a+toPages number per total items = Pages+    { pagesCurrent = toPage items number+    , pagesPrevious = [1 .. (number - 1)]+    , pagesNext = [(number + 1) .. lastPage]+    , pagesLast = lastPage+    }+    where lastPage = getLastPage total per++-- | Calculate the last page of some paginated data+--+-- >>> getLastPage 10 3+-- 4+--+-- >>> getLastPage 10 5+-- 2+--+getLastPage :: ItemsCount -> PerPage -> PageNumber+getLastPage total = fromIntegral . carry . (total `divMod`) . fromIntegral+  where+    carry (q, 0) = q+    carry (q, _) = q + 1++-- | Calculate a page's zero-based offset in the overall items+--+-- >>> pageOffset 4 3+-- 9+--+pageOffset :: PageNumber -> PerPage -> ItemsCount+pageOffset p per = fromIntegral $ (fromIntegral p - 1) * per
+ src/Yesod/Paginator/Paginate.hs view
@@ -0,0 +1,92 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}++module Yesod.Paginator.Paginate+    ( paginate+    , paginate'+    , selectPaginated+    , selectPaginated'+    )+where++import Yesod.Paginator.Prelude++import Control.Monad.Trans.Reader (ReaderT)+import Database.Persist+import Yesod.Core+import Yesod.Paginator.Pages+import Yesod.Persist.Core++-- | Paginate a list of items+paginate :: Yesod site => PerPage -> [a] -> HandlerFor site (Pages a)+paginate per items = paginate' per items <$> getCurrentPage++-- | A version where the current page is given+--+-- This can be used to avoid the monadic context altogether.+--+-- >>> paginate' 3 ([1..10] :: [Int]) 1+-- Pages {pagesCurrent = Page {pageItems = [1,2,3], pageNumber = 1}, pagesPrevious = [], pagesNext = [2,3,4], pagesLast = 4}+--+-- >>> paginate' 3 ([1..10] :: [Int]) 2+-- Pages {pagesCurrent = Page {pageItems = [4,5,6], pageNumber = 2}, pagesPrevious = [1], pagesNext = [3,4], pagesLast = 4}+--+-- >>> paginate' 3 ([1..10] :: [Int]) 3+-- Pages {pagesCurrent = Page {pageItems = [7,8,9], pageNumber = 3}, pagesPrevious = [1,2], pagesNext = [4], pagesLast = 4}+--+-- >>> paginate' 3 ([1..10] :: [Int]) 4+-- Pages {pagesCurrent = Page {pageItems = [10], pageNumber = 4}, pagesPrevious = [1,2,3], pagesNext = [], pagesLast = 4}+--+-- >>> paginate' 3 ([1..10] :: [Int]) 5+-- Pages {pagesCurrent = Page {pageItems = [], pageNumber = 5}, pagesPrevious = [1,2,3,4], pagesNext = [], pagesLast = 4}+--+paginate' :: PerPage -> [a] -> PageNumber -> Pages a+paginate' per items p =+    toPages p per (genericLength items) $ genericTake per $ genericDrop+        (pageOffset p per)+        items++-- | Paginate out of a persistent database+selectPaginated+    :: ( PersistEntity val+       , PersistEntityBackend val ~ BaseBackend (YesodPersistBackend site)+       , PersistQuery (YesodPersistBackend site)+       , Yesod site+       )+    => PerPage+    -> [Filter val]+    -> [SelectOpt val]+    -> YesodDB site (Pages (Entity val))+selectPaginated per filters options =+    selectPaginated' per filters options =<< lift getCurrentPage++-- | A version where the current page is given+--+-- This can be used to avoid the Yesod context+--+selectPaginated'+    :: ( MonadIO m+       , PersistEntity record+       , PersistEntityBackend record ~ BaseBackend backend+       , PersistQueryRead backend+       )+    => PerPage+    -> [Filter record]+    -> [SelectOpt record]+    -> PageNumber+    -> ReaderT backend m (Pages (Entity record))+selectPaginated' per filters options p =+    toPages p per <$> (fromIntegral <$> count filters) <*> selectList+        filters+        (options+        <> [ OffsetBy $ fromIntegral $ pageOffset p per+           , LimitTo $ fromIntegral per+           ]+        )++getCurrentPage :: MonadHandler m => m PageNumber+getCurrentPage = fromMaybe 1 . go <$> lookupGetParam "p"+  where+    go :: Maybe Text -> Maybe PageNumber+    go mp = readIntegral . unpack =<< mp
+ src/Yesod/Paginator/Prelude.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE TypeApplications #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Yesod.Paginator.Prelude+    ( module X+    , module Yesod.Paginator.Prelude+    )+where++import Prelude as X++import Control.Monad as X+import Data.List as X+import Data.Maybe as X+import Data.Semigroup as X ((<>))+import Data.Text as X (Text, pack, unpack)+import Numeric.Natural as X+import Safe as X++import Data.Function (on)+import Web.PathPieces++instance PathPiece Natural where+    toPathPiece = toPathPiece @Int . fromIntegral+    fromPathPiece p = do+        n <- fromPathPiece @Int p+        guard $ n >= 0+        pure $ fromIntegral n++tshow :: Show a => a -> Text+tshow = pack . show++nubOn :: Eq b => (a -> b) -> [a] -> [a]+nubOn f = nubBy ((==) `on` f)
− src/Yesod/Paginator/Widget.hs
@@ -1,173 +0,0 @@-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE QuasiQuotes       #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE FlexibleContexts #-}-module Yesod.Paginator.Widget- ( getCurrentPage- , paginationWidget- , defaultWidget- , defaultPageWidgetConfig- , simplePaginationWidget- , PageWidget- , PageWidgetConfig(..)- , PageLink(..)- , showLink- ) where--import Yesod-import Control.Monad (when, liftM)-import Data.Maybe    (fromMaybe)-import Data.Text (Text)--import qualified Data.Text as T---- | currentPage, itemsPerPage, totalItems -> widget-type PageWidget m = Int -> Int -> Int -> WidgetT m IO ()--data PageWidgetConfig = PageWidgetConfig-    { prevText     :: Text   -- ^ The text for the 'previous page' link.-    , nextText     :: Text   -- ^ The text for the 'next page' link.-    , pageCount    :: Int    -- ^ The number of page links to show-    , ascending    :: Bool   -- ^ Whether to list pages in ascending order.-    , showEllipsis :: Bool   -- ^ Whether to show an ellipsis if there are-                             --   more pages than pageCount-    , listClasses  :: [Text] -- ^ Additional classes for top level list-    }---- | Individual links to pages need to follow strict (but sane) markup---   to be styled correctly by bootstrap. This type allows construction---   of such links in both enabled and disabled states.-data PageLink = Enabled Int Text Text -- ^ page, content, class-              | Disabled    Text Text -- ^ content, class---- | Correctly show one of the constructed links-showLink :: [(Text, Text)] -> PageLink -> WidgetT m IO ()-showLink params (Enabled pg cnt cls) = do-    let param = ("p", showT pg)--    [whamlet|$newline never-        <li .#{cls}>-            <a href="#{updateGetParam params param}">#{cnt}-        |]--    where-        updateGetParam :: [(Text,Text)] -> (Text,Text) -> Text-        updateGetParam getParams (p, n) = T.cons '?' . T.intercalate "&"-                                        . map (\(k,v) -> k `T.append` "=" `T.append` v)-                                        . (++ [(p, n)]) . filter ((/= p) . fst) $ getParams--showLink _ (Disabled cnt cls) =-    [whamlet|$newline never-        <li .#{cls} .disabled>-            <a>#{cnt}-        |]---- | Default widget config provided for easy overriding of only some fields.-defaultPageWidgetConfig :: PageWidgetConfig-defaultPageWidgetConfig = PageWidgetConfig { prevText     = "«"-                                           , nextText     = "»"-                                           , pageCount    = 9-                                           , ascending    = True-                                           , showEllipsis = True-                                           , listClasses  = ["pagination"]-                                           }--defaultWidget :: Yesod m => PageWidget m-defaultWidget = paginationWidget defaultPageWidgetConfig---- | A widget showing pagination links. Follows bootstrap principles.---   Utilizes a \"p\" GET param but leaves all other GET params intact.-paginationWidget :: Yesod m => PageWidgetConfig -> PageWidget m-paginationWidget PageWidgetConfig {..} page per tot = do-    -- total / per + 1 for any remainder-    let pages = (\(n, r) -> n + min r 1) $ tot `divMod` per--    when (pages > 1) $ do-        curParams <- handlerToWidget $ liftM reqGetParams getRequest--        [whamlet|$newline never-            <ul class="#{cls}">-                $forall link <- buildLinks page pages-                    ^{showLink curParams link}-            |]--    where-        -- | Concatenate all additional classes.-        cls = T.intercalate " " listClasses--        -- | Build up each component of the overall list of links. We'll-        --   use empty lists to denote ommissions along the way then-        --   concatenate.-        buildLinks :: Int -> Int -> [PageLink]-        buildLinks pg pgs =-            let prev = [1      .. pg - 1]-                next = [pg + 1 .. pgs   ]--                -- these always appear-                prevLink = [(if null prev then Disabled else Enabled (pg - 1)) prevText "prev"]-                nextLink = [(if null next then Disabled else Enabled (pg + 1)) nextText "next"]--                -- show first/last unless we're on it-                firstLink = [ Enabled 1   "1"        "prev" | pg > 1   ]-                lastLink  = [ Enabled pgs (showT pgs) "next" | pg < pgs ]--                -- we'll show ellipsis if there are enough links that some will-                -- be ommitted from the list-                prevEllipsis = [ Disabled "..." "prev" | showEllipsis && length prev > pageCount + 1 ]-                nextEllipsis = [ Disabled "..." "next" | showEllipsis && length next > pageCount + 1 ]--                -- the middle lists, strip the first/last pages and-                -- correctly take up to limit away from current-                prevLinks = reverse . take pageCount . reverse . drop 1 $ map (\p -> Enabled p (showT p) "prev") prev-                nextLinks = take pageCount . reverse . drop 1 . reverse $ map (\p -> Enabled p (showT p) "next") next--                -- finally, this page itself-                curLink = [Disabled (showT pg) "active"]--            in concat $ (if ascending then id else reverse) [ prevLink-                      , firstLink-                      , prevEllipsis-                      , prevLinks-                      , curLink-                      , nextLinks-                      , nextEllipsis-                      , lastLink-                      , nextLink-                      ]---- | A simple widget that only shows next and previous links. Follows bootstrap---   principles. Utilizes a \"p\" GET param but leaves all other GET params---   intact. Uses the same config data type as the main pagination widget, but---   ignores all of the values with the exception of the text for the next and---   previous links.-simplePaginationWidget :: Yesod m => PageWidgetConfig -> PageWidget m-simplePaginationWidget PageWidgetConfig {..} page per tot = do-    -- total number of pages-    let pages = (\(n, r) -> n + min r 1) $ tot `divMod` per--    when (pages > 1) $ do-        curParams <- handlerToWidget $ liftM reqGetParams getRequest--        [whamlet|$newline never-            <ul class="pagination">-                $forall link <- buildLinks page pages-                    ^{showLink curParams link}-            |]--    where-        buildLinks :: Int -> Int -> [PageLink]-        buildLinks pg pgs =-            [ Enabled (pg - 1) prevText "prev" | pg > 1 ] ++-            [ Enabled (pg + 1) nextText "next" | pg < pgs ]---- | looks up the \"p\" GET param and converts it to an Int. returns a---   default of 1 when conversion fails.-getCurrentPage :: Yesod m => HandlerT m IO Int-getCurrentPage = liftM (fromMaybe 1 . go) $ lookupGetParam "p"--    where-        go :: Maybe Text -> Maybe Int-        go mp = readIntegral . T.unpack =<< mp--showT :: (Show a) => a -> Text-showT = T.pack . show
+ src/Yesod/Paginator/Widgets.hs view
@@ -0,0 +1,170 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}++module Yesod.Paginator.Widgets+    ( PaginationWidget+    , simple+    , ellipsed+    )+where++import Yesod.Paginator.Prelude++import qualified Data.Text as T+import Network.URI.Encode (encodeText)+import Yesod.Core+import Yesod.Paginator.Pages++type PaginationWidget site a = Pages a -> WidgetFor site ()++-- | Simple widget, limited to show the given number of total page elements+--+-- Pseudo-HTML for @'simple' 5@, on page 1:+--+-- @+--   \<ul .pagination>+--     \<li .prev .disabled>\<a>«+--     \<li .active .disabled>\<a>1+--     \<li .next>\<a href=\"?p=2\">2+--     \<li .next>\<a href=\"?p=3\">3+--     \<li .next>\<a href=\"?p=4\">4+--     \<li .next>\<a href=\"?p=5\">5+--     \<li .next>\<a href=\"?p=2\">»+-- @+--+-- And page 7:+--+-- @+--   \<ul .pagination>+--     \<li .prev>\<a href=\"?p=6\">«+--     \<li .prev>\<a href=\"?p=5\">5+--     \<li .prev>\<a href=\"?p=6\">6+--     \<li .active .disabled>\<a>7+--     \<li .next>\<a href=\"?p=8\">8+--     \<li .next>\<a href=\"?p=9\">9+--     \<li .next>\<a href=\"?p=8\">»+-- @+--+simple :: Natural -> PaginationWidget m a+simple elements pages = do+    updateGetParams <- getUpdateGetParams++    let (prevPages, nextPages) = getBalancedPages elements pages+        mPrevPage = getPreviousPage pages+        mNextPage = getNextPage pages++    [whamlet|$newline never+        <ul .pagination>+            $maybe prevPage <- mPrevPage+                <li .prev>+                    <a href=#{renderGetParams $ updateGetParams prevPage}>«+            $nothing+                <li .prev .disabled>+                    <a>«+            $forall number <- prevPages+                <li .prev >+                    <a href=#{renderGetParams $ updateGetParams number}>#{number}+            $with number <- pageNumber $ pagesCurrent pages+                <li .active .disabled>+                    <a>#{number}+            $forall number <- nextPages+                <li .next>+                    <a href=#{renderGetParams $ updateGetParams number}>#{number}+            $maybe nextPage <- mNextPage+                <li .next>+                    <a href=#{renderGetParams $ updateGetParams nextPage}>»+            $nothing+                <li .next .disabled>+                    <a>»+        |]++-- | Show pages before and after, ellipsis, and first/last+ellipsed :: Natural -> PaginationWidget m a+ellipsed elements pages = do+    updateGetParams <- getUpdateGetParams++    let (prevPages, nextPages) = getBalancedPages elements pages++        mPrevPage = getPreviousPage pages+        mNextPage = getNextPage pages++        (mFirstPage, firstEllipses)+            | pageNumber (pagesCurrent pages) == 1 = (Nothing, False)+            | headMay prevPages == Just 1 = (Nothing, False)+            | headMay prevPages == Just 2 = (Just 1, False)+            | otherwise = (Just 1, True)++        (mLastPage, lastEllipses)+            | pageNumber (pagesCurrent pages) == pagesLast pages = (Nothing, False)+            | lastMay nextPages == Just (pagesLast pages) = (Nothing, False)+            | lastMay nextPages == Just (pagesLast pages - 1) = (Just $ pagesLast pages, False)+            | otherwise = (Just $ pagesLast pages, True)++    [whamlet|$newline never+        <ul .pagination>+            $maybe prevPage <- mPrevPage+                <li .prev>+                    <a href=#{renderGetParams $ updateGetParams prevPage}>«+            $nothing+                <li .prev .disabled>+                    <a>«+            $maybe firstPage <- mFirstPage+                <li .prev>+                    <a href=#{renderGetParams $ updateGetParams firstPage}>#{firstPage}+                $if firstEllipses+                    <li .prev .disabled>+                        <a>…+            $forall number <- prevPages+                <li .prev >+                    <a href=#{renderGetParams $ updateGetParams number}>#{number}+            $with number <- pageNumber $ pagesCurrent pages+                <li .active .disabled>+                    <a>#{number}+            $forall number <- nextPages+                <li .next>+                    <a href=#{renderGetParams $ updateGetParams number}>#{number}+            $maybe lastPage <- mLastPage+                $if lastEllipses+                    <li .next .disabled>+                        <a>…+                <li .next>+                    <a href=#{renderGetParams $ updateGetParams lastPage}>#{lastPage}+            $maybe nextPage <- mNextPage+                <li .next>+                    <a href=#{renderGetParams $ updateGetParams nextPage}>»+            $nothing+                <li .next .disabled>+                    <a>»+        |]++-- | Calculate previous and next pages to produce an overall number of elements+--+-- >>> let page n = toPages n 2 20 [] :: Pages Int+-- >>> getBalancedPages 6 $ page 1+-- ([],[2,3,4,5,6])+--+-- >>> getBalancedPages 6 $ page 6+-- ([3,4,5],[7,8])+--+-- >>> getBalancedPages 6 $ page 10+-- ([5,6,7,8,9],[])+--+getBalancedPages :: Natural -> Pages a -> ([PageNumber], [PageNumber])+getBalancedPages elements pages =+    if genericLength nextPages >= (elements `div` 2)+        then (prevPagesNaive, nextPages)+        else (prevPagesCalcd, nextPages)+  where+    nextPages = takeNextPages (elements - genericLength prevPagesNaive - 1) pages+    prevPagesNaive = takePreviousPages (elements `div` 2) pages+    prevPagesCalcd = takePreviousPages (elements - genericLength nextPages - 1) pages++getUpdateGetParams :: WidgetFor site (PageNumber -> [(Text, Text)])+getUpdateGetParams = do+    params <- handlerToWidget $ reqGetParams <$> getRequest+    pure $ \number -> nubOn fst $ [("p", tshow number)] <> params++renderGetParams :: [(Text, Text)] -> Text+renderGetParams [] = ""+renderGetParams ps = "?" <> T.intercalate "&" (map renderGetParam ps)+    where renderGetParam (k, v) = encodeText k <> "=" <> encodeText v
+ test/SpecHelper.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ViewPatterns #-}++module SpecHelper+    ( module SpecHelper+    , module X+    )+where++import Test.Hspec as X+import Yesod.Core+import Yesod.Paginator as X+import Yesod.Paginator.Prelude as X+import Yesod.Test as X++data App = App++mkYesod "App" [parseRoutes|+    /simple/#ItemsCount/#PerPage/#Natural SimpleR GET+    /ellipsed/#ItemsCount/#PerPage/#Natural EllipsedR GET+|]++instance Yesod App++getSimpleR :: ItemsCount -> PerPage -> Natural -> Handler Html+getSimpleR total per elements = do+    pages <- paginate per $ genericReplicate total ()+    defaultLayout [whamlet|^{simple elements pages}|]++getEllipsedR :: ItemsCount -> PerPage -> Natural -> Handler Html+getEllipsedR total per elements = do+    pages <- paginate per $ genericReplicate total ()+    defaultLayout [whamlet|^{ellipsed elements pages}|]++withApp :: SpecWith (TestApp App) -> Spec+withApp = before $ pure (App, id)
+ test/Yesod/Paginator/WidgetsSpec.hs view
@@ -0,0 +1,100 @@+{-# LANGUAGE OverloadedStrings #-}+module Yesod.Paginator.WidgetsSpec+    ( spec+    )+where++import SpecHelper++spec :: Spec+spec = withApp $ do+    describe "simple" $ it "works" $ do+        get $ SimpleR 10 3 3++        statusIs 200+        bodyContains $ concat+            [ "<ul class=\"pagination\">"+            , "<li class=\"prev disabled\"><a>«</a></li>"+            , "<li class=\"active disabled\"><a>1</a></li>"+            , "<li class=\"next\"><a href=\"?p=2\">2</a></li>"+            , "<li class=\"next\"><a href=\"?p=3\">3</a></li>"+            , "<li class=\"next\"><a href=\"?p=2\">»</a></li>"+            , "</ul>"+            ]++        request $ do+            addGetParam "p" "3"+            setUrl $ SimpleR 10 3 3++        statusIs 200+        bodyContains $ concat+            [ "<ul class=\"pagination\">"+            , "<li class=\"prev\"><a href=\"?p=2\">«</a></li>"+            , "<li class=\"prev\"><a href=\"?p=2\">2</a></li>"+            , "<li class=\"active disabled\"><a>3</a></li>"+            , "<li class=\"next\"><a href=\"?p=4\">4</a></li>"+            , "<li class=\"next\"><a href=\"?p=4\">»</a></li>"+            , "</ul>"+            ]++        request $ do+            addGetParam "p" "4"+            setUrl $ SimpleR 10 3 3++        statusIs 200+        bodyContains $ concat+            [ "<ul class=\"pagination\">"+            , "<li class=\"prev\"><a href=\"?p=3\">«</a></li>"+            , "<li class=\"prev\"><a href=\"?p=2\">2</a></li>"+            , "<li class=\"prev\"><a href=\"?p=3\">3</a></li>"+            , "<li class=\"active disabled\"><a>4</a></li>"+            , "<li class=\"next disabled\"><a>»</a></li>"+            , "</ul>"+            ]++    describe "ellipsed" $ it "works" $ do+        get $ EllipsedR 10 3 3++        statusIs 200+        bodyContains $ concat+            [ "<ul class=\"pagination\">"+            , "<li class=\"prev disabled\"><a>«</a></li>"+            , "<li class=\"active disabled\"><a>1</a></li>"+            , "<li class=\"next\"><a href=\"?p=2\">2</a></li>"+            , "<li class=\"next\"><a href=\"?p=3\">3</a></li>"+            , "<li class=\"next\"><a href=\"?p=4\">4</a></li>"+            , "<li class=\"next\"><a href=\"?p=2\">»</a></li>"+            , "</ul>"+            ]++        get $ EllipsedR 15 3 3++        statusIs 200+        bodyContains $ concat+            [ "<ul class=\"pagination\">"+            , "<li class=\"prev disabled\"><a>«</a></li>"+            , "<li class=\"active disabled\"><a>1</a></li>"+            , "<li class=\"next\"><a href=\"?p=2\">2</a></li>"+            , "<li class=\"next\"><a href=\"?p=3\">3</a></li>"+            , "<li class=\"next disabled\"><a>…</a></li>"+            , "<li class=\"next\"><a href=\"?p=5\">5</a></li>"+            , "<li class=\"next\"><a href=\"?p=2\">»</a></li>"+            , "</ul>"+            ]++        request $ do+            addGetParam "p" "5"+            setUrl $ EllipsedR 15 3 3++        statusIs 200+        bodyContains $ concat+            [ "<ul class=\"pagination\">"+            , "<li class=\"prev\"><a href=\"?p=4\">«</a></li>"+            , "<li class=\"prev\"><a href=\"?p=1\">1</a></li>"+            , "<li class=\"prev disabled\"><a>…</a></li>"+            , "<li class=\"prev\"><a href=\"?p=3\">3</a></li>"+            , "<li class=\"prev\"><a href=\"?p=4\">4</a></li>"+            , "<li class=\"active disabled\"><a>5</a></li>"+            , "<li class=\"next disabled\"><a>»</a></li>"+            , "</ul>"+            ]
yesod-paginator.cabal view
@@ -1,61 +1,100 @@-name:                   yesod-paginator-version:                0.11.0-synopsis:               A pagination approach for yesod-description:            Paginate a list showing a per-item widget and links to other pages-homepage:               http://github.com/pbrisbin/yesod-paginator-license:                BSD3-license-file:           LICENSE-author:                 Patrick Brisbin-maintainer:             pbrisbin@gmail.com-category:               Web, Yesod-build-type:             Simple-cabal-version:          >=1.8+-- This file has been generated from package.yaml by hpack version 0.28.2.+--+-- see: https://github.com/sol/hpack+--+-- hash: 4dbcdeb4bab34224d2c65b70f8a52d4759b83a23c55b5e77e4e616bce3152296 -flag example-  description: Build the example application-  default: False+name:           yesod-paginator+version:        1.1.0.0+synopsis:       A pagination approach for yesod+description:    Paginate a list showing a per-item widget and links to other pages+category:       Web, Yesod+homepage:       http://github.com/pbrisbin/yesod-paginator+author:         Patrick Brisbin+maintainer:     pbrisbin@gmail.com+license:        BSD3+license-file:   LICENSE+build-type:     Simple+cabal-version:  >= 1.10 -library-  hs-source-dirs:       src-  exposed-modules:      Yesod.Paginator-                        Yesod.Paginator.Widget+source-repository head+  type: git+  location: git://github.com/pbrisbin/yesod-paginator.git -  build-depends:        base       >= 4    && < 5-                      , text       >= 0.11 && < 2.0-                      , yesod      >= 1.4-                      , persistent >= 2.0-                      , resourcet  >= 0.4.4-                      , transformers+flag examples+  description: Build the examples+  manual: False+  default: False +library+  exposed-modules:+      Yesod.Paginator+      Yesod.Paginator.Pages+      Yesod.Paginator.Paginate+      Yesod.Paginator.Prelude+      Yesod.Paginator.Widgets+  other-modules:+      Paths_yesod_paginator+  hs-source-dirs:+      src+  default-extensions: NoImplicitPrelude   ghc-options: -Wall--test-suite test-    type:               exitcode-stdio-1.0-    main-is:            Spec.hs-    hs-source-dirs:     test-    ghc-options:        -Wall-    build-depends:      base-                      , hspec-                      , yesod-paginator-                      , data-default-                      , wai-extra-                      , yesod-core-                      , yesod-test+  build-depends:+      base >4.8.0 && <5+    , blaze-markup+    , path-pieces+    , persistent >=2.5+    , safe+    , text >=0.11 && <2.0+    , transformers+    , uri-encode+    , yesod-core >=1.4+    , yesod-persistent >=1.4+  default-language: Haskell2010  executable yesod-paginator-example-  if flag(example)-    buildable: True-  else+  main-is: Main.hs+  other-modules:+      Paths_yesod_paginator+  hs-source-dirs:+      example+  ghc-options: -Wall+  build-depends:+      base >4.8.0 && <5+    , warp+    , yesod+    , yesod-paginator+  if !(flag(examples))     buildable: False+  default-language: Haskell2010 -  main-is:              Main.hs-  hs-source-dirs:       example-  ghc-options:          -Wall-  build-depends:        base-                      , yesod-                      , yesod-paginator-                      , warp+test-suite doctests+  type: exitcode-stdio-1.0+  main-is: Main.hs+  other-modules:+      Paths_yesod_paginator+  hs-source-dirs:+      doctest+  ghc-options: -Wall+  build-depends:+      base >4.8.0 && <5+    , doctest+  default-language: Haskell2010 -source-repository       head-  type:                 git-  location:             git://github.com/pbrisbin/yesod-paginator.git+test-suite test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      SpecHelper+      Yesod.Paginator.WidgetsSpec+      Paths_yesod_paginator+  hs-source-dirs:+      test+  ghc-options: -Wall+  build-depends:+      base >4.8.0 && <5+    , hspec+    , yesod-core+    , yesod-paginator+    , yesod-test+  default-language: Haskell2010