packages feed

web-routes-quasi 0.2.0 → 0.3.0

raw patch · 3 files changed

+129/−152 lines, 3 filesdep +HUnitdep +test-frameworkdep +test-framework-hunitdep ~web-encodingsPVP ok

version bump matches the API change (PVP)

Dependencies added: HUnit, test-framework, test-framework-hunit, test-framework-quickcheck2

Dependency ranges changed: web-encodings

API changes (from Hackage documentation)

- Web.Routes.Quasi: IntPiece :: (Maybe String) -> Piece
- Web.Routes.Quasi: SlurpPiece :: (Maybe String) -> Piece
- Web.Routes.Quasi: StringPiece :: (Maybe String) -> Piece
- Web.Routes.Quasi: class IsSlurp s
- Web.Routes.Quasi: class IsString a
- Web.Routes.Quasi: class (IsString s) => ToString s
- Web.Routes.Quasi: fromSlurp :: (IsSlurp s) => [String] -> s
- Web.Routes.Quasi: toSlurp :: (IsSlurp s) => s -> [String]
- Web.Routes.Quasi: toString :: (ToString s) => s -> String
+ Web.Routes.Quasi: MultiPiece :: String -> Piece
+ Web.Routes.Quasi: SinglePiece :: String -> Piece
+ Web.Routes.Quasi: class MultiPiece s
+ Web.Routes.Quasi: class SinglePiece s
+ Web.Routes.Quasi: fromMultiPiece :: (MultiPiece s) => [String] -> Either String s
+ Web.Routes.Quasi: fromSinglePiece :: (SinglePiece s) => String -> Either String s
+ Web.Routes.Quasi: instance MultiPiece [String]
+ Web.Routes.Quasi: instance SinglePiece Int
+ Web.Routes.Quasi: instance SinglePiece Integer
+ Web.Routes.Quasi: instance SinglePiece String
+ Web.Routes.Quasi: toMultiPiece :: (MultiPiece s) => s -> [String]
+ Web.Routes.Quasi: toSinglePiece :: (SinglePiece s) => s -> String
+ Web.Routes.Quasi: type Strings = [String]

Files

