179 lines
5.9 KiB
Haskell
179 lines
5.9 KiB
Haskell
{-# LANGUAGE TemplateHaskell #-}
|
|
module Yesod.Routes.TH.ParseRoute
|
|
( -- ** ParseRoute
|
|
mkParseRouteInstance
|
|
) where
|
|
|
|
import Yesod.Routes.TH.Types
|
|
import Language.Haskell.TH.Syntax
|
|
import Data.Text (pack)
|
|
import Web.PathPieces (PathPiece (..), PathMultiPiece (..))
|
|
import Yesod.Routes.Class
|
|
import qualified Yesod.Routes.Dispatch as D
|
|
import Data.List (foldl')
|
|
import Control.Applicative ((<$>))
|
|
import Data.Maybe (catMaybes)
|
|
import Control.Monad (forM)
|
|
import Control.Monad (join)
|
|
|
|
-- | Clauses for the 'parseRoute' method.
|
|
mkParseRouteClauses :: [ResourceTree a] -> Q [Clause]
|
|
mkParseRouteClauses ress' = do
|
|
pieces <- newName "pieces0"
|
|
dispatch <- newName "dispatch"
|
|
query <- newName "_query"
|
|
|
|
-- The 'D.Route's used in the dispatch function
|
|
routes <- mapM (buildRoute query) ress
|
|
|
|
-- The dispatch function itself
|
|
toDispatch <- [|D.toDispatch|]
|
|
let dispatchFun = FunD dispatch
|
|
[Clause
|
|
[]
|
|
(NormalB $ toDispatch `AppE` ListE routes)
|
|
[]
|
|
]
|
|
|
|
join' <- [|join|]
|
|
let body = join' `AppE` (VarE dispatch `AppE` VarE pieces)
|
|
return $ return $ Clause
|
|
[TupP [VarP pieces, VarP query]]
|
|
(NormalB body)
|
|
[dispatchFun]
|
|
where
|
|
ress = map noMethods $ flatten ress'
|
|
noMethods (FlatResource a b c d) = FlatResource a b c $ noMethods' d
|
|
noMethods' (Methods a _) = Methods a []
|
|
noMethods' (Subsite a b) = Subsite a b
|
|
|
|
mkParseRouteInstance :: Type -> [ResourceTree a] -> Q Dec
|
|
mkParseRouteInstance typ ress = do
|
|
cls <- mkParseRouteClauses ress
|
|
return $ InstanceD [] (ConT ''ParseRoute `AppT` typ)
|
|
[ FunD 'parseRoute cls
|
|
]
|
|
|
|
-- | Build a single 'D.Route' expression.
|
|
buildRoute :: Name -> FlatResource a -> Q Exp
|
|
buildRoute query (FlatResource parents name resPieces resDisp) = do
|
|
-- First two arguments to D.Route
|
|
routePieces <- ListE <$> mapM (convertPiece . snd) allPieces
|
|
isMulti <-
|
|
case resDisp of
|
|
Methods Nothing _ -> [|False|]
|
|
_ -> [|True|]
|
|
|
|
[|D.Route
|
|
$(return routePieces)
|
|
$(return isMulti)
|
|
$(routeArg3
|
|
query
|
|
parents
|
|
name
|
|
(map snd allPieces)
|
|
resDisp)
|
|
|]
|
|
where
|
|
allPieces = concat $ map snd parents ++ [resPieces]
|
|
|
|
routeArg3 :: Name -- ^ query string parameters
|
|
-> [(String, [(CheckOverlap, Piece a)])]
|
|
-> String -- ^ name of resource
|
|
-> [Piece a]
|
|
-> Dispatch a
|
|
-> Q Exp
|
|
routeArg3 query parents name resPieces resDisp = do
|
|
pieces <- newName "pieces"
|
|
|
|
-- Allocate input piece variables (xs) and variables that have been
|
|
-- converted via fromPathPiece (ys)
|
|
xs <- forM resPieces $ \piece ->
|
|
case piece of
|
|
Static _ -> return Nothing
|
|
Dynamic _ -> Just <$> newName "x"
|
|
|
|
-- Note: the zipping with Ints is just a workaround for (apparently) a bug
|
|
-- in GHC where the identifiers are considered to be overlapping. Using
|
|
-- newName should avoid the problem, but it doesn't.
|
|
ys <- forM (zip (catMaybes xs) [1..]) $ \(x, i) -> do
|
|
y <- newName $ "y" ++ show (i :: Int)
|
|
return (x, y)
|
|
|
|
-- In case we have multi pieces at the end
|
|
xrest <- newName "xrest"
|
|
yrest <- newName "yrest"
|
|
|
|
-- Determine the pattern for matching the pieces
|
|
pat <-
|
|
case resDisp of
|
|
Methods Nothing _ -> return $ ListP $ map (maybe WildP VarP) xs
|
|
_ -> do
|
|
let cons = mkName ":"
|
|
return $ foldr (\a b -> ConP cons [maybe WildP VarP a, b]) (VarP xrest) xs
|
|
|
|
-- Convert the xs
|
|
fromPathPiece' <- [|fromPathPiece|]
|
|
xstmts <- forM ys $ \(x, y) -> return $ BindS (VarP y) (fromPathPiece' `AppE` VarE x)
|
|
|
|
-- Convert the xrest if appropriate
|
|
(reststmts, yrest') <-
|
|
case resDisp of
|
|
Methods (Just _) _ -> do
|
|
fromPathMultiPiece' <- [|fromPathMultiPiece|]
|
|
return ([BindS (VarP yrest) (fromPathMultiPiece' `AppE` VarE xrest)], [yrest])
|
|
_ -> return ([], [])
|
|
|
|
-- The final expression that actually uses the values we've computed
|
|
caller <- buildCaller query xrest parents name resDisp $ map snd ys ++ yrest'
|
|
|
|
-- Put together all the statements
|
|
just <- [|Just|]
|
|
let stmts = concat
|
|
[ xstmts
|
|
, reststmts
|
|
, [NoBindS $ just `AppE` caller]
|
|
]
|
|
|
|
errorMsg <- [|error "Invariant violated"|]
|
|
let matches =
|
|
[ Match pat (NormalB $ DoE stmts) []
|
|
, Match WildP (NormalB errorMsg) []
|
|
]
|
|
|
|
return $ LamE [VarP pieces] $ CaseE (VarE pieces) matches
|
|
|
|
-- | The final expression in the individual Route definitions.
|
|
buildCaller :: Name -- ^ query string parameters
|
|
-> Name -- ^ xrest
|
|
-> [(String, [(CheckOverlap, Piece a)])]
|
|
-> String -- ^ name of resource
|
|
-> Dispatch a
|
|
-> [Name] -- ^ ys
|
|
-> Q Exp
|
|
buildCaller query xrest parents name resDisp ys = do
|
|
-- Create the route
|
|
let route = routeFromDynamics parents name ys
|
|
|
|
case resDisp of
|
|
Methods _ _ -> [|Just $(return route)|]
|
|
Subsite _ _ -> [|fmap $(return route) $ parseRoute ($(return $ VarE xrest), $(return $ VarE query))|]
|
|
|
|
-- | Convert a 'Piece' to a 'D.Piece'
|
|
convertPiece :: Piece a -> Q Exp
|
|
convertPiece (Static s) = [|D.Static (pack $(lift s))|]
|
|
convertPiece (Dynamic _) = [|D.Dynamic|]
|
|
|
|
routeFromDynamics :: [(String, [(CheckOverlap, Piece a)])] -- ^ parents
|
|
-> String -- ^ constructor name
|
|
-> [Name]
|
|
-> Exp
|
|
routeFromDynamics [] name ys = foldl' (\a b -> a `AppE` VarE b) (ConE $ mkName name) ys
|
|
routeFromDynamics ((parent, pieces):rest) name ys =
|
|
foldl' (\a b -> a `AppE` b) (ConE $ mkName parent) here
|
|
where
|
|
(here', ys') = splitAt (length $ filter (isDynamic . snd) pieces) ys
|
|
isDynamic Dynamic{} = True
|
|
isDynamic _ = False
|
|
here = map VarE here' ++ [routeFromDynamics rest name ys']
|