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 +30/−0
- README.md +62/−0
- Setup.hs +2/−0
- src/Yesod/Site/Class.hs +46/−0
- src/Yesod/Site/Util.hs +79/−0
- src/Yesod/Trans.hs +26/−0
- src/Yesod/Trans/Class.hs +32/−0
- src/Yesod/Trans/Class/Reader.hs +83/−0
- src/Yesod/Trans/Class/Writer.hs +108/−0
- src/Yesod/Trans/TH.hs +161/−0
- ytl.cabal +48/−0
+ 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