web-routes-quasi 0.4.0 → 0.5.0
raw patch · 6 files changed
+416/−712 lines, 6 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Web.Routes.Quasi: BlankArgs :: BlankArgs routes
- Web.Routes.Quasi: ByMethod :: [(String, String)] -> Handler
- Web.Routes.Quasi: MultiPiece :: String -> Piece
- Web.Routes.Quasi: QuasiSite :: QuasiDispatch app sub master -> (Routes sub -> [String]) -> ([String] -> Either String (Routes sub)) -> QuasiSite app sub master
- Web.Routes.Quasi: QuasiSiteDecs :: Dec -> Dec -> Dec -> Dec -> QuasiSiteDecs
- Web.Routes.Quasi: QuasiSiteSettings :: Name -> Type -> Type -> Exp -> [Resource] -> Name -> Either Type [(String, [Name])] -> QuasiSiteSettings
- Web.Routes.Quasi: Resource :: String -> [Piece] -> Handler -> Resource
- Web.Routes.Quasi: Single :: String -> Handler
- Web.Routes.Quasi: SinglePiece :: String -> Piece
- Web.Routes.Quasi: StaticPiece :: String -> Piece
- Web.Routes.Quasi: SubSite :: String -> String -> String -> Handler
- Web.Routes.Quasi: crApplication :: QuasiSiteSettings -> Type
- Web.Routes.Quasi: crArgument :: QuasiSiteSettings -> Type
- Web.Routes.Quasi: crExplode :: QuasiSiteSettings -> Exp
- Web.Routes.Quasi: crMaster :: QuasiSiteSettings -> Either Type [(String, [Name])]
- Web.Routes.Quasi: crResources :: QuasiSiteSettings -> [Resource]
- Web.Routes.Quasi: crRoutes :: QuasiSiteSettings -> Name
- Web.Routes.Quasi: crSite :: QuasiSiteSettings -> Name
- Web.Routes.Quasi: createQuasiDispatch :: QuasiSiteSettings -> Q [Clause]
- Web.Routes.Quasi: createQuasiSite :: QuasiSiteSettings -> Q QuasiSiteDecs
- Web.Routes.Quasi: createQuasiSite' :: QuasiSiteSettings -> Q [Dec]
- Web.Routes.Quasi: data BlankArgs routes
- Web.Routes.Quasi: data Handler
- Web.Routes.Quasi: data Piece
- Web.Routes.Quasi: data QuasiSite app sub master
- Web.Routes.Quasi: data QuasiSiteDecs
- Web.Routes.Quasi: data QuasiSiteSettings
- Web.Routes.Quasi: data Resource
- Web.Routes.Quasi: decRoutes :: QuasiSiteDecs -> Dec
- Web.Routes.Quasi: decRoutesSyn :: QuasiSiteDecs -> Dec
- Web.Routes.Quasi: decSite :: QuasiSiteDecs -> Dec
- Web.Routes.Quasi: decSiteType :: QuasiSiteDecs -> Dec
- Web.Routes.Quasi: instance Data Handler
- Web.Routes.Quasi: instance Data Piece
- Web.Routes.Quasi: instance Data Resource
- Web.Routes.Quasi: instance Eq Handler
- Web.Routes.Quasi: instance Eq Piece
- Web.Routes.Quasi: instance Eq Resource
- Web.Routes.Quasi: instance MultiPiece [String]
- Web.Routes.Quasi: instance Read Handler
- Web.Routes.Quasi: instance Read Piece
- Web.Routes.Quasi: instance Read Resource
- Web.Routes.Quasi: instance Show Handler
- Web.Routes.Quasi: instance Show Piece
- Web.Routes.Quasi: instance Show Resource
- Web.Routes.Quasi: instance SinglePiece Int
- Web.Routes.Quasi: instance SinglePiece Int64
- Web.Routes.Quasi: instance SinglePiece Integer
- Web.Routes.Quasi: instance SinglePiece String
- Web.Routes.Quasi: instance Typeable Handler
- Web.Routes.Quasi: instance Typeable Piece
- Web.Routes.Quasi: instance Typeable Resource
- Web.Routes.Quasi: liftResources :: [Resource] -> Q Exp
- Web.Routes.Quasi: quasiDispatch :: QuasiSite app sub master -> QuasiDispatch app sub master
- Web.Routes.Quasi: quasiFromSite :: Site surl app -> QuasiSite app (BlankArgs surl) master
- Web.Routes.Quasi: quasiParse :: QuasiSite app sub master -> [String] -> Either String (Routes sub)
- Web.Routes.Quasi: quasiRender :: QuasiSite app sub master -> Routes sub -> [String]
- Web.Routes.Quasi: quasiToSite :: QuasiSite app sub sub -> ((String -> app) -> app) -> app -> sub -> Site (Routes sub) app
- Web.Routes.Quasi: type QuasiDispatch app sub master = (Routes master -> String) -> Routes sub -> (Routes sub -> Routes master) -> master -> (master -> sub) -> app -> String -> app
+ Web.Routes.Quasi: createRoutes :: [THResource] -> Q [Con]
+ Web.Routes.Quasi.Classes: class MultiPiece s
+ Web.Routes.Quasi.Classes: class SinglePiece s
+ Web.Routes.Quasi.Classes: fromMultiPiece :: (MultiPiece s) => [String] -> Either String s
+ Web.Routes.Quasi.Classes: fromSinglePiece :: (SinglePiece s) => String -> Either String s
+ Web.Routes.Quasi.Classes: instance MultiPiece [String]
+ Web.Routes.Quasi.Classes: instance SinglePiece Int
+ Web.Routes.Quasi.Classes: instance SinglePiece Int64
+ Web.Routes.Quasi.Classes: instance SinglePiece Integer
+ Web.Routes.Quasi.Classes: instance SinglePiece String
+ Web.Routes.Quasi.Classes: toMultiPiece :: (MultiPiece s) => s -> [String]
+ Web.Routes.Quasi.Classes: toSinglePiece :: (SinglePiece s) => s -> String
+ Web.Routes.Quasi.Classes: type Strings = [String]
+ Web.Routes.Quasi.Parse: MultiPiece :: String -> Piece
+ Web.Routes.Quasi.Parse: Resource :: String -> [Piece] -> [String] -> Resource
+ Web.Routes.Quasi.Parse: SinglePiece :: String -> Piece
+ Web.Routes.Quasi.Parse: StaticPiece :: String -> Piece
+ Web.Routes.Quasi.Parse: data Piece
+ Web.Routes.Quasi.Parse: data Resource
+ Web.Routes.Quasi.Parse: instance Data Piece
+ Web.Routes.Quasi.Parse: instance Data Resource
+ Web.Routes.Quasi.Parse: instance Eq Piece
+ Web.Routes.Quasi.Parse: instance Eq Resource
+ Web.Routes.Quasi.Parse: instance Lift Piece
+ Web.Routes.Quasi.Parse: instance Lift Resource
+ Web.Routes.Quasi.Parse: instance Read Piece
+ Web.Routes.Quasi.Parse: instance Read Resource
+ Web.Routes.Quasi.Parse: instance Show Piece
+ Web.Routes.Quasi.Parse: instance Show Resource
+ Web.Routes.Quasi.Parse: instance Typeable Piece
+ Web.Routes.Quasi.Parse: instance Typeable Resource
+ Web.Routes.Quasi.Parse: parseRoutes :: QuasiQuoter
+ Web.Routes.Quasi.Parse: parseRoutesNoCheck :: QuasiQuoter
+ Web.Routes.Quasi.TH: Simple :: [Piece] -> [String] -> Pieces
+ Web.Routes.Quasi.TH: SubSite :: Type -> Exp -> Exp -> Exp -> Exp -> [String] -> Pieces
+ Web.Routes.Quasi.TH: createDispatch :: Exp -> Exp -> [THResource] -> Q [Clause]
+ Web.Routes.Quasi.TH: createParse :: [THResource] -> Q [Clause]
+ Web.Routes.Quasi.TH: createRender :: [THResource] -> Q [Clause]
+ Web.Routes.Quasi.TH: createRoutes :: [THResource] -> Q [Con]
+ Web.Routes.Quasi.TH: data Pieces
+ Web.Routes.Quasi.TH: ssDispatch :: Pieces -> Exp
+ Web.Routes.Quasi.TH: ssParse :: Pieces -> Exp
+ Web.Routes.Quasi.TH: ssPieces :: Pieces -> [String]
+ Web.Routes.Quasi.TH: ssRender :: Pieces -> Exp
+ Web.Routes.Quasi.TH: ssToMasterArg :: Pieces -> Exp
+ Web.Routes.Quasi.TH: ssType :: Pieces -> Type
+ Web.Routes.Quasi.TH: type THResource = (String, Pieces)
- Web.Routes.Quasi: createParse :: QuasiSiteSettings -> [Resource] -> Q [Clause]
+ Web.Routes.Quasi: createParse :: [THResource] -> Q [Clause]
- Web.Routes.Quasi: createRender :: QuasiSiteSettings -> [Resource] -> Q [Clause]
+ Web.Routes.Quasi: createRender :: [THResource] -> Q [Clause]
Files
- Web/Routes/Quasi.hs +5/−711
- Web/Routes/Quasi/Classes.hs +39/−0
- Web/Routes/Quasi/Parse.hs +119/−0
- Web/Routes/Quasi/TH.hs +200/−0
- runtests.hs +49/−0
- web-routes-quasi.cabal +4/−1
Web/Routes/Quasi.hs view
@@ -1,9 +1,3 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE TypeSynonymInstances #-} module Web.Routes.Quasi ( -- * Quasi quoter@@ -11,715 +5,15 @@ , parseRoutesNoCheck -- * Template haskell -- ** Low level- , createQuasiDispatch+ , createRoutes , createRender , createParse- -- ** High level for 'QuasiSite's- , createQuasiSite- , createQuasiSite'- , QuasiSiteSettings (..)- , QuasiSiteDecs (..)- -- * Quasi site- , QuasiDispatch- , QuasiSite (..)- , quasiFromSite- , quasiToSite- , Routes- , BlankArgs (..)- -- * Underlying data types- , Resource (..)- , Handler (..)- , Piece (..)- , liftResources- -- * FIXME+ -- * Type classes , SinglePiece (..) , MultiPiece (..) , Strings-#if TEST- , testSuite-#endif ) where -import Data.Char-import Language.Haskell.TH.Syntax-import Language.Haskell.TH.Quote-import Data.Data-import Data.Maybe-import Control.Monad-import Web.Routes.Site-import Data.Either-import Data.List-import Data.Int (Int64)--#if TEST-import Test.Framework (testGroup, Test)-import Test.Framework.Providers.HUnit-import Test.HUnit hiding (Test)-#endif---- | A single resource pattern.------ First argument is the name of the constructor, second is the URL pattern to--- match, third is how to dispatch.-data Resource = Resource String [Piece] Handler- deriving (Read, Show, Eq, Data, Typeable)---- | Defines how to dispatch a request for a specific resource.------ ByMethod allows a different function to be called for each request method.--- The first value in each pair is the method, the second is the name of the--- handler.------ Single dispatches to a single function for all methods.------ SubSite passes dispatch to a different site. The first argument is the name--- of the datatype for the routes. The second is a function returning a--- 'QuasiSite' for that type of routes. The third is a function converting the--- master argument to the subsite argument.-data Handler = ByMethod [(String, String)] -- ^ (method, handler)- | Single String- | SubSite String String String- deriving (Read, Show, Eq, Data, Typeable)---- | A single piece of a URL, delimited by slashes.------ In the case of StaticPiece, the argument is the value of the piece; for the--- other constructors, it is the name of the parameter represented by this--- piece. That value is not used here, but may be useful elsewhere.-data Piece = StaticPiece String- | SinglePiece String- | MultiPiece String- deriving (Read, Show, Eq, Data, Typeable)--type family Routes a---- | The type for quasiDispatch; separated out for clarity of Haddock docs.-type QuasiDispatch app sub master- = (Routes master -> String)- -> Routes sub- -> (Routes sub -> Routes master)- -> master- -> (master -> sub)- -> app -- ^ bad method handler- -> String -- ^ method- -> app---- | Very similar in principle to 'Site', but with special support for--- arguments and subsites.-data QuasiSite app sub master = QuasiSite- { quasiDispatch :: QuasiDispatch app sub master- , quasiRender :: Routes sub -> [String]- , quasiParse :: [String] -> Either String (Routes sub)- }---- | Used for applications with no arguments. In particular, this facilitates a--- translation from a 'Site' to a 'QuasiSite' via 'quasiFromSite'.-data BlankArgs routes = BlankArgs-type instance Routes (BlankArgs routes) = routes---- | Convert a 'Site' to a 'QuasiSite'. 'quasiRender' and 'quasiParse' are--- identical to 'formatPathSegments' and 'parsePathSegments'; for the--- 'quasiDispatch' function, we just ignore the extra arguments that 'Site'--- does not use.-quasiFromSite :: Site surl app -> QuasiSite app (BlankArgs surl) master-quasiFromSite (Site dispatch render parse) = QuasiSite- { quasiDispatch = \mrender surl constr _ _ _ _ ->- dispatch (mrender . constr) surl- , quasiRender = render- , quasiParse = parse- }---- | Convert a 'QuasiSite' to a 'Site'. 'quasiRender' and 'quasiParse' are--- identical to 'formatPathSegments' and 'parsePathSegments'; for the--- 'handleSite' function, we need some extra information passed to this--- function. We also restrict the resulting 'QuasiSite' to cases where subsite--- and master site are the same.-quasiToSite :: QuasiSite app sub sub- -> ((String -> app) -> app) -- ^ grab method- -> app -- ^ bad method- -> sub- -> Site (Routes sub) app-quasiToSite (QuasiSite dispatch render parse) grabMethod badMethod sub = Site- { handleSite = \rend url -> grabMethod (dispatch- rend- url- id- sub- id- badMethod)- , formatPathSegments = render- , parsePathSegments = parse- }--isStatic :: Piece -> Bool-isStatic (StaticPiece _) = True-isStatic _ = False--isSubSite :: Handler -> Bool-isSubSite (SubSite _ _ _) = True-isSubSite _ = False--{- FIXME-isString :: Piece -> Bool-isString (StringPiece _) = True-isString _ = False--}---- | Drop leading whitespace.-trim :: String -> String-trim = dropWhile isSpace---- | 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 -> [Resource]-resourcesFromString = map go . filter (not . null) . map trim . lines where- go s =- case words s of- (pattern:constr:rest) ->- let pieces = piecesFromString $ drop1Slash pattern- handler = go' constr rest- in if all isStatic pieces || not (isSubSite handler)- then Resource constr pieces handler- else error "Subsites must have static pieces"- _ -> error $ "Invalid resource line: " ++ s- go' constr [] = Single $ "handle" ++ constr- go' _ [routes, getSite@(x:_), grabArgs@(y:_)]- | isLower x && isLower y = SubSite routes getSite grabArgs- go' constr rest = ByMethod $ map helper rest- where- helper x =- case break (== ':') x of- (method, ':' : func) -> (method, func)- _ -> (x, map toLower x ++ constr)--drop1Slash :: String -> String-drop1Slash ('/':x) = x-drop1Slash x = x--piecesFromString :: String -> [Piece]-piecesFromString "" = []-piecesFromString x =- let (y, z) = break (== '/') x- in pieceFromString y : piecesFromString (drop1Slash z)--pieceFromString :: String -> Piece-pieceFromString ('#':x) = SinglePiece x-pieceFromString ('*':x) = MultiPiece x-pieceFromString x = StaticPiece x---- | 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 x y where- x s = do- let res = resourcesFromString s- case findOverlaps res of- [] -> liftResources res- _ -> error $ "Overlapping routes: " ++ unlines (map show res)- y = dataToPatQ (const Nothing) . resourcesFromString---- | Same as 'parseRoutes', but performs no overlap checking.-parseRoutesNoCheck :: QuasiQuoter-parseRoutesNoCheck = QuasiQuoter x y where- x = liftResources . resourcesFromString- y = dataToPatQ (const Nothing) . resourcesFromString--liftResources :: [Resource] -> Q Exp-liftResources = fmap ListE . mapM go where- go :: Resource -> Q Exp- go (Resource s ps h) = do- r <- [|Resource|]- s' <- lift s- ps' <- liftPieces ps- h' <- liftHandler h- return $ r `AppE` s' `AppE` ps' `AppE` h'--liftPieces :: [Piece] -> Q Exp-liftPieces = fmap ListE . mapM go where- go (StaticPiece s) = do- c <- [|StaticPiece|]- s' <- lift s- return $ c `AppE` s'- go (SinglePiece s) = do- c <- [|SinglePiece|]- s' <- lift s- return $ c `AppE` s'- go (MultiPiece s) = do- c <- [|MultiPiece|]- s' <- lift s- return $ c `AppE` s'--liftHandler :: Handler -> Q Exp-liftHandler (ByMethod s) = do- c <- [|ByMethod|]- s' <- lift s- return $ c `AppE` s'-liftHandler (Single s) = do- c <- [|Single|]- s' <- lift s- return $ c `AppE` s'-liftHandler (SubSite x y z) = do- c <- [|SubSite|]- x' <- lift x- y' <- lift y- z' <- lift z- return $ c `AppE` x' `AppE` y' `AppE` z'--dataTypeDec :: QuasiSiteSettings -> Q Dec-dataTypeDec set =- return $ DataD [] (crRoutes set) []- (map go $ crResources set) claz- where- go (Resource n pieces h) = NormalC (mkName n)- $ mapMaybe go' pieces- ++ go'' h- go' (SinglePiece x) = Just (NotStrict, ConT $ mkName x)- go' (MultiPiece x) = Just (NotStrict, ConT $ mkName x)- go' (StaticPiece _) = Nothing- go'' (SubSite t _ _) = [(NotStrict, ConT ''Routes `AppT` ConT (mkName t))]- go'' _ = []- claz = [''Show, ''Read, ''Eq]--findOverlaps :: [Resource] -> [(Resource, Resource)]-findOverlaps = gos . map justPieces- where- justPieces r@(Resource _ ps (SubSite{})) = (ps ++ [MultiPiece ""], r)- justPieces r@(Resource _ ps _) = (ps, r)- gos [] = []- gos (x:xs) = mapMaybe (go x) xs ++ gos xs- go (StaticPiece x:xs, xr) (StaticPiece y:ys, yr)- | x == y = go (xs, xr) (ys, yr)- | otherwise = Nothing- go (MultiPiece _:_, xr) (_, yr) = Just (xr, yr)- go (_, xr) (MultiPiece _:_, yr) = Just (xr, yr)- go ([], xr) ([], yr) = Just (xr, yr)- go ([], _) (_, _) = Nothing- go (_, _) ([], _) = Nothing- go (_:xs, xr) (_:ys, yr) = go (xs, xr) (ys, yr)---- | Whether the set of resources cover all possible URLs.-areResourcesComplete :: [Resource] -> Bool-areResourcesComplete res =- let (slurps, noSlurps) = partitionEithers $ mapMaybe go res- in case slurps of- [] -> False- _ -> let minSlurp = minimum slurps- in helper minSlurp $ reverse $ sort noSlurps- where- go :: Resource -> Maybe (Either Int Int)- go (Resource _ ps (SubSite _ _ _)) = go' Left ps- go (Resource _ ps _) =- case reverse ps of- [] -> Just $ Right 0- (MultiPiece _:rest) -> go' Left rest- x -> go' Right x- go' b x = if all isSingle x then Just (b $ length x) else Nothing- helper 0 _ = True- helper _ [] = False- helper m (i:is)- | i >= m = helper m is- | i + 1 == m = helper i is- | otherwise = False- isSingle (SinglePiece _) = True- isSingle _ = False---- | Generates the set of clauses necesary to parse the given 'Resource's. See 'quasiParse'.-createParse :: QuasiSiteSettings -> [Resource] -> Q [Clause]-createParse set res = do- final' <- final- clauses <- mapM go res- return $ if areResourcesComplete res- then clauses- else clauses ++ [final']- where- final = do- no <- [|Left "Invalid URL"|]- return $ Clause [WildP] (NormalB no) []- mkPat' :: Exp -> [Piece] -> Exp -> Q (Pat, Exp)- mkPat' be [MultiPiece s] parse = do- v <- newName $ "var" ++ s- fmp <- [|fromMultiPiece|]- let parse' = InfixE (Just parse) be $ Just $ fmp `AppE` VarE v- return (VarP v, parse')- mkPat' _ (MultiPiece _:_) _parse = error "MultiPiece must be last"- mkPat' be (StaticPiece s:rest) parse = do- (x, parse') <- mkPat' be rest parse- let cons = ConP $ mkName ":"- return $ (cons [LitP $ StringL s, x], parse')- mkPat' be (SinglePiece s:rest) parse = do- fsp <- [|fromSinglePiece|]- v <- newName $ "var" ++ s- let parse' = InfixE (Just parse) be $ Just $ fsp `AppE` VarE v- (x, parse'') <- mkPat' be rest parse'- let cons = ConP $ mkName ":"- return (cons [VarP v, x], parse'')- mkPat' _ [] parse = return (ListP [], parse)- go (Resource n ps (SubSite argType f _)) = do- unless (all isStatic ps) $ error "SubSite cannot have parameters"- let strs = map (\(StaticPiece s) -> s) ps- parse <- [|quasiParse|]- let siteType = ConT ''QuasiSite- `AppT` crApplication set- `AppT` ConT (mkName argType)- `AppT` crArgument set- siteVar = VarE (mkName f) `SigE` siteType -- FIXME siteType necessary?- let parse' = parse `AppE` siteVar- var <- newName "var"- let rhs = parse' `AppE` VarE var- fm <- [|fmape|]- let body = NormalB $ fm `AppE` ConE (mkName n) `AppE` rhs- let cons s p = ConP (mkName ":") [LitP $ StringL s, p]- let pat = foldr cons (VarP var) strs- return $ Clause [pat] body []- go (Resource n ps _) = do- ri <- [|Right|]- be <- [|ape|]- (pat, parse) <- mkPat' be ps $ ri `AppE` ConE (mkName n)- return $ Clause [pat] (NormalB parse) []---- | 'ap' for 'Either'-ape :: Either String (a -> b) -> Either String a -> Either String b-ape (Left e) _ = Left e-ape (Right _) (Left e) = Left e-ape (Right f) (Right a) = Right $ f a--fmape :: (a -> b) -> Either String a -> Either String b-fmape _ (Left e) = Left e-fmape f (Right a) = Right $ f a---- | Generates the set of clauses necesary to render the given 'Resource's. See--- 'quasiRender'.-createRender :: QuasiSiteSettings -> [Resource] -> Q [Clause]-createRender set res = mapM go res- where- go (Resource n ps h) = do- let ps' = zip [1..] ps- let pat = ConP (mkName n) $ mapMaybe go' ps' ++ lastPat h- bod <- mkBod ps' h- return $ Clause [pat] (NormalB bod) []- lastPat (SubSite _ _ _) = [VarP $ mkName "var0"]- lastPat _ = []- go' (_, StaticPiece _) = Nothing- go' (i, _) = Just $ VarP $ mkName $ "var" ++ show (i :: Int)- mkBod [] (SubSite argType f _) = do- format <- [|quasiRender|]- let siteType = ConT ''QuasiSite- `AppT` crApplication set- `AppT` ConT (mkName argType)- `AppT` crArgument set- siteVar = VarE (mkName f) `SigE` siteType- let format' = format `AppE` siteVar- return $ format' `AppE` VarE (mkName "var0")- mkBod [] _ = lift ([] :: [String])- mkBod ((_, StaticPiece x):xs) h = do- x' <- lift x- xs' <- mkBod xs h- return $ ConE (mkName ":") `AppE` x' `AppE` xs'- mkBod ((i, SinglePiece _):xs) h = do- let x' = VarE $ mkName $ "var" ++ show i- tsp <- [|toSinglePiece|]- let x'' = tsp `AppE` x'- xs' <- mkBod xs h- return $ ConE (mkName ":") `AppE` x'' `AppE` xs'- mkBod ((i, MultiPiece _):_) _ = do- let x' = VarE $ mkName $ "var" ++ show i- tmp <- [|toMultiPiece|]- return $ tmp `AppE` x'---- | Generate the set of clauses necesary to dispatch the given 'Resource's.--- See 'quasiDispatch'.-createQuasiDispatch :: QuasiSiteSettings -> Q [Clause]-createQuasiDispatch set = do- mrender <- newName "_mrender"- tomurl <- newName "_tomurl"- marg <- newName "_marg"- tosarg <- newName "_tosarg"- method <- newName "_method"- badMethod <- newName "_badMethod"- mapM (go mrender tomurl marg tosarg method badMethod) $ crResources set- where- go mrender tomurl marg tosarg method badMethod- (Resource constr ps handler) = do- conArgs <- go' ps handler- url <- newName "_url"- let pat = [ VarP mrender- , AsP url $ ConP (mkName constr) $ map VarP conArgs- , VarP tomurl- , VarP marg- , VarP tosarg- , VarP badMethod- , VarP method- ]- b <- case handler of- Single s' -> do- unexploded <- foldM go'' (VarE $ mkName s') conArgs- let exploded = crExplode set `AppE` unexploded- return $ exploded- `AppE` VarE mrender- `AppE` VarE url- `AppE` VarE tomurl- `AppE` VarE marg- `AppE` VarE tosarg- `AppE` VarE badMethod- `AppE` VarE method- ByMethod methods -> do- matches <- forM methods $ \(m, f) -> do- let pat' = LitP $ StringL m- unexploded <- foldM go'' (VarE $ mkName f) conArgs- let exploded = crExplode set `AppE` unexploded- let bod = exploded- `AppE` VarE mrender- `AppE` VarE url- `AppE` VarE tomurl- `AppE` VarE marg- `AppE` VarE tosarg- `AppE` VarE badMethod- `AppE` VarE method- return $ Match pat' (NormalB bod) []- let final =- if length methods == 4- then []- else [Match WildP (NormalB $ VarE badMethod) []]- return $ CaseE (VarE method) $ matches ++ final- SubSite argType f getArg -> do- qd <- [|quasiDispatch|]- let siteType = ConT ''QuasiSite- `AppT` crApplication set- `AppT` ConT (mkName argType)- `AppT` crArgument set- siteVar = VarE (mkName f) `SigE` siteType- let disp = qd `AppE` siteVar- o <- [|(.)|]- let tomurl' = InfixE (Just $ VarE tomurl) o- $ Just $ ConE $ mkName constr- let tosarg' = InfixE (Just $ VarE $ mkName getArg) o- $ Just $ VarE tosarg- return $ disp- `AppE` VarE mrender- `AppE` VarE (last conArgs)- `AppE` tomurl'- `AppE` VarE marg- `AppE` tosarg'- `AppE` VarE badMethod- `AppE` VarE method- return $ Clause pat (NormalB b) []- go' [] (SubSite _ _ _) = do- n <- newName "arg"- return [n]- go' [] _ = return []- go' (StaticPiece _:rest) h = go' rest h- go' (_:rest) h = do- n <- newName "arg"- ns <- go' rest h- return $ n : ns- go'' base arg = return $ base `AppE` VarE arg--siteDecType :: QuasiSiteSettings -> Q Dec-siteDecType set = do- let core = ConT ''QuasiSite `AppT` crApplication set `AppT` crArgument set- let ty = case crMaster set of- Left master -> core `AppT` master- Right classes ->- let foralls = map (PlainTV . mkName)- $ nub- $ filter firstLower- $ concatMap (words . fst) classes- cxt = concatMap mkContext classes- in ForallT foralls cxt- $ core `AppT` (VarT $ mkName "master")- return $ SigD (crSite set) ty- where- mkContext (dt, typs) = do- let dt' = mkDataType' $ words dt- typ <- typs- return $ ClassP typ [dt']- mkDataType' [] = error "mkDataType with null"- mkDataType' x = foldl1 AppT $ map go x- go "" = error "go with null"- go x@(y:_)- | isUpper y = ConT $ mkName x- | otherwise = VarT $ mkName x- firstLower (x:_) | isLower x = True- firstLower _ = False--siteDec :: Name -- ^ name of resulting function- -> [Clause] -- ^ parse- -> [Clause] -- ^ render- -> [Clause] -- ^ dispatch- -> Q Dec-siteDec name parse render dispatch = do- si <- [|QuasiSite|]- dname <- newName "dispatch"- rname <- newName "render"- pname <- newName "parse"- let body = si `AppE` VarE dname- `AppE` VarE rname- `AppE` VarE pname- return $ FunD name- [ Clause [] (NormalB body)- [ FunD dname dispatch- , FunD rname render- , FunD pname parse- ]- ]---- | Template haskell code to convert a list of 'Resource's into appropriate--- declarations for a 'QuasiSite'. See the 'QuasiSiteSettings' and--- 'QuasiSiteDecs' data types for an explanation for the input and output,--- respectively, of this function.-createQuasiSite :: QuasiSiteSettings -> Q QuasiSiteDecs-createQuasiSite set = do- dt <- dataTypeDec set- let tySyn = TySynInstD ''Routes [crArgument set] $ ConT $ crRoutes set- parseClauses <- createParse set $ crResources set- renderClauses <- createRender set $ crResources set- dispatchClauses <- createQuasiDispatch set- st <- siteDecType set- s <- siteDec (crSite set) parseClauses renderClauses dispatchClauses- return QuasiSiteDecs- { decRoutes = dt- , decRoutesSyn = tySyn- , decSiteType = st- , decSite = s- }---- | The arguments passed to 'createQuasiSite' for generating applications--- based on the 'QuasiSite' datatype.-data QuasiSiteSettings = QuasiSiteSettings- { -- | The name for the URL data type to be created.- crRoutes :: Name- -- | The type for underlying applications.- , crApplication :: Type- -- | The type for the argument value to be passed to dispatch functions.- , crArgument :: Type- -- | Underlying applications will often want to program against some- -- datatype. The explode function converts that datatype into a function- -- that will generate an application ('crApplication'). In particular,- -- the value of crExplode should have a type signature of:- --- -- > explode :: handler- -- > -> ('Routes' master -> String)- -- > -> 'Routes' sub- -- > -> ('Routes' sub -> 'Routes' master)- -- > -> master- -- > -> (master -> sub)- -- > -> app- -- > -> String- -- > -> app- --- -- handler is some datatype handled by the calling application;- -- web-routes-quasi needn't know about it. sub and master are the- -- arguments for the subsite and master site, respectively. app is the- -- datatype for the underlying application; the app argument above is the- -- handler for unsupported method. The 'String' argument is the request- -- method.- , crExplode :: Exp- -- | The 'Resource's upon which we are building the set of URLs and- -- dispatches. Usually generated by 'parseRoutes'.- , crResources :: [Resource]- -- | The name for the resulting function which will return the 'QuasiSite'.- , crSite :: Name- -- | Describes the type of the master argument. This can either be a- -- 'Left' concrete datatype, or 'Right' a list of 'Pred's describing the- -- context for master.- , crMaster :: Either Type [(String, [Name])]- }---- | The template Haskell declarations returned from 'createQuasiSite'.-data QuasiSiteDecs = QuasiSiteDecs- { -- | Defines the actual URL datatype, with all its constructors.- decRoutes :: Dec- -- | Defines the 'Routes' type synonym instance between the argument- -- ('crArgument') and URL datatype.- , decRoutesSyn :: Dec- -- | The type signature for the site function ('decSite').- , decSiteType :: Dec- -- | Function which returns a 'QuasiSite'. The type parameters for the- -- 'QuasiSite' will be 'crApplication', 'crArgument' and a forall master.- , decSite :: Dec- }--createQuasiSite' :: QuasiSiteSettings -> Q [Dec]-createQuasiSite' s = do- QuasiSiteDecs a b c d <- createQuasiSite s- return [a, b, c, d]--#if TEST-testSuite :: Test-testSuite = testGroup "Web.Routes.Quasi"- [ testCase "overlaps" caseOverlaps- , testCase "complete" caseComplete- ]--caseOverlaps :: Assertion-caseOverlaps = do- assertBool "empty" $ null $ findOverlaps []- assertBool "single" $ null $ findOverlaps- [ Resource "Foo" [] $ Single "foo"- ]- assertBool "two empties" $ not $ null $ findOverlaps- [ Resource "Foo" [] $ Single "foo"- , Resource "Bar" [] $ Single "bar"- ]- assertBool "slurp versus empty" $ not $ null $ findOverlaps- [ Resource "Foo" [] $ Single "foo"- , Resource "Bar" [] $ SubSite "a" "b" "c"- ]- assertBool "static + slurp versus empty" $ null $ findOverlaps- [ Resource "Foo" [] $ Single "foo"- , Resource "Bar" [StaticPiece "5"] $ SubSite "a" "b" "c"- ]--caseComplete :: Assertion-caseComplete = do- assertBool "empty" $ not $ areResourcesComplete []- assertBool "slurp" $ areResourcesComplete- [ Resource "Foo" [MultiPiece "Foos"] $ Single "foo"- ]- assertBool "subsite" $ areResourcesComplete- [ Resource "Foo" [] $ SubSite "a" "b" "c"- ]- assertBool "string + subsite" $ areResourcesComplete- [ Resource "Foo" [SinglePiece "Foo"] $ SubSite "a" "b" "c"- , Resource "Bar" [] $ Single "bar"- ]- assertBool "static + subsite" $ not $ areResourcesComplete- [ Resource "Foo" [StaticPiece "foo"] $ SubSite "a" "b" "c"- ]- assertBool "two pieces" $ not $ areResourcesComplete- [ Resource "Foo" [SinglePiece "Foo"] $ Single "foo"- , Resource "Bar" [StaticPiece "foo"] $ SubSite "a" "b" "c"- ]-#endif--class SinglePiece s where- fromSinglePiece :: String -> Either String s- toSinglePiece :: s -> String-instance SinglePiece String where- fromSinglePiece = Right- toSinglePiece = id-instance SinglePiece Integer where- fromSinglePiece s = case reads s of- (i, _):_ -> Right i- _ -> Left $ "Invalid integer: " ++ s- toSinglePiece = show-instance SinglePiece Int where- fromSinglePiece s = case reads s of- (i, _):_ -> Right i- _ -> Left $ "Invalid integer: " ++ s- toSinglePiece = show-instance SinglePiece Int64 where- fromSinglePiece s = case reads s of- (i, _):_ -> Right i- _ -> Left $ "Invalid integer: " ++ s- toSinglePiece = show--class MultiPiece s where- fromMultiPiece :: [String] -> Either String s- toMultiPiece :: s -> [String]-instance MultiPiece [String] where- fromMultiPiece = Right- toMultiPiece = id-type Strings = [String]+import Web.Routes.Quasi.TH+import Web.Routes.Quasi.Parse+import Web.Routes.Quasi.Classes
+ Web/Routes/Quasi/Classes.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeSynonymInstances #-}+module Web.Routes.Quasi.Classes+ ( SinglePiece (..)+ , MultiPiece (..)+ , Strings+ ) where++import Data.Int (Int64)++class SinglePiece s where+ fromSinglePiece :: String -> Either String s+ toSinglePiece :: s -> String+instance SinglePiece String where+ fromSinglePiece = Right+ toSinglePiece = id+instance SinglePiece Integer where+ fromSinglePiece s = case reads s of+ (i, _):_ -> Right i+ _ -> Left $ "Invalid integer: " ++ s+ toSinglePiece = show+instance SinglePiece Int where+ fromSinglePiece s = case reads s of+ (i, _):_ -> Right i+ _ -> Left $ "Invalid integer: " ++ s+ toSinglePiece = show+instance SinglePiece Int64 where+ fromSinglePiece s = case reads s of+ (i, _):_ -> Right i+ _ -> Left $ "Invalid integer: " ++ s+ toSinglePiece = show++class MultiPiece s where+ fromMultiPiece :: [String] -> Either String s+ toMultiPiece :: s -> [String]+instance MultiPiece [String] where+ fromMultiPiece = Right+ toMultiPiece = id+type Strings = [String]
+ Web/Routes/Quasi/Parse.hs view
@@ -0,0 +1,119 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE DeriveDataTypeable #-}+module Web.Routes.Quasi.Parse+ ( -- * Quasi quoter+ parseRoutes+ , parseRoutesNoCheck+ , Resource (..)+ , Piece (..)+ ) where++import Language.Haskell.TH.Syntax+import Language.Haskell.TH.Quote+import Data.Data+import Data.Char+import Data.Maybe++-- | 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 x y where+ x s = do+ let res = resourcesFromString s+ case findOverlaps res of+ [] -> lift res+ z -> error $ "Overlapping routes: " ++ unlines (map show z)+ y = dataToPatQ (const Nothing) . resourcesFromString++-- | Same as 'parseRoutes', but performs no overlap checking.+parseRoutesNoCheck :: QuasiQuoter+parseRoutesNoCheck = QuasiQuoter x y where+ x = lift . resourcesFromString+ y = dataToPatQ (const Nothing) . resourcesFromString++instance Lift Resource where+ lift (Resource s ps h) = do+ r <- [|Resource|]+ s' <- lift s+ ps' <- lift ps+ h' <- lift h+ return $ r `AppE` s' `AppE` ps' `AppE` h'++-- | A single resource pattern.+--+-- First argument is the name of the constructor, second is the URL pattern to+-- match, third is how to dispatch.+data Resource = Resource String [Piece] [String]+ deriving (Read, Show, Eq, Data, Typeable)++-- | A single piece of a URL, delimited by slashes.+--+-- In the case of StaticPiece, the argument is the value of the piece; for the+-- other constructors, it is the name of the parameter represented by this+-- piece. That value is not used here, but may be useful elsewhere.+data Piece = StaticPiece String+ | SinglePiece String+ | MultiPiece String+ deriving (Read, Show, Eq, Data, Typeable)++instance Lift Piece where+ lift (StaticPiece s) = do+ c <- [|StaticPiece|]+ s' <- lift s+ return $ c `AppE` s'+ lift (SinglePiece s) = do+ c <- [|SinglePiece|]+ s' <- lift s+ return $ c `AppE` s'+ lift (MultiPiece s) = do+ c <- [|MultiPiece|]+ s' <- lift s+ return $ c `AppE` s'++-- | 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 -> [Resource]+resourcesFromString = map go . filter (not . null) . map trim . lines where+ go s =+ case words s of+ (pattern:constr:rest) ->+ let pieces = piecesFromString $ drop1Slash pattern+ in Resource constr pieces rest+ _ -> error $ "Invalid resource line: " ++ s++-- | Drop leading whitespace.+trim :: String -> String+trim = dropWhile isSpace++drop1Slash :: String -> String+drop1Slash ('/':x) = x+drop1Slash x = x++piecesFromString :: String -> [Piece]+piecesFromString "" = []+piecesFromString x =+ let (y, z) = break (== '/') x+ in pieceFromString y : piecesFromString (drop1Slash z)++pieceFromString :: String -> Piece+pieceFromString ('#':x) = SinglePiece x+pieceFromString ('*':x) = MultiPiece x+pieceFromString x = StaticPiece x++findOverlaps :: [Resource] -> [(Resource, Resource)]+findOverlaps = gos . map justPieces+ where+ justPieces r@(Resource _ ps _) = (ps, r)+ gos [] = []+ gos (x:xs) = mapMaybe (go x) xs ++ gos xs+ go (StaticPiece x:xs, xr) (StaticPiece y:ys, yr)+ | x == y = go (xs, xr) (ys, yr)+ | otherwise = Nothing+ go (MultiPiece _:_, xr) (_, yr) = Just (xr, yr)+ go (_, xr) (MultiPiece _:_, yr) = Just (xr, yr)+ go ([], xr) ([], yr) = Just (xr, yr)+ go ([], _) (_, _) = Nothing+ go (_, _) ([], _) = Nothing+ go (_:xs, xr) (_:ys, yr) = go (xs, xr) (ys, yr)
+ Web/Routes/Quasi/TH.hs view
@@ -0,0 +1,200 @@+{-# LANGUAGE TemplateHaskell #-}+module Web.Routes.Quasi.TH+ ( createRoutes+ , createRender+ , createParse+ , createDispatch+ , Pieces (..)+ , THResource+ ) where++import Web.Routes.Quasi.Parse+import Web.Routes.Quasi.Classes+import Language.Haskell.TH.Syntax+import Data.Maybe+import Data.Either+import Data.List+import Data.Char (toLower)++data Pieces =+ SubSite+ { ssType :: Type+ , ssParse :: Exp+ , ssRender :: Exp+ , ssDispatch :: Exp+ , ssToMasterArg :: Exp+ , ssPieces :: [String]+ }+ | Simple [Piece] [String] -- ^ methods+type THResource = (String, Pieces)++createRoutes :: [THResource] -> Q [Con]+createRoutes res =+ return $ map go res+ where+ go (n, SubSite{ssType = s}) =+ NormalC (mkName n) [(NotStrict, s)]+ go (n, Simple pieces _) = NormalC (mkName n) $ mapMaybe go' pieces+ go' (SinglePiece x) = Just (NotStrict, ConT $ mkName x)+ go' (MultiPiece x) = Just (NotStrict, ConT $ mkName x)+ go' (StaticPiece _) = Nothing++-- | Generates the set of clauses necesary to parse the given 'Resource's. See 'quasiParse'.+createParse :: [THResource] -> Q [Clause]+createParse res = do+ final' <- final+ clauses <- mapM go res+ return $ if areResourcesComplete res+ then clauses+ else clauses ++ [final']+ where+ go (constr, SubSite{ssParse = p, ssPieces = pieces}) = do+ let cons = ConP $ mkName ":"+ x <- newName "x"+ let pat = foldr (\a b -> cons [LitP (StringL a), b]) (VarP x) pieces+ let eitherSub = p `AppE` VarE x+ fmape' <- [|fmape|]+ let bod = fmape' `AppE` ConE (mkName constr) `AppE` eitherSub+ return $ Clause [pat] (NormalB bod) []+ go (n, Simple ps _) = do+ ri <- [|Right|]+ be <- [|ape|]+ (pat, parse) <- mkPat' be ps $ ri `AppE` ConE (mkName n)+ return $ Clause [pat] (NormalB parse) []+ final = do+ no <- [|Left "Invalid URL"|]+ return $ Clause [WildP] (NormalB no) []+ mkPat' :: Exp -> [Piece] -> Exp -> Q (Pat, Exp)+ mkPat' be [MultiPiece s] parse = do+ v <- newName $ "var" ++ s+ fmp <- [|fromMultiPiece|]+ let parse' = InfixE (Just parse) be $ Just $ fmp `AppE` VarE v+ return (VarP v, parse')+ mkPat' _ (MultiPiece _:_) _parse = error "MultiPiece must be last"+ mkPat' be (StaticPiece s:rest) parse = do+ (x, parse') <- mkPat' be rest parse+ let cons = ConP $ mkName ":"+ return $ (cons [LitP $ StringL s, x], parse')+ mkPat' be (SinglePiece s:rest) parse = do+ fsp <- [|fromSinglePiece|]+ v <- newName $ "var" ++ s+ let parse' = InfixE (Just parse) be $ Just $ fsp `AppE` VarE v+ (x, parse'') <- mkPat' be rest parse'+ let cons = ConP $ mkName ":"+ return (cons [VarP v, x], parse'')+ mkPat' _ [] parse = return (ListP [], parse)++fmape :: (a -> b) -> Either String a -> Either String b+fmape _ (Left s) = Left s+fmape f (Right a) = Right $ f a++-- | 'ap' for 'Either'+ape :: Either String (a -> b) -> Either String a -> Either String b+ape (Left e) _ = Left e+ape (Right _) (Left e) = Left e+ape (Right f) (Right a) = Right $ f a++-- | Generates the set of clauses necesary to render the given 'Resource's. See+-- 'quasiRender'.+createRender :: [THResource] -> Q [Clause]+createRender = mapM go+ where+ go (n, Simple ps _) = do+ let ps' = zip [1..] ps+ let pat = ConP (mkName n) $ mapMaybe go' ps'+ bod <- mkBod ps'+ return $ Clause [pat] (NormalB bod) []+ go (n, SubSite{ssRender = r, ssPieces = pieces}) = do+ let cons a b = InfixE (Just a) (ConE $ mkName ":") (Just b)+ x <- newName "x"+ let r' = r `AppE` VarE x+ let pat = ConP (mkName n) [VarP x]+ let bod = foldr (\a b -> cons (LitE $ StringL a) b) r' pieces+ return $ Clause [pat] (NormalB bod) []+ go' (_, StaticPiece _) = Nothing+ go' (i, _) = Just $ VarP $ mkName $ "var" ++ show (i :: Int)+ mkBod [] = lift ([] :: [String])+ mkBod ((_, StaticPiece x):xs) = do+ x' <- lift x+ xs' <- mkBod xs+ return $ ConE (mkName ":") `AppE` x' `AppE` xs'+ mkBod ((i, SinglePiece _):xs) = do+ let x' = VarE $ mkName $ "var" ++ show i+ tsp <- [|toSinglePiece|]+ let x'' = tsp `AppE` x'+ xs' <- mkBod xs+ return $ ConE (mkName ":") `AppE` x'' `AppE` xs'+ mkBod ((i, MultiPiece _):_) = do+ let x' = VarE $ mkName $ "var" ++ show i+ tmp <- [|toMultiPiece|]+ return $ tmp `AppE` x'++-- | Whether the set of resources cover all possible URLs.+areResourcesComplete :: [THResource] -> Bool+areResourcesComplete res =+ let (slurps, noSlurps) = partitionEithers $ mapMaybe go res+ in case slurps of+ [] -> False+ _ -> let minSlurp = minimum slurps+ in helper minSlurp $ reverse $ sort noSlurps+ where+ go :: THResource -> Maybe (Either Int Int)+ go (_, Simple ps _) =+ case reverse ps of+ [] -> Just $ Right 0+ (MultiPiece _:rest) -> go' Left rest+ x -> go' Right x+ go (n, SubSite{ssPieces = ps}) =+ go (n, Simple (map StaticPiece ps ++ [MultiPiece ""]) [])+ go' b x = if all isSingle x then Just (b $ length x) else Nothing+ helper 0 _ = True+ helper _ [] = False+ helper m (i:is)+ | i >= m = helper m is+ | i + 1 == m = helper i is+ | otherwise = False+ isSingle (SinglePiece _) = True+ isSingle _ = False++notStatic :: Piece -> Bool+notStatic StaticPiece{} = False+notStatic _ = True++createDispatch :: Exp -- ^ modify a master handler+ -> Exp -- ^ convert a subsite handler to a master handler+ -> [THResource]+ -> Q [Clause]+createDispatch modMaster toMaster = mapM go+ where+ go (n, Simple ps methods) = do+ meth <- newName "method"+ xs <- mapM newName $ replicate (length $ filter notStatic ps) "x"+ let pat = [ ConP (mkName n) $ map VarP xs+ , if null methods then WildP else VarP meth+ ]+ bod <- go' n meth xs methods+ return $ Clause pat (NormalB bod) []+ go (n, SubSite{ssDispatch = d, ssToMasterArg = tma}) = do+ meth <- newName "method"+ x <- newName "x"+ let pat = [ConP (mkName n) [VarP x], VarP meth]+ let bod = d `AppE` VarE x `AppE` VarE meth+ fmap' <- [|fmap|]+ let toMaster' = toMaster `AppE` ConE (mkName n) `AppE` tma `AppE` VarE x+ let bod' = InfixE (Just toMaster') fmap' (Just bod)+ let bod'' = InfixE (Just modMaster) fmap' (Just bod')+ return $ Clause pat (NormalB bod'') []+ go' n _ xs [] = do+ jus <- [|Just|]+ let bod = foldl AppE (VarE $ mkName $ "handle" ++ n) $ map VarE xs+ return $ jus `AppE` (modMaster `AppE` bod)+ go' n meth xs methods = do+ noth <- [|Nothing|]+ j <- [|Just|]+ let noMatch = Match WildP (NormalB noth) []+ return $ CaseE (VarE meth) $ map (go'' n xs j) methods ++ [noMatch]+ go'' n xs j method =+ let pat = LitP $ StringL method+ func = map toLower method ++ n+ bod = foldl AppE (VarE $ mkName func) $ map VarE xs+ in Match pat (NormalB $ j `AppE` (modMaster `AppE` bod)) []
runtests.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE CPP #-} module Main ( main , waiSite@@ -155,3 +156,51 @@ drop1Slash :: String -> String drop1Slash ('/':x) = x drop1Slash x = x++#if TEST+testSuite :: Test+testSuite = testGroup "Web.Routes.Quasi"+ [ testCase "overlaps" caseOverlaps+ , testCase "complete" caseComplete+ ]++caseOverlaps :: Assertion+caseOverlaps = do+ assertBool "empty" $ null $ findOverlaps []+ assertBool "single" $ null $ findOverlaps+ [ Resource "Foo" [] $ Single "foo"+ ]+ assertBool "two empties" $ not $ null $ findOverlaps+ [ Resource "Foo" [] $ Single "foo"+ , Resource "Bar" [] $ Single "bar"+ ]+ assertBool "slurp versus empty" $ not $ null $ findOverlaps+ [ Resource "Foo" [] $ Single "foo"+ , Resource "Bar" [] $ SubSite "a" "b" "c"+ ]+ assertBool "static + slurp versus empty" $ null $ findOverlaps+ [ Resource "Foo" [] $ Single "foo"+ , Resource "Bar" [StaticPiece "5"] $ SubSite "a" "b" "c"+ ]++caseComplete :: Assertion+caseComplete = do+ assertBool "empty" $ not $ areResourcesComplete []+ assertBool "slurp" $ areResourcesComplete+ [ Resource "Foo" [MultiPiece "Foos"] $ Single "foo"+ ]+ assertBool "subsite" $ areResourcesComplete+ [ Resource "Foo" [] $ SubSite "a" "b" "c"+ ]+ assertBool "string + subsite" $ areResourcesComplete+ [ Resource "Foo" [SinglePiece "Foo"] $ SubSite "a" "b" "c"+ , Resource "Bar" [] $ Single "bar"+ ]+ assertBool "static + subsite" $ not $ areResourcesComplete+ [ Resource "Foo" [StaticPiece "foo"] $ SubSite "a" "b" "c"+ ]+ assertBool "two pieces" $ not $ areResourcesComplete+ [ Resource "Foo" [SinglePiece "Foo"] $ Single "foo"+ , Resource "Bar" [StaticPiece "foo"] $ SubSite "a" "b" "c"+ ]+#endif
web-routes-quasi.cabal view
@@ -1,5 +1,5 @@ name: web-routes-quasi-version: 0.4.0+version: 0.5.0 license: BSD3 license-file: LICENSE author: Michael Snoyman <michael@snoyman.com>@@ -20,6 +20,9 @@ web-routes >= 0.22 && < 0.23, template-haskell >= 2.4 && < 2.5 exposed-modules: Web.Routes.Quasi+ Web.Routes.Quasi.Classes+ Web.Routes.Quasi.Parse+ Web.Routes.Quasi.TH ghc-options: -Wall executable runtests