Merge pull request #1366 from jprider63/dev.jp

Contexts can be parsed and included in instances. Standalone deriving…
This commit is contained in:
Michael Snoyman 2017-06-05 11:33:11 +03:00 committed by GitHub
commit 0b1a4b114c
7 changed files with 128 additions and 32 deletions

View File

@ -1,3 +1,8 @@
## 1.4.35
* Contexts can be included in generated TH instances. [1365](https://github.com/yesodweb/yesod/issues/1365)
* Type variables can be included in routes.
## 1.4.34 ## 1.4.34
* Add `WaiSubsiteWithAuth`. [#1394](https://github.com/yesodweb/yesod/pull/1394) * Add `WaiSubsiteWithAuth`. [#1394](https://github.com/yesodweb/yesod/pull/1394)

View File

@ -4,6 +4,7 @@
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
module Yesod.Core.Internal.TH where module Yesod.Core.Internal.TH where
import Prelude hiding (exp) import Prelude hiding (exp)
@ -15,12 +16,18 @@ import Language.Haskell.TH.Syntax
import qualified Network.Wai as W import qualified Network.Wai as W
import Data.ByteString.Lazy.Char8 () import Data.ByteString.Lazy.Char8 ()
#if MIN_VERSION_base(4,8,0)
import Data.List (foldl', uncons)
#else
import Data.List (foldl') import Data.List (foldl')
#endif
#if __GLASGOW_HASKELL__ < 710 #if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<$>)) import Control.Applicative ((<$>))
#endif #endif
import Control.Monad (replicateM, void) import Control.Monad (replicateM, void)
import Data.Either (partitionEithers) import Data.Either (partitionEithers)
import Text.Parsec (parse, many1, many, eof, try, option, sepBy1)
import Text.ParserCombinators.Parsec.Char (alphaNum, spaces, string, char)
import Yesod.Routes.TH import Yesod.Routes.TH
import Yesod.Routes.Parse import Yesod.Routes.Parse
@ -55,8 +62,40 @@ mkYesodSubData name = mkYesodDataGeneral name True
mkYesodDataGeneral :: String -> Bool -> [ResourceTree String] -> Q [Dec] mkYesodDataGeneral :: String -> Bool -> [ResourceTree String] -> Q [Dec]
mkYesodDataGeneral name isSub res = do mkYesodDataGeneral name isSub res = do
let (name':rest) = words name let (name', rest, cxt) = case parse parseName "" name of
fst <$> mkYesodGeneral name' (fmap Left rest) isSub return res Left err -> error $ show err
Right a -> a
fst <$> mkYesodGeneral' cxt name' (fmap Left rest) isSub return res
where
parseName = do
cxt <- option [] parseContext
name' <- parseWord
args <- many parseWord
spaces
eof
return ( name', args, cxt)
parseWord = do
spaces
many1 alphaNum
parseContext = try $ do
cxts <- parseParen parseContexts
spaces
_ <- string "=>"
return cxts
parseParen p = do
spaces
_ <- char '('
r <- p
spaces
_ <- char ')'
return r
parseContexts =
sepBy1 (many1 parseWord) (spaces >> char ',' >> return ())
-- | See 'mkYesodData'. -- | See 'mkYesodData'.
mkYesodDispatch :: String -> [ResourceTree String] -> Q [Dec] mkYesodDispatch :: String -> [ResourceTree String] -> Q [Dec]
@ -80,7 +119,23 @@ mkYesodGeneral :: String -- ^ foundation type
-> (Exp -> Q Exp) -- ^ unwrap handler -> (Exp -> Q Exp) -- ^ unwrap handler
-> [ResourceTree String] -> [ResourceTree String]
-> Q([Dec],[Dec]) -> Q([Dec],[Dec])
mkYesodGeneral namestr args isSub f resS = do mkYesodGeneral = mkYesodGeneral' []
mkYesodGeneral' :: [[String]] -- ^ Appliction context. Used in RenderRoute, RouteAttrs, and ParseRoute instances.
-> String -- ^ foundation type
-> [Either String [String]] -- ^ arguments for the type
-> Bool -- ^ is this a subsite
-> (Exp -> Q Exp) -- ^ unwrap handler
-> [ResourceTree String]
-> Q([Dec],[Dec])
mkYesodGeneral' appCxt' namestr args isSub f resS = do
let appCxt = fmap (\(c:rest) ->
#if MIN_VERSION_template_haskell(2,10,0)
foldl' (\acc v -> acc `AppT` nameToType v) (ConT $ mkName c) rest
#else
ClassP (mkName c) $ fmap nameToType rest
#endif
) appCxt'
mname <- lookupTypeName namestr mname <- lookupTypeName namestr
arity <- case mname of arity <- case mname of
Just name -> do Just name -> do
@ -105,10 +160,13 @@ mkYesodGeneral namestr args isSub f resS = do
vns <- replicateM (arity - length mtys) $ newName "t" vns <- replicateM (arity - length mtys) $ newName "t"
-- Base type (site type with variables) -- Base type (site type with variables)
let (argtypes,cxt) = (\(ns,r,cs) -> (ns ++ fmap VarT r, cs)) $ let (argtypes,cxt) = (\(ns,r,cs) -> (ns ++ fmap VarT r, cs)) $
foldr (\arg (xs,n:ns,cs) -> foldr (\arg (xs,vns',cs) ->
case arg of case arg of
Left t -> ( ConT (mkName t):xs, n:ns, cs ) Left t ->
Right ts -> ( VarT n :xs, ns ( nameToType t:xs, vns', cs )
Right ts ->
let (n, ns) = maybe (error "mkYesodGeneral: Should be unreachable.") id $ uncons vns' in
( VarT n : xs, ns
, fmap (\t -> , fmap (\t ->
#if MIN_VERSION_template_haskell(2,10,0) #if MIN_VERSION_template_haskell(2,10,0)
AppT (ConT $ mkName t) (VarT n) AppT (ConT $ mkName t) (VarT n)
@ -118,11 +176,11 @@ mkYesodGeneral namestr args isSub f resS = do
) ts ++ cs ) ) ts ++ cs )
) ([],vns,[]) args ) ([],vns,[]) args
site = foldl' AppT (ConT name) argtypes site = foldl' AppT (ConT name) argtypes
res = map (fmap parseType) resS res = map (fmap (parseType . dropBracket)) resS
renderRouteDec <- mkRenderRouteInstance site res renderRouteDec <- mkRenderRouteInstance' appCxt site res
routeAttrsDec <- mkRouteAttrsInstance site res routeAttrsDec <- mkRouteAttrsInstance' appCxt site res
dispatchDec <- mkDispatchInstance site cxt f res dispatchDec <- mkDispatchInstance site cxt f res
parse <- mkParseRouteInstance site res parseRoute <- mkParseRouteInstance' appCxt site res
let rname = mkName $ "resources" ++ namestr let rname = mkName $ "resources" ++ namestr
eres <- lift resS eres <- lift resS
let resourcesDec = let resourcesDec =
@ -130,7 +188,7 @@ mkYesodGeneral namestr args isSub f resS = do
, FunD rname [Clause [] (NormalB eres) []] , FunD rname [Clause [] (NormalB eres) []]
] ]
let dataDec = concat let dataDec = concat
[ [parse] [ [parseRoute]
, renderRouteDec , renderRouteDec
, [routeAttrsDec] , [routeAttrsDec]
, resourcesDec , resourcesDec
@ -138,6 +196,12 @@ mkYesodGeneral namestr args isSub f resS = do
] ]
return (dataDec, dispatchDec) return (dataDec, dispatchDec)
#if !MIN_VERSION_base(4,8,0)
where
uncons (h:t) = Just (h,t)
uncons _ = Nothing
#endif
mkMDS :: (Exp -> Q Exp) -> Q Exp -> MkDispatchSettings a site b mkMDS :: (Exp -> Q Exp) -> Q Exp -> MkDispatchSettings a site b
mkMDS f rh = MkDispatchSettings mkMDS f rh = MkDispatchSettings
{ mdsRunHandler = rh { mdsRunHandler = rh

View File

@ -10,10 +10,12 @@ module Yesod.Routes.Parse
, parseType , parseType
, parseTypeTree , parseTypeTree
, TypeTree (..) , TypeTree (..)
, dropBracket
, nameToType
) where ) where
import Language.Haskell.TH.Syntax import Language.Haskell.TH.Syntax
import Data.Char (isUpper, isSpace) import Data.Char (isUpper, isLower, isSpace)
import Language.Haskell.TH.Quote import Language.Haskell.TH.Quote
import qualified System.IO as SIO import qualified System.IO as SIO
import Yesod.Routes.TH import Yesod.Routes.TH
@ -252,14 +254,18 @@ toTypeTree orig = do
gos' (front . (t:)) xs' gos' (front . (t:)) xs'
ttToType :: TypeTree -> Type ttToType :: TypeTree -> Type
ttToType (TTTerm s) = ConT $ mkName s ttToType (TTTerm s) = nameToType s
ttToType (TTApp x y) = ttToType x `AppT` ttToType y ttToType (TTApp x y) = ttToType x `AppT` ttToType y
ttToType (TTList t) = ListT `AppT` ttToType t ttToType (TTList t) = ListT `AppT` ttToType t
nameToType :: String -> Type
nameToType t@(h:_) | isLower h = VarT $ mkName t
nameToType t = ConT $ mkName t
pieceFromString :: String -> Either (CheckOverlap, String) (CheckOverlap, Piece String) pieceFromString :: String -> Either (CheckOverlap, String) (CheckOverlap, Piece String)
pieceFromString ('#':'!':x) = Right $ (False, dynamicPieceFromString x) pieceFromString ('#':'!':x) = Right $ (False, Dynamic $ dropBracket x)
pieceFromString ('!':'#':x) = Right $ (False, dynamicPieceFromString x) -- https://github.com/yesodweb/yesod/issues/652 pieceFromString ('!':'#':x) = Right $ (False, Dynamic $ dropBracket x) -- https://github.com/yesodweb/yesod/issues/652
pieceFromString ('#':x) = Right $ (True, dynamicPieceFromString x) pieceFromString ('#':x) = Right $ (True, Dynamic $ dropBracket x)
pieceFromString ('*':'!':x) = Left (False, x) pieceFromString ('*':'!':x) = Left (False, x)
pieceFromString ('+':'!':x) = Left (False, x) pieceFromString ('+':'!':x) = Left (False, x)
@ -273,9 +279,9 @@ pieceFromString ('+':x) = Left (True, x)
pieceFromString ('!':x) = Right $ (False, Static x) pieceFromString ('!':x) = Right $ (False, Static x)
pieceFromString x = Right $ (True, Static x) pieceFromString x = Right $ (True, Static x)
dynamicPieceFromString :: String -> Piece String dropBracket :: String -> String
dynamicPieceFromString str@('{':x) = case break (== '}') x of dropBracket str@('{':x) = case break (== '}') x of
(s, "}") -> Dynamic s (s, "}") -> s
_ -> error $ "Invalid path piece: " ++ str _ -> error $ "Unclosed bracket ('{'): " ++ str
dynamicPieceFromString x = Dynamic x dropBracket x = x
-- JP: Should we check if there are curly brackets or other invalid characters?

View File

@ -3,6 +3,7 @@
module Yesod.Routes.TH.ParseRoute module Yesod.Routes.TH.ParseRoute
( -- ** ParseRoute ( -- ** ParseRoute
mkParseRouteInstance mkParseRouteInstance
, mkParseRouteInstance'
) where ) where
import Yesod.Routes.TH.Types import Yesod.Routes.TH.Types
@ -12,7 +13,10 @@ import Yesod.Routes.Class
import Yesod.Routes.TH.Dispatch import Yesod.Routes.TH.Dispatch
mkParseRouteInstance :: Type -> [ResourceTree a] -> Q Dec mkParseRouteInstance :: Type -> [ResourceTree a] -> Q Dec
mkParseRouteInstance typ ress = do mkParseRouteInstance = mkParseRouteInstance' []
mkParseRouteInstance' :: Cxt -> Type -> [ResourceTree a] -> Q Dec
mkParseRouteInstance' cxt typ ress = do
cls <- mkDispatchClause cls <- mkDispatchClause
MkDispatchSettings MkDispatchSettings
{ mdsRunHandler = [|\_ _ x _ -> x|] { mdsRunHandler = [|\_ _ x _ -> x|]
@ -28,7 +32,7 @@ mkParseRouteInstance typ ress = do
(map removeMethods ress) (map removeMethods ress)
helper <- newName "helper" helper <- newName "helper"
fixer <- [|(\f x -> f () x) :: (() -> ([Text], [(Text, Text)]) -> Maybe (Route a)) -> ([Text], [(Text, Text)]) -> Maybe (Route a)|] fixer <- [|(\f x -> f () x) :: (() -> ([Text], [(Text, Text)]) -> Maybe (Route a)) -> ([Text], [(Text, Text)]) -> Maybe (Route a)|]
return $ instanceD [] (ConT ''ParseRoute `AppT` typ) return $ instanceD cxt (ConT ''ParseRoute `AppT` typ)
[ FunD 'parseRoute $ return $ Clause [ FunD 'parseRoute $ return $ Clause
[] []
(NormalB $ fixer `AppE` VarE helper) (NormalB $ fixer `AppE` VarE helper)

View File

@ -12,6 +12,9 @@ import Yesod.Routes.TH.Types
import Language.Haskell.TH (conT) import Language.Haskell.TH (conT)
#endif #endif
import Language.Haskell.TH.Syntax import Language.Haskell.TH.Syntax
#if MIN_VERSION_template_haskell(2,11,0)
import Data.Bits (xor)
#endif
import Data.Maybe (maybeToList) import Data.Maybe (maybeToList)
import Control.Monad (replicateM) import Control.Monad (replicateM)
import Data.Text (pack) import Data.Text (pack)
@ -156,18 +159,28 @@ mkRenderRouteInstance' cxt typ ress = do
cls <- mkRenderRouteClauses ress cls <- mkRenderRouteClauses ress
(cons, decs) <- mkRouteCons ress (cons, decs) <- mkRouteCons ress
#if MIN_VERSION_template_haskell(2,12,0) #if MIN_VERSION_template_haskell(2,12,0)
did <- DataInstD [] ''Route [typ] Nothing cons <$> fmap (pure . DerivClause Nothing) (mapM conT clazzes) did <- DataInstD [] ''Route [typ] Nothing cons <$> fmap (pure . DerivClause Nothing) (mapM conT (clazzes False))
let sds = fmap (\t -> StandaloneDerivD cxt $ ConT t `AppT` ( ConT ''Route `AppT` typ)) (clazzes True)
#elif MIN_VERSION_template_haskell(2,11,0) #elif MIN_VERSION_template_haskell(2,11,0)
did <- DataInstD [] ''Route [typ] Nothing cons <$> mapM conT clazzes did <- DataInstD [] ''Route [typ] Nothing cons <$> mapM conT (clazzes False)
let sds = fmap (\t -> StandaloneDerivD cxt $ ConT t `AppT` ( ConT ''Route `AppT` typ)) (clazzes True)
#else #else
let did = DataInstD [] ''Route [typ] cons clazzes let did = DataInstD [] ''Route [typ] cons clazzes'
let sds = []
#endif #endif
return $ instanceD cxt (ConT ''RenderRoute `AppT` typ) return $ instanceD cxt (ConT ''RenderRoute `AppT` typ)
[ did [ did
, FunD (mkName "renderRoute") cls , FunD (mkName "renderRoute") cls
] : decs ]
: sds ++ decs
where where
clazzes = [''Show, ''Eq, ''Read] #if MIN_VERSION_template_haskell(2,11,0)
clazzes standalone = if standalone `xor` null cxt then
clazzes'
else
[]
#endif
clazzes' = [''Show, ''Eq, ''Read]
#if MIN_VERSION_template_haskell(2,11,0) #if MIN_VERSION_template_haskell(2,11,0)
notStrict :: Bang notStrict :: Bang

View File

@ -3,6 +3,7 @@
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards #-}
module Yesod.Routes.TH.RouteAttrs module Yesod.Routes.TH.RouteAttrs
( mkRouteAttrsInstance ( mkRouteAttrsInstance
, mkRouteAttrsInstance'
) where ) where
import Yesod.Routes.TH.Types import Yesod.Routes.TH.Types
@ -15,9 +16,12 @@ import Control.Applicative ((<$>))
#endif #endif
mkRouteAttrsInstance :: Type -> [ResourceTree a] -> Q Dec mkRouteAttrsInstance :: Type -> [ResourceTree a] -> Q Dec
mkRouteAttrsInstance typ ress = do mkRouteAttrsInstance = mkRouteAttrsInstance' []
mkRouteAttrsInstance' :: Cxt -> Type -> [ResourceTree a] -> Q Dec
mkRouteAttrsInstance' cxt typ ress = do
clauses <- mapM (goTree id) ress clauses <- mapM (goTree id) ress
return $ instanceD [] (ConT ''RouteAttrs `AppT` typ) return $ instanceD cxt (ConT ''RouteAttrs `AppT` typ)
[ FunD 'routeAttrs $ concat clauses [ FunD 'routeAttrs $ concat clauses
] ]

View File

@ -1,5 +1,5 @@
name: yesod-core name: yesod-core
version: 1.4.34 version: 1.4.35
license: MIT license: MIT
license-file: LICENSE license-file: LICENSE
author: Michael Snoyman <michael@snoyman.com> author: Michael Snoyman <michael@snoyman.com>