packages feed

wai-routes 0.9.8 → 0.9.9

raw patch · 33 files changed

+2252/−2199 lines, 33 filesdep ~aesondep ~basedep ~hspecPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: aeson, base, hspec, hspec-wai, hspec-wai-json

API changes (from Hackage documentation)

- Network.Wai.Middleware.Routes: DefaultMaster :: DefaultMaster
- Network.Wai.Middleware.Routes: Env :: master -> sub -> (Route sub -> Route master) -> Env sub master
- Network.Wai.Middleware.Routes: [envMaster] :: Env sub master -> master
- Network.Wai.Middleware.Routes: [envSub] :: Env sub master -> sub
- Network.Wai.Middleware.Routes: [envToMaster] :: Env sub master -> Route sub -> Route master
- Network.Wai.Middleware.Routes: asContent :: ByteString -> Text -> HandlerM sub master ()
- Network.Wai.Middleware.Routes: catchall :: Application -> RouteM ()
- Network.Wai.Middleware.Routes: class RenderRoute a => ParseRoute a
- Network.Wai.Middleware.Routes: class Eq (Route a) => RenderRoute a where data family Route a
- Network.Wai.Middleware.Routes: class Routable sub master
- Network.Wai.Middleware.Routes: class RenderRoute a => RouteAttrs a
- Network.Wai.Middleware.Routes: css :: Text -> HandlerM sub master ()
- Network.Wai.Middleware.Routes: currentRoute :: RequestData master -> Maybe (Route master)
- Network.Wai.Middleware.Routes: data DefaultMaster
- Network.Wai.Middleware.Routes: data Env sub master
- Network.Wai.Middleware.Routes: data RequestData master
- Network.Wai.Middleware.Routes: defaultAction :: Application -> RouteM ()
- Network.Wai.Middleware.Routes: dispatcher :: Routable sub master => HandlerS sub master
- Network.Wai.Middleware.Routes: file :: FilePath -> HandlerM sub master ()
- Network.Wai.Middleware.Routes: filepart :: FilePath -> FilePart -> HandlerM sub master ()
- Network.Wai.Middleware.Routes: getCookie :: Text -> HandlerM sub master (Maybe Text)
- Network.Wai.Middleware.Routes: getCookies :: HandlerM sub master CookiesText
- Network.Wai.Middleware.Routes: getFileParam :: Text -> HandlerM sub master (Maybe FileInfo)
- Network.Wai.Middleware.Routes: getFileParams :: HandlerM sub master [(Text, FileInfo)]
- Network.Wai.Middleware.Routes: getParam :: Text -> HandlerM sub master (Maybe Text)
- Network.Wai.Middleware.Routes: getParams :: HandlerM sub master [(Text, Text)]
- Network.Wai.Middleware.Routes: getPostParam :: Text -> HandlerM sub master (Maybe Text)
- Network.Wai.Middleware.Routes: getPostParams :: HandlerM sub master [(Text, Text)]
- Network.Wai.Middleware.Routes: getQueryParam :: Text -> HandlerM sub master (Maybe Text)
- Network.Wai.Middleware.Routes: getQueryParams :: HandlerM sub master [(Text, Text)]
- Network.Wai.Middleware.Routes: handler :: HandlerS DefaultMaster DefaultMaster -> RouteM ()
- Network.Wai.Middleware.Routes: header :: HeaderName -> ByteString -> HandlerM sub master ()
- Network.Wai.Middleware.Routes: html :: Text -> HandlerM sub master ()
- Network.Wai.Middleware.Routes: isWebsocket :: HandlerM sub master Bool
- Network.Wai.Middleware.Routes: javascript :: Text -> HandlerM sub master ()
- Network.Wai.Middleware.Routes: json :: ToJSON a => a -> HandlerM sub master ()
- Network.Wai.Middleware.Routes: jsonBody :: FromJSON a => HandlerM sub master (Either String a)
- Network.Wai.Middleware.Routes: lookupVault :: Key a -> HandlerM sub master (Maybe a)
- Network.Wai.Middleware.Routes: master :: HandlerM sub master master
- Network.Wai.Middleware.Routes: maybeRootRoute :: HandlerM sub master (Maybe (Route master))
- Network.Wai.Middleware.Routes: maybeRoute :: HandlerM sub master (Maybe (Route sub))
- Network.Wai.Middleware.Routes: middleware :: Middleware -> RouteM ()
- Network.Wai.Middleware.Routes: mkRoute :: String -> [ResourceTree String] -> Q [Dec]
- Network.Wai.Middleware.Routes: mkRouteSub :: String -> String -> [ResourceTree String] -> Q [Dec]
- Network.Wai.Middleware.Routes: mountedAppHandler :: Application -> HandlerS sub master
- Network.Wai.Middleware.Routes: next :: HandlerM sub master ()
- Network.Wai.Middleware.Routes: nextApp :: RequestData master -> Application
- Network.Wai.Middleware.Routes: parseRoute :: ParseRoute a => ([Text], [(Text, Text)]) -> Maybe (Route a)
- Network.Wai.Middleware.Routes: parseRoutes :: QuasiQuoter
- Network.Wai.Middleware.Routes: parseRoutesFile :: FilePath -> Q Exp
- Network.Wai.Middleware.Routes: parseRoutesFileNoCheck :: FilePath -> Q Exp
- Network.Wai.Middleware.Routes: parseRoutesNoCheck :: QuasiQuoter
- Network.Wai.Middleware.Routes: plain :: Text -> HandlerM sub master ()
- Network.Wai.Middleware.Routes: raw :: ByteString -> HandlerM sub master ()
- Network.Wai.Middleware.Routes: rawBody :: HandlerM sub master ByteString
- Network.Wai.Middleware.Routes: rawBuilder :: Builder -> HandlerM sub master ()
- Network.Wai.Middleware.Routes: readRoute :: ParseRoute master => Text -> Maybe (Route master)
- Network.Wai.Middleware.Routes: readRouteMaster :: ParseRoute master => HandlerM sub master (Text -> Maybe (Route master))
- Network.Wai.Middleware.Routes: readRouteSub :: ParseRoute sub => HandlerM sub master (Text -> Maybe (Route master))
- Network.Wai.Middleware.Routes: renderRoute :: RenderRoute a => Route a -> ([Text], [(Text, Text)])
- Network.Wai.Middleware.Routes: reqHeader :: Text -> HandlerM sub master (Maybe Text)
- Network.Wai.Middleware.Routes: reqHeaders :: HandlerM sub master RequestHeaders
- Network.Wai.Middleware.Routes: reqVault :: HandlerM sub master Vault
- Network.Wai.Middleware.Routes: request :: HandlerM sub master Request
- Network.Wai.Middleware.Routes: rootRouteAttrSet :: RouteAttrs master => HandlerM sub master (Set Text)
- Network.Wai.Middleware.Routes: route :: (Routable master master) => master -> RouteM ()
- Network.Wai.Middleware.Routes: routeAttrSet :: RouteAttrs sub => HandlerM sub master (Set Text)
- Network.Wai.Middleware.Routes: routeAttrs :: RouteAttrs a => Route a -> Set Text
- Network.Wai.Middleware.Routes: routeDispatch :: Routable master master => master -> Middleware
- Network.Wai.Middleware.Routes: runHandlerM :: HandlerM sub master () -> HandlerS sub master
- Network.Wai.Middleware.Routes: runNext :: App master
- Network.Wai.Middleware.Routes: setCookie :: SetCookie -> HandlerM sub master ()
- Network.Wai.Middleware.Routes: showRoute :: RenderRoute master => Route master -> Text
- Network.Wai.Middleware.Routes: showRouteMaster :: RenderRoute master => HandlerM sub master (Route master -> Text)
- Network.Wai.Middleware.Routes: showRouteQuery :: RenderRoute master => Route master -> [(Text, Text)] -> Text
- Network.Wai.Middleware.Routes: showRouteQueryMaster :: RenderRoute master => HandlerM sub master (Route master -> [(Text, Text)] -> Text)
- Network.Wai.Middleware.Routes: showRouteQuerySub :: RenderRoute master => HandlerM sub master (Route sub -> [(Text, Text)] -> Text)
- Network.Wai.Middleware.Routes: showRouteSub :: RenderRoute master => HandlerM sub master (Route sub -> Text)
- Network.Wai.Middleware.Routes: status :: Status -> HandlerM sub master ()
- Network.Wai.Middleware.Routes: stream :: StreamingBody -> HandlerM sub master ()
- Network.Wai.Middleware.Routes: sub :: HandlerM sub master sub
- Network.Wai.Middleware.Routes: textBody :: HandlerM master master Text
- Network.Wai.Middleware.Routes: toWaiApp :: Monad m => RouteM () -> m Application
- Network.Wai.Middleware.Routes: type HandlerM sub master a = HandlerMI sub master IO a
- Network.Wai.Middleware.Routes: type HandlerS sub master = Env sub master -> App sub
- Network.Wai.Middleware.Routes: type RouteM = F RouterF
- Network.Wai.Middleware.Routes: updateVault :: (Vault -> Vault) -> HandlerM sub master ()
- Network.Wai.Middleware.Routes: waiApp :: RouteM () -> Application
- Network.Wai.Middleware.Routes: waiReq :: RequestData master -> Request
+ Wai.Routes: DefaultMaster :: DefaultMaster
+ Wai.Routes: Env :: master -> sub -> (Route sub -> Route master) -> Env sub master
+ Wai.Routes: [envMaster] :: Env sub master -> master
+ Wai.Routes: [envSub] :: Env sub master -> sub
+ Wai.Routes: [envToMaster] :: Env sub master -> Route sub -> Route master
+ Wai.Routes: asContent :: ByteString -> Text -> HandlerM sub master ()
+ Wai.Routes: catchall :: Application -> RouteM ()
+ Wai.Routes: class RenderRoute a => ParseRoute a
+ Wai.Routes: class Eq (Route a) => RenderRoute a where data Route a where {
+ Wai.Routes: class Routable sub master
+ Wai.Routes: class RenderRoute a => RouteAttrs a
+ Wai.Routes: css :: Text -> HandlerM sub master ()
+ Wai.Routes: currentRoute :: RequestData master -> Maybe (Route master)
+ Wai.Routes: data DefaultMaster
+ Wai.Routes: data Env sub master
+ Wai.Routes: data RequestData master
+ Wai.Routes: data family Route a;
+ Wai.Routes: defaultAction :: Application -> RouteM ()
+ Wai.Routes: dispatcher :: Routable sub master => HandlerS sub master
+ Wai.Routes: file :: FilePath -> HandlerM sub master ()
+ Wai.Routes: filepart :: FilePath -> FilePart -> HandlerM sub master ()
+ Wai.Routes: getCookie :: Text -> HandlerM sub master (Maybe Text)
+ Wai.Routes: getCookies :: HandlerM sub master CookiesText
+ Wai.Routes: getFileParam :: Text -> HandlerM sub master (Maybe FileInfo)
+ Wai.Routes: getFileParams :: HandlerM sub master [(Text, FileInfo)]
+ Wai.Routes: getParam :: Text -> HandlerM sub master (Maybe Text)
+ Wai.Routes: getParams :: HandlerM sub master [(Text, Text)]
+ Wai.Routes: getPostParam :: Text -> HandlerM sub master (Maybe Text)
+ Wai.Routes: getPostParams :: HandlerM sub master [(Text, Text)]
+ Wai.Routes: getQueryParam :: Text -> HandlerM sub master (Maybe Text)
+ Wai.Routes: getQueryParams :: HandlerM sub master [(Text, Text)]
+ Wai.Routes: handler :: HandlerS DefaultMaster DefaultMaster -> RouteM ()
+ Wai.Routes: header :: HeaderName -> ByteString -> HandlerM sub master ()
+ Wai.Routes: html :: Text -> HandlerM sub master ()
+ Wai.Routes: isWebsocket :: HandlerM sub master Bool
+ Wai.Routes: javascript :: Text -> HandlerM sub master ()
+ Wai.Routes: json :: ToJSON a => a -> HandlerM sub master ()
+ Wai.Routes: jsonBody :: FromJSON a => HandlerM sub master (Either String a)
+ Wai.Routes: lookupVault :: Key a -> HandlerM sub master (Maybe a)
+ Wai.Routes: master :: HandlerM sub master master
+ Wai.Routes: maybeRootRoute :: HandlerM sub master (Maybe (Route master))
+ Wai.Routes: maybeRoute :: HandlerM sub master (Maybe (Route sub))
+ Wai.Routes: middleware :: Middleware -> RouteM ()
+ Wai.Routes: mkRoute :: String -> [ResourceTree String] -> Q [Dec]
+ Wai.Routes: mkRouteSub :: String -> String -> [ResourceTree String] -> Q [Dec]
+ Wai.Routes: mountedAppHandler :: Application -> HandlerS sub master
+ Wai.Routes: next :: HandlerM sub master ()
+ Wai.Routes: nextApp :: RequestData master -> Application
+ Wai.Routes: parseRoute :: ParseRoute a => ([Text], [(Text, Text)]) -> Maybe (Route a)
+ Wai.Routes: parseRoutes :: QuasiQuoter
+ Wai.Routes: parseRoutesFile :: FilePath -> Q Exp
+ Wai.Routes: parseRoutesFileNoCheck :: FilePath -> Q Exp
+ Wai.Routes: parseRoutesNoCheck :: QuasiQuoter
+ Wai.Routes: plain :: Text -> HandlerM sub master ()
+ Wai.Routes: raw :: ByteString -> HandlerM sub master ()
+ Wai.Routes: rawBody :: HandlerM sub master ByteString
+ Wai.Routes: rawBuilder :: Builder -> HandlerM sub master ()
+ Wai.Routes: readRoute :: ParseRoute master => Text -> Maybe (Route master)
+ Wai.Routes: readRouteMaster :: ParseRoute master => HandlerM sub master (Text -> Maybe (Route master))
+ Wai.Routes: readRouteSub :: ParseRoute sub => HandlerM sub master (Text -> Maybe (Route master))
+ Wai.Routes: renderRoute :: RenderRoute a => Route a -> ([Text], [(Text, Text)])
+ Wai.Routes: reqHeader :: Text -> HandlerM sub master (Maybe Text)
+ Wai.Routes: reqHeaders :: HandlerM sub master RequestHeaders
+ Wai.Routes: reqVault :: HandlerM sub master Vault
+ Wai.Routes: request :: HandlerM sub master Request
+ Wai.Routes: rootRouteAttrSet :: RouteAttrs master => HandlerM sub master (Set Text)
+ Wai.Routes: route :: (Routable master master) => master -> RouteM ()
+ Wai.Routes: routeAttrSet :: RouteAttrs sub => HandlerM sub master (Set Text)
+ Wai.Routes: routeAttrs :: RouteAttrs a => Route a -> Set Text
+ Wai.Routes: routeDispatch :: Routable master master => master -> Middleware
+ Wai.Routes: runHandlerM :: HandlerM sub master () -> HandlerS sub master
+ Wai.Routes: runNext :: App master
+ Wai.Routes: setCookie :: SetCookie -> HandlerM sub master ()
+ Wai.Routes: showRoute :: RenderRoute master => Route master -> Text
+ Wai.Routes: showRouteMaster :: RenderRoute master => HandlerM sub master (Route master -> Text)
+ Wai.Routes: showRouteQuery :: RenderRoute master => Route master -> [(Text, Text)] -> Text
+ Wai.Routes: showRouteQueryMaster :: RenderRoute master => HandlerM sub master (Route master -> [(Text, Text)] -> Text)
+ Wai.Routes: showRouteQuerySub :: RenderRoute master => HandlerM sub master (Route sub -> [(Text, Text)] -> Text)
+ Wai.Routes: showRouteSub :: RenderRoute master => HandlerM sub master (Route sub -> Text)
+ Wai.Routes: status :: Status -> HandlerM sub master ()
+ Wai.Routes: stream :: StreamingBody -> HandlerM sub master ()
+ Wai.Routes: sub :: HandlerM sub master sub
+ Wai.Routes: textBody :: HandlerM master master Text
+ Wai.Routes: toWaiApp :: Monad m => RouteM () -> m Application
+ Wai.Routes: type HandlerM sub master a = HandlerMI sub master IO a
+ Wai.Routes: type HandlerS sub master = Env sub master -> App sub
+ Wai.Routes: type RouteM = F RouterF
+ Wai.Routes: updateVault :: (Vault -> Vault) -> HandlerM sub master ()
+ Wai.Routes: waiApp :: RouteM () -> Application
+ Wai.Routes: waiReq :: RequestData master -> Request
+ Wai.Routes: }

Files