Web/Routes/Quasi.hs view
@@ -2,6 +2,8 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE CPP #-} {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeSynonymInstances #-} module Web.Routes.Quasi     (       -- * Quasi quoter@@ -30,9 +32,9 @@     , Piece (..)     , liftResources       -- * FIXME-    , ToString (..)-    , IsString-    , IsSlurp (..)+    , SinglePiece (..)+    , MultiPiece (..)+    , Strings #if TEST     , testSuite #endif@@ -47,7 +49,6 @@ import Web.Routes.Site import Data.Either import Data.List-import Data.String  #if TEST import Test.Framework (testGroup, Test)@@ -57,7 +58,7 @@  -- | A single resource pattern. ----- First argument is the name of the constructor, second is that URL pattern to+-- 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)@@ -85,9 +86,8 @@ -- 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-           | StringPiece (Maybe String)-           | IntPiece (Maybe String)-           | SlurpPiece (Maybe String)+           | SinglePiece String+           | MultiPiece String     deriving (Read, Show, Eq, Data, Typeable)  type family Routes a@@ -158,9 +158,11 @@ isSubSite (SubSite _ _ _) = True isSubSite _ = False +{- FIXME isString :: Piece -> Bool isString (StringPiece _) = True isString _ = False+-}  -- | Drop leading whitespace. trim :: String -> String@@ -183,8 +185,12 @@     go' constr [] = Single $ "handle" ++ constr     go' _ [routes, getSite@(x:_), grabArgs@(y:_)]         | isLower x && isLower y = SubSite routes getSite grabArgs-    go' constr rest = ByMethod-                      $ map (\x -> (x, map toLower x ++ constr)) rest+    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@@ -197,15 +203,10 @@      in pieceFromString y : piecesFromString (drop1Slash z)  pieceFromString :: String -> Piece-pieceFromString ('$':x) = StringPiece $ getConstr x-pieceFromString ('#':x) = IntPiece $ getConstr x-pieceFromString ('*':x) = SlurpPiece $ getConstr x+pieceFromString ('#':x) = SinglePiece x+pieceFromString ('*':x) = MultiPiece x pieceFromString x = StaticPiece x -getConstr :: String -> Maybe String-getConstr a@(x:_) | isUpper x = Just a-getConstr _ = Nothing- -- | 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.@@ -215,7 +216,7 @@         let res = resourcesFromString s         case findOverlaps res of             [] -> liftResources res-            _ -> error $ "Overlapping routes: " ++ show res+            _ -> error $ "Overlapping routes: " ++ unlines (map show res)     y = dataToPatQ (const Nothing) . resourcesFromString  -- | Same as 'parseRoutes', but performs no overlap checking.@@ -240,16 +241,12 @@         c <- [|StaticPiece|]         s' <- lift s         return $ c `AppE` s'-    go (StringPiece s) = do-        c <- [|StringPiece|]-        s' <- lift s-        return $ c `AppE` s'-    go (IntPiece s) = do-        c <- [|IntPiece|]+    go (SinglePiece s) = do+        c <- [|SinglePiece|]         s' <- lift s         return $ c `AppE` s'-    go (SlurpPiece s) = do-        c <- [|SlurpPiece|]+    go (MultiPiece s) = do+        c <- [|MultiPiece|]         s' <- lift s         return $ c `AppE` s' @@ -277,37 +274,25 @@     go (Resource n pieces h) = NormalC (mkName n)                              $ mapMaybe go' pieces                             ++ go'' h-    go' (StringPiece x) = Just (NotStrict, getConstr' (ConT ''String) x)-    go' (IntPiece x) = Just (NotStrict, getConstr' (ConT ''Integer) x)-    go' (SlurpPiece x) =-        Just (NotStrict, getConstr' (AppT ListT $ ConT ''String) x)-    go' _ = Nothing+    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] -getConstr' :: Type -> Maybe String -> Type-getConstr' t Nothing = t-getConstr' _ (Just s) = ConT $ mkName s- findOverlaps :: [Resource] -> [(Resource, Resource)] findOverlaps = gos . map justPieces   where-    justPieces r@(Resource _ ps (SubSite{})) = (ps ++ [SlurpPiece Nothing], r)+    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 (SlurpPiece _:_, xr) (_, yr) = Just (xr, yr)-    go (_, xr) (SlurpPiece _:_, yr) = Just (xr, yr)-    go (StaticPiece x:xs, xr) (IntPiece _:ys, yr)-        | isInt x = go (xs, xr) (ys, yr)-        | otherwise = Nothing-    go (IntPiece _:xs, xr) (StaticPiece y:ys, yr)-        | isInt 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@@ -327,15 +312,17 @@     go (Resource _ ps _) =         case reverse ps of             [] -> Just $ Right 0-            (SlurpPiece _:rest) -> go' Left rest+            (MultiPiece _:rest) -> go' Left rest             x -> go' Right x-    go' b x = if all isString x then Just (b $ length x) else Nothing+    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]@@ -349,77 +336,57 @@     final = do         no <- [|Left "Invalid URL"|]         return $ Clause [WildP] (NormalB no) []-    go (Resource n ps h) = do-        let ps' = zip [1..] ps-        let pat = mkPat ps' h-        bod <- foldM go' (ConE $ mkName n) ps'-        bod' <- case h of-                    SubSite argType f _ -> do-                        parse <- [|quasiParse|]-                        let siteType = ConT ''QuasiSite-                                        `AppT` crApplication set-                                        `AppT` ConT (mkName argType)-                                        `AppT` crArgument set-                            siteVar = VarE (mkName f) `SigE` siteType-                        let parse' = parse `AppE` siteVar-                        let rhs = parse' `AppE` VarE (mkName "var0")-                        fm <- [|fmape|]-                        return $ fm `AppE` bod `AppE` rhs-                    _ -> do-                        ri <- [|Right|]-                        return $ AppE ri bod-        checkInts' <- checkInts ps'-        return $ Clause [pat]-                        (GuardedB [(NormalG checkInts', bod')]) []-    mkPat [] (SubSite _ _ _) = VarP $ mkName "var0" -- FIXME use newName-    mkPat [] _ = ConP (mkName "[]") []-    mkPat ((_, StaticPiece t):rest) h =-        ConP (mkName ":") [ LitP (StringL t)-                          , mkPat rest h-                          ]-    mkPat ((i, SlurpPiece _):_) _ = VarP $ mkName $ "var" ++ show i-    mkPat ((i, _):rest) h = ConP (mkName ":")-        [ VarP $ mkName $ "var" ++ show (i :: Int)-        , mkPat rest h-        ]-    go' x (_, StaticPiece _) = return x-    go' x (i, SlurpPiece y) = do-        let e = VarE (mkName $ "var" ++ show i)-        e' <- case y of-                Nothing -> return e-                Just _ -> do-                    fs <- [|fromSlurp|]-                    return $ fs `AppE` e-        return $ x `AppE` e'-    go' x (i, StringPiece y) = do-        let e = VarE $ mkName $ "var" ++ show i-        e' <- case y of-                Nothing -> return e-                Just _ -> do-                    fs <- [|fromString|]-                    return $ fs `AppE` e-        return $ x `AppE` e'-    go' x (i, IntPiece _) = do-        re <- [|fromInteger . read|]-        return $ x `AppE` (re `AppE` VarE (mkName $ "var" ++ show i))-    checkInts [] = [|True|]-    checkInts ((i, IntPiece _):rest) = do-        ii <- [|isInt|]-        a <- [|(&&)|]-        rest' <- checkInts rest-        return $ a `AppE` (ii `AppE` VarE (mkName $ "var" ++ show i))-                   `AppE` rest'-    checkInts (_:rest) = checkInts rest+    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) [] --- | 'fmap' for 'Either'-fmape :: (a -> b) -> Either l a -> Either l b-fmape _ (Left l) = Left l-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 -isInt :: String -> Bool-isInt [] = False-isInt ('-':rest) = all isDigit rest-isInt x = all isDigit x+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'.@@ -449,28 +416,16 @@         x' <- lift x         xs' <- mkBod xs h         return $ ConE (mkName ":") `AppE` x' `AppE` xs'-    mkBod ((i, StringPiece y):xs) h = do+    mkBod ((i, SinglePiece _):xs) h = do         let x' = VarE $ mkName $ "var" ++ show i-        x'' <- case y of-                Nothing -> return x'-                Just _ -> do-                    ts <- [|toString|]-                    return $ ts `AppE` x'+        tsp <- [|toSinglePiece|]+        let x'' = tsp `AppE` x'         xs' <- mkBod xs h         return $ ConE (mkName ":") `AppE` x'' `AppE` xs'-    mkBod ((i, IntPiece _):xs) h= do-        sh <- [|show . (fromIntegral :: Integral i => i -> Integer)|]-        let x' = AppE sh $ VarE $ mkName $ "var" ++ show i-        xs' <- mkBod xs h-        return $ ConE (mkName ":") `AppE` x' `AppE` xs'-    mkBod ((i, SlurpPiece y):_) _ = do+    mkBod ((i, MultiPiece _):_) _ = do         let x' = VarE $ mkName $ "var" ++ show i-        x'' <- case y of-                Nothing -> return x'-                Just _ -> do-                    ts <- [|toSlurp|]-                    return $ ts `AppE` x'-        return x''+        tmp <- [|toMultiPiece|]+        return $ tmp `AppE` x'  -- | Generate the set of clauses necesary to dispatch the given 'Resource's. -- See 'quasiDispatch'.@@ -695,36 +650,54 @@                 [ Resource "Foo" [] $ Single "foo"                 , Resource "Bar" [] $ SubSite "a" "b" "c"                 ]-    assertBool "int + slurp versus empty" $ null $ findOverlaps+    assertBool "static + slurp versus empty" $ null $ findOverlaps                 [ Resource "Foo" [] $ Single "foo"-                , Resource "Bar" [IntPiece ""] $ SubSite "a" "b" "c"+                , Resource "Bar" [StaticPiece "5"] $ SubSite "a" "b" "c"                 ]  caseComplete :: Assertion caseComplete = do     assertBool "empty" $ not $ areResourcesComplete []     assertBool "slurp" $ areResourcesComplete-                [ Resource "Foo" [SlurpPiece "foo"] $ Single "foo"+                [ Resource "Foo" [MultiPiece "Foos"] $ Single "foo"                 ]     assertBool "subsite" $ areResourcesComplete                 [ Resource "Foo" [] $ SubSite "a" "b" "c"                 ]     assertBool "string + subsite" $ areResourcesComplete-                [ Resource "Foo" [StringPiece "x"] $ SubSite "a" "b" "c"+                [ Resource "Foo" [SinglePiece "Foo"] $ SubSite "a" "b" "c"                 , Resource "Bar" [] $ Single "bar"                 ]-    assertBool "int + subsite" $ not $ areResourcesComplete-                [ Resource "Foo" [IntPiece "x"] $ SubSite "a" "b" "c"+    assertBool "static + subsite" $ not $ areResourcesComplete+                [ Resource "Foo" [StaticPiece "foo"] $ SubSite "a" "b" "c"                 ]     assertBool "two pieces" $ not $ areResourcesComplete-                [ Resource "Foo" [StringPiece "x"] $ Single "foo"-                , Resource "Bar" [IntPiece "x"] $ SubSite "a" "b" "c"+                [ Resource "Foo" [SinglePiece "Foo"] $ Single "foo"+                , Resource "Bar" [StaticPiece "foo"] $ SubSite "a" "b" "c"                 ] #endif -class IsString s => ToString s where-    toString :: s -> String+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 -class IsSlurp s where-    fromSlurp :: [String] -> s-    toSlurp :: s -> [String]+class MultiPiece s where+    fromMultiPiece :: [String] -> Either String s+    toMultiPiece :: s -> [String]+instance MultiPiece [String] where+    fromMultiPiece = Right+    toMultiPiece = id+type Strings = [String]
runtests.hs view
@@ -18,8 +18,8 @@ import Data.ByteString.Lazy.Char8 (pack) import Data.ByteString.Char8 (unpack) import Web.Routes.Site-import Web.Encodings import Language.Haskell.TH.Syntax+import Web.Encodings  data StaticArgs = StaticArgs @@ -56,10 +56,10 @@     , crExplode = VarE $ mkName "explode"     , crResources = [$parseRoutes| /                    Home       GET-/user/#userid        User       GET PUT DELETE+/user/#Integer       User       GET PUT DELETE /static              Static     StaticArgs siteStatic getStaticArgs-/foo/*slurp          Foo-/bar/$barparam       Bar+/foo/*Strings        Foo+/bar/#String         Bar |]     , crSite = mkName "theSite"     , crMaster = Left $ ConT ''Int
web-routes-quasi.cabal view
@@ -1,5 +1,5 @@ name:            web-routes-quasi-version:         0.2.0+version:         0.3.0 license:         BSD3 license-file:    LICENSE author:          Michael Snoyman <michael@snoyman.com>@@ -27,9 +27,13 @@         Buildable: True         cpp-options:   -DTEST         build-depends: QuickCheck >= 2 && < 3,-                       web-encodings,+                       HUnit,+                       test-framework,+                       test-framework-hunit,+                       test-framework-quickcheck2,                        bytestring,                        wai,+                       web-encodings >= 0.2.6.1 && < 0.3,                        wai-extra     else         Buildable: False