From 16260f3e37918940b751d8ab1543f046ca4692ce Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sun, 21 Apr 2013 17:13:41 +0300 Subject: [PATCH] RouteAttrs (fixes #531) --- yesod-core/Yesod/Core.hs | 1 + yesod-core/Yesod/Core/Internal/TH.hs | 2 ++ yesod-core/test/YesodCoreTest/Reps.hs | 15 +++++++-- yesod-routes/Yesod/Routes/Class.hs | 5 +++ yesod-routes/Yesod/Routes/TH.hs | 2 ++ yesod-routes/Yesod/Routes/TH/RouteAttrs.hs | 38 ++++++++++++++++++++++ yesod-routes/test/main.hs | 23 +++++++++++-- yesod-routes/yesod-routes.cabal | 1 + 8 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 yesod-routes/Yesod/Routes/TH/RouteAttrs.hs diff --git a/yesod-core/Yesod/Core.hs b/yesod-core/Yesod/Core.hs index 2ed8a7f2..40de8dd3 100644 --- a/yesod-core/Yesod/Core.hs +++ b/yesod-core/Yesod/Core.hs @@ -11,6 +11,7 @@ module Yesod.Core , YesodSubDispatch (..) , RenderRoute (..) , ParseRoute (..) + , RouteAttrs (..) -- ** Breadcrumbs , YesodBreadcrumbs (..) , breadcrumbs diff --git a/yesod-core/Yesod/Core/Internal/TH.hs b/yesod-core/Yesod/Core/Internal/TH.hs index c66a1849..7e84c1cb 100644 --- a/yesod-core/Yesod/Core/Internal/TH.hs +++ b/yesod-core/Yesod/Core/Internal/TH.hs @@ -66,6 +66,7 @@ mkYesodGeneral :: String -- ^ foundation type -> Q([Dec],[Dec]) mkYesodGeneral name args isSub resS = do renderRouteDec <- mkRenderRouteInstance site res + routeAttrsDec <- mkRouteAttrsInstance site res dispatchDec <- mkDispatchInstance site res parse <- mkParseRouteInstance site res let rname = mkName $ "resources" ++ name @@ -77,6 +78,7 @@ mkYesodGeneral name args isSub resS = do let dataDec = concat [ [parse] , renderRouteDec + , [routeAttrsDec] , resourcesDec , if isSub then [] else masterTypeSyns site ] diff --git a/yesod-core/test/YesodCoreTest/Reps.hs b/yesod-core/test/YesodCoreTest/Reps.hs index 3c3e61e6..c1576797 100644 --- a/yesod-core/test/YesodCoreTest/Reps.hs +++ b/yesod-core/test/YesodCoreTest/Reps.hs @@ -12,12 +12,15 @@ import Data.Text (Text) import Data.Maybe (fromJust) import Data.Monoid (Endo (..)) import qualified Control.Monad.Trans.Writer as Writer +import qualified Data.Set as Set data App = App mkYesod "App" [parseRoutes| -/ HomeR GET +/ HomeR GET !home /json JsonR GET +/parent/#Int ParentR: + /#Text/child ChildR !child |] instance Yesod App @@ -40,6 +43,9 @@ getJsonR = selectRep $ do rep typeHtml "HTML" provideRep $ return $ object ["message" .= ("Invalid Login" :: Text)] +handleChildR :: Int -> Text -> Handler () +handleChildR _ _ = return () + testRequest :: Int -- ^ http status code -> Request -> ByteString -- ^ expected body @@ -63,7 +69,8 @@ acceptRequest accept = defaultRequest } specs :: Spec -specs = describe "selectRep" $ do +specs = do + describe "selectRep" $ do test "application/json" "JSON" test (S8.unpack typeJson) "JSON" test "text/xml" "XML" @@ -77,3 +84,7 @@ specs = describe "selectRep" $ do testRequest 406 (acceptRequest "text/foo") "no match found for accept header" test "text/*" "HTML" test "*/*" "HTML" + describe "routeAttrs" $ do + it "HomeR" $ routeAttrs HomeR `shouldBe` Set.singleton "home" + it "JsonR" $ routeAttrs JsonR `shouldBe` Set.empty + it "ChildR" $ routeAttrs (ParentR 5 $ ChildR "ignored") `shouldBe` Set.singleton "child" diff --git a/yesod-routes/Yesod/Routes/Class.hs b/yesod-routes/Yesod/Routes/Class.hs index bb87636b..9a9e8a41 100644 --- a/yesod-routes/Yesod/Routes/Class.hs +++ b/yesod-routes/Yesod/Routes/Class.hs @@ -3,9 +3,11 @@ module Yesod.Routes.Class ( RenderRoute (..) , ParseRoute (..) + , RouteAttrs (..) ) where import Data.Text (Text) +import Data.Set (Set) class Eq (Route a) => RenderRoute a where -- | The type-safe URLs associated with a site argument. @@ -14,3 +16,6 @@ class Eq (Route a) => RenderRoute a where class RenderRoute a => ParseRoute a where parseRoute :: ([Text], [(Text, Text)]) -> Maybe (Route a) + +class RenderRoute a => RouteAttrs a where + routeAttrs :: Route a -> Set Text diff --git a/yesod-routes/Yesod/Routes/TH.hs b/yesod-routes/Yesod/Routes/TH.hs index 3c9d8a8b..7b2e50b7 100644 --- a/yesod-routes/Yesod/Routes/TH.hs +++ b/yesod-routes/Yesod/Routes/TH.hs @@ -4,6 +4,7 @@ module Yesod.Routes.TH -- * Functions , module Yesod.Routes.TH.RenderRoute , module Yesod.Routes.TH.ParseRoute + , module Yesod.Routes.TH.RouteAttrs -- ** Dispatch , module Yesod.Routes.TH.Dispatch ) where @@ -11,4 +12,5 @@ module Yesod.Routes.TH import Yesod.Routes.TH.Types import Yesod.Routes.TH.RenderRoute import Yesod.Routes.TH.ParseRoute +import Yesod.Routes.TH.RouteAttrs import Yesod.Routes.TH.Dispatch diff --git a/yesod-routes/Yesod/Routes/TH/RouteAttrs.hs b/yesod-routes/Yesod/Routes/TH/RouteAttrs.hs new file mode 100644 index 00000000..539e038c --- /dev/null +++ b/yesod-routes/Yesod/Routes/TH/RouteAttrs.hs @@ -0,0 +1,38 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE RecordWildCards #-} +module Yesod.Routes.TH.RouteAttrs + ( mkRouteAttrsInstance + ) where + +import Yesod.Routes.TH.Types +import Yesod.Routes.Class +import Language.Haskell.TH.Syntax +import Data.Set (fromList) +import Data.Text (pack) + +mkRouteAttrsInstance :: Type -> [ResourceTree a] -> Q Dec +mkRouteAttrsInstance typ ress = do + clauses <- mapM (goTree id) ress + return $ InstanceD [] (ConT ''RouteAttrs `AppT` typ) + [ FunD 'routeAttrs $ concat clauses + ] + +goTree :: (Pat -> Pat) -> ResourceTree a -> Q [Clause] +goTree front (ResourceLeaf res) = fmap return $ goRes front res +goTree front (ResourceParent name pieces trees) = + fmap concat $ mapM (goTree front') trees + where + ignored = ((replicate toIgnore WildP ++) . return) + toIgnore = length $ filter (isDynamic . snd) pieces + isDynamic Dynamic{} = True + isDynamic Static{} = False + front' = front . ConP (mkName name) . ignored + +goRes :: (Pat -> Pat) -> Resource a -> Q Clause +goRes front Resource {..} = + return $ Clause + [front $ RecP (mkName resourceName) []] + (NormalB $ VarE 'fromList `AppE` ListE (map toText resourceAttrs)) + [] + where + toText s = VarE 'pack `AppE` LitE (StringL s) diff --git a/yesod-routes/test/main.hs b/yesod-routes/test/main.hs index 9a45d502..38fe652b 100644 --- a/yesod-routes/test/main.hs +++ b/yesod-routes/test/main.hs @@ -23,6 +23,7 @@ import Yesod.Routes.TH hiding (Dispatch) import Language.Haskell.TH.Syntax import Hierarchy import qualified Data.ByteString.Char8 as S8 +import qualified Data.Set as Set result :: ([Text] -> Maybe Int) -> Dispatch Int result f ts = f ts @@ -105,15 +106,24 @@ getMySubParam _ = MySubParam do texts <- [t|[Text]|] - let ress = map ResourceLeaf - [ Resource "RootR" [] (Methods Nothing ["GET"]) [] + let resLeaves = map ResourceLeaf + [ Resource "RootR" [] (Methods Nothing ["GET"]) ["foo", "bar"] , Resource "BlogPostR" (addCheck [Static "blog", Dynamic $ ConT ''Text]) (Methods Nothing ["GET", "POST"]) [] , Resource "WikiR" (addCheck [Static "wiki"]) (Methods (Just texts) []) [] , Resource "SubsiteR" (addCheck [Static "subsite"]) (Subsite (ConT ''MySub) "getMySub") [] , Resource "SubparamR" (addCheck [Static "subparam", Dynamic $ ConT ''Int]) (Subsite (ConT ''MySubParam) "getMySubParam") [] ] + resParent = ResourceParent + "ParentR" + [ (True, Static "foo") + , (True, Dynamic $ ConT ''Text) + ] + [ ResourceLeaf $ Resource "ChildR" [] (Methods Nothing ["GET"]) ["child"] + ] + ress = resParent : resLeaves addCheck = map ((,) True) rrinst <- mkRenderRouteInstance (ConT ''MyApp) ress + rainst <- mkRouteAttrsInstance (ConT ''MyApp) ress prinst <- mkParseRouteInstance (ConT ''MyApp) ress dispatch <- mkDispatchClause MkDispatchSettings { mdsRunHandler = [|runHandler|] @@ -133,6 +143,7 @@ do `AppT` ConT ''MyApp) [FunD (mkName "dispatcher") [dispatch]] : prinst + : rainst : rrinst instance Dispatcher MySub master where @@ -336,6 +347,11 @@ main = hspec $ do /bar/baz Foo3 |] findOverlapNames routes @?= [] + describe "routeAttrs" $ do + it "works" $ do + routeAttrs RootR @?= Set.fromList [pack "foo", pack "bar"] + it "hierarchy" $ do + routeAttrs (ParentR (pack "ignored") ChildR) @?= Set.singleton (pack "child") hierarchy getRootR :: Text @@ -349,3 +365,6 @@ postBlogPostR t = pack $ "POST some blog post: " ++ unpack t handleWikiR :: [Text] -> String handleWikiR ts = "the wiki: " ++ show ts + +getChildR :: Text -> Text +getChildR = id diff --git a/yesod-routes/yesod-routes.cabal b/yesod-routes/yesod-routes.cabal index a9d8f800..5f6a2f14 100644 --- a/yesod-routes/yesod-routes.cabal +++ b/yesod-routes/yesod-routes.cabal @@ -30,6 +30,7 @@ library other-modules: Yesod.Routes.TH.Dispatch Yesod.Routes.TH.RenderRoute Yesod.Routes.TH.ParseRoute + Yesod.Routes.TH.RouteAttrs Yesod.Routes.TH.Types ghc-options: -Wall