packages feed

ytl (empty) → 0.1.0.0

raw patch · 11 files changed

+677/−0 lines, 11 filesdep +basedep +mtldep +pointedsetup-changed

Dependencies added: base, mtl, pointed, template-haskell, yesod-core

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Isaac van Bakel (c) 2020++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Isaac van Bakel nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,62 @@+# ytl - Yesod Transformer Library++[Yesod](https://github.com/yesodweb/yesod) is a powerful, type-safe webserver library. At its core, Yesod lets you describe *foundation sites* which carry the configuration of your webserver, and *handlers* and *widgets* which use that foundation to handle requests, generate HTML and CSS, and even access a database.++Haskellers who are familiar with [mtl](https://github.com/haskell/mtl) are used to transforming monad stacks, adding or computing monadic features at whim, to introduce wide-ranging effects to a program with a few small, type-safe changes. Unfortunately, Yesod does not support monad transformers - its basic `HandlerFor` and `WidgetFor` monads are woven into the server code, and won't allow for other monads to stack on top.++However, a `mtl`-style approach can be done for Yesod - the secret is, rather than transforming Yesod's `HandlerFor` and `WidgetFor` monads, to *transform the foundation site itself*. The result is `ytl` - the Yesod transfomer library, which exports a similar API to `mtl`, but in terms of site transformers.++A site transformer wraps a Yesod foundation site in additional information. Yesod's code is fine with custom foundation types, so the wrapped site can be used in Yesod code as normal - giving additional effects to existing Yesod code for "free".++## An example++The `ReaderSite` transformer adds additional data to a site, similar to how `ReaderT` adds data to a monad stack. The data accessible by `ReaderSite` can be accessed through a `MonadReader`-style API in Yesod's `HandlerFor` and `WidgetFor` monads - like this:++```haskell+useValue+  :: (SiteReader r site)+  => HandlerFor site a+  -> (r -> m site b)+  -> HandlerFor site (a, b)+useValue mArg f = do+  a <- mArg+  r <- ask+  b <- f r+  pure (a, b)+```++## Writing your own transformers++Writing your own `ytl` site transformers is relatively simple. Define your site wrapper, and some class for its API - in the above example, `ReaderSite` is the wrapper, and `SiteReader` is the API class.++Implement the class for the wrapper (duh!). Then, to interact nicely with the other transformers, you'll want to define a lifting instance, which lifts the API implementation through any transformer layers. For `ReaderSite`, this looks like+```haskell+instance {-# OVERLAPPABLE #-}+  (SiteTrans t, SiteReader r site) => SiteReader r (t site) where+  ...+```++### Implement `RenderRoute`, and maybe `Yesod`++You'll also need to implement `RenderRoute` for your wrapped site type - make sure to make the route type coercible to the route type of the base site, because `ytl` relies on them being representation-identical (see `SiteCompatible`)! This is normally fine, because you don't want to modify the routes anyways.++If your site transformer changes some `Yesod` behaviour - like logging, middleware, etc. - you need to also implement `Yesod` for your wrapped site type. `ytl` comes with some Template Haskell helpers for writing `Yesod` implementations that pass through to the base site most of the time; so if you just want to override how logging works, you can ignore the other `Yesod` methods and they'll be implemented for you.++### Why isn't my translation of this mtl type working?++Remember that, in Yesod's handlers and widgets, the site is in *reader* position - it gets passed into Yesod functions. That means that the site type is basically contravariant (as evidenced by `withSiteT`) - so `HandlerFor` and `WidgetFor` are an awful lot like profunctors. That means that you can't translate `mtl`-style code directly - you have to consider the difference in data flow.++For example, `ReaderSite` is not an exact copy of `ReaderT`: `ReaderT` takes the read value as an argument:+```+data ReaderT m a = ReaderT (r -> m a)+```+But for Yesod, the site is already in reader position - taking `r` as an argument wouldn't have the right semantics. Instead, `ReaderSite` includes `r` *alongside* the site:+```+data ReaderSite r site = ReaderSite r site+```+That looks very different. However, they end up being very similar in use:+```haskell+runReaderT :: ReaderT r m a -> r -> m a++runReaderSite :: HandlerFor (ReaderSite r site) a -> r -> HandlerFor site a+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/Yesod/Site/Class.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE QuantifiedConstraints #-}++module Yesod.Site.Class+  ( MonadSite (..)+  ) where++import Yesod.Site.Util++import Control.Monad.Reader+import Yesod.Core.Types++-- | A unified class instance for Yesod's site-using monads+--+-- This is used for functions which work for both 'WidgetFor' and 'HandlerFor'.+class (forall site. MonadIO (m site)) => MonadSite (m :: * -> * -> *) where+  -- | Get the site itself in a computation+  askSite :: m site site++  -- | Run a computation under a given site transformation+  --+  -- This is the main entry point for site transformations - note that the+  -- site parameter is contravariant.+  withSiteT+    :: SiteCompatible site site'+    => (site -> site')+    -> m site' a+    -> m site a++instance MonadSite HandlerFor where+  askSite = do+    hd <- ask+    pure (getSite hd)++  withSiteT siteT (HandlerFor innerHandler)+    = HandlerFor (innerHandler . withSite siteT)++instance MonadSite WidgetFor where+  askSite = do+    wd <- ask+    pure (getWidgetSite wd)++  withSiteT siteT (WidgetFor innerWidget)+    = WidgetFor (innerWidget . withWidgetSite siteT)+
+ src/Yesod/Site/Util.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RecordWildCards #-}++module Yesod.Site.Util+  ( SiteCompatible++  , getSite+  , getWidgetSite++  , withSite+  , withWidgetSite+  ) where++import Data.Coerce+import Yesod.Core.Types++getSite :: HandlerData child site -> site+getSite = rheSite . handlerEnv++getWidgetSite :: WidgetData site -> site+getWidgetSite = getSite . wdHandler++type SiteCompatible site site' = (Coercible (Route site) (Route site'), Coercible (Route site') (Route site))++withWidgetSite+  :: SiteCompatible site site'+  => (site -> site')+  -> WidgetData site+  -> WidgetData site'+withWidgetSite f WidgetData{..}+  = WidgetData+      { wdRef = coerce wdRef+      , wdHandler = withSite f wdHandler+      }++withSite+  :: SiteCompatible site site'+  => (site -> site')+  -> HandlerData site site+  -> HandlerData site' site'+withSite f = withSubSite f . withSuperSite f++withSubSite+  :: SiteCompatible site site'+  => (site -> site')+  -> HandlerData site parent+  -> HandlerData site' parent+withSubSite f HandlerData{..}+  = let RunHandlerEnv{..} = handlerEnv+    in+      HandlerData+        { handlerEnv = RunHandlerEnv+            { rheChild = f rheChild+            , rheRoute = coerce rheRoute+            , rheRouteToMaster = rheRouteToMaster . coerce+            , ..+            }+        , ..+        }++withSuperSite+  :: SiteCompatible site site'+  => (site -> site')+  -> HandlerData child site+  -> HandlerData child site'+withSuperSite f HandlerData{..}+  = let RunHandlerEnv{..} = handlerEnv+    in+      HandlerData+        { handlerEnv = RunHandlerEnv+            { rheSite = f rheSite+            , rheRender = rheRender . coerce+            , rheRouteToMaster = coerce . rheRouteToMaster+            , ..+            }+        , ..+        }+
+ src/Yesod/Trans.hs view
@@ -0,0 +1,26 @@+{-# OPTIONS_GHC -Wno-orphans -Wno-overlapping-patterns #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE UndecidableInstances #-}+module Yesod.Trans+  ( module Yesod.Trans.Class+  ) where++import Yesod.Site.Util+import Yesod.Trans.Class+import Yesod.Trans.TH++import Data.Copointed+import Yesod.Core++defaultYesodInstanceExcept [| copoint |] [d|+  instance {-# OVERLAPPABLE #-}+    ( SiteTrans t+    , RenderRoute (t site)+    , SiteCompatible site (t site)+    , Copointed t+    , Yesod site+    ) => Yesod (t site) where+  |]
+ src/Yesod/Trans/Class.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE UndecidableInstances #-}++module Yesod.Trans.Class+  ( SiteTrans (..)+  ) where++import Yesod.Site.Class+import Yesod.Site.Util++-- | The class of site transformations+--+-- A site transformation is a wrapper around a Yesod foundation site which+-- augments it with additional functionality.+class SiteTrans (t :: * -> *) where+  -- | Lift a Yesod computation to a transformed computation, typically by+  -- wrapping the foundation site directly+  lift :: (MonadSite m) => m site a -> m (t site) a++  -- | Transform the Yesod computation under a site transformation+  --+  -- Unlike transformers and mtl, ytl does not allow transformers which are+  -- not functors in the category of (Yesod) monads. This is because all such+  -- transformed monads must still be isomorphic to @ReaderT@ (since that is+  -- their underlying representation in Yesod).+  mapSiteT+    :: (MonadSite m, MonadSite n, SiteCompatible site site')+    => (m site a -> n site' b)+    -> m (t site) a -> n (t site') b+
+ src/Yesod/Trans/Class/Reader.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++module Yesod.Trans.Class.Reader+  ( ReaderSite (..)+  , runReaderSite++  , SiteReader (..)+  ) where++import Yesod.Site.Class+import Yesod.Trans.Class++import Data.Copointed+import Yesod.Core+  ( RenderRoute (..)+  )++-- | The class of sites which can read some data+class SiteReader r site | site -> r where+  {-# MINIMAL (ask | reader), local #-}+  -- | Get the data value+  ask :: (MonadSite m) => m site r+  ask = reader id+  -- | Extract a value from the data+  reader :: (MonadSite m) => (r -> a) -> m site a+  reader f = f <$> ask++  -- | Run a computation with a transformed version of the current data+  -- value+  local :: (MonadSite m) => (r -> r) -> m site a -> m site a++instance {-# OVERLAPPABLE #-}+  (SiteTrans t, SiteReader r site) => SiteReader r (t site) where+  ask = lift ask+  reader f = lift $ reader f++  local f = mapSiteT (local f)++-- | A site transformation which extends a site with some additional data+-- which can be read+data ReaderSite r site = ReaderSite+  { readVal :: r+  , unReaderSite :: site+  }++-- | Compute the effect of 'ReaderSite' by passing in the data value to be+-- used when reading+runReaderSite+  :: (MonadSite m)+  => r+  -> m (ReaderSite r site) a+  -> m site a+runReaderSite r+  = withSiteT (ReaderSite r)++instance Copointed (ReaderSite r) where+  copoint = unReaderSite++instance {-# OVERLAPPING #-} SiteReader r (ReaderSite r site) where+  ask = do+    ReaderSite r _ <- askSite+    pure r++  local f = withSiteT (\(ReaderSite r site) -> ReaderSite (f r) site)++instance RenderRoute site => RenderRoute (ReaderSite r site) where+  newtype Route (ReaderSite r site) = ReaderRoute (Route site)+  renderRoute (ReaderRoute route) = renderRoute route++deriving instance Eq (Route site) => Eq (Route (ReaderSite r site))++instance SiteTrans (ReaderSite r) where+  lift = withSiteT unReaderSite++  mapSiteT runner argM = do+    ReaderSite r _ <- askSite+    withSiteT unReaderSite $ runner $ withSiteT (ReaderSite r) argM
+ src/Yesod/Trans/Class/Writer.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++module Yesod.Trans.Class.Writer+  ( WriterSite (..)+  , runWriterSite++  , SiteWriter (..)+  ) where++import Yesod.Site.Class+import Yesod.Trans.Class+import Yesod.Trans.Class.Reader++import Data.Copointed+import Data.IORef+import Yesod.Core+  ( liftIO+  , RenderRoute (..)+  )++-- TODO: Decide if we want a lazy/strict distinction++-- | The class of sites which have some writing output+class (Monoid w) => SiteWriter w site | site -> w where+  {-# MINIMAL (writer | tell), listen, pass #-}++  -- | Write something to the output, returning a wrapped value+  writer :: (MonadSite m) => (a, w) -> m site a+  writer (a, w) = tell w >> pure a++  -- | Write something to the output+  tell :: (MonadSite m) => w -> m site ()+  tell w = writer ((), w)++  -- | Run a computation, returning the resulting contents of the output+  listen :: (MonadSite m) => m site a -> m site (a, w)++  -- | Run a computation which modifies the output+  pass :: MonadSite m => m site (a, w -> w) -> m site a++instance {-# OVERLAPPABLE #-}+  (SiteTrans t, SiteWriter w site) => SiteWriter w (t site) where+  writer = lift . writer+  tell = lift . tell++  listen = mapSiteT listen+  pass = mapSiteT pass++-- | A site transformation which extends a site with some writing output+newtype WriterSite w site = WriterSite+  { unWriterSite :: ReaderSite (IORef w) site+    -- "Is an IORef safe in Yesod sites?"+    --+    -- Yes, because the Yesod code uses one to build its web pages.+  }++-- | Compute the effect of a 'WriterSite', getting back the output after having+-- run the computation+runWriterSite+  :: (MonadSite m, Monoid w)+  => m (WriterSite w site) a+  -> m site (a, w)+runWriterSite inner = do+  wRef <- liftIO (newIORef mempty)+  a <- runReaderSite wRef $ withSiteT WriterSite $ inner+  w <- liftIO $ readIORef wRef+  pure (a, w)++instance Copointed (WriterSite w) where+  copoint = copoint . unWriterSite++instance {-# OVERLAPPING #-} (Monoid w) => SiteWriter w (WriterSite w site) where+  tell v = withSiteT unWriterSite do+    wRef <- ask+    liftIO $ modifyIORef' wRef (<> v)++  listen argM = do+    a <- argM+    withSiteT unWriterSite do+      wRef <- ask+      w <- liftIO $ readIORef wRef+      pure (a, w)++  pass modM = do+    (a, f) <- modM+    withSiteT unWriterSite do+      wRef <- ask+      liftIO $ modifyIORef' wRef f+      pure a++instance RenderRoute site => RenderRoute (WriterSite w site) where+  newtype Route (WriterSite w site) = WriterRoute (Route (ReaderSite (IORef w) site))+  renderRoute (WriterRoute route) = renderRoute route++deriving instance Eq (Route site) => Eq (Route (WriterSite w site))++instance SiteTrans (WriterSite w) where+  lift = withSiteT unWriterSite . lift++  mapSiteT runner argM = do+    withSiteT unWriterSite $ mapSiteT runner $ withSiteT WriterSite argM
+ src/Yesod/Trans/TH.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}++module Yesod.Trans.TH+  ( defaultYesodInstanceExcept+  ) where++import Yesod.Site.Util+import Yesod.Trans.Class++import Data.Coerce+import Yesod.Core+  ( Approot (..)+  , guessApproot+  , HtmlUrl+  , RenderRoute (..)+  , ScriptLoadPosition (..)+  , Yesod (..)+  )++import Language.Haskell.TH++coerceHtmlUrl+  :: SiteCompatible site site'+  => HtmlUrl (Route site) -> HtmlUrl (Route site')+coerceHtmlUrl url = url . (. coerce)++-- | Fills in an instance for the 'Yesod' class for a 'SiteTrans' wrapper with+-- implementations which just invoke the base class, *except* for those implementations+-- which are defined on the instance itself.+--+-- This is useful for 'SiteTrans' implementations that want to modify some of+-- the 'Yesod' behaviour of the site, but mostly want to delegate behaviour to+-- the base site. Instead of writing out a whole 'Yesod' instance, you can just+-- override the class methods that you need. The rest will be filled in with+-- working implementations that do what you expect.+defaultYesodInstanceExcept+  :: Q Exp -- ^ How to go from the wrapped site to the base site.+           -- If the instance is for a type of the form 't site', then this+           -- should be an expression of type 't site -> site'. This operation+           -- is necessary to use some of the default implementations.+  -> Q [Dec] -- ^ The partial 'Yesod' instance. Should just be a since @instance@+             -- declaration, except that its body can be as empty as you like. For+             -- example:+             --+             -- @+             -- defaultYesodInstanceExcept [| myLowerer |] [d|+             --   instance (Yesod site) => Yesod (MyWrapper site) where+             --     yesodMiddleware = ... -- insert some middleware+             --+             --     -- But everything else should be defined in the default way+             --   |]+             -- @+             --+             -- This declaration will include the custom definition for+             -- 'yesodMiddleware', as well as implementations for the other+             -- class methods that just delegate to the base class.+  -> Q [Dec]+defaultYesodInstanceExcept baseSite partialInstanceQ = do+  [InstanceD overlap ctxt instanceHead exceptions] <- partialInstanceQ+  defaultImplementations <- defaultImplementationsQ++  let fullBody = exceptions <> (filter (`undeclaredIn` exceptions) defaultImplementations)++  pure [InstanceD overlap ctxt instanceHead fullBody]+  where+    decName :: Dec -> Maybe Name+    decName (FunD name _) = Just name+    decName (ValD (VarP name) _ _) = Just name+    decName _ = Nothing++    undeclaredIn :: Dec -> [Dec] -> Bool+    undeclaredIn dec+      | Just name <- decName dec+      = not . any (maybe False (== name) . decName)+    undeclaredIn _ = const True++    defaultImplementationsQ = [d|+      $(pure $ VarP 'approot) = case $([|approot|]) of+        ApprootRelative -> ApprootRelative+        ApprootStatic t -> ApprootStatic t+        ApprootMaster f -> ApprootMaster (f . $(baseSite))+        ApprootRequest f -> ApprootRequest (f. $(baseSite))+        -- Approot is non-exhaustive, so for API compatibility, we need a+        -- (apparently redundant) fallthrough case+        _ -> guessApproot++      $(pure $ VarP 'errorHandler) = lift . $([|errorHandler|])++      $(pure $ VarP 'defaultLayout) = mapSiteT $([|defaultLayout|])++      $(pure $ VarP 'urlParamRenderOverride) = \site route ->+        $([|urlParamRenderOverride|]) ($(baseSite) site) (coerce route)++      $(pure $ VarP 'isAuthorized) = \route isWrite ->+        lift ($([|isAuthorized|]) (coerce route) isWrite)++      $(pure $ VarP 'isWriteRequest)+        = lift . $([|isWriteRequest|]) . coerce++      $(pure $ VarP 'authRoute)+        = fmap coerce . $([|authRoute|]) . $(baseSite)++      $(pure $ VarP 'cleanPath) = $([|cleanPath|]) . $(baseSite)++      $(pure $ VarP 'joinPath) = $([|joinPath|]) . $(baseSite)++      $(pure $ VarP 'addStaticContent) = \fn mime content -> do+        ret <- lift $ $([|addStaticContent|]) fn mime content+        pure $ case ret of+          Nothing -> Nothing+          Just (Left t) -> Just (Left t)+          Just (Right (route, params))+            -> Just (Right (coerce route, params))++      $(pure $ VarP 'maximumContentLength) = \site mRoute ->+        $([|maximumContentLength|]) ($(baseSite) site) (coerce <$> mRoute)++      $(pure $ VarP 'maximumContentLengthIO) = \site mRoute ->+        $([|maximumContentLengthIO|]) ($(baseSite) site) (coerce <$> mRoute)++      $(pure $ VarP 'makeLogger)+         = $([|makeLogger|]) . $(baseSite)++      $(pure $ VarP 'messageLoggerSource)+        = $([|messageLoggerSource|]) . $(baseSite)++      $(pure $ VarP 'jsLoader) = \site ->+        case $([|jsLoader|]) ($(baseSite) site) of+            BottomOfBody -> BottomOfBody+            BottomOfHeadBlocking -> BottomOfHeadBlocking+            BottomOfHeadAsync async+              -> BottomOfHeadAsync+                  (\urls mHtml ->+                      coerceHtmlUrl $ async urls (coerceHtmlUrl <$> mHtml))++      $(pure $ VarP 'jsAttributes)+        = $([|jsAttributes|]) . $(baseSite)++      $(pure $ VarP 'jsAttributesHandler)+        = lift $([|jsAttributesHandler|])++      $(pure $ VarP 'makeSessionBackend)+        = $([|makeSessionBackend|]) . $(baseSite)++      $(pure $ VarP 'fileUpload)+        = $([|fileUpload|]) . $(baseSite)++      $(pure $ VarP 'shouldLogIO)+        = $([|shouldLogIO|]) . $(baseSite)++      $(pure $ VarP 'yesodMiddleware)+        = mapSiteT $([|yesodMiddleware|])++      $(pure $ VarP 'yesodWithInternalState) = \site mRoute ->+        $([|yesodWithInternalState|]) ($(baseSite) site) (coerce <$> mRoute)++      $(pure $ VarP 'defaultMessageWidget) = \html url ->+        lift $ $([|defaultMessageWidget|]) html (url . coerce)+      |]
+ ytl.cabal view
@@ -0,0 +1,48 @@+name:                ytl+version:             0.1.0.0+synopsis:            mtl-style transformations for Yesod sites+description:         A library of transformers for extending the behaviour of+                     Yesod sites in an mtl-style API, through transformations+                     of the foundation site.+                     .+                     This package contains:+                     .+                      * An API for declaring and using site transformers,+                        which modify the behaviour of a Yesod site while still+                        allowing it to be used with existing Yesod code, until+                        monad transformers+                     .+                     * Some useful site transformers and their corresponding+                       APIs+homepage:            https://github.com/ivanbakel/ytl#readme+license:             BSD3+license-file:        LICENSE+author:              Isaac van Bakel+maintainer:          ivb@vanbakel.io+copyright:           2020 Isaac van Bakel+category:            Web+build-type:          Simple+extra-source-files:  README.md+cabal-version:       >=1.10++library+  hs-source-dirs:      src+  exposed-modules:     Yesod.Site.Class+                      ,Yesod.Site.Util++                      ,Yesod.Trans+                      ,Yesod.Trans.Class+                      ,Yesod.Trans.Class.Reader+                      ,Yesod.Trans.Class.Writer+                      ,Yesod.Trans.TH++  build-depends:       base >= 4.7 && < 5+                      ,mtl+                      ,pointed+                      ,template-haskell+                      ,yesod-core+  default-language:    Haskell2010++source-repository head+  type:     git+  location: https://github.com/ivanbakel/ytl