Move route parsing to yesod-routes

This commit is contained in:
Michael Snoyman 2012-01-05 05:38:31 +02:00
parent 03da3b021a
commit 1e8b15acff
7 changed files with 66 additions and 95 deletions

View File

@ -31,7 +31,7 @@ import Yesod.Handler hiding (lift)
import Yesod.Widget (GWidget) import Yesod.Widget (GWidget)
import Web.PathPieces import Web.PathPieces
import Yesod.Internal.RouteParsing (parseRoutes, parseRoutesNoCheck, parseRoutesFile, parseRoutesFileNoCheck) import Yesod.Routes.Parse (parseRoutes, parseRoutesNoCheck, parseRoutesFile, parseRoutesFileNoCheck)
import Language.Haskell.TH.Syntax import Language.Haskell.TH.Syntax
import qualified Network.Wai as W import qualified Network.Wai as W
@ -51,7 +51,7 @@ import qualified Blaze.ByteString.Builder
import Network.HTTP.Types (status301) import Network.HTTP.Types (status301)
import Yesod.Routes.TH import Yesod.Routes.TH
import Yesod.Content (chooseRep) import Yesod.Content (chooseRep)
import Yesod.Internal.RouteParsing import Yesod.Routes.Parse
type Texts = [Text] type Texts = [Text]
@ -59,7 +59,7 @@ type Texts = [Text]
-- is used for creating sites, /not/ subsites. See 'mkYesodSub' for the latter. -- is used for creating sites, /not/ subsites. See 'mkYesodSub' for the latter.
-- Use 'parseRoutes' to create the 'Resource's. -- Use 'parseRoutes' to create the 'Resource's.
mkYesod :: String -- ^ name of the argument datatype mkYesod :: String -- ^ name of the argument datatype
-> RouteString -> [Resource String]
-> Q [Dec] -> Q [Dec]
mkYesod name = fmap (uncurry (++)) . mkYesodGeneral name [] [] False mkYesod name = fmap (uncurry (++)) . mkYesodGeneral name [] [] False
@ -70,7 +70,7 @@ mkYesod name = fmap (uncurry (++)) . mkYesodGeneral name [] [] False
-- be embedded in other sites. -- be embedded in other sites.
mkYesodSub :: String -- ^ name of the argument datatype mkYesodSub :: String -- ^ name of the argument datatype
-> Cxt -> Cxt
-> RouteString -> [Resource String]
-> Q [Dec] -> Q [Dec]
mkYesodSub name clazzes = mkYesodSub name clazzes =
fmap (uncurry (++)) . mkYesodGeneral name' rest clazzes True fmap (uncurry (++)) . mkYesodGeneral name' rest clazzes True
@ -81,28 +81,28 @@ mkYesodSub name clazzes =
-- your handlers elsewhere. For example, this is the only way to break up a -- your handlers elsewhere. For example, this is the only way to break up a
-- monolithic file into smaller parts. Use this function, paired with -- monolithic file into smaller parts. Use this function, paired with
-- 'mkYesodDispatch', to do just that. -- 'mkYesodDispatch', to do just that.
mkYesodData :: String -> RouteString -> Q [Dec] mkYesodData :: String -> [Resource String] -> Q [Dec]
mkYesodData name res = mkYesodDataGeneral name [] False res mkYesodData name res = mkYesodDataGeneral name [] False res
mkYesodSubData :: String -> Cxt -> RouteString -> Q [Dec] mkYesodSubData :: String -> Cxt -> [Resource String] -> Q [Dec]
mkYesodSubData name clazzes res = mkYesodDataGeneral name clazzes True res mkYesodSubData name clazzes res = mkYesodDataGeneral name clazzes True res
mkYesodDataGeneral :: String -> Cxt -> Bool -> RouteString -> Q [Dec] mkYesodDataGeneral :: String -> Cxt -> Bool -> [Resource String] -> Q [Dec]
mkYesodDataGeneral name clazzes isSub res = do mkYesodDataGeneral name clazzes isSub res = do
let (name':rest) = words name let (name':rest) = words name
(x, _) <- mkYesodGeneral name' rest clazzes isSub res (x, _) <- mkYesodGeneral name' rest clazzes isSub res
let rname = mkName $ "resources" ++ name let rname = mkName $ "resources" ++ name
eres <- [|parseRouteString $(lift res)|] eres <- [|fmap parseType $(lift res)|]
let y = [ SigD rname $ ListT `AppT` ConT ''Resource let y = [ SigD rname $ ListT `AppT` ConT ''Resource
, FunD rname [Clause [] (NormalB eres) []] , FunD rname [Clause [] (NormalB eres) []]
] ]
return $ x ++ y return $ x ++ y
-- | See 'mkYesodData'. -- | See 'mkYesodData'.
mkYesodDispatch :: String -> RouteString -> Q [Dec] mkYesodDispatch :: String -> [Resource String] -> Q [Dec]
mkYesodDispatch name = fmap snd . mkYesodGeneral name [] [] False mkYesodDispatch name = fmap snd . mkYesodGeneral name [] [] False
mkYesodSubDispatch :: String -> Cxt -> RouteString -> Q [Dec] mkYesodSubDispatch :: String -> Cxt -> [Resource String] -> Q [Dec]
mkYesodSubDispatch name clazzes = fmap snd . mkYesodGeneral name' rest clazzes True mkYesodSubDispatch name clazzes = fmap snd . mkYesodGeneral name' rest clazzes True
where (name':rest) = words name where (name':rest) = words name
@ -110,10 +110,10 @@ mkYesodGeneral :: String -- ^ foundation name
-> [String] -- ^ parameters for foundation -> [String] -- ^ parameters for foundation
-> Cxt -- ^ classes -> Cxt -- ^ classes
-> Bool -- ^ is subsite? -> Bool -- ^ is subsite?
-> RouteString -> [Resource String]
-> Q ([Dec], [Dec]) -> Q ([Dec], [Dec])
mkYesodGeneral name args clazzes isSub resS = do mkYesodGeneral name args clazzes isSub resS = do
let res = parseRouteString resS let res = map (fmap parseType) resS
renderRouteDec <- mkRenderRouteInstance (ConT name') res renderRouteDec <- mkRenderRouteInstance (ConT name') res
disp <- mkDispatchClause [|yesodRunner|] [|yesodDispatch|] [|fmap chooseRep|] res disp <- mkDispatchClause [|yesodRunner|] [|yesodDispatch|] [|fmap chooseRep|] res