README.md view
@@ -1,4 +1,4 @@-[Wai-Routes](https://ajnsit.github.io/wai-routes) [![Hackage](https://img.shields.io/badge/hackage-v0.9.8-brightgreen.svg)](https://hackage.haskell.org/package/wai-routes) [![Hackage-Deps](https://img.shields.io/hackage-deps/v/wai-routes.svg)](http://packdeps.haskellers.com/feed?needle=wai-routes) [![Build Status](https://img.shields.io/travis/ajnsit/wai-routes.svg)](https://travis-ci.org/ajnsit/wai-routes) [![Join the chat at https://gitter.im/ajnsit/wai-routes](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%A3-blue.svg)](https://gitter.im/ajnsit/wai-routes)+[Wai-Routes](https://ajnsit.github.io/wai-routes) [![Hackage](https://img.shields.io/badge/hackage-v0.9.9-brightgreen.svg)](https://hackage.haskell.org/package/wai-routes) [![Hackage-Deps](https://img.shields.io/hackage-deps/v/wai-routes.svg)](http://packdeps.haskellers.com/feed?needle=wai-routes) [![Build Status](https://img.shields.io/travis/ajnsit/wai-routes.svg)](https://travis-ci.org/ajnsit/wai-routes) [![Join the chat at https://gitter.im/ajnsit/wai-routes](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%A3-blue.svg)](https://gitter.im/ajnsit/wai-routes) ====================================  Wai-routes is a micro web framework for Haskell that focuses on typesafe URLs.@@ -114,6 +114,7 @@ Changelog ========= +* 0.9.9 : GHC 8 compatibility. Change namespace from Network.Wai.Middleware.Routes -> Wai.Routes * 0.9.8 : Allow Data.Default-0.1.0. Allow comments in route definitions. Some other minor changes. * 0.9.7 : Allow Aeson-0.11. Export Env, RequestData, and show/readRoute to enable "bare" handlers. * 0.9.6 : Subsites now receive parent route arguments, in line with regular nested routes
src/Network/Wai/Middleware/Routes.hs view
@@ -8,122 +8,11 @@ Portability :  non-portable (uses ghc extensions)  This package provides typesafe URLs for Wai applications.--}-module Network.Wai.Middleware.Routes-    ( -- * Declaring Routes using Template Haskell-      parseRoutes-    , parseRoutesFile        -- | Parse routes declared in a file-    , parseRoutesNoCheck-    , parseRoutesFileNoCheck -- | Same as parseRoutesFile, but performs no overlap checking. -    , mkRoute-    , mkRouteSub--    -- * Dispatch-    , routeDispatch--    -- * URL rendering and parsing-    , showRoute-    , showRouteQuery-    , readRoute-    , showRouteMaster-    , showRouteQueryMaster-    , readRouteMaster-    , showRouteSub-    , showRouteQuerySub-    , readRouteSub--    -- * Application Handlers-    , Handler-    , HandlerS--    -- * Generated Datatypes-    , Routable(..)           -- | Used internally. However needs to be exported for TH to work.-    , RenderRoute(..)        -- | A `RenderRoute` instance for your site datatype is automatically generated by `mkRoute`-    , ParseRoute(..)         -- | A `ParseRoute` instance for your site datatype is automatically generated by `mkRoute`-    , RouteAttrs(..)         -- | A `RouteAttrs` instance for your site datatype is automatically generated by `mkRoute`--    -- * Accessing Raw Request Data-    , RequestData            -- | An abstract representation of the request data. You can get the wai request object by using `waiReq`-    , waiReq                 -- | Extract the wai `Request` object from `RequestData`-    , nextApp                -- | Extract the next Application in the stack-    , runNext                -- | Run the next application in the stack--    -- * Route Monad makes it easy to compose routes together-    , RouteM-    , DefaultMaster(..)-    , Route(DefaultRoute)-    , handler                -- | Add a wai-routes handler-    , catchall               -- | Catch all routes with the supplied application-    , defaultAction          -- | A synonym for `catchall`, kept for backwards compatibility-    , middleware             -- | Add another middleware to the app-    , route                  -- | Add another routed middleware to the app-    , waiApp                 -- | Convert a RouteM to a wai Application-    , toWaiApp               -- | Similar to waiApp, but result is wrapped in a monad. Kept for backwards compatibility--    -- * HandlerM Monad makes it easy to build a handler-    , HandlerM()-    , runHandlerM            -- | Run a HandlerM to get a Handler-    , mountedAppHandler      -- | Convert a full wai application to a HandlerS-    , request                -- | Access the request data-    , isWebsocket            -- | Is this a websocket request-    , reqHeader              -- | Get a particular request header (case insensitive)-    , reqHeaders             -- | Get all request headers (case insensitive)-    , maybeRootRoute         -- | Access the current route for root route-    , maybeRoute             -- | Access the current route-    , routeAttrSet           -- | Access the current route attributes as a set-    , rootRouteAttrSet       -- | Access the current root route attributes as a set-    , master                 -- | Access the master datatype-    , sub                    -- | Access the sub datatype-    , rawBody                -- | Consume and return the request body as ByteString-    , textBody               -- | Consume and return the request body as Text-    , jsonBody               -- | Consume and return the request body as JSON-    , header                 -- | Add a header to the response-    , status                 -- | Set the response status-    , file                   -- | Send a file as response-    , filepart               -- | Send a part of a file as response-    , stream                 -- | Stream a response-    , raw                    -- | Set the raw response body-    , rawBuilder             -- | Set the raw response body as a ByteString Builder-    , json                   -- | Set the json response body-    , plain                  -- | Set the plain text response body-    , html                   -- | Set the html response body-    , css                    -- | Set the css response body-    , javascript             -- | Set the javascript response body-    , asContent              -- | Set the contentType and a 'Text' body-    , next                   -- | Run the next application in the stack-    , getParams              -- | Get all params (query or post, not file)-    , getParam               -- | Get a particular param (query or post, not file)-    , getQueryParams         -- | Get all query params-    , getQueryParam          -- | Get a particular query param-    , getPostParams          -- | Get all post params-    , getPostParam           -- | Get a particular post param-    , getFileParams          -- | Get all file params-    , getFileParam           -- | Get a particular file param-    , setCookie              -- | Add a cookie to the response-    , getCookie              -- | Get a cookie from the request-    , getCookies             -- | Get all cookies from the request-    , reqVault               -- | Access the vault from the request-    , lookupVault            -- | Lookup a key in the request vault-    , updateVault            -- | Update the request vault--    -- * Bare Handlers-    , Env(..)-    , RequestData            -- | An abstract representation of the request data. You can get the wai request object by using `waiReq`-    , waiReq                 -- | Extract the wai `Request` object from `RequestData`-    , nextApp                -- | Extract the next Application in the stack-    , currentRoute           -- | Extract the current `Route` from `RequestData`-    , runNext                -- | Run the next application in the stack--    , module Network.HTTP.Types.Status-    , module Network.Wai.Middleware.RequestLogger-    , module Network.Wai.Application.Static-  )+* Deprecated*: Use Wai.Routes instead.+-}+module Network.Wai.Middleware.Routes {-# DEPRECATED "Use Wai.Routes instead" #-}+  ( module Wai.Routes )   where -import Network.Wai.Middleware.Routes.Routes-import Network.Wai.Middleware.Routes.Monad-import Network.Wai.Middleware.Routes.Handler-import Network.HTTP.Types.Status-import Network.Wai.Middleware.RequestLogger-import Network.Wai.Application.Static+import Wai.Routes
− src/Network/Wai/Middleware/Routes/Class.hs
@@ -1,24 +0,0 @@-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE FlexibleContexts #-}-module Network.Wai.Middleware.Routes.Class-    ( RenderRoute (..)-    , ParseRoute (..)-    , RouteAttrs (..)-    ) where--import Data.Text (Text)-import Data.Set (Set)--class Eq (Route a) => RenderRoute a where-    -- | The <http://www.yesodweb.com/book/routing-and-handlers type-safe URLs> associated with a site argument.-    data Route a-    renderRoute :: Route a-                -> ([Text], [(Text, Text)]) -- ^ The path of the URL split on forward slashes, and a list of query parameters with their associated value.--class RenderRoute a => ParseRoute a where-    parseRoute :: ([Text], [(Text, Text)]) -- ^ The path of the URL split on forward slashes, and a list of query parameters with their associated value.-               -> Maybe (Route a)--class RenderRoute a => RouteAttrs a where-    routeAttrs :: Route a-               -> Set Text -- ^ A set of <http://www.yesodweb.com/book/route-attributes attributes associated with the route>.
− src/Network/Wai/Middleware/Routes/ContentTypes.hs
@@ -1,95 +0,0 @@-{-# LANGUAGE OverloadedStrings, TypeFamilies #-}--{- |-Module      :  Network.Wai.Middleware.Routes.ContentTypes-Copyright   :  (c) Anupam Jain 2013-License     :  MIT (see the file LICENSE)--Maintainer  :  ajnsit@gmail.com-Stability   :  experimental-Portability :  non-portable (uses ghc extensions)--Defines the commonly used content types--}-module Network.Wai.Middleware.Routes.ContentTypes-    ( -- * Construct content Type-      acceptContentType-    , contentType, contentTypeFromFile-      -- * Various common content types-    , typeAll-    , typeHtml, typePlain, typeJson-    , typeXml, typeAtom, typeRss-    , typeJpeg, typePng, typeGif-    , typeSvg, typeJavascript, typeCss-    , typeFlv, typeOgv, typeOctet-    )-    where--import qualified Data.Text as T (pack)-import Data.ByteString (ByteString)-import Data.ByteString.Char8 () -- Import IsString instance for ByteString-import Network.HTTP.Types.Header (HeaderName())-import Network.Mime (defaultMimeLookup)-import System.FilePath (takeFileName)---- | The request header for accpetable content types-acceptContentType :: HeaderName-acceptContentType = "Accept"---- | Construct an appropriate content type header from a file name-contentTypeFromFile :: FilePath -> ByteString-contentTypeFromFile = defaultMimeLookup . T.pack . takeFileName---- | Creates a content type header--- Ready to be passed to `responseLBS`-contentType :: HeaderName-contentType = "Content-Type"--typeAll :: ByteString-typeAll = "*/*"--typeHtml :: ByteString-typeHtml = "text/html; charset=utf-8"--typePlain :: ByteString-typePlain = "text/plain; charset=utf-8"--typeJson :: ByteString-typeJson = "application/json; charset=utf-8"--typeXml :: ByteString-typeXml = "text/xml"--typeAtom :: ByteString-typeAtom = "application/atom+xml"--typeRss :: ByteString-typeRss = "application/rss+xml"--typeJpeg :: ByteString-typeJpeg = "image/jpeg"--typePng :: ByteString-typePng = "image/png"--typeGif :: ByteString-typeGif = "image/gif"--typeSvg :: ByteString-typeSvg = "image/svg+xml"--typeJavascript :: ByteString-typeJavascript = "text/javascript; charset=utf-8"--typeCss :: ByteString-typeCss = "text/css; charset=utf-8"--typeFlv :: ByteString-typeFlv = "video/x-flv"--typeOgv :: ByteString-typeOgv = "video/ogg"--typeOctet :: ByteString-typeOctet = "application/octet-stream"-
− src/Network/Wai/Middleware/Routes/DefaultRoute.hs
@@ -1,35 +0,0 @@-{-# LANGUAGE TypeFamilies #-}--{- |-Module      :  Network.Wai.Middleware.Routes.DefaultRoute-Copyright   :  (c) Anupam Jain 2013 - 2015-License     :  MIT (see the file LICENSE)--Maintainer  :  ajnsit@gmail.com-Stability   :  experimental-Portability :  non-portable (uses ghc extensions)--Defines a DefaultMaster datatype and associated route (DefaultRoute) which is used for "unrouted" handlers--}-module Network.Wai.Middleware.Routes.DefaultRoute-  ( DefaultMaster(..)-  , Route(DefaultRoute)-  )-  where--import Data.Text (Text)-import Data.Set (empty)--import Network.Wai.Middleware.Routes.Routes---- Default master datatype, which is used for "unrouted" handlers-data DefaultMaster = DefaultMaster deriving (Eq, Show, Ord)--- This makes it possible to define handlers without routing stuff-instance RenderRoute DefaultMaster where-  -- The associated route simply contains all path information-  data Route DefaultMaster = DefaultRoute ([Text],[(Text, Text)]) deriving (Eq, Show, Ord)-  renderRoute (DefaultRoute r) = r-instance ParseRoute DefaultMaster where-  parseRoute = Just . DefaultRoute-instance RouteAttrs DefaultMaster where-  routeAttrs = const empty
− src/Network/Wai/Middleware/Routes/Handler.hs
@@ -1,599 +0,0 @@-{-# LANGUAGE OverloadedStrings, GeneralizedNewtypeDeriving, CPP #-}-{- |-Module      :  Network.Wai.Middleware.Routes.Handler-Copyright   :  (c) Anupam Jain 2013-License     :  MIT (see the file LICENSE)--Maintainer  :  ajnsit@gmail.com-Stability   :  experimental-Portability :  non-portable (uses ghc extensions)--Provides a HandlerM Monad that makes it easy to build Handlers--}-module Network.Wai.Middleware.Routes.Handler-    ( HandlerM()             -- | A Monad that makes it easier to build a Handler-    , runHandlerM            -- | Run a HandlerM to get a Handler-    , mountedAppHandler      -- | Convert a full wai application to a HandlerS-    , request                -- | Access the request data-    , isWebsocket            -- | Is this a websocket request-    , reqHeader              -- | Get a particular request header (case insensitive)-    , reqHeaders             -- | Get all request headers (case insensitive)-    , routeAttrSet           -- | Access the route attribute list-    , rootRouteAttrSet       -- | Access the route attribute list for the root route-    , maybeRoute             -- | Access the route data-    , maybeRootRoute         -- | Access the root route data-    , showRouteMaster        -- | Get the route rendering function for the master site-    , showRouteSub           -- | Get the route rendering function for the subsite-    , showRouteQueryMaster   -- | Get the route + query params rendering function for the master site-    , showRouteQuerySub      -- | Get the route + query params rendering function for the subsite-    , readRouteMaster        -- | Get the route parsing function for the master site-    , readRouteSub           -- | Get the route parsing function for the subsite-    , master                 -- | Access the master datatype-    , sub                    -- | Access the sub datatype-    , rawBody                -- | Consume and return the request body as ByteString-    , textBody               -- | Consume and return the request body as Text-    , jsonBody               -- | Consume and return the request body as JSON-    , header                 -- | Add a header to the response-    , status                 -- | Set the response status-    , file                   -- | Send a file as response-    , filepart               -- | Send a part of a file as response-    , stream                 -- | Stream a response-    , raw                    -- | Set the raw response body-    , rawBuilder             -- | Set the raw response body as a ByteString Builder-    , json                   -- | Set the json response body-    , plain                  -- | Set the plain text response body-    , html                   -- | Set the html response body-    , css                    -- | Set the css response body-    , javascript             -- | Set the javascript response body-    , content                -- | Sets the response body when the content type is acceptable-    , asContent              -- | Set the contentType and a 'Text' body-    , whenContent            -- | Runs the action when a content type is acceptable-    , next                   -- | Run the next application in the stack-    , getParams              -- | Get all params (query or post, not file)-    , getParam               -- | Get a particular param (query or post, not file)-    , getQueryParams         -- | Get all query params-    , getQueryParam          -- | Get a particular query param-    , getPostParams          -- | Get all post params-    , getPostParam           -- | Get a particular post param-    , getFileParams          -- | Get all file params-    , getFileParam           -- | Get a particular file param-    , setCookie              -- | Add a cookie to the response-    , getCookie              -- | Get a cookie from the request-    , getCookies             -- | Get all cookies from the request-    , reqVault               -- | Access the vault from the request-    , lookupVault            -- | Lookup a key in the request vault-    , updateVault            -- | Update the request vault-    )-    where--import Network.Wai (Application, Request, responseRaw, responseFile, responseBuilder, responseStream, queryString, StreamingBody, requestHeaders, FilePart)-#if MIN_VERSION_wai(3,0,1)-import Network.Wai (strictRequestBody, vault)-#endif-import Network.Wai.Middleware.Routes.Routes (Env(..), RequestData, HandlerS, waiReq, currentRoute, runNext, showRoute, showRouteQuery, readRoute, readQueryString)-import Network.Wai.Middleware.Routes.Class (Route, RenderRoute, ParseRoute, RouteAttrs(..))-import Network.Wai.Middleware.Routes.ContentTypes (acceptContentType, contentType, contentTypeFromFile, typeHtml, typeJson, typePlain, typeCss, typeJavascript, typeAll)--import Control.Monad (liftM, when)-import Control.Monad.State (StateT, get, put, modify, runStateT, MonadState, MonadIO, liftIO, MonadTrans)--import Control.Arrow ((***))-import Control.Applicative (Applicative, (<$>), (<*>))--import Data.Maybe (fromMaybe)-import Data.ByteString (ByteString)-import qualified Data.ByteString as B-import qualified Data.ByteString.Lazy as BL-import Blaze.ByteString.Builder (Builder, toByteString, fromByteString)-import Network.HTTP.Types.Header (HeaderName(), RequestHeaders)-import Network.HTTP.Types.Status (Status(), status200)--import Data.Aeson (ToJSON, FromJSON, eitherDecodeStrict)--import qualified Data.Aeson as A-import qualified Data.Aeson.Encode as AE--import Data.Set (Set)-import qualified Data.Set as S (empty)--import Data.Text (Text)-import Data.Text.Encoding (encodeUtf8, decodeUtf8)--import Data.CaseInsensitive (CI, mk)--import Web.Cookie (CookiesText, parseCookiesText, renderSetCookie, SetCookie(..))-import Data.List (intersect)--import qualified Data.Vault.Lazy as V--import qualified Network.Wai.Parse as P---- | The internal implementation of the HandlerM monad--- TODO: Should change this to StateT over ReaderT (but performance may suffer)-newtype HandlerMI sub master m a = H { extractH :: StateT (HandlerState sub master) m a }-    deriving (Applicative, Monad, MonadIO, Functor, MonadTrans, MonadState (HandlerState sub master))---- | The HandlerM Monad-type HandlerM sub master a = HandlerMI sub master IO a---- | Modeled after Network.Wai.Parse.FileInfo, but uses Text for names, and lazy ByteString for content-data FileInfo = FileInfo-  { fileName        :: Text-  , fileContentType :: Text-  , fileContent     :: BL.ByteString-  }---- Post Params--- Files are read into memory--- TODO: Check for security issues. Allow automatic storing of files on disk.-type PostParams = ([(Text, Text)], [(Text, FileInfo)])---- Private--- Convert from Network.Wai.Parse ([Param], [File y])  to PostParams-_toPostParams :: ([P.Param], [P.File BL.ByteString])  -> PostParams-_toPostParams (params, files) = (params', files')-  where-    params' = map (decodeUtf8 *** decodeUtf8)     params-    files'  = map (decodeUtf8 *** decodeFileInfo) files-    decodeFileInfo fi = FileInfo-      { fileName = decodeUtf8 $ P.fileName fi-      , fileContentType = decodeUtf8 $ P.fileContentType fi-      , fileContent = P.fileContent fi-      }---- A Raw Response handler :: source -> sink -> IO-type RespRawHandler = IO B.ByteString -> (B.ByteString -> IO ()) -> IO ()---- | The state kept in a HandlerM Monad-data HandlerState sub master = HandlerState-  { getMaster      :: master-  , getRequestData :: RequestData sub-  -- TODO: Experimental-  -- Streaming request body, consumed, and stored as a ByteString-  , reqBody        :: Maybe ByteString-  , respHeaders    :: [(HeaderName, ByteString)]-  , respStatus     :: Status-  , respResp       :: Maybe MkResponse-  , respRaw        :: Maybe RespRawHandler-  , respCookies    :: [SetCookie]-  , getSub         :: sub-  , toMasterRoute  :: Route sub -> Route master-  -- TODO: Experimental-  -- Parsed POST request body, in the same format as Network.Wai.Parse-  , postParams     :: Maybe PostParams-  , acceptCTypes   :: Maybe [ByteString]-  }---- Initial Handler State-defaultHandlerState :: Env sub master -> RequestData sub -> HandlerState sub master-defaultHandlerState env req = HandlerState-  { getMaster = envMaster env-  , getRequestData = req-  , reqBody = Nothing-  , respHeaders = []-  , respStatus = status200-  , respResp = Nothing-  , respRaw = Nothing-  , respCookies = []-  , getSub = envSub env-  , toMasterRoute = envToMaster env-  , postParams = Nothing-  , acceptCTypes = Nothing-  }---- Internal: Type of response--- Similar to Wai's Response type-data MkResponse-    = ResponseFile FilePath (Maybe FilePart)-    | ResponseBuilder Builder-    | ResponseStream StreamingBody-    | ResponseNext---- Default response in case none is set by the handler-defaultResponse :: MkResponse-defaultResponse = ResponseBuilder ""---- The header name for request cookies-cookieHeaderName :: CI ByteString-cookieHeaderName = mk "Cookie"---- The header name for response cookies-cookieSetHeaderName :: CI ByteString-cookieSetHeaderName = mk "Set-Cookie"---- | Convert a full wai application to a Handler--- A bit like subsites, but at a higher level.-mountedAppHandler :: Application -> HandlerS sub master-mountedAppHandler app _env = app . waiReq---- | "Run" HandlerM, resulting in a Handler-runHandlerM :: HandlerM sub master () -> HandlerS sub master-runHandlerM h env req hh = do-  (_, st) <- runStateT (extractH h) (defaultHandlerState env req)-  -- Fetch the internal response structure-  let respData = fromMaybe defaultResponse (respResp st)-  -- Handle cookies (add them to headers)-  let headers' = map mkSetCookie (respCookies st) ++ respHeaders st-  -- Construct the actual wai response-  case mkResponse (respStatus st) headers' respData of-    -- Abort handling current response and move to next handler-    Nothing -> runNext (getRequestData st) hh-    -- Normal handling-    Just resp ->-      -- Check if we are trying to send a raw response-      case respRaw st of-        Nothing -> hh resp-        -- TODO: Ensure the body has not been read before using raw response-        Just rawHandler -> hh $ responseRaw rawHandler resp-  where-    mkSetCookie s = (cookieSetHeaderName, toByteString $ renderSetCookie s)-    mkResponse rstatus headers (ResponseFile path part) = Just $ responseFile rstatus headers path part-    mkResponse rstatus headers (ResponseBuilder builder) = Just $ responseBuilder rstatus headers builder-    mkResponse rstatus headers (ResponseStream streaming) = Just $ responseStream rstatus headers streaming-    mkResponse _ _ ResponseNext = Nothing---- | Get the request body as a bytestring. Consumes the entire body into memory at once.--- TODO: Implement streaming. Prevent clash with direct use of `Network.Wai.requestBody`-rawBody :: HandlerM sub master ByteString-rawBody = do-  s <- get-  case reqBody s of-    Just consumedBody -> return consumedBody-    Nothing -> do-      req <- request-      rbody <- liftIO $ BL.toStrict <$> _readStrictRequestBody req-      put s {reqBody = Just rbody}-      return rbody---- | Get the request body as a Text. However consumes the entire body at once.--- TODO: Implement streaming. Prevent clash with direct use of `Network.Wai.requestBody`-textBody :: HandlerM master master Text-textBody = liftM decodeUtf8 rawBody---- PRIVATE-_readStrictRequestBody :: Request -> IO BL.ByteString-_readStrictRequestBody =-#if MIN_VERSION_wai(3,0,1)-        -- Use the `strictRequestBody` function available in wai > 3.0.1-        strictRequestBody-#else-        -- Consume the entire body, and cache-        BL.fromChunks <$> unfoldWhileM (not . B.null) . requestBody-#endif---- | Parse the body as a JSON object-jsonBody :: FromJSON a => HandlerM sub master (Either String a)-jsonBody = liftM eitherDecodeStrict rawBody---- | Get the master-master :: HandlerM sub master master-master = liftM getMaster get---- | Get the sub-sub :: HandlerM sub master sub-sub = liftM getSub get---- | Get the request-request :: HandlerM sub master Request-request = liftM (waiReq . getRequestData) get---- | Is this a websocket request-isWebsocket :: HandlerM sub master Bool-isWebsocket = liftM (maybe False (== "websocket")) (_reqHeaderBS "upgrade")---- | Get a particular request header (Case insensitive)-reqHeader :: Text -> HandlerM sub master (Maybe Text)-reqHeader name = liftM (fmap decodeUtf8) (_reqHeaderBS nameText)-  where-    nameText = mk $ encodeUtf8 name---- PRIVATE-_reqHeaderBS :: CI ByteString -> HandlerM sub master (Maybe ByteString)-_reqHeaderBS name = liftM (lookup name) reqHeaders---- VAULT--- | Access the vault-reqVault :: HandlerM sub master V.Vault-reqVault = liftM vault request---- Lookup a value in the request vault-lookupVault :: V.Key a -> HandlerM sub master (Maybe a)-lookupVault k = liftM (V.lookup k) reqVault---- Update the request vault--- For example: `updateVault (V.insert key val)`-updateVault :: (V.Vault -> V.Vault) -> HandlerM sub master ()-updateVault f = modify $ \st ->-  let rd = getRequestData st-      r = waiReq rd-      v = f $ vault r-  in st { getRequestData = rd { waiReq = r { vault = v } } }--- END VAULT---- | Get all request headers as raw case-insensitive bytestrings-reqHeaders :: HandlerM sub master RequestHeaders-reqHeaders = liftM requestHeaders request---- | Get the current route-maybeRoute :: HandlerM sub master (Maybe (Route sub))-maybeRoute = liftM (currentRoute . getRequestData) get---- | Get the current root route-maybeRootRoute :: HandlerM sub master (Maybe (Route master))-maybeRootRoute = do-  s <- get-  return $ toMasterRoute s <$> currentRoute (getRequestData s)---- | Get the route rendering function for the master site-showRouteMaster :: RenderRoute master => HandlerM sub master (Route master -> Text)-showRouteMaster = return showRoute---- | Get the route rendering function for the subsite-showRouteSub :: RenderRoute master => HandlerM sub master (Route sub -> Text)-showRouteSub = do-  s <- get-  return $ showRoute . toMasterRoute s---- | Get the route rendering function for the master site-showRouteQueryMaster :: RenderRoute master => HandlerM sub master (Route master -> [(Text,Text)] -> Text)-showRouteQueryMaster = return showRouteQuery---- | Get the route rendering function for the subsite-showRouteQuerySub :: RenderRoute master => HandlerM sub master (Route sub -> [(Text,Text)] -> Text)-showRouteQuerySub = do-  s <- get-  return $ showRouteQuery . toMasterRoute s---- | Get the route parsing function for the master site-readRouteMaster :: ParseRoute master => HandlerM sub master (Text -> Maybe (Route master))-readRouteMaster = return readRoute---- | Get the route parsing function for the subsite-readRouteSub :: ParseRoute sub => HandlerM sub master (Text -> Maybe (Route master))-readRouteSub = do-  s <- get-  return $ (toMasterRoute s <$>) . readRoute---- | Get the current route attributes-routeAttrSet :: RouteAttrs sub => HandlerM sub master (Set Text)-routeAttrSet = liftM (maybe S.empty routeAttrs . currentRoute . getRequestData) get---- | Get the attributes for the current root route-rootRouteAttrSet :: RouteAttrs master => HandlerM sub master (Set Text)-rootRouteAttrSet = do-  s <- get-  return $ maybe S.empty (routeAttrs . toMasterRoute s) $ currentRoute $ getRequestData s---- | Add a header to the application response--- TODO: Differentiate between setting and adding headers-header :: HeaderName -> ByteString -> HandlerM sub master ()-header h b = modify addHeader-  where-    addHeader :: HandlerState sub master -> HandlerState sub master-    addHeader st@(HandlerState {respHeaders=hs}) = st {respHeaders=(h,b):hs}---- | Set the response status-status :: Status -> HandlerM sub master ()-status s = modify setStatus-  where-    setStatus :: HandlerState sub master -> HandlerState sub master-    setStatus st = st{respStatus=s}---- | Send a file as response-file :: FilePath -> HandlerM sub master ()-file f = do-  header contentType $ contentTypeFromFile f-  modify addFile-  where-    addFile st = _setResp st $ ResponseFile f Nothing---- | Send a part of a file as response-filepart :: FilePath -> FilePart -> HandlerM sub master ()-filepart f part = do-  header contentType $ contentTypeFromFile f-  modify addFile-  where-    addFile st = _setResp st $ ResponseFile f (Just part)---- | Stream the response-stream :: StreamingBody -> HandlerM sub master ()-stream s = modify addStream-  where-    addStream st = _setResp st $ ResponseStream s---- | Set the response body-raw :: ByteString -> HandlerM sub master ()-raw = rawBuilder . fromByteString---- | Set the response body as a builder-rawBuilder :: Builder -> HandlerM sub master ()-rawBuilder b = modify addBody-  where-    addBody st = _setResp st $ ResponseBuilder b---- | Run the next application-next :: HandlerM sub master ()-next = modify rNext-  where-    rNext st = _setResp st ResponseNext---- Util--- Set the response handler (don't overwrite an existing response)-_setResp :: HandlerState sub master -> MkResponse -> HandlerState sub master-_setResp st r = case respResp st of-  Nothing -> st{respResp=Just r}-  _ -> st----- Standard response bodies---- | Set the body of the response to the JSON encoding of the given value. Also sets \"Content-Type\"--- header to \"application/json\".-json :: ToJSON a => a -> HandlerM sub master ()--- TODO: Use Accept header parsing--- json a = whenContent [typeJson, typeJavascript, typePlain] $ do-json a = do-  header contentType typeJson-  rawBuilder $ _encode $ A.toJSON a-  where-#if MIN_VERSION_aeson(0,9,0)-    _encode = AE.encodeToBuilder-#else-    _encode = AE.encodeToByteStringBuilder-#endif---- | Set the body of the response to the given 'Text' value. Also sets \"Content-Type\"--- header to \"text/plain\".-plain :: Text -> HandlerM sub master ()--- TODO: Use Accept header parsing--- plain = content [typePlain]-plain = asContent typePlain---- | Set the body of the response to the given 'Text' value. Also sets \"Content-Type\"--- header to \"text/html\".-html :: Text -> HandlerM sub master ()--- TODO: Use Accept header parsing--- html = content [typeHtml, typePlain]-html = asContent typeHtml---- | Set the body of the response to the given 'Text' value. Also sets \"Content-Type\"--- header to \"text/css\".-css :: Text -> HandlerM sub master ()--- TODO: Use Accept header parsing--- css = content [typeCss, typePlain]-css = asContent typeCss---- | Set the body of the response to the given 'Text' value. Also sets \"Content-Type\"--- header to \"text/javascript\".-javascript :: Text -> HandlerM sub master ()--- TODO: Use Accept header parsing--- javascript = content [typeJavascript, typePlain]-javascript = asContent typeJavascript---- | Sets the content-type header to the given Bytestring---  (look in Network.Wai.Middleware.Routes.ContentTypes for examples)---  And sets the body of the response to the given Text-asContent :: ByteString -> Text -> HandlerM sub master ()-asContent ctype s = do-  header contentType ctype-  raw $ encodeUtf8 s---- | Sets the response body when the content type is acceptable-content :: [ByteString] -> Text -> HandlerM sub master ()-content [] _ = return ()-content ctypes s = whenContent ctypes (asContent (head ctypes) s)---- | Perform an action only when there is no accept list or the given contentType is acceptable-whenContent :: [ByteString] -> HandlerM sub master () -> HandlerM sub master ()-whenContent ctypes respHandler = do-  atypes <- acceptableContentTypes-  let noAcceptList = not $ null atypes-  let acceptableTypeFound = not $ null $ intersect (typeAll:ctypes) atypes-  when (noAcceptList || acceptableTypeFound) respHandler---- | Get a list of content types acceptable to the request-acceptableContentTypes :: HandlerM sub master [ByteString]-acceptableContentTypes = do-  st <- get-  maybe (getCTypes st) return (acceptCTypes st)-  where-    getCTypes st = do-      h <- _reqHeaderBS acceptContentType-      let parsedCTypes = maybe [] P.parseHttpAccept h-      put st{acceptCTypes = Just parsedCTypes}-      return parsedCTypes---- | Sets a cookie to the response-setCookie :: SetCookie -> HandlerM sub master ()-setCookie s = modify setCookie'-  where-    setCookie' st = st {respCookies = s : respCookies st}---- | Get all cookies-getCookies :: HandlerM sub master CookiesText-getCookies = do-  -- Note: We don't cache the parsedCookies for all requests to avoid overhead-  -- However it is pretty easy to cache cookies in the app itself-  cookies <- _reqHeaderBS cookieHeaderName-  return $ case cookies of-    Nothing -> []-    Just cookies' -> parseCookiesText cookies'---- | Get a particular cookie-getCookie :: Text -> HandlerM sub master (Maybe Text)-getCookie name = do-  cookies <- getCookies-  return $ lookup name cookies---- PRIVATE--- Get the cached post params (if any)-_getCachedPostParams :: HandlerM sub master (Maybe PostParams)-_getCachedPostParams = postParams <$> get---- PRIVATE--- Util: Parse and cache post params-_populatePostParams :: HandlerM sub master PostParams-_populatePostParams = do-  st <- get-  case postParams st of-    Just params -> return params-    Nothing -> do-      req <- request-      params <- case P.getRequestBodyType req of-        Nothing -> return ([],[])-        Just _ -> do-          -- TODO: Use cached request body instead of reading it from wai request-          params <- liftIO $ P.parseRequestBody P.lbsBackEnd req-          return $ _toPostParams params-      put $ st{postParams=Just params}-      return params---- PRIVATE--- Get a list of post parameters-_getAllFileOrPostParams :: HandlerM sub master PostParams-_getAllFileOrPostParams = do-  cachedPostParams <- _getCachedPostParams-  case cachedPostParams of-    Nothing -> _populatePostParams-    Just params -> return params---- | Get all Query params-getQueryParams :: HandlerM sub master [(Text,Text)]-getQueryParams = readQueryString . queryString <$> request---- | Get a particular Query param-getQueryParam :: Text -> HandlerM sub master (Maybe Text)-getQueryParam name = lookup name <$> getQueryParams---- | Get all Post params-getPostParams :: HandlerM sub master [(Text,Text)]-getPostParams = do-  (params,_) <- _getAllFileOrPostParams-  return params---- | Get a particular Post param-getPostParam :: Text -> HandlerM sub master (Maybe Text)-getPostParam name = lookup name <$> getPostParams---- | Get all File params-getFileParams :: HandlerM sub master [(Text,FileInfo)]-getFileParams = do-  (_,files) <- _getAllFileOrPostParams-  return files---- | Get a particular File param-getFileParam :: Text -> HandlerM sub master (Maybe FileInfo)-getFileParam name = lookup name <$> getFileParams---- | Get all params (query or post, NOT file)--- Duplicate parameters are preserved-getParams :: HandlerM sub master [(Text, Text)]-getParams = (++) <$> getQueryParams <*> getPostParams---- | Get a param (query or post, NOT file)-getParam :: Text -> HandlerM sub master (Maybe Text)-getParam name = do-  getLookup <- getQueryParam name-  case getLookup of-    Nothing -> getPostParam name-    Just _ -> return getLookup
− src/Network/Wai/Middleware/Routes/Monad.hs
@@ -1,86 +0,0 @@-{-# LANGUAGE OverloadedStrings, TypeFamilies, RankNTypes, DeriveFunctor #-}--{- |-Module      :  Network.Wai.Middleware.Routes.Monad-Copyright   :  (c) Anupam Jain 2013-License     :  MIT (see the file LICENSE)--Maintainer  :  ajnsit@gmail.com-Stability   :  experimental-Portability :  non-portable (uses ghc extensions)--Defines a Routing Monad that provides easy composition of Routes--}-module Network.Wai.Middleware.Routes.Monad-    ( -- * Route Monad-      RouteM-      -- * Compose Routes-    , DefaultMaster(..)-    , Route(DefaultRoute)-    , handler-    , middleware-    , route-    , catchall-    , defaultAction-      -- * Convert to Wai Application-    , waiApp-    , toWaiApp-    )-    where--import Network.Wai-import Network.Wai.Middleware.Routes.Routes-import Network.Wai.Middleware.Routes.DefaultRoute-import Network.HTTP.Types (status404)--import Util.Free (F(..), liftF)---- A Router functor can either add a middleware, or resolve to an app itself.-data RouterF x = M Middleware x | D Application deriving Functor---- Router type-type RouteM = F RouterF---- | Catch all routes and process them with the supplied application.--- Note: As expected from the name, no request proceeds past a catchall.-catchall :: Application -> RouteM ()-catchall a = liftF $ D a---- | Synonym of `catchall`. Kept for backwards compatibility-defaultAction :: Application -> RouteM ()-defaultAction = catchall---- | Add a middleware to the application--- Middleware are ordered so the one declared earlier wraps the ones later-middleware :: Middleware -> RouteM ()-middleware m = liftF $ M m ()---- | Add a wai-routes handler-handler :: HandlerS DefaultMaster DefaultMaster -> RouteM ()-handler h = middleware $ customRouteDispatch dispatcher' DefaultMaster-  where-    dispatcher' env req = runHandler h env (Just $ DefaultRoute $ getRoute req) req-    getRoute req = (pathInfo $ waiReq req, readQueryString $ queryString $ waiReq req)---- | Add a route to the application.--- Routes are ordered so the one declared earlier is matched first.-route :: (Routable master master) => master -> RouteM ()-route = middleware . routeDispatch---- The final "catchall" application, simply returns a 404 response--- Ideally you should put your own default application-defaultApplication :: Application-defaultApplication _req h = h $ responseLBS status404 [("Content-Type", "text/plain")] "Error : 404 - Document not found"---- | Convert a RouteM monad into a wai application.--- Note: We ignore the return type of the monad-waiApp :: RouteM () -> Application-waiApp (F r) = r (const defaultApplication) f-  where-    f (M m r') = m r'-    f (D a) = a---- | Similar to waiApp but returns the app in an arbitrary monad--- Kept for backwards compatibility-toWaiApp :: Monad m => RouteM () -> m Application-toWaiApp = return . waiApp
− src/Network/Wai/Middleware/Routes/Overlap.hs
@@ -1,88 +0,0 @@--- | Check for overlapping routes.-module Network.Wai.Middleware.Routes.Overlap-    ( findOverlapNames-    , Overlap (..)-    ) where--import Network.Wai.Middleware.Routes.TH.Types-import Data.List (intercalate)--data Flattened t = Flattened-    { fNames :: [String]-    , fPieces :: [Piece t]-    , fHasSuffix :: Bool-    , fCheck :: CheckOverlap-    }--flatten :: ResourceTree t -> [Flattened t]-flatten =-    go id id True-  where-    go names pieces check (ResourceLeaf r) = return Flattened-        { fNames = names [resourceName r]-        , fPieces = pieces (resourcePieces r)-        , fHasSuffix = hasSuffix $ ResourceLeaf r-        , fCheck = check && resourceCheck r-        }-    go names pieces check (ResourceParent newname check' newpieces children) =-        concatMap (go names' pieces' (check && check')) children-      where-        names' = names . (newname:)-        pieces' = pieces . (newpieces ++)--data Overlap t = Overlap-    { overlapParents :: [String] -> [String] -- ^ parent resource trees-    , overlap1 :: ResourceTree t-    , overlap2 :: ResourceTree t-    }--data OverlapF = OverlapF-    { _overlapF1 :: [String]-    , _overlapF2 :: [String]-    }--overlaps :: [Piece t] -> [Piece t] -> Bool -> Bool -> Bool---- No pieces on either side, will overlap regardless of suffix-overlaps [] [] _ _ = True---- No pieces on the left, will overlap if the left side has a suffix-overlaps [] _ suffixX _ = suffixX---- Ditto for the right-overlaps _ [] _ suffixY = suffixY---- Compare the actual pieces-overlaps (pieceX:xs) (pieceY:ys) suffixX suffixY =-    piecesOverlap pieceX pieceY && overlaps xs ys suffixX suffixY--piecesOverlap :: Piece t -> Piece t -> Bool--- Statics only match if they equal. Dynamics match with anything-piecesOverlap (Static x) (Static y) = x == y-piecesOverlap _ _ = True--findOverlapNames :: [ResourceTree t] -> [(String, String)]-findOverlapNames =-    map go . findOverlapsF . filter fCheck . concatMap Network.Wai.Middleware.Routes.Overlap.flatten-  where-    go (OverlapF x y) =-        (go' x, go' y)-      where-        go' = intercalate "/"--findOverlapsF :: [Flattened t] -> [OverlapF]-findOverlapsF [] = []-findOverlapsF (x:xs) = concatMap (findOverlapF x) xs ++ findOverlapsF xs--findOverlapF :: Flattened t -> Flattened t -> [OverlapF]-findOverlapF x y-    | overlaps (fPieces x) (fPieces y) (fHasSuffix x) (fHasSuffix y) = [OverlapF (fNames x) (fNames y)]-    | otherwise = []--hasSuffix :: ResourceTree t -> Bool-hasSuffix (ResourceLeaf r) =-    case resourceDispatch r of-        Subsite{} -> True-        Methods Just{} _ -> True-        Methods Nothing _ -> False-hasSuffix ResourceParent{} = True
− src/Network/Wai/Middleware/Routes/Parse.hs
@@ -1,254 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE PatternGuards #-}-{-# OPTIONS_GHC -fno-warn-missing-fields #-} -- QuasiQuoter-module Network.Wai.Middleware.Routes.Parse-    ( parseRoutes-    , parseRoutesFile-    , parseRoutesNoCheck-    , parseRoutesFileNoCheck-    , parseType-    , parseTypeTree-    , TypeTree (..)-    ) where--import Language.Haskell.TH.Syntax-import Data.Char (isUpper)-import Language.Haskell.TH.Quote-import qualified System.IO as SIO-import Network.Wai.Middleware.Routes.TH-import Network.Wai.Middleware.Routes.Overlap (findOverlapNames)-import Data.List (foldl', isPrefixOf)-import Data.Maybe (mapMaybe)-import qualified Data.Set as Set---- | A quasi-quoter to parse a string into a list of 'Resource's. Checks for--- overlapping routes, failing if present; use 'parseRoutesNoCheck' to skip the--- checking. See documentation site for details on syntax.-parseRoutes :: QuasiQuoter-parseRoutes = QuasiQuoter { quoteExp = x }-  where-    x s = do-        let res = resourcesFromString s-        case findOverlapNames res of-            [] -> lift res-            z -> error $ unlines $ "Overlapping routes: " : map show z--parseRoutesFile :: FilePath -> Q Exp-parseRoutesFile = parseRoutesFileWith parseRoutes--parseRoutesFileNoCheck :: FilePath -> Q Exp-parseRoutesFileNoCheck = parseRoutesFileWith parseRoutesNoCheck--parseRoutesFileWith :: QuasiQuoter -> FilePath -> Q Exp-parseRoutesFileWith qq fp = do-    qAddDependentFile fp-    s <- qRunIO $ readUtf8File fp-    quoteExp qq s--readUtf8File :: FilePath -> IO String-readUtf8File fp = do-    h <- SIO.openFile fp SIO.ReadMode-    SIO.hSetEncoding h SIO.utf8_bom-    SIO.hGetContents h---- | Same as 'parseRoutes', but performs no overlap checking.-parseRoutesNoCheck :: QuasiQuoter-parseRoutesNoCheck = QuasiQuoter-    { quoteExp = lift . resourcesFromString-    }---- | Convert a multi-line string to a set of resources. See documentation for--- the format of this string. This is a partial function which calls 'error' on--- invalid input.-resourcesFromString :: String -> [ResourceTree String]-resourcesFromString =-    fst . parse 0 . filter (not . all (== ' ')) . lines-  where-    parse _ [] = ([], [])-    parse indent (thisLine:otherLines)-        | length spaces < indent = ([], thisLine : otherLines)-        | otherwise = (this others, remainder)-      where-        parseAttr ('!':x) = Just x-        parseAttr _ = Nothing--        stripColonLast =-            go id-          where-            go _ [] = Nothing-            go front [x]-                | null x = Nothing-                | last x == ':' = Just $ front [init x]-                | otherwise = Nothing-            go front (x:xs) = go (front . (x:)) xs--        spaces = takeWhile (== ' ') thisLine-        (others, remainder) = parse indent otherLines'-        (this, otherLines') =-            case takeWhile (not . isPrefixOf "--") $ words thisLine of-                (pattern:rest0)-                    | Just (constr:rest) <- stripColonLast rest0-                    , Just attrs <- mapM parseAttr rest ->-                    let (children, otherLines'') = parse (length spaces + 1) otherLines-                        children' = addAttrs attrs children-                        (pieces, Nothing, check) = piecesFromStringCheck pattern-                     in ((ResourceParent constr check pieces children' :), otherLines'')-                (pattern:constr:rest) ->-                    let (pieces, mmulti, check) = piecesFromStringCheck pattern-                        (attrs, rest') = takeAttrs rest-                        disp = dispatchFromString rest' mmulti-                     in ((ResourceLeaf (Resource constr pieces disp attrs check):), otherLines)-                [] -> (id, otherLines)-                _ -> error $ "Invalid resource line: " ++ thisLine--piecesFromStringCheck :: String -> ([Piece String], Maybe String, Bool)-piecesFromStringCheck s0 =-    (pieces, mmulti, check)-  where-    (s1, check1) = stripBang s0-    (pieces', mmulti') = piecesFromString $ drop1Slash s1-    pieces = map snd pieces'-    mmulti = fmap snd mmulti'-    check = check1 && all fst pieces' && maybe True fst mmulti'--    stripBang ('!':rest) = (rest, False)-    stripBang x = (x, True)--addAttrs :: [String] -> [ResourceTree String] -> [ResourceTree String]-addAttrs attrs =-    map goTree-  where-    goTree (ResourceLeaf res) = ResourceLeaf (goRes res)-    goTree (ResourceParent w x y z) = ResourceParent w x y (map goTree z)--    goRes res =-        res { resourceAttrs = noDupes ++ resourceAttrs res }-      where-        usedKeys = Set.fromList $ map fst $ mapMaybe toPair $ resourceAttrs res-        used attr =-            case toPair attr of-                Nothing -> False-                Just (key, _) -> key `Set.member` usedKeys-        noDupes = filter (not . used) attrs--    toPair s =-        case break (== '=') s of-            (x, '=':y) -> Just (x, y)-            _ -> Nothing---- | Take attributes out of the list and put them in the first slot in the--- result tuple.-takeAttrs :: [String] -> ([String], [String])-takeAttrs =-    go id id-  where-    go x y [] = (x [], y [])-    go x y (('!':attr):rest) = go (x . (attr:)) y rest-    go x y (z:rest) = go x (y . (z:)) rest--dispatchFromString :: [String] -> Maybe String -> Dispatch String-dispatchFromString rest mmulti-    | null rest = Methods mmulti []-    | all (all isUpper) rest = Methods mmulti rest-dispatchFromString [subTyp, subFun] Nothing =-    Subsite subTyp subFun-dispatchFromString [_, _] Just{} =-    error "Subsites cannot have a multipiece"-dispatchFromString rest _ = error $ "Invalid list of methods: " ++ show rest--drop1Slash :: String -> String-drop1Slash ('/':x) = x-drop1Slash x = x--piecesFromString :: String -> ([(CheckOverlap, Piece String)], Maybe (CheckOverlap, String))-piecesFromString "" = ([], Nothing)-piecesFromString x =-    case (this, rest) of-        (Left typ, ([], Nothing)) -> ([], Just typ)-        (Left _, _) -> error "Multipiece must be last piece"-        (Right piece, (pieces, mtyp)) -> (piece:pieces, mtyp)-  where-    (y, z) = break (== '/') x-    this = pieceFromString y-    rest = piecesFromString $ drop 1 z--parseType :: String -> Type-parseType orig =-    maybe (error $ "Invalid type: " ++ show orig) ttToType $ parseTypeTree orig--parseTypeTree :: String -> Maybe TypeTree-parseTypeTree orig =-    toTypeTree pieces-  where-    pieces = filter (not . null) $ splitOn '-' $ addDashes orig-    addDashes [] = []-    addDashes (x:xs) =-        front $ addDashes xs-      where-        front rest-            | x `elem` "()[]" = '-' : x : '-' : rest-            | otherwise = x : rest-    splitOn c s =-        case y' of-            _:y -> x : splitOn c y-            [] -> [x]-      where-        (x, y') = break (== c) s--data TypeTree = TTTerm String-              | TTApp TypeTree TypeTree-              | TTList TypeTree-    deriving (Show, Eq)--toTypeTree :: [String] -> Maybe TypeTree-toTypeTree orig = do-    (x, []) <- gos orig-    return x-  where-    go [] = Nothing-    go ("(":xs) = do-        (x, rest) <- gos xs-        case rest of-            ")":rest' -> Just (x, rest')-            _ -> Nothing-    go ("[":xs) = do-        (x, rest) <- gos xs-        case rest of-            "]":rest' -> Just (TTList x, rest')-            _ -> Nothing-    go (x:xs) = Just (TTTerm x, xs)--    gos xs1 = do-        (t, xs2) <- go xs1-        (ts, xs3) <- gos' id xs2-        Just (foldl' TTApp t ts, xs3)--    gos' front [] = Just (front [], [])-    gos' front (x:xs)-        | x `elem` words ") ]" = Just (front [], x:xs)-        | otherwise = do-            (t, xs') <- go $ x:xs-            gos' (front . (t:)) xs'--ttToType :: TypeTree -> Type-ttToType (TTTerm s) = ConT $ mkName s-ttToType (TTApp x y) = ttToType x `AppT` ttToType y-ttToType (TTList t) = ListT `AppT` ttToType t--pieceFromString :: String -> Either (CheckOverlap, String) (CheckOverlap, Piece String)-pieceFromString ('#':'!':x) = Right $ (False, Dynamic x)-pieceFromString ('!':'#':x) = Right $ (False, Dynamic x) -- https://github.com/yesodweb/yesod/issues/652-pieceFromString ('#':x) = Right $ (True, Dynamic x)--pieceFromString ('*':'!':x) = Left (False, x)-pieceFromString ('+':'!':x) = Left (False, x)--pieceFromString ('!':'*':x) = Left (False, x)-pieceFromString ('!':'+':x) = Left (False, x)--pieceFromString ('*':x) = Left (True, x)-pieceFromString ('+':x) = Left (True, x)--pieceFromString ('!':x) = Right $ (False, Static x)-pieceFromString x = Right $ (True, Static x)
− src/Network/Wai/Middleware/Routes/Routes.hs
@@ -1,299 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}-{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE CPP #-}-{- |-Module      :  Network.Wai.Middleware.Routes.Routes-Copyright   :  (c) Anupam Jain 2013-License     :  MIT (see the file LICENSE)--Maintainer  :  ajnsit@gmail.com-Stability   :  experimental-Portability :  non-portable (uses ghc extensions)--This package provides typesafe URLs for Wai applications.--}-module Network.Wai.Middleware.Routes.Routes-    ( -- * Quasi Quoters-      parseRoutes            -- | Parse Routes declared inline-    , parseRoutesFile        -- | Parse routes declared in a file-    , parseRoutesNoCheck     -- | Parse routes declared inline, without checking for overlaps-    , parseRoutesFileNoCheck -- | Parse routes declared in a file, without checking for overlaps--    -- * Template Haskell methods-    , mkRoute-    , mkRouteSub--    -- * Dispatch-    , routeDispatch-    , customRouteDispatch--    -- * URL rendering and parsing-    , showRoute-    , showRouteQuery-    , readRoute--    -- * Application Handlers-    , Handler-    , HandlerS--    -- * As of Wai 3, Application datatype now follows continuation passing style-    --   A `ResponseHandler` represents a continuation passed to the application-    , ResponseHandler--    -- * Generated Datatypes-    , Routable(..)           -- | Used internally. However needs to be exported for TH to work.-    , RenderRoute(..)        -- | A `RenderRoute` instance for your site datatype is automatically generated by `mkRoute`-    , ParseRoute(..)         -- | A `ParseRoute` instance for your site datatype is automatically generated by `mkRoute`-    , RouteAttrs(..)         -- | A `RouteAttrs` instance for your site datatype is automatically generated by `mkRoute`--    -- * Accessing Request Data-    , Env(..)-    , RequestData            -- | An abstract representation of the request data. You can get the wai request object by using `waiReq`-    , waiReq                 -- | Extract the wai `Request` object from `RequestData`-    , nextApp                -- | Extract the next Application in the stack-    , currentRoute           -- | Extract the current `Route` from `RequestData`-    , runNext                -- | Run the next application in the stack--    -- * Not exported outside wai-routes-    , runHandler-    , readQueryString-    )-    where---- Wai-import Network.Wai (ResponseReceived, Middleware, Application, pathInfo, requestMethod, requestMethod, Response, Request(..))-import Network.HTTP.Types (Query, decodePath, encodePath, queryTextToQuery, queryToQueryText)---- Network.Wai.Middleware.Routes-import Network.Wai.Middleware.Routes.Class (Route, RenderRoute(..), ParseRoute(..), RouteAttrs(..))-import Network.Wai.Middleware.Routes.Parse (parseRoutes, parseRoutesNoCheck, parseRoutesFile, parseRoutesFileNoCheck, parseType)-import Network.Wai.Middleware.Routes.TH (mkRenderRouteInstance, mkParseRouteInstance, mkRouteAttrsInstance, mkDispatchClause, ResourceTree(..), MkDispatchSettings(..), defaultGetHandler)---- Text and Bytestring-import Data.ByteString (ByteString)-import Data.Text (Text)-import Data.Text.Encoding (encodeUtf8, decodeUtf8)-import Blaze.ByteString.Builder (toByteString)---- TH-import Language.Haskell.TH.Syntax---- Convenience-import Control.Arrow (second)-import Data.Maybe (fromMaybe)---- An abstract request-data RequestData master = RequestData-  { waiReq  :: Request-  , nextApp :: Application-  , currentRoute :: Maybe (Route master)-  }---- AJ: Experimental-type ResponseHandler = (Response -> IO ResponseReceived) -> IO ResponseReceived---- Wai uses Application :: Wai.Request -> ResponseHandler--- However, instead of Request, we use RequestData which has more information-type App master = RequestData master -> ResponseHandler--data Env sub master = Env-  { envMaster   :: master-  , envSub      :: sub-  , envToMaster :: Route sub -> Route master-  }---- | Run the next application in the stack-runNext :: App master-runNext req = nextApp req $ waiReq req---- | A `Handler` generates an App from the master datatype-type Handler sub = forall master. RenderRoute master => HandlerS sub master-type HandlerS sub master = Env sub master -> App sub---- | Generates everything except actual dispatch-mkRouteData :: String -> [ResourceTree String] -> Q [Dec]-mkRouteData typName routes = do-  let typ = parseType typName-  let rname = mkName $ "_resources" ++ typName-  let resourceTrees = map (fmap parseType) routes-  eres <- lift routes-  let resourcesDec =-          [ SigD rname $ ListT `AppT` (ConT ''ResourceTree `AppT` ConT ''String)-          , FunD rname [Clause [] (NormalB eres) []]-          ]-  rinst <- mkRenderRouteInstance typ resourceTrees-  pinst <- mkParseRouteInstance typ resourceTrees-  ainst <- mkRouteAttrsInstance typ resourceTrees-  return $ concat [ [ainst]-                  , [pinst]-                  , resourcesDec-                  , rinst-                  ]---- | Generates a 'Routable' instance and dispatch function-mkRouteDispatch :: String -> [ResourceTree String] -> Q [Dec]-mkRouteDispatch typName routes = do-  let typ = parseType typName-  disp <- mkRouteDispatchClause routes-  return [InstanceD []-          (ConT ''Routable `AppT` typ `AppT` typ)-          [FunD (mkName "dispatcher") [disp]]]---- | Same as mkRouteDispatch but for subsites-mkRouteSubDispatch :: String -> String -> [ResourceTree a] -> Q [Dec]-mkRouteSubDispatch typName constraint routes = do-  let typ = parseType typName-  disp <- mkRouteDispatchClause routes-  master <- newName "master"-  -- We don't simply use parseType for GHC 7.8 (TH-2.9) compatibility-  -- ParseType only works on Type (not Pred)-  -- In GHC 7.10 (TH-2.10) onwards, Pred is aliased to Type-  className <- lookupTypeName constraint-  -- Check if this is a classname or a type-  let contract = maybe (error $ "Unknown typeclass " ++ show constraint) (getContract master) className-  return [InstanceD [contract]-          (ConT ''Routable `AppT` typ `AppT` VarT master)-          [FunD (mkName "dispatcher") [disp]]]-  where-    getContract master className =-#if MIN_VERSION_template_haskell(2,10,0)-      ConT className `AppT` VarT master-#else-      ClassP className [VarT master]-#endif---- Helper that creates the dispatch clause-mkRouteDispatchClause :: [ResourceTree a] -> Q Clause-mkRouteDispatchClause =-  mkDispatchClause MkDispatchSettings-    { mdsRunHandler    = [| runHandler    |]-    , mdsSubDispatcher = [| subDispatcher |]-    , mdsGetPathInfo   = [| getPathInfo   |]-    , mdsMethod        = [| getReqMethod  |]-    , mdsSetPathInfo   = [| setPathInfo   |]-    , mds404           = [| app404        |]-    , mds405           = [| app405        |]-    , mdsGetHandler    = defaultGetHandler-    , mdsUnwrapper     = return-    }----- | Generates all the things needed for efficient routing.--- Including your application's `Route` datatype,--- `RenderRoute`, `ParseRoute`, `RouteAttrs`, and `Routable` instances.--- Use this for everything except subsites-mkRoute :: String -> [ResourceTree String] -> Q [Dec]-mkRoute typName routes = do-  dat <- mkRouteData typName routes-  disp <- mkRouteDispatch typName routes-  return (disp++dat)---- TODO: Also allow using the master datatype name directly, instead of a constraint class--- | Same as mkRoute, but for subsites-mkRouteSub :: String -> String -> [ResourceTree String] -> Q [Dec]-mkRouteSub typName constraint routes = do-  dat <- mkRouteData typName routes-  disp <- mkRouteSubDispatch typName constraint routes-  return (disp++dat)---- | A `Routable` instance can be used in dispatching.---   An appropriate instance for your site datatype is---   automatically generated by `mkRoute`.-class Routable sub master where-  dispatcher :: HandlerS sub master---- | Generates the application middleware from a `Routable` master datatype-routeDispatch :: Routable master master => master -> Middleware-routeDispatch = customRouteDispatch dispatcher---- | Like routeDispatch but generates the application middleware from a custom dispatcher-customRouteDispatch :: HandlerS master master -> master -> Middleware--- TODO: Should this have master master instead of sub master?--- TODO: Verify that this plays well with subsites--- Env master master is converted to Env sub master by subDispatcher--- Route information is filled in by runHandler-customRouteDispatch customDispatcher master def req = customDispatcher (_masterToEnv master) RequestData{waiReq=req, nextApp=def, currentRoute=Nothing}---- | Render a `Route` and Query parameters to Text-showRouteQuery :: RenderRoute master => Route master -> [(Text,Text)] -> Text-showRouteQuery r q = uncurry _encodePathInfo $ second (map (second Just) . (++ q)) $ renderRoute r---- | Renders a `Route` as Text-showRoute :: RenderRoute master => Route master -> Text-showRoute = uncurry _encodePathInfo . second (map $ second Just) . renderRoute--_encodePathInfo :: [Text] -> [(Text, Maybe Text)] -> Text--- Slightly hackish: Convert "" into "/"-_encodePathInfo [] = _encodePathInfo [""]-_encodePathInfo segments = decodeUtf8 . toByteString . encodePath segments . queryTextToQuery---- | Read a route from Text--- Returns Nothing if Route reading failed. Just route otherwise-readRoute :: ParseRoute master => Text -> Maybe (Route master)-readRoute = parseRoute . second readQueryString . decodePath . encodeUtf8---- | Convert a Query to the format expected by parseRoute-readQueryString :: Query -> [(Text, Text)]-readQueryString = map (second (fromMaybe "")) . queryToQueryText---- PRIVATE---- Get the request method from a RequestData-getReqMethod :: RequestData master -> ByteString-getReqMethod = requestMethod . waiReq---- Get the path info from a RequestData-getPathInfo :: RequestData master -> [Text]-getPathInfo = pathInfo . waiReq---- Set the path info in a RequestData-setPathInfo :: [Text] -> RequestData master -> RequestData master-setPathInfo p reqData = reqData { waiReq = (waiReq reqData){pathInfo=p} }---- Baked in applications that handle 404 and 405 errors--- On no matching route, skip to next application-app404 :: HandlerS sub master-app404 _master = runNext---- On matching route, but no matching http method, skip to next application--- This allows a later route to handle methods not implemented by the previous routes-app405 :: HandlerS sub master-app405 _master = runNext---- Run a route handler function--- Currently all this does is populate the route into RequestData--- But it may do more in the future-runHandler-    :: HandlerS sub master-    -> Env sub master-    -> Maybe (Route sub)-    -> App sub-runHandler h env route reqdata = h env reqdata{currentRoute=route}---- Run a route subsite handler function-subDispatcher-    :: Routable sub master-    => (HandlerS sub master -> Env sub master -> Maybe (Route sub) -> App sub)-    -> (master -> sub)-    -> (Route sub -> Route master)-    -> Env master master-    -> App master-subDispatcher _runhandler getSub toMasterRoute env reqData = dispatcher env' reqData'-  where-    env' = _envToSub getSub toMasterRoute env-    reqData' = reqData{currentRoute=Nothing}-    -- qq (k,mv) = (decodeUtf8 k, maybe "" decodeUtf8 mv)-    -- req = waiReq reqData--_masterToEnv :: master -> Env master master-_masterToEnv master = Env master master id--_envToSub :: (master -> sub) -> (Route sub -> Route master) -> Env master master -> Env sub master-_envToSub getSub toMasterRoute env = Env master sub toMasterRoute-  where-    master = envMaster env-    sub = getSub master
− src/Network/Wai/Middleware/Routes/TH.hs
@@ -1,16 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-module Network.Wai.Middleware.Routes.TH-    ( module Network.Wai.Middleware.Routes.TH.Types-      -- * Functions-    , module Network.Wai.Middleware.Routes.TH.RenderRoute-    , module Network.Wai.Middleware.Routes.TH.ParseRoute-    , module Network.Wai.Middleware.Routes.TH.RouteAttrs-      -- ** Dispatch-    , module Network.Wai.Middleware.Routes.TH.Dispatch-    ) where--import Network.Wai.Middleware.Routes.TH.Types-import Network.Wai.Middleware.Routes.TH.RenderRoute-import Network.Wai.Middleware.Routes.TH.ParseRoute-import Network.Wai.Middleware.Routes.TH.RouteAttrs-import Network.Wai.Middleware.Routes.TH.Dispatch
− src/Network/Wai/Middleware/Routes/TH/Dispatch.hs
@@ -1,202 +0,0 @@-{-# LANGUAGE RecordWildCards, TemplateHaskell, ViewPatterns #-}-module Network.Wai.Middleware.Routes.TH.Dispatch-    ( MkDispatchSettings (..)-    , mkDispatchClause-    , defaultGetHandler-    ) where--import Prelude hiding (exp)-import Language.Haskell.TH.Syntax-import Web.PathPieces-import Data.Maybe (catMaybes)-import Control.Monad (forM)-import Data.List (foldl')-import Control.Arrow (second)-import System.Random (randomRIO)-import Network.Wai.Middleware.Routes.TH.Types-import Data.Char (toLower)--data MkDispatchSettings b site c = MkDispatchSettings-    { mdsRunHandler :: Q Exp-    , mdsSubDispatcher :: Q Exp-    , mdsGetPathInfo :: Q Exp-    , mdsSetPathInfo :: Q Exp-    , mdsMethod :: Q Exp-    , mds404 :: Q Exp-    , mds405 :: Q Exp-    , mdsGetHandler :: Maybe String -> String -> Q Exp-    , mdsUnwrapper :: Exp -> Q Exp-    }--data SDC = SDC-    { clause404 :: Clause-    , extraParams :: [Exp]-    , extraCons :: [Exp]-    , envExp :: Exp-    , reqExp :: Exp-    }---- | A simpler version of Network.Wai.Middleware.Routes.TH.Dispatch.mkDispatchClause, based on--- view patterns.------ Since 1.4.0-mkDispatchClause :: MkDispatchSettings b site c -> [ResourceTree a] -> Q Clause-mkDispatchClause MkDispatchSettings {..} resources = do-    suffix <- qRunIO $ randomRIO (1000, 9999 :: Int)-    envName <- newName $ "env" ++ show suffix-    reqName <- newName $ "req" ++ show suffix-    helperName <- newName $ "helper" ++ show suffix--    let envE = VarE envName-        reqE = VarE reqName-        helperE = VarE helperName--    clause404' <- mkClause404 envE reqE-    getPathInfo <- mdsGetPathInfo-    let pathInfo = getPathInfo `AppE` reqE--    let sdc = SDC-            { clause404 = clause404'-            , extraParams = []-            , extraCons = []-            , envExp = envE-            , reqExp = reqE-            }-    clauses <- mapM (go sdc) resources--    return $ Clause-        [VarP envName, VarP reqName]-        (NormalB $ helperE `AppE` pathInfo)-        [FunD helperName $ clauses ++ [clause404']]-  where-    handlePiece :: Piece a -> Q (Pat, Maybe Exp)-    handlePiece (Static str) = return (LitP $ StringL str, Nothing)-    handlePiece (Dynamic _) = do-        x <- newName "dyn"-        let pat = ViewP (VarE 'fromPathPiece) (ConP 'Just [VarP x])-        return (pat, Just $ VarE x)--    handlePieces :: [Piece a] -> Q ([Pat], [Exp])-    handlePieces = fmap (second catMaybes . unzip) . mapM handlePiece--    mkCon :: String -> [Exp] -> Exp-    mkCon name = foldl' AppE (ConE $ mkName name)--    mkPathPat :: Pat -> [Pat] -> Pat-    mkPathPat final =-        foldr addPat final-      where-        addPat x y = ConP '(:) [x, y]--    go :: SDC -> ResourceTree a -> Q Clause-    go sdc (ResourceParent name _check pieces children) = do-        (pats, dyns) <- handlePieces pieces-        let sdc' = sdc-                { extraParams = extraParams sdc ++ dyns-                , extraCons = extraCons sdc ++ [mkCon name dyns]-                }-        childClauses <- mapM (go sdc') children--        restName <- newName "rest"-        let restE = VarE restName-            restP = VarP restName--        helperName <- newName $ "helper" ++ name-        let helperE = VarE helperName--        return $ Clause-            [mkPathPat restP pats]-            (NormalB $ helperE `AppE` restE)-            [FunD helperName $ childClauses ++ [clause404 sdc]]-    go SDC {..} (ResourceLeaf (Resource name pieces dispatch _ _check)) = do-        (pats, dyns) <- handlePieces pieces--        (chooseMethod, finalPat) <- handleDispatch dispatch dyns--        return $ Clause-            [mkPathPat finalPat pats]-            (NormalB chooseMethod)-            []-      where-        handleDispatch :: Dispatch a -> [Exp] -> Q (Exp, Pat)-        handleDispatch dispatch' dyns =-            case dispatch' of-                Methods multi methods -> do-                    (finalPat, mfinalE) <--                        case multi of-                            Nothing -> return (ConP '[] [], Nothing)-                            Just _ -> do-                                multiName <- newName "multi"-                                let pat = ViewP (VarE 'fromPathMultiPiece)-                                                (ConP 'Just [VarP multiName])-                                return (pat, Just $ VarE multiName)--                    let dynsMulti =-                            case mfinalE of-                                Nothing -> dyns-                                Just e -> dyns ++ [e]-                        route' = foldl' AppE (ConE (mkName name)) dynsMulti-                        route = foldr AppE route' extraCons-                        jroute = ConE 'Just `AppE` route-                        allDyns = extraParams ++ dynsMulti-                        mkRunExp mmethod = do-                            runHandlerE <- mdsRunHandler-                            handlerE' <- mdsGetHandler mmethod name-                            handlerE <- mdsUnwrapper $ foldl' AppE handlerE' allDyns-                            return $ runHandlerE-                                `AppE` handlerE-                                `AppE` envExp-                                `AppE` jroute-                                `AppE` reqExp--                    func <--                        case methods of-                            [] -> mkRunExp Nothing-                            _ -> do-                                getMethod <- mdsMethod-                                let methodE = getMethod `AppE` reqExp-                                matches <- forM methods $ \method -> do-                                    exp <- mkRunExp (Just method)-                                    return $ Match (LitP $ StringL method) (NormalB exp) []-                                match405 <- do-                                    runHandlerE <- mdsRunHandler-                                    handlerE <- mds405-                                    let exp = runHandlerE-                                            `AppE` handlerE-                                            `AppE` envExp-                                            `AppE` jroute-                                            `AppE` reqExp-                                    return $ Match WildP (NormalB exp) []-                                return $ CaseE methodE $ matches ++ [match405]--                    return (func, finalPat)-                Subsite _ getSub -> do-                    restPath <- newName "restPath"-                    setPathInfoE <- mdsSetPathInfo-                    subDispatcherE <- mdsSubDispatcher-                    runHandlerE <- mdsRunHandler-                    sub <- newName "sub"-                    let allDyns = extraParams ++ dyns-                    sroute <- newName "sroute"-                    let sub2 = LamE [VarP sub]-                            (foldl' (\a b -> a `AppE` b) (VarE (mkName getSub) `AppE` VarE sub) allDyns)-                    let reqExp' = setPathInfoE `AppE` VarE restPath `AppE` reqExp-                        route' = foldl' AppE (ConE (mkName name)) dyns-                        route = LamE [VarP sroute] $ foldr AppE (AppE route' $ VarE sroute) extraCons-                        exp = subDispatcherE-                            `AppE` runHandlerE-                            `AppE` sub2-                            `AppE` route-                            `AppE` envExp-                            `AppE` reqExp'-                    return (exp, VarP restPath)--    mkClause404 envE reqE = do-        handler <- mds404-        runHandler <- mdsRunHandler-        let exp = runHandler `AppE` handler `AppE` envE `AppE` ConE 'Nothing `AppE` reqE-        return $ Clause [WildP] (NormalB exp) []--defaultGetHandler :: Maybe String -> String -> Q Exp-defaultGetHandler Nothing s = return $ VarE $ mkName $ "handle" ++ s-defaultGetHandler (Just method) s = return $ VarE $ mkName $ map toLower method ++ s
− src/Network/Wai/Middleware/Routes/TH/ParseRoute.hs
@@ -1,44 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-module Network.Wai.Middleware.Routes.TH.ParseRoute-    ( -- ** ParseRoute-      mkParseRouteInstance-    ) where--import Network.Wai.Middleware.Routes.TH.Types-import Language.Haskell.TH.Syntax-import Data.Text (Text)-import Network.Wai.Middleware.Routes.Class-import Network.Wai.Middleware.Routes.TH.Dispatch--mkParseRouteInstance :: Type -> [ResourceTree a] -> Q Dec-mkParseRouteInstance typ ress = do-    cls <- mkDispatchClause-        MkDispatchSettings-            { mdsRunHandler = [|\_ _ x _ -> x|]-            , mds404 = [|error "mds404"|]-            , mds405 = [|error "mds405"|]-            , mdsGetPathInfo = [|fst|]-            , mdsMethod = [|error "mdsMethod"|]-            , mdsGetHandler = \_ _ -> [|error "mdsGetHandler"|]-            , mdsSetPathInfo = [|\p (_, q) -> (p, q)|]-            , mdsSubDispatcher = [|\_runHandler _getSub toMaster _env -> fmap toMaster . parseRoute|]-            , mdsUnwrapper = return-            }-        (map removeMethods ress)-    helper <- newName "helper"-    fixer <- [|(\f x -> f () x) :: (() -> ([Text], [(Text, Text)]) -> Maybe (Route a)) -> ([Text], [(Text, Text)]) -> Maybe (Route a)|]-    return $ InstanceD [] (ConT ''ParseRoute `AppT` typ)-        [ FunD 'parseRoute $ return $ Clause-            []-            (NormalB $ fixer `AppE` VarE helper)-            [FunD helper [cls]]-        ]-  where-    -- We do this in order to ski the unnecessary method parsing-    removeMethods (ResourceLeaf res) = ResourceLeaf $ removeMethodsLeaf res-    removeMethods (ResourceParent w x y z) = ResourceParent w x y $ map removeMethods z--    removeMethodsLeaf res = res { resourceDispatch = fixDispatch $ resourceDispatch res }--    fixDispatch (Methods x _) = Methods x []-    fixDispatch x = x
− src/Network/Wai/Middleware/Routes/TH/RenderRoute.hs
@@ -1,174 +0,0 @@-{-# LANGUAGE TemplateHaskell, CPP #-}-module Network.Wai.Middleware.Routes.TH.RenderRoute-    ( -- ** RenderRoute-      mkRenderRouteInstance-    , mkRenderRouteInstance'-    , mkRouteCons-    , mkRenderRouteClauses-    ) where--import Network.Wai.Middleware.Routes.TH.Types-#if MIN_VERSION_template_haskell(2,11,0)-import Language.Haskell.TH (conT)-#endif-import Language.Haskell.TH.Syntax-import Data.Maybe (maybeToList)-import Control.Monad (replicateM)-import Data.Text (pack)-import Web.PathPieces (PathPiece (..), PathMultiPiece (..))-import Network.Wai.Middleware.Routes.Class-#if __GLASGOW_HASKELL__ < 710-import Control.Applicative ((<$>))-import Data.Monoid (mconcat)-#endif---- | Generate the constructors of a route data type.-mkRouteCons :: [ResourceTree Type] -> Q ([Con], [Dec])-mkRouteCons rttypes =-    mconcat <$> mapM mkRouteCon rttypes-  where-    mkRouteCon (ResourceLeaf res) =-        return ([con], [])-      where-        con = NormalC (mkName $ resourceName res)-            $ map (\x -> (notStrict, x))-            $ concat [singles, multi, sub]-        singles = concatMap toSingle $ resourcePieces res-        toSingle Static{} = []-        toSingle (Dynamic typ) = [typ]--        multi = maybeToList $ resourceMulti res--        sub =-            case resourceDispatch res of-                Subsite { subsiteType = typ } -> [ConT ''Route `AppT` typ]-                _ -> []--    mkRouteCon (ResourceParent name _check pieces children) = do-        (cons, decs) <- mkRouteCons children-#if MIN_VERSION_template_haskell(2,11,0)-        dec <- DataD [] (mkName name) [] Nothing cons <$> mapM conT [''Show, ''Read, ''Eq]-#else-        let dec = DataD [] (mkName name) [] cons [''Show, ''Read, ''Eq]-#endif-        return ([con], dec : decs)-      where-        con = NormalC (mkName name)-            $ map (\x -> (notStrict, x))-            $ concat [singles, [ConT $ mkName name]]--        singles = concatMap toSingle pieces-        toSingle Static{} = []-        toSingle (Dynamic typ) = [typ]---- | Clauses for the 'renderRoute' method.-mkRenderRouteClauses :: [ResourceTree Type] -> Q [Clause]-mkRenderRouteClauses =-    mapM go-  where-    isDynamic Dynamic{} = True-    isDynamic _ = False--    go (ResourceParent name _check pieces children) = do-        let cnt = length $ filter isDynamic pieces-        dyns <- replicateM cnt $ newName "dyn"-        child <- newName "child"-        let pat = ConP (mkName name) $ map VarP $ dyns ++ [child]--        pack' <- [|pack|]-        tsp <- [|toPathPiece|]-        let piecesSingle = mkPieces (AppE pack' . LitE . StringL) tsp pieces dyns--        childRender <- newName "childRender"-        let rr = VarE childRender-        childClauses <- mkRenderRouteClauses children--        a <- newName "a"-        b <- newName "b"--        colon <- [|(:)|]-        let cons y ys = InfixE (Just y) colon (Just ys)-        let pieces' = foldr cons (VarE a) piecesSingle--        let body = LamE [TupP [VarP a, VarP b]] (TupE [pieces', VarE b]) `AppE` (rr `AppE` VarE child)--        return $ Clause [pat] (NormalB body) [FunD childRender childClauses]--    go (ResourceLeaf res) = do-        let cnt = length (filter isDynamic $ resourcePieces res) + maybe 0 (const 1) (resourceMulti res)-        dyns <- replicateM cnt $ newName "dyn"-        sub <--            case resourceDispatch res of-                Subsite{} -> fmap return $ newName "sub"-                _ -> return []-        let pat = ConP (mkName $ resourceName res) $ map VarP $ dyns ++ sub--        pack' <- [|pack|]-        tsp <- [|toPathPiece|]-        let piecesSingle = mkPieces (AppE pack' . LitE . StringL) tsp (resourcePieces res) dyns--        piecesMulti <--            case resourceMulti res of-                Nothing -> return $ ListE []-                Just{} -> do-                    tmp <- [|toPathMultiPiece|]-                    return $ tmp `AppE` VarE (last dyns)--        body <--            case sub of-                [x] -> do-                    rr <- [|renderRoute|]-                    a <- newName "a"-                    b <- newName "b"--                    colon <- [|(:)|]-                    let cons y ys = InfixE (Just y) colon (Just ys)-                    let pieces = foldr cons (VarE a) piecesSingle--                    return $ LamE [TupP [VarP a, VarP b]] (TupE [pieces, VarE b]) `AppE` (rr `AppE` VarE x)-                _ -> do-                    colon <- [|(:)|]-                    let cons a b = InfixE (Just a) colon (Just b)-                    return $ TupE [foldr cons piecesMulti piecesSingle, ListE []]--        return $ Clause [pat] (NormalB body) []--    mkPieces _ _ [] _ = []-    mkPieces toText tsp (Static s:ps) dyns = toText s : mkPieces toText tsp ps dyns-    mkPieces toText tsp (Dynamic{}:ps) (d:dyns) = tsp `AppE` VarE d : mkPieces toText tsp ps dyns-    mkPieces _ _ ((Dynamic _) : _) [] = error "mkPieces 120"---- | Generate the 'RenderRoute' instance.------ This includes both the 'Route' associated type and the--- 'renderRoute' method.  This function uses both 'mkRouteCons' and--- 'mkRenderRouteClasses'.-mkRenderRouteInstance :: Type -> [ResourceTree Type] -> Q [Dec]-mkRenderRouteInstance = mkRenderRouteInstance' []---- | A more general version of 'mkRenderRouteInstance' which takes an--- additional context.--mkRenderRouteInstance' :: Cxt -> Type -> [ResourceTree Type] -> Q [Dec]-mkRenderRouteInstance' cxt typ ress = do-    cls <- mkRenderRouteClauses ress-    (cons, decs) <- mkRouteCons ress-#if MIN_VERSION_template_haskell(2,11,0)-    did <- DataInstD [] ''Route [typ] Nothing cons <$> mapM conT clazzes-#else-    let did = DataInstD [] ''Route [typ] cons clazzes-#endif-    return $ InstanceD cxt (ConT ''RenderRoute `AppT` typ)-        [ did-        , FunD (mkName "renderRoute") cls-        ] : decs-  where-    clazzes = [''Show, ''Eq, ''Read]--#if MIN_VERSION_template_haskell(2,11,0)-notStrict :: Bang-notStrict = Bang NoSourceUnpackedness NoSourceStrictness-#else-notStrict :: Strict-notStrict = NotStrict-#endif
− src/Network/Wai/Middleware/Routes/TH/RouteAttrs.hs
@@ -1,38 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE RecordWildCards #-}-module Network.Wai.Middleware.Routes.TH.RouteAttrs-    ( mkRouteAttrsInstance-    ) where--import Network.Wai.Middleware.Routes.TH.Types-import Network.Wai.Middleware.Routes.Class-import Language.Haskell.TH.Syntax-import Data.Set (fromList)-import Data.Text (pack)--mkRouteAttrsInstance :: Type -> [ResourceTree a] -> Q Dec-mkRouteAttrsInstance typ ress = do-    clauses <- mapM (goTree id) ress-    return $ InstanceD [] (ConT ''RouteAttrs `AppT` typ)-        [ FunD 'routeAttrs $ concat clauses-        ]--goTree :: (Pat -> Pat) -> ResourceTree a -> Q [Clause]-goTree front (ResourceLeaf res) = fmap return $ goRes front res-goTree front (ResourceParent name _check pieces trees) =-    fmap concat $ mapM (goTree front') trees-  where-    ignored = ((replicate toIgnore WildP ++) . return)-    toIgnore = length $ filter isDynamic pieces-    isDynamic Dynamic{} = True-    isDynamic Static{} = False-    front' = front . ConP (mkName name) . ignored--goRes :: (Pat -> Pat) -> Resource a -> Q Clause-goRes front Resource {..} =-    return $ Clause-        [front $ RecP (mkName resourceName) []]-        (NormalB $ VarE 'fromList `AppE` ListE (map toText resourceAttrs))-        []-  where-    toText s = VarE 'pack `AppE` LitE (StringL s)
− src/Network/Wai/Middleware/Routes/TH/Types.hs
@@ -1,101 +0,0 @@-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE TemplateHaskell #-}--- | Warning! This module is considered internal and may have breaking changes-module Network.Wai.Middleware.Routes.TH.Types-    ( -- * Data types-      Resource (..)-    , ResourceTree (..)-    , Piece (..)-    , Dispatch (..)-    , CheckOverlap-    , FlatResource (..)-      -- ** Helper functions-    , resourceMulti-    , resourceTreePieces-    , resourceTreeName-    , flatten-    ) where--import Language.Haskell.TH.Syntax--data ResourceTree typ-    = ResourceLeaf (Resource typ)-    | ResourceParent String CheckOverlap [Piece typ] [ResourceTree typ]-    deriving Functor--resourceTreePieces :: ResourceTree typ -> [Piece typ]-resourceTreePieces (ResourceLeaf r) = resourcePieces r-resourceTreePieces (ResourceParent _ _ x _) = x--resourceTreeName :: ResourceTree typ -> String-resourceTreeName (ResourceLeaf r) = resourceName r-resourceTreeName (ResourceParent x _ _ _) = x--instance Lift t => Lift (ResourceTree t) where-    lift (ResourceLeaf r) = [|ResourceLeaf $(lift r)|]-    lift (ResourceParent a b c d) = [|ResourceParent $(lift a) $(lift b) $(lift c) $(lift d)|]--data Resource typ = Resource-    { resourceName :: String-    , resourcePieces :: [Piece typ]-    , resourceDispatch :: Dispatch typ-    , resourceAttrs :: [String]-    , resourceCheck :: CheckOverlap-    }-    deriving (Show, Functor)--type CheckOverlap = Bool--instance Lift t => Lift (Resource t) where-    lift (Resource a b c d e) = [|Resource a b c d e|]--data Piece typ = Static String | Dynamic typ-    deriving Show--instance Functor Piece where-    fmap _ (Static s) = (Static s)-    fmap f (Dynamic t) = Dynamic (f t)--instance Lift t => Lift (Piece t) where-    lift (Static s) = [|Static $(lift s)|]-    lift (Dynamic t) = [|Dynamic $(lift t)|]--data Dispatch typ =-    Methods-        { methodsMulti :: Maybe typ -- ^ type of the multi piece at the end-        , methodsMethods :: [String] -- ^ supported request methods-        }-    | Subsite-        { subsiteType :: typ-        , subsiteFunc :: String-        }-    deriving Show--instance Functor Dispatch where-    fmap f (Methods a b) = Methods (fmap f a) b-    fmap f (Subsite a b) = Subsite (f a) b--instance Lift t => Lift (Dispatch t) where-    lift (Methods Nothing b) = [|Methods Nothing $(lift b)|]-    lift (Methods (Just t) b) = [|Methods (Just $(lift t)) $(lift b)|]-    lift (Subsite t b) = [|Subsite $(lift t) $(lift b)|]--resourceMulti :: Resource typ -> Maybe typ-resourceMulti Resource { resourceDispatch = Methods (Just t) _ } = Just t-resourceMulti _ = Nothing--data FlatResource a = FlatResource-    { frParentPieces :: [(String, [Piece a])]-    , frName :: String-    , frPieces :: [Piece a]-    , frDispatch :: Dispatch a-    , frCheck :: Bool-    }--flatten :: [ResourceTree a] -> [FlatResource a]-flatten =-    concatMap (go id True)-  where-    go front check' (ResourceLeaf (Resource a b c _ check)) = [FlatResource (front []) a b c (check' && check)]-    go front check' (ResourceParent name check pieces children) =-        concatMap (go (front . ((name, pieces):)) (check && check')) children
+ src/Routes/Class.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleContexts #-}+module Routes.Class+    ( RenderRoute (..)+    , ParseRoute (..)+    , RouteAttrs (..)+    ) where++import Data.Text (Text)+import Data.Set (Set)++class Eq (Route a) => RenderRoute a where+    -- | The <http://www.yesodweb.com/book/routing-and-handlers type-safe URLs> associated with a site argument.+    data Route a+    renderRoute :: Route a+                -> ([Text], [(Text, Text)]) -- ^ The path of the URL split on forward slashes, and a list of query parameters with their associated value.++class RenderRoute a => ParseRoute a where+    parseRoute :: ([Text], [(Text, Text)]) -- ^ The path of the URL split on forward slashes, and a list of query parameters with their associated value.+               -> Maybe (Route a)++class RenderRoute a => RouteAttrs a where+    routeAttrs :: Route a+               -> Set Text -- ^ A set of <http://www.yesodweb.com/book/route-attributes attributes associated with the route>.
+ src/Routes/ContentTypes.hs view
@@ -0,0 +1,94 @@+{-# LANGUAGE OverloadedStrings, TypeFamilies #-}++{- |+Module      :  Routes.ContentTypes+Copyright   :  (c) Anupam Jain 2013+License     :  MIT (see the file LICENSE)++Maintainer  :  ajnsit@gmail.com+Stability   :  experimental+Portability :  non-portable (uses ghc extensions)++Defines the commonly used content types+-}+module Routes.ContentTypes+    ( -- * Construct content Type+      acceptContentType+    , contentType, contentTypeFromFile+      -- * Various common content types+    , typeAll+    , typeHtml, typePlain, typeJson+    , typeXml, typeAtom, typeRss+    , typeJpeg, typePng, typeGif+    , typeSvg, typeJavascript, typeCss+    , typeFlv, typeOgv, typeOctet+    )+    where++import qualified Data.Text as T (pack)+import Data.ByteString (ByteString)+import Data.ByteString.Char8 () -- Import IsString instance for ByteString+import Network.HTTP.Types.Header (HeaderName())+import Network.Mime (defaultMimeLookup)+import System.FilePath (takeFileName)++-- | The request header for accpetable content types+acceptContentType :: HeaderName+acceptContentType = "Accept"++-- | Construct an appropriate content type header from a file name+contentTypeFromFile :: FilePath -> ByteString+contentTypeFromFile = defaultMimeLookup . T.pack . takeFileName++-- | Creates a content type header+-- Ready to be passed to `responseLBS`+contentType :: HeaderName+contentType = "Content-Type"++typeAll :: ByteString+typeAll = "*/*"++typeHtml :: ByteString+typeHtml = "text/html; charset=utf-8"++typePlain :: ByteString+typePlain = "text/plain; charset=utf-8"++typeJson :: ByteString+typeJson = "application/json; charset=utf-8"++typeXml :: ByteString+typeXml = "text/xml"++typeAtom :: ByteString+typeAtom = "application/atom+xml"++typeRss :: ByteString+typeRss = "application/rss+xml"++typeJpeg :: ByteString+typeJpeg = "image/jpeg"++typePng :: ByteString+typePng = "image/png"++typeGif :: ByteString+typeGif = "image/gif"++typeSvg :: ByteString+typeSvg = "image/svg+xml"++typeJavascript :: ByteString+typeJavascript = "text/javascript; charset=utf-8"++typeCss :: ByteString+typeCss = "text/css; charset=utf-8"++typeFlv :: ByteString+typeFlv = "video/x-flv"++typeOgv :: ByteString+typeOgv = "video/ogg"++typeOctet :: ByteString+typeOctet = "application/octet-stream"
+ src/Routes/DefaultRoute.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE TypeFamilies #-}++{- |+Module      :  Routes.DefaultRoute+Copyright   :  (c) Anupam Jain 2013 - 2015+License     :  MIT (see the file LICENSE)++Maintainer  :  ajnsit@gmail.com+Stability   :  experimental+Portability :  non-portable (uses ghc extensions)++Defines a DefaultMaster datatype and associated route (DefaultRoute) which is used for "unrouted" handlers+-}+module Routes.DefaultRoute+  ( DefaultMaster(..)+  , Route(DefaultRoute)+  )+  where++import Data.Text (Text)+import Data.Set (empty)++import Routes.Routes++-- Default master datatype, which is used for "unrouted" handlers+data DefaultMaster = DefaultMaster deriving (Eq, Show, Ord)+-- This makes it possible to define handlers without routing stuff+instance RenderRoute DefaultMaster where+  -- The associated route simply contains all path information+  data Route DefaultMaster = DefaultRoute ([Text],[(Text, Text)]) deriving (Eq, Show, Ord)+  renderRoute (DefaultRoute r) = r+instance ParseRoute DefaultMaster where+  parseRoute = Just . DefaultRoute+instance RouteAttrs DefaultMaster where+  routeAttrs = const empty
+ src/Routes/Handler.hs view
@@ -0,0 +1,604 @@+{-# LANGUAGE OverloadedStrings, GeneralizedNewtypeDeriving, CPP #-}+{- |+Module      :  Routes.Handler+Copyright   :  (c) Anupam Jain 2013+License     :  MIT (see the file LICENSE)++Maintainer  :  ajnsit@gmail.com+Stability   :  experimental+Portability :  non-portable (uses ghc extensions)++Provides a HandlerM Monad that makes it easy to build Handlers+-}+module Routes.Handler+    ( HandlerM()             -- | A Monad that makes it easier to build a Handler+    , runHandlerM            -- | Run a HandlerM to get a Handler+    , mountedAppHandler      -- | Convert a full wai application to a HandlerS+    , request                -- | Access the request data+    , isWebsocket            -- | Is this a websocket request+    , reqHeader              -- | Get a particular request header (case insensitive)+    , reqHeaders             -- | Get all request headers (case insensitive)+    , routeAttrSet           -- | Access the route attribute list+    , rootRouteAttrSet       -- | Access the route attribute list for the root route+    , maybeRoute             -- | Access the route data+    , maybeRootRoute         -- | Access the root route data+    , showRouteMaster        -- | Get the route rendering function for the master site+    , showRouteSub           -- | Get the route rendering function for the subsite+    , showRouteQueryMaster   -- | Get the route + query params rendering function for the master site+    , showRouteQuerySub      -- | Get the route + query params rendering function for the subsite+    , readRouteMaster        -- | Get the route parsing function for the master site+    , readRouteSub           -- | Get the route parsing function for the subsite+    , master                 -- | Access the master datatype+    , sub                    -- | Access the sub datatype+    , rawBody                -- | Consume and return the request body as ByteString+    , textBody               -- | Consume and return the request body as Text+    , jsonBody               -- | Consume and return the request body as JSON+    , header                 -- | Add a header to the response+    , status                 -- | Set the response status+    , file                   -- | Send a file as response+    , filepart               -- | Send a part of a file as response+    , stream                 -- | Stream a response+    , raw                    -- | Set the raw response body+    , rawBuilder             -- | Set the raw response body as a ByteString Builder+    , json                   -- | Set the json response body+    , plain                  -- | Set the plain text response body+    , html                   -- | Set the html response body+    , css                    -- | Set the css response body+    , javascript             -- | Set the javascript response body+    , content                -- | Sets the response body when the content type is acceptable+    , asContent              -- | Set the contentType and a 'Text' body+    , whenContent            -- | Runs the action when a content type is acceptable+    , next                   -- | Run the next application in the stack+    , getParams              -- | Get all params (query or post, not file)+    , getParam               -- | Get a particular param (query or post, not file)+    , getQueryParams         -- | Get all query params+    , getQueryParam          -- | Get a particular query param+    , getPostParams          -- | Get all post params+    , getPostParam           -- | Get a particular post param+    , getFileParams          -- | Get all file params+    , getFileParam           -- | Get a particular file param+    , setCookie              -- | Add a cookie to the response+    , getCookie              -- | Get a cookie from the request+    , getCookies             -- | Get all cookies from the request+    , reqVault               -- | Access the vault from the request+    , lookupVault            -- | Lookup a key in the request vault+    , updateVault            -- | Update the request vault+    )+    where++import Network.Wai (Application, Request, responseRaw, responseFile, responseBuilder, responseStream, queryString, StreamingBody, requestHeaders, FilePart)+#if MIN_VERSION_wai(3,0,1)+import Network.Wai (strictRequestBody, vault)+#endif+import Routes.Routes (Env(..), RequestData, HandlerS, waiReq, currentRoute, runNext, showRoute, showRouteQuery, readRoute, readQueryString)+import Routes.Class (Route, RenderRoute, ParseRoute, RouteAttrs(..))+import Routes.ContentTypes (acceptContentType, contentType, contentTypeFromFile, typeHtml, typeJson, typePlain, typeCss, typeJavascript, typeAll)++import Control.Monad (liftM, when)+import Control.Monad.State (StateT, get, put, modify, runStateT, MonadState, MonadIO, liftIO, MonadTrans)++import Control.Arrow ((***))+import Control.Applicative (Applicative, (<$>), (<*>))++import Data.Maybe (fromMaybe)+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as BL+import Blaze.ByteString.Builder (Builder, toByteString, fromByteString)+import Network.HTTP.Types.Header (HeaderName(), RequestHeaders)+import Network.HTTP.Types.Status (Status(), status200)++import Data.Aeson (ToJSON, FromJSON, eitherDecodeStrict)++import qualified Data.Aeson as A+#if MIN_VERSION_aeson(0,10,0)+#else+import qualified Data.Aeson.Encode as AE+#endif++import Data.Set (Set)+import qualified Data.Set as S (empty)++import Data.Text (Text)+import Data.Text.Encoding (encodeUtf8, decodeUtf8)++import Data.CaseInsensitive (CI, mk)++import Web.Cookie (CookiesText, parseCookiesText, renderSetCookie, SetCookie(..))+import Data.List (intersect)++import qualified Data.Vault.Lazy as V++import qualified Network.Wai.Parse as P++-- | The internal implementation of the HandlerM monad+-- TODO: Should change this to StateT over ReaderT (but performance may suffer)+newtype HandlerMI sub master m a = H { extractH :: StateT (HandlerState sub master) m a }+    deriving (Applicative, Monad, MonadIO, Functor, MonadTrans, MonadState (HandlerState sub master))++-- | The HandlerM Monad+type HandlerM sub master a = HandlerMI sub master IO a++-- | Modeled after Network.Wai.Parse.FileInfo, but uses Text for names, and lazy ByteString for content+data FileInfo = FileInfo+  { fileName        :: Text+  , fileContentType :: Text+  , fileContent     :: BL.ByteString+  }++-- Post Params+-- Files are read into memory+-- TODO: Check for security issues. Allow automatic storing of files on disk.+type PostParams = ([(Text, Text)], [(Text, FileInfo)])++-- Private+-- Convert from Network.Wai.Parse ([Param], [File y])  to PostParams+_toPostParams :: ([P.Param], [P.File BL.ByteString])  -> PostParams+_toPostParams (params, files) = (params', files')+  where+    params' = map (decodeUtf8 *** decodeUtf8)     params+    files'  = map (decodeUtf8 *** decodeFileInfo) files+    decodeFileInfo fi = FileInfo+      { fileName = decodeUtf8 $ P.fileName fi+      , fileContentType = decodeUtf8 $ P.fileContentType fi+      , fileContent = P.fileContent fi+      }++-- A Raw Response handler :: source -> sink -> IO+type RespRawHandler = IO B.ByteString -> (B.ByteString -> IO ()) -> IO ()++-- | The state kept in a HandlerM Monad+data HandlerState sub master = HandlerState+  { getMaster      :: master+  , getRequestData :: RequestData sub+  -- TODO: Experimental+  -- Streaming request body, consumed, and stored as a ByteString+  , reqBody        :: Maybe ByteString+  , respHeaders    :: [(HeaderName, ByteString)]+  , respStatus     :: Status+  , respResp       :: Maybe MkResponse+  , respRaw        :: Maybe RespRawHandler+  , respCookies    :: [SetCookie]+  , getSub         :: sub+  , toMasterRoute  :: Route sub -> Route master+  -- TODO: Experimental+  -- Parsed POST request body, in the same format as Network.Wai.Parse+  , postParams     :: Maybe PostParams+  , acceptCTypes   :: Maybe [ByteString]+  }++-- Initial Handler State+defaultHandlerState :: Env sub master -> RequestData sub -> HandlerState sub master+defaultHandlerState env req = HandlerState+  { getMaster = envMaster env+  , getRequestData = req+  , reqBody = Nothing+  , respHeaders = []+  , respStatus = status200+  , respResp = Nothing+  , respRaw = Nothing+  , respCookies = []+  , getSub = envSub env+  , toMasterRoute = envToMaster env+  , postParams = Nothing+  , acceptCTypes = Nothing+  }++-- Internal: Type of response+-- Similar to Wai's Response type+data MkResponse+    = ResponseFile FilePath (Maybe FilePart)+    | ResponseBuilder Builder+    | ResponseStream StreamingBody+    | ResponseNext++-- Default response in case none is set by the handler+defaultResponse :: MkResponse+defaultResponse = ResponseBuilder ""++-- The header name for request cookies+cookieHeaderName :: CI ByteString+cookieHeaderName = mk "Cookie"++-- The header name for response cookies+cookieSetHeaderName :: CI ByteString+cookieSetHeaderName = mk "Set-Cookie"++-- | Convert a full wai application to a Handler+-- A bit like subsites, but at a higher level.+mountedAppHandler :: Application -> HandlerS sub master+mountedAppHandler app _env = app . waiReq++-- | "Run" HandlerM, resulting in a Handler+runHandlerM :: HandlerM sub master () -> HandlerS sub master+runHandlerM h env req hh = do+  (_, st) <- runStateT (extractH h) (defaultHandlerState env req)+  -- Fetch the internal response structure+  let respData = fromMaybe defaultResponse (respResp st)+  -- Handle cookies (add them to headers)+  let headers' = map mkSetCookie (respCookies st) ++ respHeaders st+  -- Construct the actual wai response+  case mkResponse (respStatus st) headers' respData of+    -- Abort handling current response and move to next handler+    Nothing -> runNext (getRequestData st) hh+    -- Normal handling+    Just resp ->+      -- Check if we are trying to send a raw response+      case respRaw st of+        Nothing -> hh resp+        -- TODO: Ensure the body has not been read before using raw response+        Just rawHandler -> hh $ responseRaw rawHandler resp+  where+    mkSetCookie s = (cookieSetHeaderName, toByteString $ renderSetCookie s)+    mkResponse rstatus headers (ResponseFile path part) = Just $ responseFile rstatus headers path part+    mkResponse rstatus headers (ResponseBuilder builder) = Just $ responseBuilder rstatus headers builder+    mkResponse rstatus headers (ResponseStream streaming) = Just $ responseStream rstatus headers streaming+    mkResponse _ _ ResponseNext = Nothing++-- | Get the request body as a bytestring. Consumes the entire body into memory at once.+-- TODO: Implement streaming. Prevent clash with direct use of `Network.Wai.requestBody`+rawBody :: HandlerM sub master ByteString+rawBody = do+  s <- get+  case reqBody s of+    Just consumedBody -> return consumedBody+    Nothing -> do+      req <- request+      rbody <- liftIO $ BL.toStrict <$> _readStrictRequestBody req+      put s {reqBody = Just rbody}+      return rbody++-- | Get the request body as a Text. However consumes the entire body at once.+-- TODO: Implement streaming. Prevent clash with direct use of `Network.Wai.requestBody`+textBody :: HandlerM master master Text+textBody = liftM decodeUtf8 rawBody++-- PRIVATE+_readStrictRequestBody :: Request -> IO BL.ByteString+_readStrictRequestBody =+#if MIN_VERSION_wai(3,0,1)+        -- Use the `strictRequestBody` function available in wai > 3.0.1+        strictRequestBody+#else+        -- Consume the entire body, and cache+        BL.fromChunks <$> unfoldWhileM (not . B.null) . requestBody+#endif++-- | Parse the body as a JSON object+jsonBody :: FromJSON a => HandlerM sub master (Either String a)+jsonBody = liftM eitherDecodeStrict rawBody++-- | Get the master+master :: HandlerM sub master master+master = liftM getMaster get++-- | Get the sub+sub :: HandlerM sub master sub+sub = liftM getSub get++-- | Get the request+request :: HandlerM sub master Request+request = liftM (waiReq . getRequestData) get++-- | Is this a websocket request+isWebsocket :: HandlerM sub master Bool+isWebsocket = liftM (maybe False (== "websocket")) (_reqHeaderBS "upgrade")++-- | Get a particular request header (Case insensitive)+reqHeader :: Text -> HandlerM sub master (Maybe Text)+reqHeader name = liftM (fmap decodeUtf8) (_reqHeaderBS nameText)+  where+    nameText = mk $ encodeUtf8 name++-- PRIVATE+_reqHeaderBS :: CI ByteString -> HandlerM sub master (Maybe ByteString)+_reqHeaderBS name = liftM (lookup name) reqHeaders++-- VAULT+-- | Access the vault+reqVault :: HandlerM sub master V.Vault+reqVault = liftM vault request++-- Lookup a value in the request vault+lookupVault :: V.Key a -> HandlerM sub master (Maybe a)+lookupVault k = liftM (V.lookup k) reqVault++-- Update the request vault+-- For example: `updateVault (V.insert key val)`+updateVault :: (V.Vault -> V.Vault) -> HandlerM sub master ()+updateVault f = modify $ \st ->+  let rd = getRequestData st+      r = waiReq rd+      v = f $ vault r+  in st { getRequestData = rd { waiReq = r { vault = v } } }+-- END VAULT++-- | Get all request headers as raw case-insensitive bytestrings+reqHeaders :: HandlerM sub master RequestHeaders+reqHeaders = liftM requestHeaders request++-- | Get the current route+maybeRoute :: HandlerM sub master (Maybe (Route sub))+maybeRoute = liftM (currentRoute . getRequestData) get++-- | Get the current root route+maybeRootRoute :: HandlerM sub master (Maybe (Route master))+maybeRootRoute = do+  s <- get+  return $ toMasterRoute s <$> currentRoute (getRequestData s)++-- | Get the route rendering function for the master site+showRouteMaster :: RenderRoute master => HandlerM sub master (Route master -> Text)+showRouteMaster = return showRoute++-- | Get the route rendering function for the subsite+showRouteSub :: RenderRoute master => HandlerM sub master (Route sub -> Text)+showRouteSub = do+  s <- get+  return $ showRoute . toMasterRoute s++-- | Get the route rendering function for the master site+showRouteQueryMaster :: RenderRoute master => HandlerM sub master (Route master -> [(Text,Text)] -> Text)+showRouteQueryMaster = return showRouteQuery++-- | Get the route rendering function for the subsite+showRouteQuerySub :: RenderRoute master => HandlerM sub master (Route sub -> [(Text,Text)] -> Text)+showRouteQuerySub = do+  s <- get+  return $ showRouteQuery . toMasterRoute s++-- | Get the route parsing function for the master site+readRouteMaster :: ParseRoute master => HandlerM sub master (Text -> Maybe (Route master))+readRouteMaster = return readRoute++-- | Get the route parsing function for the subsite+readRouteSub :: ParseRoute sub => HandlerM sub master (Text -> Maybe (Route master))+readRouteSub = do+  s <- get+  return $ (toMasterRoute s <$>) . readRoute++-- | Get the current route attributes+routeAttrSet :: RouteAttrs sub => HandlerM sub master (Set Text)+routeAttrSet = liftM (maybe S.empty routeAttrs . currentRoute . getRequestData) get++-- | Get the attributes for the current root route+rootRouteAttrSet :: RouteAttrs master => HandlerM sub master (Set Text)+rootRouteAttrSet = do+  s <- get+  return $ maybe S.empty (routeAttrs . toMasterRoute s) $ currentRoute $ getRequestData s++-- | Add a header to the application response+-- TODO: Differentiate between setting and adding headers+header :: HeaderName -> ByteString -> HandlerM sub master ()+header h b = modify addHeader+  where+    addHeader :: HandlerState sub master -> HandlerState sub master+    addHeader st@(HandlerState {respHeaders=hs}) = st {respHeaders=(h,b):hs}++-- | Set the response status+status :: Status -> HandlerM sub master ()+status s = modify setStatus+  where+    setStatus :: HandlerState sub master -> HandlerState sub master+    setStatus st = st{respStatus=s}++-- | Send a file as response+file :: FilePath -> HandlerM sub master ()+file f = do+  header contentType $ contentTypeFromFile f+  modify addFile+  where+    addFile st = _setResp st $ ResponseFile f Nothing++-- | Send a part of a file as response+filepart :: FilePath -> FilePart -> HandlerM sub master ()+filepart f part = do+  header contentType $ contentTypeFromFile f+  modify addFile+  where+    addFile st = _setResp st $ ResponseFile f (Just part)++-- | Stream the response+stream :: StreamingBody -> HandlerM sub master ()+stream s = modify addStream+  where+    addStream st = _setResp st $ ResponseStream s++-- | Set the response body+raw :: ByteString -> HandlerM sub master ()+raw = rawBuilder . fromByteString++-- | Set the response body as a builder+rawBuilder :: Builder -> HandlerM sub master ()+rawBuilder b = modify addBody+  where+    addBody st = _setResp st $ ResponseBuilder b++-- | Run the next application+next :: HandlerM sub master ()+next = modify rNext+  where+    rNext st = _setResp st ResponseNext++-- Util+-- Set the response handler (don't overwrite an existing response)+_setResp :: HandlerState sub master -> MkResponse -> HandlerState sub master+_setResp st r = case respResp st of+  Nothing -> st{respResp=Just r}+  _ -> st+++-- Standard response bodies++-- | Set the body of the response to the JSON encoding of the given value. Also sets \"Content-Type\"+-- header to \"application/json\".+json :: ToJSON a => a -> HandlerM sub master ()+-- TODO: Use Accept header parsing+-- json a = whenContent [typeJson, typeJavascript, typePlain] $ do+json a = do+  header contentType typeJson+  rawBuilder $ _encode $ A.toJSON a+  where+#if MIN_VERSION_aeson(0,10,0)+    _encode = A.fromEncoding . A.toEncoding+#elif MIN_VERSION_aeson(0,9,0)+    _encode = AE.encodeToBuilder+#else+    _encode = AE.encodeToByteStringBuilder+#endif++-- | Set the body of the response to the given 'Text' value. Also sets \"Content-Type\"+-- header to \"text/plain\".+plain :: Text -> HandlerM sub master ()+-- TODO: Use Accept header parsing+-- plain = content [typePlain]+plain = asContent typePlain++-- | Set the body of the response to the given 'Text' value. Also sets \"Content-Type\"+-- header to \"text/html\".+html :: Text -> HandlerM sub master ()+-- TODO: Use Accept header parsing+-- html = content [typeHtml, typePlain]+html = asContent typeHtml++-- | Set the body of the response to the given 'Text' value. Also sets \"Content-Type\"+-- header to \"text/css\".+css :: Text -> HandlerM sub master ()+-- TODO: Use Accept header parsing+-- css = content [typeCss, typePlain]+css = asContent typeCss++-- | Set the body of the response to the given 'Text' value. Also sets \"Content-Type\"+-- header to \"text/javascript\".+javascript :: Text -> HandlerM sub master ()+-- TODO: Use Accept header parsing+-- javascript = content [typeJavascript, typePlain]+javascript = asContent typeJavascript++-- | Sets the content-type header to the given Bytestring+--  (look in Routes.ContentTypes for examples)+--  And sets the body of the response to the given Text+asContent :: ByteString -> Text -> HandlerM sub master ()+asContent ctype s = do+  header contentType ctype+  raw $ encodeUtf8 s++-- | Sets the response body when the content type is acceptable+content :: [ByteString] -> Text -> HandlerM sub master ()+content [] _ = return ()+content ctypes s = whenContent ctypes (asContent (head ctypes) s)++-- | Perform an action only when there is no accept list or the given contentType is acceptable+whenContent :: [ByteString] -> HandlerM sub master () -> HandlerM sub master ()+whenContent ctypes respHandler = do+  atypes <- acceptableContentTypes+  let noAcceptList = not $ null atypes+  let acceptableTypeFound = not $ null $ intersect (typeAll:ctypes) atypes+  when (noAcceptList || acceptableTypeFound) respHandler++-- | Get a list of content types acceptable to the request+acceptableContentTypes :: HandlerM sub master [ByteString]+acceptableContentTypes = do+  st <- get+  maybe (getCTypes st) return (acceptCTypes st)+  where+    getCTypes st = do+      h <- _reqHeaderBS acceptContentType+      let parsedCTypes = maybe [] P.parseHttpAccept h+      put st{acceptCTypes = Just parsedCTypes}+      return parsedCTypes++-- | Sets a cookie to the response+setCookie :: SetCookie -> HandlerM sub master ()+setCookie s = modify setCookie'+  where+    setCookie' st = st {respCookies = s : respCookies st}++-- | Get all cookies+getCookies :: HandlerM sub master CookiesText+getCookies = do+  -- Note: We don't cache the parsedCookies for all requests to avoid overhead+  -- However it is pretty easy to cache cookies in the app itself+  cookies <- _reqHeaderBS cookieHeaderName+  return $ case cookies of+    Nothing -> []+    Just cookies' -> parseCookiesText cookies'++-- | Get a particular cookie+getCookie :: Text -> HandlerM sub master (Maybe Text)+getCookie name = do+  cookies <- getCookies+  return $ lookup name cookies++-- PRIVATE+-- Get the cached post params (if any)+_getCachedPostParams :: HandlerM sub master (Maybe PostParams)+_getCachedPostParams = postParams <$> get++-- PRIVATE+-- Util: Parse and cache post params+_populatePostParams :: HandlerM sub master PostParams+_populatePostParams = do+  st <- get+  case postParams st of+    Just params -> return params+    Nothing -> do+      req <- request+      params <- case P.getRequestBodyType req of+        Nothing -> return ([],[])+        Just _ -> do+          -- TODO: Use cached request body instead of reading it from wai request+          params <- liftIO $ P.parseRequestBody P.lbsBackEnd req+          return $ _toPostParams params+      put $ st{postParams=Just params}+      return params++-- PRIVATE+-- Get a list of post parameters+_getAllFileOrPostParams :: HandlerM sub master PostParams+_getAllFileOrPostParams = do+  cachedPostParams <- _getCachedPostParams+  case cachedPostParams of+    Nothing -> _populatePostParams+    Just params -> return params++-- | Get all Query params+getQueryParams :: HandlerM sub master [(Text,Text)]+getQueryParams = readQueryString . queryString <$> request++-- | Get a particular Query param+getQueryParam :: Text -> HandlerM sub master (Maybe Text)+getQueryParam name = lookup name <$> getQueryParams++-- | Get all Post params+getPostParams :: HandlerM sub master [(Text,Text)]+getPostParams = do+  (params,_) <- _getAllFileOrPostParams+  return params++-- | Get a particular Post param+getPostParam :: Text -> HandlerM sub master (Maybe Text)+getPostParam name = lookup name <$> getPostParams++-- | Get all File params+getFileParams :: HandlerM sub master [(Text,FileInfo)]+getFileParams = do+  (_,files) <- _getAllFileOrPostParams+  return files++-- | Get a particular File param+getFileParam :: Text -> HandlerM sub master (Maybe FileInfo)+getFileParam name = lookup name <$> getFileParams++-- | Get all params (query or post, NOT file)+-- Duplicate parameters are preserved+getParams :: HandlerM sub master [(Text, Text)]+getParams = (++) <$> getQueryParams <*> getPostParams++-- | Get a param (query or post, NOT file)+getParam :: Text -> HandlerM sub master (Maybe Text)+getParam name = do+  getLookup <- getQueryParam name+  case getLookup of+    Nothing -> getPostParam name+    Just _ -> return getLookup
+ src/Routes/Monad.hs view
@@ -0,0 +1,86 @@+{-# LANGUAGE OverloadedStrings, TypeFamilies, RankNTypes, DeriveFunctor #-}++{- |+Module      :  Routes.Monad+Copyright   :  (c) Anupam Jain 2013+License     :  MIT (see the file LICENSE)++Maintainer  :  ajnsit@gmail.com+Stability   :  experimental+Portability :  non-portable (uses ghc extensions)++Defines a Routing Monad that provides easy composition of Routes+-}+module Routes.Monad+    ( -- * Route Monad+      RouteM+      -- * Compose Routes+    , DefaultMaster(..)+    , Route(DefaultRoute)+    , handler+    , middleware+    , route+    , catchall+    , defaultAction+      -- * Convert to Wai Application+    , waiApp+    , toWaiApp+    )+    where++import Network.Wai+import Routes.Routes+import Routes.DefaultRoute+import Network.HTTP.Types (status404)++import Util.Free (F(..), liftF)++-- A Router functor can either add a middleware, or resolve to an app itself.+data RouterF x = M Middleware x | D Application deriving Functor++-- Router type+type RouteM = F RouterF++-- | Catch all routes and process them with the supplied application.+-- Note: As expected from the name, no request proceeds past a catchall.+catchall :: Application -> RouteM ()+catchall a = liftF $ D a++-- | Synonym of `catchall`. Kept for backwards compatibility+defaultAction :: Application -> RouteM ()+defaultAction = catchall++-- | Add a middleware to the application+-- Middleware are ordered so the one declared earlier wraps the ones later+middleware :: Middleware -> RouteM ()+middleware m = liftF $ M m ()++-- | Add a wai-routes handler+handler :: HandlerS DefaultMaster DefaultMaster -> RouteM ()+handler h = middleware $ customRouteDispatch dispatcher' DefaultMaster+  where+    dispatcher' env req = runHandler h env (Just $ DefaultRoute $ getRoute req) req+    getRoute req = (pathInfo $ waiReq req, readQueryString $ queryString $ waiReq req)++-- | Add a route to the application.+-- Routes are ordered so the one declared earlier is matched first.+route :: (Routable master master) => master -> RouteM ()+route = middleware . routeDispatch++-- The final "catchall" application, simply returns a 404 response+-- Ideally you should put your own default application+defaultApplication :: Application+defaultApplication _req h = h $ responseLBS status404 [("Content-Type", "text/plain")] "Error : 404 - Document not found"++-- | Convert a RouteM monad into a wai application.+-- Note: We ignore the return type of the monad+waiApp :: RouteM () -> Application+waiApp (F r) = r (const defaultApplication) f+  where+    f (M m r') = m r'+    f (D a) = a++-- | Similar to waiApp but returns the app in an arbitrary monad+-- Kept for backwards compatibility+toWaiApp :: Monad m => RouteM () -> m Application+toWaiApp = return . waiApp
+ src/Routes/Overlap.hs view
@@ -0,0 +1,88 @@+-- | Check for overlapping routes.+module Routes.Overlap+    ( findOverlapNames+    , Overlap (..)+    ) where++import Routes.TH.Types+import Data.List (intercalate)++data Flattened t = Flattened+    { fNames :: [String]+    , fPieces :: [Piece t]+    , fHasSuffix :: Bool+    , fCheck :: CheckOverlap+    }++flatten :: ResourceTree t -> [Flattened t]+flatten =+    go id id True+  where+    go names pieces check (ResourceLeaf r) = return Flattened+        { fNames = names [resourceName r]+        , fPieces = pieces (resourcePieces r)+        , fHasSuffix = hasSuffix $ ResourceLeaf r+        , fCheck = check && resourceCheck r+        }+    go names pieces check (ResourceParent newname check' newpieces children) =+        concatMap (go names' pieces' (check && check')) children+      where+        names' = names . (newname:)+        pieces' = pieces . (newpieces ++)++data Overlap t = Overlap+    { overlapParents :: [String] -> [String] -- ^ parent resource trees+    , overlap1 :: ResourceTree t+    , overlap2 :: ResourceTree t+    }++data OverlapF = OverlapF+    { _overlapF1 :: [String]+    , _overlapF2 :: [String]+    }++overlaps :: [Piece t] -> [Piece t] -> Bool -> Bool -> Bool++-- No pieces on either side, will overlap regardless of suffix+overlaps [] [] _ _ = True++-- No pieces on the left, will overlap if the left side has a suffix+overlaps [] _ suffixX _ = suffixX++-- Ditto for the right+overlaps _ [] _ suffixY = suffixY++-- Compare the actual pieces+overlaps (pieceX:xs) (pieceY:ys) suffixX suffixY =+    piecesOverlap pieceX pieceY && overlaps xs ys suffixX suffixY++piecesOverlap :: Piece t -> Piece t -> Bool+-- Statics only match if they equal. Dynamics match with anything+piecesOverlap (Static x) (Static y) = x == y+piecesOverlap _ _ = True++findOverlapNames :: [ResourceTree t] -> [(String, String)]+findOverlapNames =+    map go . findOverlapsF . filter fCheck . concatMap Routes.Overlap.flatten+  where+    go (OverlapF x y) =+        (go' x, go' y)+      where+        go' = intercalate "/"++findOverlapsF :: [Flattened t] -> [OverlapF]+findOverlapsF [] = []+findOverlapsF (x:xs) = concatMap (findOverlapF x) xs ++ findOverlapsF xs++findOverlapF :: Flattened t -> Flattened t -> [OverlapF]+findOverlapF x y+    | overlaps (fPieces x) (fPieces y) (fHasSuffix x) (fHasSuffix y) = [OverlapF (fNames x) (fNames y)]+    | otherwise = []++hasSuffix :: ResourceTree t -> Bool+hasSuffix (ResourceLeaf r) =+    case resourceDispatch r of+        Subsite{} -> True+        Methods Just{} _ -> True+        Methods Nothing _ -> False+hasSuffix ResourceParent{} = True
+ src/Routes/Parse.hs view
@@ -0,0 +1,254 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE PatternGuards #-}+{-# OPTIONS_GHC -fno-warn-missing-fields #-} -- QuasiQuoter+module Routes.Parse+    ( parseRoutes+    , parseRoutesFile+    , parseRoutesNoCheck+    , parseRoutesFileNoCheck+    , parseType+    , parseTypeTree+    , TypeTree (..)+    ) where++import Language.Haskell.TH.Syntax+import Data.Char (isUpper)+import Language.Haskell.TH.Quote+import qualified System.IO as SIO+import Routes.TH+import Routes.Overlap (findOverlapNames)+import Data.List (foldl', isPrefixOf)+import Data.Maybe (mapMaybe)+import qualified Data.Set as Set++-- | A quasi-quoter to parse a string into a list of 'Resource's. Checks for+-- overlapping routes, failing if present; use 'parseRoutesNoCheck' to skip the+-- checking. See documentation site for details on syntax.+parseRoutes :: QuasiQuoter+parseRoutes = QuasiQuoter { quoteExp = x }+  where+    x s = do+        let res = resourcesFromString s+        case findOverlapNames res of+            [] -> lift res+            z -> error $ unlines $ "Overlapping routes: " : map show z++parseRoutesFile :: FilePath -> Q Exp+parseRoutesFile = parseRoutesFileWith parseRoutes++parseRoutesFileNoCheck :: FilePath -> Q Exp+parseRoutesFileNoCheck = parseRoutesFileWith parseRoutesNoCheck++parseRoutesFileWith :: QuasiQuoter -> FilePath -> Q Exp+parseRoutesFileWith qq fp = do+    qAddDependentFile fp+    s <- qRunIO $ readUtf8File fp+    quoteExp qq s++readUtf8File :: FilePath -> IO String+readUtf8File fp = do+    h <- SIO.openFile fp SIO.ReadMode+    SIO.hSetEncoding h SIO.utf8_bom+    SIO.hGetContents h++-- | Same as 'parseRoutes', but performs no overlap checking.+parseRoutesNoCheck :: QuasiQuoter+parseRoutesNoCheck = QuasiQuoter+    { quoteExp = lift . resourcesFromString+    }++-- | Convert a multi-line string to a set of resources. See documentation for+-- the format of this string. This is a partial function which calls 'error' on+-- invalid input.+resourcesFromString :: String -> [ResourceTree String]+resourcesFromString =+    fst . parse 0 . filter (not . all (== ' ')) . lines+  where+    parse _ [] = ([], [])+    parse indent (thisLine:otherLines)+        | length spaces < indent = ([], thisLine : otherLines)+        | otherwise = (this others, remainder)+      where+        parseAttr ('!':x) = Just x+        parseAttr _ = Nothing++        stripColonLast =+            go id+          where+            go _ [] = Nothing+            go front [x]+                | null x = Nothing+                | last x == ':' = Just $ front [init x]+                | otherwise = Nothing+            go front (x:xs) = go (front . (x:)) xs++        spaces = takeWhile (== ' ') thisLine+        (others, remainder) = parse indent otherLines'+        (this, otherLines') =+            case takeWhile (not . isPrefixOf "--") $ words thisLine of+                (pattern:rest0)+                    | Just (constr:rest) <- stripColonLast rest0+                    , Just attrs <- mapM parseAttr rest ->+                    let (children, otherLines'') = parse (length spaces + 1) otherLines+                        children' = addAttrs attrs children+                        (pieces, Nothing, check) = piecesFromStringCheck pattern+                     in ((ResourceParent constr check pieces children' :), otherLines'')+                (pattern:constr:rest) ->+                    let (pieces, mmulti, check) = piecesFromStringCheck pattern+                        (attrs, rest') = takeAttrs rest+                        disp = dispatchFromString rest' mmulti+                     in ((ResourceLeaf (Resource constr pieces disp attrs check):), otherLines)+                [] -> (id, otherLines)+                _ -> error $ "Invalid resource line: " ++ thisLine++piecesFromStringCheck :: String -> ([Piece String], Maybe String, Bool)+piecesFromStringCheck s0 =+    (pieces, mmulti, check)+  where+    (s1, check1) = stripBang s0+    (pieces', mmulti') = piecesFromString $ drop1Slash s1+    pieces = map snd pieces'+    mmulti = fmap snd mmulti'+    check = check1 && all fst pieces' && maybe True fst mmulti'++    stripBang ('!':rest) = (rest, False)+    stripBang x = (x, True)++addAttrs :: [String] -> [ResourceTree String] -> [ResourceTree String]+addAttrs attrs =+    map goTree+  where+    goTree (ResourceLeaf res) = ResourceLeaf (goRes res)+    goTree (ResourceParent w x y z) = ResourceParent w x y (map goTree z)++    goRes res =+        res { resourceAttrs = noDupes ++ resourceAttrs res }+      where+        usedKeys = Set.fromList $ map fst $ mapMaybe toPair $ resourceAttrs res+        used attr =+            case toPair attr of+                Nothing -> False+                Just (key, _) -> key `Set.member` usedKeys+        noDupes = filter (not . used) attrs++    toPair s =+        case break (== '=') s of+            (x, '=':y) -> Just (x, y)+            _ -> Nothing++-- | Take attributes out of the list and put them in the first slot in the+-- result tuple.+takeAttrs :: [String] -> ([String], [String])+takeAttrs =+    go id id+  where+    go x y [] = (x [], y [])+    go x y (('!':attr):rest) = go (x . (attr:)) y rest+    go x y (z:rest) = go x (y . (z:)) rest++dispatchFromString :: [String] -> Maybe String -> Dispatch String+dispatchFromString rest mmulti+    | null rest = Methods mmulti []+    | all (all isUpper) rest = Methods mmulti rest+dispatchFromString [subTyp, subFun] Nothing =+    Subsite subTyp subFun+dispatchFromString [_, _] Just{} =+    error "Subsites cannot have a multipiece"+dispatchFromString rest _ = error $ "Invalid list of methods: " ++ show rest++drop1Slash :: String -> String+drop1Slash ('/':x) = x+drop1Slash x = x++piecesFromString :: String -> ([(CheckOverlap, Piece String)], Maybe (CheckOverlap, String))+piecesFromString "" = ([], Nothing)+piecesFromString x =+    case (this, rest) of+        (Left typ, ([], Nothing)) -> ([], Just typ)+        (Left _, _) -> error "Multipiece must be last piece"+        (Right piece, (pieces, mtyp)) -> (piece:pieces, mtyp)+  where+    (y, z) = break (== '/') x+    this = pieceFromString y+    rest = piecesFromString $ drop 1 z++parseType :: String -> Type+parseType orig =+    maybe (error $ "Invalid type: " ++ show orig) ttToType $ parseTypeTree orig++parseTypeTree :: String -> Maybe TypeTree+parseTypeTree orig =+    toTypeTree pieces+  where+    pieces = filter (not . null) $ splitOn '-' $ addDashes orig+    addDashes [] = []+    addDashes (x:xs) =+        front $ addDashes xs+      where+        front rest+            | x `elem` "()[]" = '-' : x : '-' : rest+            | otherwise = x : rest+    splitOn c s =+        case y' of+            _:y -> x : splitOn c y+            [] -> [x]+      where+        (x, y') = break (== c) s++data TypeTree = TTTerm String+              | TTApp TypeTree TypeTree+              | TTList TypeTree+    deriving (Show, Eq)++toTypeTree :: [String] -> Maybe TypeTree+toTypeTree orig = do+    (x, []) <- gos orig+    return x+  where+    go [] = Nothing+    go ("(":xs) = do+        (x, rest) <- gos xs+        case rest of+            ")":rest' -> Just (x, rest')+            _ -> Nothing+    go ("[":xs) = do+        (x, rest) <- gos xs+        case rest of+            "]":rest' -> Just (TTList x, rest')+            _ -> Nothing+    go (x:xs) = Just (TTTerm x, xs)++    gos xs1 = do+        (t, xs2) <- go xs1+        (ts, xs3) <- gos' id xs2+        Just (foldl' TTApp t ts, xs3)++    gos' front [] = Just (front [], [])+    gos' front (x:xs)+        | x `elem` words ") ]" = Just (front [], x:xs)+        | otherwise = do+            (t, xs') <- go $ x:xs+            gos' (front . (t:)) xs'++ttToType :: TypeTree -> Type+ttToType (TTTerm s) = ConT $ mkName s+ttToType (TTApp x y) = ttToType x `AppT` ttToType y+ttToType (TTList t) = ListT `AppT` ttToType t++pieceFromString :: String -> Either (CheckOverlap, String) (CheckOverlap, Piece String)+pieceFromString ('#':'!':x) = Right $ (False, Dynamic x)+pieceFromString ('!':'#':x) = Right $ (False, Dynamic x) -- https://github.com/yesodweb/yesod/issues/652+pieceFromString ('#':x) = Right $ (True, Dynamic x)++pieceFromString ('*':'!':x) = Left (False, x)+pieceFromString ('+':'!':x) = Left (False, x)++pieceFromString ('!':'*':x) = Left (False, x)+pieceFromString ('!':'+':x) = Left (False, x)++pieceFromString ('*':x) = Left (True, x)+pieceFromString ('+':x) = Left (True, x)++pieceFromString ('!':x) = Right $ (False, Static x)+pieceFromString x = Right $ (True, Static x)
+ src/Routes/Routes.hs view
@@ -0,0 +1,309 @@+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}+{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE CPP #-}+{- |+Module      :  Routes.Routes+Copyright   :  (c) Anupam Jain 2013+License     :  MIT (see the file LICENSE)++Maintainer  :  ajnsit@gmail.com+Stability   :  experimental+Portability :  non-portable (uses ghc extensions)++This package provides typesafe URLs for Wai applications.+-}+module Routes.Routes+    ( -- * Quasi Quoters+      parseRoutes            -- | Parse Routes declared inline+    , parseRoutesFile        -- | Parse routes declared in a file+    , parseRoutesNoCheck     -- | Parse routes declared inline, without checking for overlaps+    , parseRoutesFileNoCheck -- | Parse routes declared in a file, without checking for overlaps++    -- * Template Haskell methods+    , mkRoute+    , mkRouteSub++    -- * Dispatch+    , routeDispatch+    , customRouteDispatch++    -- * URL rendering and parsing+    , showRoute+    , showRouteQuery+    , readRoute++    -- * Application Handlers+    , Handler+    , HandlerS++    -- * As of Wai 3, Application datatype now follows continuation passing style+    --   A `ResponseHandler` represents a continuation passed to the application+    , ResponseHandler++    -- * Generated Datatypes+    , Routable(..)           -- | Used internally. However needs to be exported for TH to work.+    , RenderRoute(..)        -- | A `RenderRoute` instance for your site datatype is automatically generated by `mkRoute`+    , ParseRoute(..)         -- | A `ParseRoute` instance for your site datatype is automatically generated by `mkRoute`+    , RouteAttrs(..)         -- | A `RouteAttrs` instance for your site datatype is automatically generated by `mkRoute`++    -- * Accessing Request Data+    , Env(..)+    , RequestData            -- | An abstract representation of the request data. You can get the wai request object by using `waiReq`+    , waiReq                 -- | Extract the wai `Request` object from `RequestData`+    , nextApp                -- | Extract the next Application in the stack+    , currentRoute           -- | Extract the current `Route` from `RequestData`+    , runNext                -- | Run the next application in the stack++    -- * Not exported outside wai-routes+    , runHandler+    , readQueryString+    )+    where++-- Wai+import Network.Wai (ResponseReceived, Middleware, Application, pathInfo, requestMethod, requestMethod, Response, Request(..))+import Network.HTTP.Types (Query, decodePath, encodePath, queryTextToQuery, queryToQueryText)++-- Routes+import Routes.Class (Route, RenderRoute(..), ParseRoute(..), RouteAttrs(..))+import Routes.Parse (parseRoutes, parseRoutesNoCheck, parseRoutesFile, parseRoutesFileNoCheck, parseType)+import Routes.TH (mkRenderRouteInstance, mkParseRouteInstance, mkRouteAttrsInstance, mkDispatchClause, ResourceTree(..), MkDispatchSettings(..), defaultGetHandler)++-- Text and Bytestring+import Data.ByteString (ByteString)+import Data.Text (Text)+import Data.Text.Encoding (encodeUtf8, decodeUtf8)+import Blaze.ByteString.Builder (toByteString)++-- TH+import Language.Haskell.TH.Syntax++-- Convenience+import Control.Arrow (second)+import Data.Maybe (fromMaybe)++-- An abstract request+data RequestData master = RequestData+  { waiReq  :: Request+  , nextApp :: Application+  , currentRoute :: Maybe (Route master)+  }++-- AJ: Experimental+type ResponseHandler = (Response -> IO ResponseReceived) -> IO ResponseReceived++-- Wai uses Application :: Wai.Request -> ResponseHandler+-- However, instead of Request, we use RequestData which has more information+type App master = RequestData master -> ResponseHandler++data Env sub master = Env+  { envMaster   :: master+  , envSub      :: sub+  , envToMaster :: Route sub -> Route master+  }++-- | Run the next application in the stack+runNext :: App master+runNext req = nextApp req $ waiReq req++-- | A `Handler` generates an App from the master datatype+type Handler sub = forall master. RenderRoute master => HandlerS sub master+type HandlerS sub master = Env sub master -> App sub++-- | Generates everything except actual dispatch+mkRouteData :: String -> [ResourceTree String] -> Q [Dec]+mkRouteData typName routes = do+  let typ = parseType typName+  let rname = mkName $ "_resources" ++ typName+  let resourceTrees = map (fmap parseType) routes+  eres <- lift routes+  let resourcesDec =+          [ SigD rname $ ListT `AppT` (ConT ''ResourceTree `AppT` ConT ''String)+          , FunD rname [Clause [] (NormalB eres) []]+          ]+  rinst <- mkRenderRouteInstance typ resourceTrees+  pinst <- mkParseRouteInstance typ resourceTrees+  ainst <- mkRouteAttrsInstance typ resourceTrees+  return $ concat [ [ainst]+                  , [pinst]+                  , resourcesDec+                  , rinst+                  ]++-- | Generates a 'Routable' instance and dispatch function+mkRouteDispatch :: String -> [ResourceTree String] -> Q [Dec]+mkRouteDispatch typName routes = do+  let typ = parseType typName+  disp <- mkRouteDispatchClause routes+#if MIN_VERSION_template_haskell(2,11,0)+  let inst = InstanceD Nothing+#else+  let inst = InstanceD+#endif+  return [inst []+          (ConT ''Routable `AppT` typ `AppT` typ)+          [FunD (mkName "dispatcher") [disp]]]++-- | Same as mkRouteDispatch but for subsites+mkRouteSubDispatch :: String -> String -> [ResourceTree a] -> Q [Dec]+mkRouteSubDispatch typName constraint routes = do+  let typ = parseType typName+  disp <- mkRouteDispatchClause routes+  master <- newName "master"+  -- We don't simply use parseType for GHC 7.8 (TH-2.9) compatibility+  -- ParseType only works on Type (not Pred)+  -- In GHC 7.10 (TH-2.10) onwards, Pred is aliased to Type+  className <- lookupTypeName constraint+  -- Check if this is a classname or a type+  let contract = maybe (error $ "Unknown typeclass " ++ show constraint) (getContract master) className+#if MIN_VERSION_template_haskell(2,11,0)+  let inst = InstanceD Nothing+#else+  let inst = InstanceD+#endif+  return [inst [contract]+          (ConT ''Routable `AppT` typ `AppT` VarT master)+          [FunD (mkName "dispatcher") [disp]]]+  where+    getContract master className =+#if MIN_VERSION_template_haskell(2,10,0)+      ConT className `AppT` VarT master+#else+      ClassP className [VarT master]+#endif++-- Helper that creates the dispatch clause+mkRouteDispatchClause :: [ResourceTree a] -> Q Clause+mkRouteDispatchClause =+  mkDispatchClause MkDispatchSettings+    { mdsRunHandler    = [| runHandler    |]+    , mdsSubDispatcher = [| subDispatcher |]+    , mdsGetPathInfo   = [| getPathInfo   |]+    , mdsMethod        = [| getReqMethod  |]+    , mdsSetPathInfo   = [| setPathInfo   |]+    , mds404           = [| app404        |]+    , mds405           = [| app405        |]+    , mdsGetHandler    = defaultGetHandler+    , mdsUnwrapper     = return+    }+++-- | Generates all the things needed for efficient routing.+-- Including your application's `Route` datatype,+-- `RenderRoute`, `ParseRoute`, `RouteAttrs`, and `Routable` instances.+-- Use this for everything except subsites+mkRoute :: String -> [ResourceTree String] -> Q [Dec]+mkRoute typName routes = do+  dat <- mkRouteData typName routes+  disp <- mkRouteDispatch typName routes+  return (disp++dat)++-- TODO: Also allow using the master datatype name directly, instead of a constraint class+-- | Same as mkRoute, but for subsites+mkRouteSub :: String -> String -> [ResourceTree String] -> Q [Dec]+mkRouteSub typName constraint routes = do+  dat <- mkRouteData typName routes+  disp <- mkRouteSubDispatch typName constraint routes+  return (disp++dat)++-- | A `Routable` instance can be used in dispatching.+--   An appropriate instance for your site datatype is+--   automatically generated by `mkRoute`.+class Routable sub master where+  dispatcher :: HandlerS sub master++-- | Generates the application middleware from a `Routable` master datatype+routeDispatch :: Routable master master => master -> Middleware+routeDispatch = customRouteDispatch dispatcher++-- | Like routeDispatch but generates the application middleware from a custom dispatcher+customRouteDispatch :: HandlerS master master -> master -> Middleware+-- TODO: Should this have master master instead of sub master?+-- TODO: Verify that this plays well with subsites+-- Env master master is converted to Env sub master by subDispatcher+-- Route information is filled in by runHandler+customRouteDispatch customDispatcher master def req = customDispatcher (_masterToEnv master) RequestData{waiReq=req, nextApp=def, currentRoute=Nothing}++-- | Render a `Route` and Query parameters to Text+showRouteQuery :: RenderRoute master => Route master -> [(Text,Text)] -> Text+showRouteQuery r q = uncurry _encodePathInfo $ second (map (second Just) . (++ q)) $ renderRoute r++-- | Renders a `Route` as Text+showRoute :: RenderRoute master => Route master -> Text+showRoute = uncurry _encodePathInfo . second (map $ second Just) . renderRoute++_encodePathInfo :: [Text] -> [(Text, Maybe Text)] -> Text+-- Slightly hackish: Convert "" into "/"+_encodePathInfo [] = _encodePathInfo [""]+_encodePathInfo segments = decodeUtf8 . toByteString . encodePath segments . queryTextToQuery++-- | Read a route from Text+-- Returns Nothing if Route reading failed. Just route otherwise+readRoute :: ParseRoute master => Text -> Maybe (Route master)+readRoute = parseRoute . second readQueryString . decodePath . encodeUtf8++-- | Convert a Query to the format expected by parseRoute+readQueryString :: Query -> [(Text, Text)]+readQueryString = map (second (fromMaybe "")) . queryToQueryText++-- PRIVATE++-- Get the request method from a RequestData+getReqMethod :: RequestData master -> ByteString+getReqMethod = requestMethod . waiReq++-- Get the path info from a RequestData+getPathInfo :: RequestData master -> [Text]+getPathInfo = pathInfo . waiReq++-- Set the path info in a RequestData+setPathInfo :: [Text] -> RequestData master -> RequestData master+setPathInfo p reqData = reqData { waiReq = (waiReq reqData){pathInfo=p} }++-- Baked in applications that handle 404 and 405 errors+-- On no matching route, skip to next application+app404 :: HandlerS sub master+app404 _master = runNext++-- On matching route, but no matching http method, skip to next application+-- This allows a later route to handle methods not implemented by the previous routes+app405 :: HandlerS sub master+app405 _master = runNext++-- Run a route handler function+-- Currently all this does is populate the route into RequestData+-- But it may do more in the future+runHandler+    :: HandlerS sub master+    -> Env sub master+    -> Maybe (Route sub)+    -> App sub+runHandler h env route reqdata = h env reqdata{currentRoute=route}++-- Run a route subsite handler function+subDispatcher+    :: Routable sub master+    => (HandlerS sub master -> Env sub master -> Maybe (Route sub) -> App sub)+    -> (master -> sub)+    -> (Route sub -> Route master)+    -> Env master master+    -> App master+subDispatcher _runhandler getSub toMasterRoute env reqData = dispatcher env' reqData'+  where+    env' = _envToSub getSub toMasterRoute env+    reqData' = reqData{currentRoute=Nothing}+    -- qq (k,mv) = (decodeUtf8 k, maybe "" decodeUtf8 mv)+    -- req = waiReq reqData++_masterToEnv :: master -> Env master master+_masterToEnv master = Env master master id++_envToSub :: (master -> sub) -> (Route sub -> Route master) -> Env master master -> Env sub master+_envToSub getSub toMasterRoute env = Env master sub toMasterRoute+  where+    master = envMaster env+    sub = getSub master
+ src/Routes/TH.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE TemplateHaskell #-}+module Routes.TH+    ( module Routes.TH.Types+      -- * Functions+    , module Routes.TH.RenderRoute+    , module Routes.TH.ParseRoute+    , module Routes.TH.RouteAttrs+      -- ** Dispatch+    , module Routes.TH.Dispatch+    ) where++import Routes.TH.Types+import Routes.TH.RenderRoute+import Routes.TH.ParseRoute+import Routes.TH.RouteAttrs+import Routes.TH.Dispatch
+ src/Routes/TH/Dispatch.hs view
@@ -0,0 +1,202 @@+{-# LANGUAGE RecordWildCards, TemplateHaskell, ViewPatterns #-}+module Routes.TH.Dispatch+    ( MkDispatchSettings (..)+    , mkDispatchClause+    , defaultGetHandler+    ) where++import Prelude hiding (exp)+import Language.Haskell.TH.Syntax+import Web.PathPieces+import Data.Maybe (catMaybes)+import Control.Monad (forM)+import Data.List (foldl')+import Control.Arrow (second)+import System.Random (randomRIO)+import Routes.TH.Types+import Data.Char (toLower)++data MkDispatchSettings b site c = MkDispatchSettings+    { mdsRunHandler :: Q Exp+    , mdsSubDispatcher :: Q Exp+    , mdsGetPathInfo :: Q Exp+    , mdsSetPathInfo :: Q Exp+    , mdsMethod :: Q Exp+    , mds404 :: Q Exp+    , mds405 :: Q Exp+    , mdsGetHandler :: Maybe String -> String -> Q Exp+    , mdsUnwrapper :: Exp -> Q Exp+    }++data SDC = SDC+    { clause404 :: Clause+    , extraParams :: [Exp]+    , extraCons :: [Exp]+    , envExp :: Exp+    , reqExp :: Exp+    }++-- | A simpler version of Routes.TH.Dispatch.mkDispatchClause, based on+-- view patterns.+--+-- Since 1.4.0+mkDispatchClause :: MkDispatchSettings b site c -> [ResourceTree a] -> Q Clause+mkDispatchClause MkDispatchSettings {..} resources = do+    suffix <- qRunIO $ randomRIO (1000, 9999 :: Int)+    envName <- newName $ "env" ++ show suffix+    reqName <- newName $ "req" ++ show suffix+    helperName <- newName $ "helper" ++ show suffix++    let envE = VarE envName+        reqE = VarE reqName+        helperE = VarE helperName++    clause404' <- mkClause404 envE reqE+    getPathInfo <- mdsGetPathInfo+    let pathInfo = getPathInfo `AppE` reqE++    let sdc = SDC+            { clause404 = clause404'+            , extraParams = []+            , extraCons = []+            , envExp = envE+            , reqExp = reqE+            }+    clauses <- mapM (go sdc) resources++    return $ Clause+        [VarP envName, VarP reqName]+        (NormalB $ helperE `AppE` pathInfo)+        [FunD helperName $ clauses ++ [clause404']]+  where+    handlePiece :: Piece a -> Q (Pat, Maybe Exp)+    handlePiece (Static str) = return (LitP $ StringL str, Nothing)+    handlePiece (Dynamic _) = do+        x <- newName "dyn"+        let pat = ViewP (VarE 'fromPathPiece) (ConP 'Just [VarP x])+        return (pat, Just $ VarE x)++    handlePieces :: [Piece a] -> Q ([Pat], [Exp])+    handlePieces = fmap (second catMaybes . unzip) . mapM handlePiece++    mkCon :: String -> [Exp] -> Exp+    mkCon name = foldl' AppE (ConE $ mkName name)++    mkPathPat :: Pat -> [Pat] -> Pat+    mkPathPat final =+        foldr addPat final+      where+        addPat x y = ConP '(:) [x, y]++    go :: SDC -> ResourceTree a -> Q Clause+    go sdc (ResourceParent name _check pieces children) = do+        (pats, dyns) <- handlePieces pieces+        let sdc' = sdc+                { extraParams = extraParams sdc ++ dyns+                , extraCons = extraCons sdc ++ [mkCon name dyns]+                }+        childClauses <- mapM (go sdc') children++        restName <- newName "rest"+        let restE = VarE restName+            restP = VarP restName++        helperName <- newName $ "helper" ++ name+        let helperE = VarE helperName++        return $ Clause+            [mkPathPat restP pats]+            (NormalB $ helperE `AppE` restE)+            [FunD helperName $ childClauses ++ [clause404 sdc]]+    go SDC {..} (ResourceLeaf (Resource name pieces dispatch _ _check)) = do+        (pats, dyns) <- handlePieces pieces++        (chooseMethod, finalPat) <- handleDispatch dispatch dyns++        return $ Clause+            [mkPathPat finalPat pats]+            (NormalB chooseMethod)+            []+      where+        handleDispatch :: Dispatch a -> [Exp] -> Q (Exp, Pat)+        handleDispatch dispatch' dyns =+            case dispatch' of+                Methods multi methods -> do+                    (finalPat, mfinalE) <-+                        case multi of+                            Nothing -> return (ConP '[] [], Nothing)+                            Just _ -> do+                                multiName <- newName "multi"+                                let pat = ViewP (VarE 'fromPathMultiPiece)+                                                (ConP 'Just [VarP multiName])+                                return (pat, Just $ VarE multiName)++                    let dynsMulti =+                            case mfinalE of+                                Nothing -> dyns+                                Just e -> dyns ++ [e]+                        route' = foldl' AppE (ConE (mkName name)) dynsMulti+                        route = foldr AppE route' extraCons+                        jroute = ConE 'Just `AppE` route+                        allDyns = extraParams ++ dynsMulti+                        mkRunExp mmethod = do+                            runHandlerE <- mdsRunHandler+                            handlerE' <- mdsGetHandler mmethod name+                            handlerE <- mdsUnwrapper $ foldl' AppE handlerE' allDyns+                            return $ runHandlerE+                                `AppE` handlerE+                                `AppE` envExp+                                `AppE` jroute+                                `AppE` reqExp++                    func <-+                        case methods of+                            [] -> mkRunExp Nothing+                            _ -> do+                                getMethod <- mdsMethod+                                let methodE = getMethod `AppE` reqExp+                                matches <- forM methods $ \method -> do+                                    exp <- mkRunExp (Just method)+                                    return $ Match (LitP $ StringL method) (NormalB exp) []+                                match405 <- do+                                    runHandlerE <- mdsRunHandler+                                    handlerE <- mds405+                                    let exp = runHandlerE+                                            `AppE` handlerE+                                            `AppE` envExp+                                            `AppE` jroute+                                            `AppE` reqExp+                                    return $ Match WildP (NormalB exp) []+                                return $ CaseE methodE $ matches ++ [match405]++                    return (func, finalPat)+                Subsite _ getSub -> do+                    restPath <- newName "restPath"+                    setPathInfoE <- mdsSetPathInfo+                    subDispatcherE <- mdsSubDispatcher+                    runHandlerE <- mdsRunHandler+                    sub <- newName "sub"+                    let allDyns = extraParams ++ dyns+                    sroute <- newName "sroute"+                    let sub2 = LamE [VarP sub]+                            (foldl' (\a b -> a `AppE` b) (VarE (mkName getSub) `AppE` VarE sub) allDyns)+                    let reqExp' = setPathInfoE `AppE` VarE restPath `AppE` reqExp+                        route' = foldl' AppE (ConE (mkName name)) dyns+                        route = LamE [VarP sroute] $ foldr AppE (AppE route' $ VarE sroute) extraCons+                        exp = subDispatcherE+                            `AppE` runHandlerE+                            `AppE` sub2+                            `AppE` route+                            `AppE` envExp+                            `AppE` reqExp'+                    return (exp, VarP restPath)++    mkClause404 envE reqE = do+        handler <- mds404+        runHandler <- mdsRunHandler+        let exp = runHandler `AppE` handler `AppE` envE `AppE` ConE 'Nothing `AppE` reqE+        return $ Clause [WildP] (NormalB exp) []++defaultGetHandler :: Maybe String -> String -> Q Exp+defaultGetHandler Nothing s = return $ VarE $ mkName $ "handle" ++ s+defaultGetHandler (Just method) s = return $ VarE $ mkName $ map toLower method ++ s
+ src/Routes/TH/ParseRoute.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE TemplateHaskell, CPP #-}+module Routes.TH.ParseRoute+    ( -- ** ParseRoute+      mkParseRouteInstance+    ) where++import Routes.TH.Types+import Language.Haskell.TH.Syntax+import Data.Text (Text)+import Routes.Class+import Routes.TH.Dispatch++mkParseRouteInstance :: Type -> [ResourceTree a] -> Q Dec+mkParseRouteInstance typ ress = do+    cls <- mkDispatchClause+        MkDispatchSettings+            { mdsRunHandler = [|\_ _ x _ -> x|]+            , mds404 = [|error "mds404"|]+            , mds405 = [|error "mds405"|]+            , mdsGetPathInfo = [|fst|]+            , mdsMethod = [|error "mdsMethod"|]+            , mdsGetHandler = \_ _ -> [|error "mdsGetHandler"|]+            , mdsSetPathInfo = [|\p (_, q) -> (p, q)|]+            , mdsSubDispatcher = [|\_runHandler _getSub toMaster _env -> fmap toMaster . parseRoute|]+            , mdsUnwrapper = return+            }+        (map removeMethods ress)+    helper <- newName "helper"+    fixer <- [|(\f x -> f () x) :: (() -> ([Text], [(Text, Text)]) -> Maybe (Route a)) -> ([Text], [(Text, Text)]) -> Maybe (Route a)|]+    return $ instanceD [] (ConT ''ParseRoute `AppT` typ)+        [ FunD 'parseRoute $ return $ Clause+            []+            (NormalB $ fixer `AppE` VarE helper)+            [FunD helper [cls]]+        ]+  where+    -- We do this in order to ski the unnecessary method parsing+    removeMethods (ResourceLeaf res) = ResourceLeaf $ removeMethodsLeaf res+    removeMethods (ResourceParent w x y z) = ResourceParent w x y $ map removeMethods z++    removeMethodsLeaf res = res { resourceDispatch = fixDispatch $ resourceDispatch res }++    fixDispatch (Methods x _) = Methods x []+    fixDispatch x = x++instanceD :: Cxt -> Type -> [Dec] -> Dec+#if MIN_VERSION_template_haskell(2,11,0)+instanceD = InstanceD Nothing+#else+instanceD = InstanceD+#endif
+ src/Routes/TH/RenderRoute.hs view
@@ -0,0 +1,181 @@+{-# LANGUAGE TemplateHaskell, CPP #-}+module Routes.TH.RenderRoute+    ( -- ** RenderRoute+      mkRenderRouteInstance+    , mkRenderRouteInstance'+    , mkRouteCons+    , mkRenderRouteClauses+    ) where++import Routes.TH.Types+#if MIN_VERSION_template_haskell(2,11,0)+import Language.Haskell.TH (conT)+#endif+import Language.Haskell.TH.Syntax+import Data.Maybe (maybeToList)+import Control.Monad (replicateM)+import Data.Text (pack)+import Web.PathPieces (PathPiece (..), PathMultiPiece (..))+import Routes.Class+#if __GLASGOW_HASKELL__ < 710+import Control.Applicative ((<$>))+import Data.Monoid (mconcat)+#endif++-- | Generate the constructors of a route data type.+mkRouteCons :: [ResourceTree Type] -> Q ([Con], [Dec])+mkRouteCons rttypes =+    mconcat <$> mapM mkRouteCon rttypes+  where+    mkRouteCon (ResourceLeaf res) =+        return ([con], [])+      where+        con = NormalC (mkName $ resourceName res)+            $ map (\x -> (notStrict, x))+            $ concat [singles, multi, sub]+        singles = concatMap toSingle $ resourcePieces res+        toSingle Static{} = []+        toSingle (Dynamic typ) = [typ]++        multi = maybeToList $ resourceMulti res++        sub =+            case resourceDispatch res of+                Subsite { subsiteType = typ } -> [ConT ''Route `AppT` typ]+                _ -> []++    mkRouteCon (ResourceParent name _check pieces children) = do+        (cons, decs) <- mkRouteCons children+#if MIN_VERSION_template_haskell(2,11,0)+        dec <- DataD [] (mkName name) [] Nothing cons <$> mapM conT [''Show, ''Read, ''Eq]+#else+        let dec = DataD [] (mkName name) [] cons [''Show, ''Read, ''Eq]+#endif+        return ([con], dec : decs)+      where+        con = NormalC (mkName name)+            $ map (\x -> (notStrict, x))+            $ concat [singles, [ConT $ mkName name]]++        singles = concatMap toSingle pieces+        toSingle Static{} = []+        toSingle (Dynamic typ) = [typ]++-- | Clauses for the 'renderRoute' method.+mkRenderRouteClauses :: [ResourceTree Type] -> Q [Clause]+mkRenderRouteClauses =+    mapM go+  where+    isDynamic Dynamic{} = True+    isDynamic _ = False++    go (ResourceParent name _check pieces children) = do+        let cnt = length $ filter isDynamic pieces+        dyns <- replicateM cnt $ newName "dyn"+        child <- newName "child"+        let pat = ConP (mkName name) $ map VarP $ dyns ++ [child]++        pack' <- [|pack|]+        tsp <- [|toPathPiece|]+        let piecesSingle = mkPieces (AppE pack' . LitE . StringL) tsp pieces dyns++        childRender <- newName "childRender"+        let rr = VarE childRender+        childClauses <- mkRenderRouteClauses children++        a <- newName "a"+        b <- newName "b"++        colon <- [|(:)|]+        let cons y ys = InfixE (Just y) colon (Just ys)+        let pieces' = foldr cons (VarE a) piecesSingle++        let body = LamE [TupP [VarP a, VarP b]] (TupE [pieces', VarE b]) `AppE` (rr `AppE` VarE child)++        return $ Clause [pat] (NormalB body) [FunD childRender childClauses]++    go (ResourceLeaf res) = do+        let cnt = length (filter isDynamic $ resourcePieces res) + maybe 0 (const 1) (resourceMulti res)+        dyns <- replicateM cnt $ newName "dyn"+        sub <-+            case resourceDispatch res of+                Subsite{} -> fmap return $ newName "sub"+                _ -> return []+        let pat = ConP (mkName $ resourceName res) $ map VarP $ dyns ++ sub++        pack' <- [|pack|]+        tsp <- [|toPathPiece|]+        let piecesSingle = mkPieces (AppE pack' . LitE . StringL) tsp (resourcePieces res) dyns++        piecesMulti <-+            case resourceMulti res of+                Nothing -> return $ ListE []+                Just{} -> do+                    tmp <- [|toPathMultiPiece|]+                    return $ tmp `AppE` VarE (last dyns)++        body <-+            case sub of+                [x] -> do+                    rr <- [|renderRoute|]+                    a <- newName "a"+                    b <- newName "b"++                    colon <- [|(:)|]+                    let cons y ys = InfixE (Just y) colon (Just ys)+                    let pieces = foldr cons (VarE a) piecesSingle++                    return $ LamE [TupP [VarP a, VarP b]] (TupE [pieces, VarE b]) `AppE` (rr `AppE` VarE x)+                _ -> do+                    colon <- [|(:)|]+                    let cons a b = InfixE (Just a) colon (Just b)+                    return $ TupE [foldr cons piecesMulti piecesSingle, ListE []]++        return $ Clause [pat] (NormalB body) []++    mkPieces _ _ [] _ = []+    mkPieces toText tsp (Static s:ps) dyns = toText s : mkPieces toText tsp ps dyns+    mkPieces toText tsp (Dynamic{}:ps) (d:dyns) = tsp `AppE` VarE d : mkPieces toText tsp ps dyns+    mkPieces _ _ ((Dynamic _) : _) [] = error "mkPieces 120"++-- | Generate the 'RenderRoute' instance.+--+-- This includes both the 'Route' associated type and the+-- 'renderRoute' method.  This function uses both 'mkRouteCons' and+-- 'mkRenderRouteClasses'.+mkRenderRouteInstance :: Type -> [ResourceTree Type] -> Q [Dec]+mkRenderRouteInstance = mkRenderRouteInstance' []++-- | A more general version of 'mkRenderRouteInstance' which takes an+-- additional context.++mkRenderRouteInstance' :: Cxt -> Type -> [ResourceTree Type] -> Q [Dec]+mkRenderRouteInstance' cxt typ ress = do+    cls <- mkRenderRouteClauses ress+    (cons, decs) <- mkRouteCons ress+#if MIN_VERSION_template_haskell(2,11,0)+    did <- DataInstD [] ''Route [typ] Nothing cons <$> mapM conT clazzes+#else+    let did = DataInstD [] ''Route [typ] cons clazzes+#endif+    return $ instanceD cxt (ConT ''RenderRoute `AppT` typ)+        [ did+        , FunD (mkName "renderRoute") cls+        ] : decs+  where+    clazzes = [''Show, ''Eq, ''Read]++#if MIN_VERSION_template_haskell(2,11,0)+notStrict :: Bang+notStrict = Bang NoSourceUnpackedness NoSourceStrictness+#else+notStrict :: Strict+notStrict = NotStrict+#endif++instanceD :: Cxt -> Type -> [Dec] -> Dec+#if MIN_VERSION_template_haskell(2,11,0)+instanceD = InstanceD Nothing+#else+instanceD = InstanceD+#endif
+ src/Routes/TH/RouteAttrs.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE TemplateHaskell, CPP #-}+{-# LANGUAGE RecordWildCards #-}+module Routes.TH.RouteAttrs+    ( mkRouteAttrsInstance+    ) where++import Routes.TH.Types+import Routes.Class+import Language.Haskell.TH.Syntax+import Data.Set (fromList)+import Data.Text (pack)++mkRouteAttrsInstance :: Type -> [ResourceTree a] -> Q Dec+mkRouteAttrsInstance typ ress = do+    clauses <- mapM (goTree id) ress+    return $ instanceD [] (ConT ''RouteAttrs `AppT` typ)+        [ FunD 'routeAttrs $ concat clauses+        ]++goTree :: (Pat -> Pat) -> ResourceTree a -> Q [Clause]+goTree front (ResourceLeaf res) = fmap return $ goRes front res+goTree front (ResourceParent name _check pieces trees) =+    fmap concat $ mapM (goTree front') trees+  where+    ignored = ((replicate toIgnore WildP ++) . return)+    toIgnore = length $ filter isDynamic pieces+    isDynamic Dynamic{} = True+    isDynamic Static{} = False+    front' = front . ConP (mkName name) . ignored++goRes :: (Pat -> Pat) -> Resource a -> Q Clause+goRes front Resource {..} =+    return $ Clause+        [front $ RecP (mkName resourceName) []]+        (NormalB $ VarE 'fromList `AppE` ListE (map toText resourceAttrs))+        []+  where+    toText s = VarE 'pack `AppE` LitE (StringL s)++instanceD :: Cxt -> Type -> [Dec] -> Dec+#if MIN_VERSION_template_haskell(2,11,0)+instanceD = InstanceD Nothing+#else+instanceD = InstanceD+#endif
+ src/Routes/TH/Types.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE TemplateHaskell #-}+-- | Warning! This module is considered internal and may have breaking changes+module Routes.TH.Types+    ( -- * Data types+      Resource (..)+    , ResourceTree (..)+    , Piece (..)+    , Dispatch (..)+    , CheckOverlap+    , FlatResource (..)+      -- ** Helper functions+    , resourceMulti+    , resourceTreePieces+    , resourceTreeName+    , flatten+    ) where++import Language.Haskell.TH.Syntax++data ResourceTree typ+    = ResourceLeaf (Resource typ)+    | ResourceParent String CheckOverlap [Piece typ] [ResourceTree typ]+    deriving Functor++resourceTreePieces :: ResourceTree typ -> [Piece typ]+resourceTreePieces (ResourceLeaf r) = resourcePieces r+resourceTreePieces (ResourceParent _ _ x _) = x++resourceTreeName :: ResourceTree typ -> String+resourceTreeName (ResourceLeaf r) = resourceName r+resourceTreeName (ResourceParent x _ _ _) = x++instance Lift t => Lift (ResourceTree t) where+    lift (ResourceLeaf r) = [|ResourceLeaf $(lift r)|]+    lift (ResourceParent a b c d) = [|ResourceParent $(lift a) $(lift b) $(lift c) $(lift d)|]++data Resource typ = Resource+    { resourceName :: String+    , resourcePieces :: [Piece typ]+    , resourceDispatch :: Dispatch typ+    , resourceAttrs :: [String]+    , resourceCheck :: CheckOverlap+    }+    deriving (Show, Functor)++type CheckOverlap = Bool++instance Lift t => Lift (Resource t) where+    lift (Resource a b c d e) = [|Resource a b c d e|]++data Piece typ = Static String | Dynamic typ+    deriving Show++instance Functor Piece where+    fmap _ (Static s) = (Static s)+    fmap f (Dynamic t) = Dynamic (f t)++instance Lift t => Lift (Piece t) where+    lift (Static s) = [|Static $(lift s)|]+    lift (Dynamic t) = [|Dynamic $(lift t)|]++data Dispatch typ =+    Methods+        { methodsMulti :: Maybe typ -- ^ type of the multi piece at the end+        , methodsMethods :: [String] -- ^ supported request methods+        }+    | Subsite+        { subsiteType :: typ+        , subsiteFunc :: String+        }+    deriving Show++instance Functor Dispatch where+    fmap f (Methods a b) = Methods (fmap f a) b+    fmap f (Subsite a b) = Subsite (f a) b++instance Lift t => Lift (Dispatch t) where+    lift (Methods Nothing b) = [|Methods Nothing $(lift b)|]+    lift (Methods (Just t) b) = [|Methods (Just $(lift t)) $(lift b)|]+    lift (Subsite t b) = [|Subsite $(lift t) $(lift b)|]++resourceMulti :: Resource typ -> Maybe typ+resourceMulti Resource { resourceDispatch = Methods (Just t) _ } = Just t+resourceMulti _ = Nothing++data FlatResource a = FlatResource+    { frParentPieces :: [(String, [Piece a])]+    , frName :: String+    , frPieces :: [Piece a]+    , frDispatch :: Dispatch a+    , frCheck :: Bool+    }++flatten :: [ResourceTree a] -> [FlatResource a]+flatten =+    concatMap (go id True)+  where+    go front check' (ResourceLeaf (Resource a b c _ check)) = [FlatResource (front []) a b c (check' && check)]+    go front check' (ResourceParent name check pieces children) =+        concatMap (go (front . ((name, pieces):)) (check && check')) children
+ src/Wai/Routes.hs view
@@ -0,0 +1,129 @@+{- |+Module      :  Wai.Routes+Copyright   :  (c) Anupam Jain 2013+License     :  MIT (see the file LICENSE)++Maintainer  :  ajnsit@gmail.com+Stability   :  experimental+Portability :  non-portable (uses ghc extensions)++This package provides typesafe URLs for Wai applications.+-}+module Wai.Routes+    ( -- * Declaring Routes using Template Haskell+      parseRoutes+    , parseRoutesFile        -- | Parse routes declared in a file+    , parseRoutesNoCheck+    , parseRoutesFileNoCheck -- | Same as parseRoutesFile, but performs no overlap checking.++    , mkRoute+    , mkRouteSub++    -- * Dispatch+    , routeDispatch++    -- * URL rendering and parsing+    , showRoute+    , showRouteQuery+    , readRoute+    , showRouteMaster+    , showRouteQueryMaster+    , readRouteMaster+    , showRouteSub+    , showRouteQuerySub+    , readRouteSub++    -- * Application Handlers+    , Handler+    , HandlerS++    -- * Generated Datatypes+    , Routable(..)           -- | Used internally. However needs to be exported for TH to work.+    , RenderRoute(..)        -- | A `RenderRoute` instance for your site datatype is automatically generated by `mkRoute`+    , ParseRoute(..)         -- | A `ParseRoute` instance for your site datatype is automatically generated by `mkRoute`+    , RouteAttrs(..)         -- | A `RouteAttrs` instance for your site datatype is automatically generated by `mkRoute`++    -- * Accessing Raw Request Data+    , RequestData            -- | An abstract representation of the request data. You can get the wai request object by using `waiReq`+    , waiReq                 -- | Extract the wai `Request` object from `RequestData`+    , nextApp                -- | Extract the next Application in the stack+    , runNext                -- | Run the next application in the stack++    -- * Route Monad makes it easy to compose routes together+    , RouteM+    , DefaultMaster(..)+    , Route(DefaultRoute)+    , handler                -- | Add a wai-routes handler+    , catchall               -- | Catch all routes with the supplied application+    , defaultAction          -- | A synonym for `catchall`, kept for backwards compatibility+    , middleware             -- | Add another middleware to the app+    , route                  -- | Add another routed middleware to the app+    , waiApp                 -- | Convert a RouteM to a wai Application+    , toWaiApp               -- | Similar to waiApp, but result is wrapped in a monad. Kept for backwards compatibility++    -- * HandlerM Monad makes it easy to build a handler+    , HandlerM()+    , runHandlerM            -- | Run a HandlerM to get a Handler+    , mountedAppHandler      -- | Convert a full wai application to a HandlerS+    , request                -- | Access the request data+    , isWebsocket            -- | Is this a websocket request+    , reqHeader              -- | Get a particular request header (case insensitive)+    , reqHeaders             -- | Get all request headers (case insensitive)+    , maybeRootRoute         -- | Access the current route for root route+    , maybeRoute             -- | Access the current route+    , routeAttrSet           -- | Access the current route attributes as a set+    , rootRouteAttrSet       -- | Access the current root route attributes as a set+    , master                 -- | Access the master datatype+    , sub                    -- | Access the sub datatype+    , rawBody                -- | Consume and return the request body as ByteString+    , textBody               -- | Consume and return the request body as Text+    , jsonBody               -- | Consume and return the request body as JSON+    , header                 -- | Add a header to the response+    , status                 -- | Set the response status+    , file                   -- | Send a file as response+    , filepart               -- | Send a part of a file as response+    , stream                 -- | Stream a response+    , raw                    -- | Set the raw response body+    , rawBuilder             -- | Set the raw response body as a ByteString Builder+    , json                   -- | Set the json response body+    , plain                  -- | Set the plain text response body+    , html                   -- | Set the html response body+    , css                    -- | Set the css response body+    , javascript             -- | Set the javascript response body+    , asContent              -- | Set the contentType and a 'Text' body+    , next                   -- | Run the next application in the stack+    , getParams              -- | Get all params (query or post, not file)+    , getParam               -- | Get a particular param (query or post, not file)+    , getQueryParams         -- | Get all query params+    , getQueryParam          -- | Get a particular query param+    , getPostParams          -- | Get all post params+    , getPostParam           -- | Get a particular post param+    , getFileParams          -- | Get all file params+    , getFileParam           -- | Get a particular file param+    , setCookie              -- | Add a cookie to the response+    , getCookie              -- | Get a cookie from the request+    , getCookies             -- | Get all cookies from the request+    , reqVault               -- | Access the vault from the request+    , lookupVault            -- | Lookup a key in the request vault+    , updateVault            -- | Update the request vault++    -- * Bare Handlers+    , Env(..)+    , RequestData            -- | An abstract representation of the request data. You can get the wai request object by using `waiReq`+    , waiReq                 -- | Extract the wai `Request` object from `RequestData`+    , nextApp                -- | Extract the next Application in the stack+    , currentRoute           -- | Extract the current `Route` from `RequestData`+    , runNext                -- | Run the next application in the stack++    , module Network.HTTP.Types.Status+    , module Network.Wai.Middleware.RequestLogger+    , module Network.Wai.Application.Static+  )+  where++import Routes.Routes+import Routes.Monad+import Routes.Handler+import Network.HTTP.Types.Status+import Network.Wai.Middleware.RequestLogger+import Network.Wai.Application.Static
test/HelloSpec.hs view
@@ -6,7 +6,7 @@  import Data.Maybe (fromMaybe) import Network.Wai (Application)-import Network.Wai.Middleware.Routes+import Wai.Routes import Data.Aeson (Value(Number), (.=), object)  import Data.Text (Text)@@ -142,4 +142,3 @@   describe "GET /lambda.png" $     it "returns a file correctly" $       get "/lambda.png" `shouldRespondWith` 200 {matchHeaders = ["Content-Type" <:> "image/png"]}-
wai-routes.cabal view
@@ -1,5 +1,5 @@ name               : wai-routes-version            : 0.9.8+version            : 0.9.9 cabal-version      : >=1.18 build-type         : Simple license            : MIT@@ -21,18 +21,18 @@  source-repository this     type     : git-    location : http://github.com/ajnsit/wai-routes/tree/v0.9.8-    tag      : v0.9.8+    location : http://github.com/ajnsit/wai-routes/tree/v0.9.9+    tag      : v0.9.9  library-    build-depends      : base               >= 4.7  && < 4.9+    build-depends      : base               >= 4.7  && < 4.10                        , wai                >= 3.0  && < 3.3                        , wai-extra          >= 3.0  && < 3.1                        , wai-app-static     >= 3.0  && < 3.2                        , text               >= 1.2  && < 1.3                        , template-haskell   >= 2.9  && < 2.12                        , mtl                >= 2.1  && < 2.3-                       , aeson              >= 0.8  && < 0.12+                       , aeson              >= 0.8  && < 1.1                        , containers         >= 0.5  && < 0.6                        , random             >= 1.1  && < 1.2                        , path-pieces        >= 0.2  && < 0.3@@ -46,21 +46,21 @@                        , cookie             >= 0.4  && < 0.5                        , data-default-class >= 0.0  && < 0.2                        , vault              >= 0.3  && < 0.4-    exposed-modules    : Network.Wai.Middleware.Routes-    other-modules      : Network.Wai.Middleware.Routes.Parse-                         Network.Wai.Middleware.Routes.Overlap-                         Network.Wai.Middleware.Routes.Class-                         Network.Wai.Middleware.Routes.Routes-                         Network.Wai.Middleware.Routes.Monad-                         Network.Wai.Middleware.Routes.Handler-                         Network.Wai.Middleware.Routes.ContentTypes-                         Network.Wai.Middleware.Routes.DefaultRoute-                         Network.Wai.Middleware.Routes.TH-                         Network.Wai.Middleware.Routes.TH.Types-                         Network.Wai.Middleware.Routes.TH.Dispatch-                         Network.Wai.Middleware.Routes.TH.ParseRoute-                         Network.Wai.Middleware.Routes.TH.RenderRoute-                         Network.Wai.Middleware.Routes.TH.RouteAttrs+    exposed-modules    : Wai.Routes, Network.Wai.Middleware.Routes+    other-modules      : Routes.Parse+                         Routes.Overlap+                         Routes.Class+                         Routes.Routes+                         Routes.Monad+                         Routes.Handler+                         Routes.ContentTypes+                         Routes.DefaultRoute+                         Routes.TH+                         Routes.TH.Types+                         Routes.TH.Dispatch+                         Routes.TH.ParseRoute+                         Routes.TH.RenderRoute+                         Routes.TH.RouteAttrs                          Util.Free     exposed            : True     buildable          : True@@ -76,12 +76,12 @@   hs-source-dirs   : test   GHC-options      : -Wall -threaded -fno-warn-orphans -  build-depends    : base           >= 4.7 && < 4.9+  build-depends    : base           >= 4.7 && < 4.10                    , wai            >= 3.0 && < 3.3-                   , aeson          >= 0.8 && < 0.12-                   , hspec          >= 2.1 && < 2.3-                   , hspec-wai      >= 0.6 && < 0.7-                   , hspec-wai-json >= 0.6 && < 0.7+                   , aeson          >= 0.8 && < 1.1+                   , hspec          >= 2.1 && < 2.4+                   , hspec-wai      >= 0.6 && < 0.9+                   , hspec-wai-json >= 0.6 && < 0.9                    , text           >= 1.2 && < 1.3                    , wai-routes   ghc-options        : -Wall