View File

@ -95,7 +95,6 @@ library
Yesod.Internal.Core Yesod.Internal.Core
Yesod.Internal.Session Yesod.Internal.Session
Yesod.Internal.Request Yesod.Internal.Request
Yesod.Internal.RouteParsing
Paths_yesod_core Paths_yesod_core
ghc-options: -Wall ghc-options: -Wall

View File

@ -1,13 +1,12 @@
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveDataTypeable #-}
{-# OPTIONS_GHC -fno-warn-missing-fields #-} -- QuasiQuoter {-# OPTIONS_GHC -fno-warn-missing-fields #-} -- QuasiQuoter
module Yesod.Internal.RouteParsing module Yesod.Routes.Parse
( parseRoutes ( parseRoutes
, parseRoutesFile , parseRoutesFile
, parseRoutesNoCheck , parseRoutesNoCheck
, parseRoutesFileNoCheck , parseRoutesFileNoCheck
, RouteString , parseType
, parseRouteString
) where ) where
import Web.PathPieces import Web.PathPieces
@ -33,20 +32,9 @@ parseRoutes = QuasiQuoter
x s = do x s = do
let res = resourcesFromString s let res = resourcesFromString s
case findOverlaps res of case findOverlaps res of
[] -> liftParse s [] -> lift res
z -> error $ "Overlapping routes: " ++ unlines (map (unwords . map resourceName) z) z -> error $ "Overlapping routes: " ++ unlines (map (unwords . map resourceName) z)
newtype RouteString = RouteString String
liftParse :: String -> Q Exp
liftParse s = [|RouteString s|]
parseRouteString :: RouteString -> [Resource]
parseRouteString (RouteString s) = resourcesFromString s
instance Lift RouteString where
lift (RouteString s) = [|RouteString $(lift s)|]
parseRoutesFile :: FilePath -> Q Exp parseRoutesFile :: FilePath -> Q Exp
parseRoutesFile fp = do parseRoutesFile fp = do
s <- qRunIO $ readUtf8File fp s <- qRunIO $ readUtf8File fp
@ -66,13 +54,13 @@ readUtf8File fp = do
-- | Same as 'parseRoutes', but performs no overlap checking. -- | Same as 'parseRoutes', but performs no overlap checking.
parseRoutesNoCheck :: QuasiQuoter parseRoutesNoCheck :: QuasiQuoter
parseRoutesNoCheck = QuasiQuoter parseRoutesNoCheck = QuasiQuoter
{ quoteExp = liftParse { quoteExp = lift . resourcesFromString
} }
-- | Convert a multi-line string to a set of resources. See documentation for -- | 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 -- the format of this string. This is a partial function which calls 'error' on
-- invalid input. -- invalid input.
resourcesFromString :: String -> [Resource] resourcesFromString :: String -> [Resource String]
resourcesFromString = resourcesFromString =
mapMaybe go . lines mapMaybe go . lines
where where
@ -85,12 +73,12 @@ resourcesFromString =
[] -> Nothing [] -> Nothing
_ -> error $ "Invalid resource line: " ++ s _ -> error $ "Invalid resource line: " ++ s
dispatchFromString :: [String] -> Maybe Type -> Dispatch dispatchFromString :: [String] -> Maybe String -> Dispatch String
dispatchFromString rest mmulti dispatchFromString rest mmulti
| null rest = Methods mmulti [] | null rest = Methods mmulti []
| all (all isUpper) rest = Methods mmulti rest | all (all isUpper) rest = Methods mmulti rest
dispatchFromString [subTyp, subFun] Nothing = dispatchFromString [subTyp, subFun] Nothing =
Subsite (parseType subTyp) subFun Subsite subTyp subFun
dispatchFromString [subTyp, subFun] Just{} = dispatchFromString [subTyp, subFun] Just{} =
error "Subsites cannot have a multipiece" error "Subsites cannot have a multipiece"
dispatchFromString rest _ = error $ "Invalid list of methods: " ++ show rest dispatchFromString rest _ = error $ "Invalid list of methods: " ++ show rest
@ -99,7 +87,7 @@ drop1Slash :: String -> String
drop1Slash ('/':x) = x drop1Slash ('/':x) = x
drop1Slash x = x drop1Slash x = x
piecesFromString :: String -> ([Piece], Maybe Type) piecesFromString :: String -> ([Piece String], Maybe String)
piecesFromString "" = ([], Nothing) piecesFromString "" = ([], Nothing)
piecesFromString x = piecesFromString x =
case (this, rest) of case (this, rest) of
@ -114,23 +102,23 @@ piecesFromString x =
parseType :: String -> Type parseType :: String -> Type
parseType = ConT . mkName -- FIXME handle more complicated stuff parseType = ConT . mkName -- FIXME handle more complicated stuff
pieceFromString :: String -> Either Type Piece pieceFromString :: String -> Either String (Piece String)
pieceFromString ('#':x) = Right $ Dynamic $ parseType x pieceFromString ('#':x) = Right $ Dynamic x
pieceFromString ('*':x) = Left $ parseType x pieceFromString ('*':x) = Left x
pieceFromString x = Right $ Static x pieceFromString x = Right $ Static x
-- n^2, should be a way to speed it up -- n^2, should be a way to speed it up
findOverlaps :: [Resource] -> [[Resource]] findOverlaps :: [Resource a] -> [[Resource a]]
findOverlaps = go . map justPieces findOverlaps = go . map justPieces
where where
justPieces :: Resource -> ([Piece], Resource) justPieces :: Resource a -> ([Piece a], Resource a)
justPieces r@(Resource _ ps _) = (ps, r) justPieces r@(Resource _ ps _) = (ps, r)
go [] = [] go [] = []
go (x:xs) = mapMaybe (mOverlap x) xs ++ go xs go (x:xs) = mapMaybe (mOverlap x) xs ++ go xs
mOverlap :: ([Piece], Resource) -> ([Piece], Resource) -> mOverlap :: ([Piece a], Resource a) -> ([Piece a], Resource a) ->
Maybe [Resource] Maybe [Resource a]
mOverlap _ _ = Nothing mOverlap _ _ = Nothing
{- FIXME mOverlap {- FIXME mOverlap
mOverlap (Static x:xs, xr) (Static y:ys, yr) mOverlap (Static x:xs, xr) (Static y:ys, yr)

View File

@ -67,7 +67,7 @@ import Data.List (foldl')
mkDispatchClause :: Q Exp -- ^ runHandler function mkDispatchClause :: Q Exp -- ^ runHandler function
-> Q Exp -- ^ dispatcher function -> Q Exp -- ^ dispatcher function
-> Q Exp -- ^ fixHandler function -> Q Exp -- ^ fixHandler function
-> [Resource] -> [Resource a]
-> Q Clause -> Q Clause
mkDispatchClause runHandler dispatcher fixHandler ress = do mkDispatchClause runHandler dispatcher fixHandler ress = do
-- Allocate the names to be used. Start off with the names passed to the -- Allocate the names to be used. Start off with the names passed to the
@ -120,7 +120,7 @@ methodMapName :: String -> Name
methodMapName s = mkName $ "methods" ++ s methodMapName s = mkName $ "methods" ++ s
buildMethodMap :: Q Exp -- ^ fixHandler buildMethodMap :: Q Exp -- ^ fixHandler
-> Resource -> Resource a
-> Q (Maybe Dec) -> Q (Maybe Dec)
buildMethodMap _ (Resource _ _ (Methods _ [])) = return Nothing -- single handle function buildMethodMap _ (Resource _ _ (Methods _ [])) = return Nothing -- single handle function
buildMethodMap fixHandler (Resource name pieces (Methods mmulti methods)) = do buildMethodMap fixHandler (Resource name pieces (Methods mmulti methods)) = do
@ -143,7 +143,7 @@ buildMethodMap fixHandler (Resource name pieces (Methods mmulti methods)) = do
buildMethodMap _ (Resource _ _ Subsite{}) = return Nothing buildMethodMap _ (Resource _ _ Subsite{}) = return Nothing
-- | Build a single 'D.Route' expression. -- | Build a single 'D.Route' expression.
buildRoute :: Q Exp -> Q Exp -> Q Exp -> Resource -> Q Exp buildRoute :: Q Exp -> Q Exp -> Q Exp -> Resource a -> Q Exp
buildRoute runHandler dispatcher fixHandler (Resource name resPieces resDisp) = do buildRoute runHandler dispatcher fixHandler (Resource name resPieces resDisp) = do
-- First two arguments to D.Route -- First two arguments to D.Route
routePieces <- ListE <$> mapM convertPiece resPieces routePieces <- ListE <$> mapM convertPiece resPieces
@ -158,8 +158,8 @@ routeArg3 :: Q Exp -- ^ runHandler
-> Q Exp -- ^ dispatcher -> Q Exp -- ^ dispatcher
-> Q Exp -- ^ fixHandler -> Q Exp -- ^ fixHandler
-> String -- ^ name of resource -> String -- ^ name of resource
-> [Piece] -> [Piece a]
-> Dispatch -> Dispatch a
-> Q Exp -> Q Exp
routeArg3 runHandler dispatcher fixHandler name resPieces resDisp = do routeArg3 runHandler dispatcher fixHandler name resPieces resDisp = do
pieces <- newName "pieces" pieces <- newName "pieces"
@ -224,7 +224,7 @@ buildCaller :: Q Exp -- ^ runHandler
-> Q Exp -- ^ fixHandler -> Q Exp -- ^ fixHandler
-> Name -- ^ xrest -> Name -- ^ xrest
-> String -- ^ name of resource -> String -- ^ name of resource
-> Dispatch -> Dispatch a
-> [Name] -- ^ ys -> [Name] -- ^ ys
-> Q Exp -> Q Exp
buildCaller runHandler dispatcher fixHandler xrest name resDisp ys = do buildCaller runHandler dispatcher fixHandler xrest name resDisp ys = do
@ -290,6 +290,6 @@ buildCaller runHandler dispatcher fixHandler xrest name resDisp ys = do
return $ LamE pat exp return $ LamE pat exp
-- | Convert a 'Piece' to a 'D.Piece' -- | Convert a 'Piece' to a 'D.Piece'
convertPiece :: Piece -> Q Exp convertPiece :: Piece a -> Q Exp
convertPiece (Static s) = [|D.Static (pack $(lift s))|] convertPiece (Static s) = [|D.Static (pack $(lift s))|]
convertPiece (Dynamic _) = [|D.Dynamic|] convertPiece (Dynamic _) = [|D.Dynamic|]

View File

@ -15,7 +15,7 @@ import Web.PathPieces (PathPiece (..), PathMultiPiece (..))
import Yesod.Routes.Class import Yesod.Routes.Class
-- | Generate the constructors of a route data type. -- | Generate the constructors of a route data type.
mkRouteCons :: [Resource] -> [Con] mkRouteCons :: [Resource Type] -> [Con]
mkRouteCons = mkRouteCons =
map mkRouteCon map mkRouteCon
where where
@ -36,7 +36,7 @@ mkRouteCons =
_ -> [] _ -> []
-- | Clauses for the 'renderRoute' method. -- | Clauses for the 'renderRoute' method.
mkRenderRouteClauses :: [Resource] -> Q [Clause] mkRenderRouteClauses :: [Resource Type] -> Q [Clause]
mkRenderRouteClauses = mkRenderRouteClauses =
mapM go mapM go
where where
@ -91,7 +91,7 @@ mkRenderRouteClauses =
-- --
-- This includes both the 'Route' associated type and the 'renderRoute' method. -- This includes both the 'Route' associated type and the 'renderRoute' method.
-- This function uses both 'mkRouteCons' and 'mkRenderRouteClasses'. -- This function uses both 'mkRouteCons' and 'mkRenderRouteClasses'.
mkRenderRouteInstance :: Type -> [Resource] -> Q Dec mkRenderRouteInstance :: Type -> [Resource Type] -> Q Dec
mkRenderRouteInstance typ ress = do mkRenderRouteInstance typ ress = do
cls <- mkRenderRouteClauses ress cls <- mkRenderRouteClauses ress
return $ InstanceD [] (ConT ''RenderRoute `AppT` typ) return $ InstanceD [] (ConT ''RenderRoute `AppT` typ)

View File

@ -10,67 +10,50 @@ module Yesod.Routes.TH.Types
import Language.Haskell.TH.Syntax import Language.Haskell.TH.Syntax
liftOccName :: OccName -> Q Exp data Resource typ = Resource
liftOccName oc = [|mkOccName $(lift $ occString oc)|]
liftNameFlavour :: NameFlavour -> Q Exp
liftNameFlavour NameS = [|NameS|]
liftName :: Name -> Q Exp
liftName (Name a b) = [|Name $(liftOccName a) $(liftNameFlavour b)|]
liftType :: Type -> Q Exp
liftType (VarT name) = [|VarT $(liftName name)|]
liftType (ConT name) = [|ConT $(liftName name)|]
liftType (TupleT i) = [|TupleT $(lift i)|]
liftType ArrowT = [|ArrowT|]
liftType ListT = [|ListT|]
liftType (AppT a b) = [|AppT $(liftType a) $(liftType b)|]
liftType (SigT a b) = [|SigT $(liftType a) $(liftKind b)|]
liftKind :: Kind -> Q Exp
liftKind StarK = [|StarK|]
liftKind (ArrowK a b) = [|ArrowK $(liftKind a) $(liftKind b)|]
data Resource = Resource
{ resourceName :: String { resourceName :: String
, resourcePieces :: [Piece] , resourcePieces :: [Piece typ]
, resourceDispatch :: Dispatch , resourceDispatch :: Dispatch typ
} }
deriving Show deriving Show
{- instance Functor Resource where
instance Lift Resource where fmap f (Resource a b c) = Resource a (map (fmap f) b) (fmap f c)
lift (Resource a b c) = [|Resource $(lift a) $(lift b) $(lift c)|]
-}
data Piece = Static String | Dynamic Type instance Lift t => Lift (Resource t) where
lift (Resource a b c) = [|Resource $(lift a) $(lift b) $(lift c)|]
data Piece typ = Static String | Dynamic typ
deriving Show deriving Show
{- instance Functor Piece where
instance Lift Piece where fmap _ (Static s) = (Static s)
lift (Static s) = [|Static $(lift s)|] fmap f (Dynamic t) = Dynamic (f t)
lift (Dynamic t) = [|Static $(liftType t)|]
-}
data Dispatch = instance Lift t => Lift (Piece t) where
lift (Static s) = [|Static $(lift s)|]
lift (Dynamic t) = [|Dynamic $(lift t)|]
data Dispatch typ =
Methods Methods
{ methodsMulti :: Maybe Type -- ^ type of the multi piece at the end { methodsMulti :: Maybe typ -- ^ type of the multi piece at the end
, methodsMethods :: [String] -- ^ supported request methods , methodsMethods :: [String] -- ^ supported request methods
} }
| Subsite | Subsite
{ subsiteType :: Type { subsiteType :: typ
, subsiteFunc :: String , subsiteFunc :: String
} }
deriving Show deriving Show
{- instance Functor Dispatch where
instance Lift Dispatch where fmap f (Methods a b) = Methods (fmap f a) b
lift (Methods Nothing b) = [|Methods Nothing $(lift b)|] fmap f (Subsite a b) = Subsite (f a) b
lift (Methods (Just t) b) = [|Methods (Just $(liftType t)) $(lift b)|]
lift (Subsite t b) = [|Subsite $(liftType t) $(lift b)|]
-}
resourceMulti :: Resource -> Maybe Type 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 Resource { resourceDispatch = Methods (Just t) _ } = Just t
resourceMulti _ = Nothing resourceMulti _ = Nothing

View File

@ -22,6 +22,7 @@ library
exposed-modules: Yesod.Routes.Dispatch exposed-modules: Yesod.Routes.Dispatch
Yesod.Routes.TH Yesod.Routes.TH
Yesod.Routes.Class Yesod.Routes.Class
Yesod.Routes.Parse
other-modules: Yesod.Routes.TH.Dispatch other-modules: Yesod.Routes.TH.Dispatch
Yesod.Routes.TH.RenderRoute Yesod.Routes.TH.RenderRoute
Yesod.Routes.TH.Types Yesod.Routes.TH.Types