Route attributes/appcache example #518
This commit is contained in:
parent
fd15efa8dd
commit
64ef26104d
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,3 +8,4 @@ cabal-dev/
|
|||||||
yesod/foobar/
|
yesod/foobar/
|
||||||
.virthualenv
|
.virthualenv
|
||||||
/vendor/
|
/vendor/
|
||||||
|
/.shelly/
|
||||||
|
|||||||
60
demo/appcache/AppCache.hs
Normal file
60
demo/appcache/AppCache.hs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
module AppCache where
|
||||||
|
|
||||||
|
import Control.Monad (when)
|
||||||
|
import Control.Monad.Trans.Writer
|
||||||
|
import Data.Hashable (hashWithSalt)
|
||||||
|
import Data.List (intercalate)
|
||||||
|
import qualified Data.Set as Set
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Data.Text (pack)
|
||||||
|
import Language.Haskell.TH.Syntax
|
||||||
|
import Yesod.Core
|
||||||
|
import Yesod.Routes.TH
|
||||||
|
|
||||||
|
newtype AppCache = AppCache { unAppCache :: Text }
|
||||||
|
|
||||||
|
appCache :: [ResourceTree String] -> Q Exp
|
||||||
|
appCache trees = do
|
||||||
|
piecesSet <- execWriterT $ mapM_ (goTree id) trees
|
||||||
|
let body = unlines $ map toPath $ Set.toList piecesSet
|
||||||
|
hash = hashWithSalt 0 body
|
||||||
|
total = concat
|
||||||
|
[ "CACHE MANIFEST\n# Version: "
|
||||||
|
, show hash
|
||||||
|
, "\n\nCACHE:\n"
|
||||||
|
, body
|
||||||
|
]
|
||||||
|
[|return (AppCache (pack total))|]
|
||||||
|
where
|
||||||
|
toPath [] = "/"
|
||||||
|
toPath x = concatMap ('/':) x
|
||||||
|
|
||||||
|
goTree :: Monad m
|
||||||
|
=> ([String] -> [String])
|
||||||
|
-> ResourceTree String
|
||||||
|
-> WriterT (Set.Set [String]) m ()
|
||||||
|
goTree front (ResourceLeaf res) = do
|
||||||
|
pieces' <- goPieces (resourceName res) $ resourcePieces res
|
||||||
|
when ("CACHE" `elem` resourceAttrs res) $
|
||||||
|
tell $ Set.singleton $ front pieces'
|
||||||
|
goTree front (ResourceParent name pieces trees) = do
|
||||||
|
pieces' <- goPieces name pieces
|
||||||
|
mapM_ (goTree $ front . (pieces' ++)) trees
|
||||||
|
|
||||||
|
goPieces :: Monad m => String -> [(CheckOverlap, Piece String)] -> m [String]
|
||||||
|
goPieces name =
|
||||||
|
mapM (goPiece . snd)
|
||||||
|
where
|
||||||
|
goPiece (Static s) = return s
|
||||||
|
goPiece (Dynamic _) = fail $ concat
|
||||||
|
[ "AppCache only applies to fully-static paths, but "
|
||||||
|
, name
|
||||||
|
, " has dynamic pieces."
|
||||||
|
]
|
||||||
|
|
||||||
|
instance ToContent AppCache where
|
||||||
|
toContent = toContent . unAppCache
|
||||||
|
instance ToTypedContent AppCache where
|
||||||
|
toTypedContent = TypedContent "text/cache-manifest" . toContent
|
||||||
23
demo/appcache/Main.hs
Normal file
23
demo/appcache/Main.hs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
import AppCache
|
||||||
|
import Routes
|
||||||
|
import Yesod.Core
|
||||||
|
|
||||||
|
instance Yesod App
|
||||||
|
|
||||||
|
mkYesodDispatch "App" resourcesApp
|
||||||
|
|
||||||
|
getHomeR :: Handler String
|
||||||
|
getHomeR = return "Hello"
|
||||||
|
|
||||||
|
getSomethingR :: Handler String
|
||||||
|
getSomethingR = return "Hello"
|
||||||
|
|
||||||
|
getAppCacheR :: Handler AppCache
|
||||||
|
getAppCacheR = $(appCache resourcesApp)
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = warp 3000 App
|
||||||
15
demo/appcache/Routes.hs
Normal file
15
demo/appcache/Routes.hs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
module Routes where
|
||||||
|
|
||||||
|
import Yesod.Core
|
||||||
|
|
||||||
|
data App = App
|
||||||
|
|
||||||
|
mkYesodData "App" [parseRoutes|
|
||||||
|
/ HomeR GET
|
||||||
|
/some/thing SomethingR GET !CACHE
|
||||||
|
/appcache AppCacheR GET
|
||||||
|
|]
|
||||||
@ -44,13 +44,7 @@ mkYesodSubData name res = mkYesodDataGeneral name True res
|
|||||||
mkYesodDataGeneral :: String -> Bool -> [ResourceTree String] -> Q [Dec]
|
mkYesodDataGeneral :: String -> Bool -> [ResourceTree String] -> Q [Dec]
|
||||||
mkYesodDataGeneral name isSub res = do
|
mkYesodDataGeneral name isSub res = do
|
||||||
let (name':rest) = words name
|
let (name':rest) = words name
|
||||||
(x, _) <- mkYesodGeneral name' rest isSub res
|
fmap fst $ mkYesodGeneral name' rest isSub res
|
||||||
let rname = mkName $ "resources" ++ name
|
|
||||||
eres <- lift res
|
|
||||||
let y = [ SigD rname $ ListT `AppT` (ConT ''ResourceTree `AppT` ConT ''String)
|
|
||||||
, FunD rname [Clause [] (NormalB eres) []]
|
|
||||||
]
|
|
||||||
return $ x ++ y
|
|
||||||
|
|
||||||
-- | See 'mkYesodData'.
|
-- | See 'mkYesodData'.
|
||||||
mkYesodDispatch :: String -> [ResourceTree String] -> Q [Dec]
|
mkYesodDispatch :: String -> [ResourceTree String] -> Q [Dec]
|
||||||
@ -71,10 +65,22 @@ mkYesodGeneral :: String -- ^ foundation type
|
|||||||
-> [ResourceTree String]
|
-> [ResourceTree String]
|
||||||
-> Q([Dec],[Dec])
|
-> Q([Dec],[Dec])
|
||||||
mkYesodGeneral name args isSub resS = do
|
mkYesodGeneral name args isSub resS = do
|
||||||
renderRouteDec <- mkRenderRouteInstance site res
|
renderRouteDec <- mkRenderRouteInstance site res
|
||||||
dispatchDec <- mkDispatchInstance site res
|
dispatchDec <- mkDispatchInstance site res
|
||||||
parse <- mkParseRouteInstance site res
|
parse <- mkParseRouteInstance site res
|
||||||
return (parse : renderRouteDec ++ if isSub then [] else masterTypeSyns site, dispatchDec)
|
let rname = mkName $ "resources" ++ name
|
||||||
|
eres <- lift resS
|
||||||
|
let resourcesDec =
|
||||||
|
[ SigD rname $ ListT `AppT` (ConT ''ResourceTree `AppT` ConT ''String)
|
||||||
|
, FunD rname [Clause [] (NormalB eres) []]
|
||||||
|
]
|
||||||
|
let dataDec = concat
|
||||||
|
[ [parse]
|
||||||
|
, renderRouteDec
|
||||||
|
, resourcesDec
|
||||||
|
, if isSub then [] else masterTypeSyns site
|
||||||
|
]
|
||||||
|
return (dataDec, dispatchDec)
|
||||||
where site = foldl' AppT (ConT $ mkName name) (map (VarT . mkName) args)
|
where site = foldl' AppT (ConT $ mkName name) (map (VarT . mkName) args)
|
||||||
res = map (fmap parseType) resS
|
res = map (fmap parseType) resS
|
||||||
|
|
||||||
|
|||||||
@ -8,5 +8,5 @@ import Yesod.Core
|
|||||||
data Y = Y
|
data Y = Y
|
||||||
mkYesodData "Y" [parseRoutes|
|
mkYesodData "Y" [parseRoutes|
|
||||||
/ RootR GET
|
/ RootR GET
|
||||||
/static StaticR GET
|
/static StaticR !IGNORED GET !alsoIgnored
|
||||||
|]
|
|]
|
||||||
|
|||||||
@ -73,11 +73,22 @@ resourcesFromString =
|
|||||||
in ((ResourceParent (init constr) pieces children :), otherLines'')
|
in ((ResourceParent (init constr) pieces children :), otherLines'')
|
||||||
(pattern:constr:rest) ->
|
(pattern:constr:rest) ->
|
||||||
let (pieces, mmulti) = piecesFromString $ drop1Slash pattern
|
let (pieces, mmulti) = piecesFromString $ drop1Slash pattern
|
||||||
disp = dispatchFromString rest mmulti
|
(attrs, rest') = takeAttrs rest
|
||||||
in ((ResourceLeaf (Resource constr pieces disp):), otherLines)
|
disp = dispatchFromString rest' mmulti
|
||||||
|
in ((ResourceLeaf (Resource constr pieces disp attrs):), otherLines)
|
||||||
[] -> (id, otherLines)
|
[] -> (id, otherLines)
|
||||||
_ -> error $ "Invalid resource line: " ++ thisLine
|
_ -> error $ "Invalid resource line: " ++ thisLine
|
||||||
|
|
||||||
|
-- | Take attributes out of the list and put them in the first slot in the
|
||||||
|
-- result tuple.
|
||||||
|
takeAttrs :: [String] -> ([String], [String])
|
||||||
|
takeAttrs =
|
||||||
|
go id id
|
||||||
|
where
|
||||||
|
go x y [] = (x [], y [])
|
||||||
|
go x y (('!':attr):rest) = go (x . (attr:)) y rest
|
||||||
|
go x y (z:rest) = go x (y . (z:)) rest
|
||||||
|
|
||||||
dispatchFromString :: [String] -> Maybe String -> Dispatch String
|
dispatchFromString :: [String] -> Maybe String -> Dispatch String
|
||||||
dispatchFromString rest mmulti
|
dispatchFromString rest mmulti
|
||||||
| null rest = Methods mmulti []
|
| null rest = Methods mmulti []
|
||||||
|
|||||||
@ -39,16 +39,17 @@ data Resource typ = Resource
|
|||||||
{ resourceName :: String
|
{ resourceName :: String
|
||||||
, resourcePieces :: [(CheckOverlap, Piece typ)]
|
, resourcePieces :: [(CheckOverlap, Piece typ)]
|
||||||
, resourceDispatch :: Dispatch typ
|
, resourceDispatch :: Dispatch typ
|
||||||
|
, resourceAttrs :: [String]
|
||||||
}
|
}
|
||||||
deriving Show
|
deriving Show
|
||||||
|
|
||||||
type CheckOverlap = Bool
|
type CheckOverlap = Bool
|
||||||
|
|
||||||
instance Functor Resource where
|
instance Functor Resource where
|
||||||
fmap f (Resource a b c) = Resource a (map (second $ fmap f) b) (fmap f c)
|
fmap f (Resource a b c d) = Resource a (map (second $ fmap f) b) (fmap f c) d
|
||||||
|
|
||||||
instance Lift t => Lift (Resource t) where
|
instance Lift t => Lift (Resource t) where
|
||||||
lift (Resource a b c) = [|Resource $(lift a) $(lift b) $(lift c)|]
|
lift (Resource a b c d) = [|Resource a b c d|]
|
||||||
|
|
||||||
data Piece typ = Static String | Dynamic typ
|
data Piece typ = Static String | Dynamic typ
|
||||||
deriving Show
|
deriving Show
|
||||||
@ -91,6 +92,6 @@ flatten :: [ResourceTree a] -> [FlatResource a]
|
|||||||
flatten =
|
flatten =
|
||||||
concatMap (go id)
|
concatMap (go id)
|
||||||
where
|
where
|
||||||
go front (ResourceLeaf (Resource a b c)) = [FlatResource (front []) a b c]
|
go front (ResourceLeaf (Resource a b c _)) = [FlatResource (front []) a b c]
|
||||||
go front (ResourceParent name pieces children) =
|
go front (ResourceParent name pieces children) =
|
||||||
concatMap (go (front . ((name, pieces):))) children
|
concatMap (go (front . ((name, pieces):))) children
|
||||||
|
|||||||
@ -106,11 +106,11 @@ getMySubParam _ = MySubParam
|
|||||||
do
|
do
|
||||||
texts <- [t|[Text]|]
|
texts <- [t|[Text]|]
|
||||||
let ress = map ResourceLeaf
|
let ress = map ResourceLeaf
|
||||||
[ Resource "RootR" [] $ Methods Nothing ["GET"]
|
[ Resource "RootR" [] (Methods Nothing ["GET"]) []
|
||||||
, Resource "BlogPostR" (addCheck [Static "blog", Dynamic $ ConT ''Text]) $ Methods Nothing ["GET", "POST"]
|
, Resource "BlogPostR" (addCheck [Static "blog", Dynamic $ ConT ''Text]) (Methods Nothing ["GET", "POST"]) []
|
||||||
, Resource "WikiR" (addCheck [Static "wiki"]) $ Methods (Just texts) []
|
, Resource "WikiR" (addCheck [Static "wiki"]) (Methods (Just texts) []) []
|
||||||
, Resource "SubsiteR" (addCheck [Static "subsite"]) $ Subsite (ConT ''MySub) "getMySub"
|
, Resource "SubsiteR" (addCheck [Static "subsite"]) (Subsite (ConT ''MySub) "getMySub") []
|
||||||
, Resource "SubparamR" (addCheck [Static "subparam", Dynamic $ ConT ''Int]) $ Subsite (ConT ''MySubParam) "getMySubParam"
|
, Resource "SubparamR" (addCheck [Static "subparam", Dynamic $ ConT ''Int]) (Subsite (ConT ''MySubParam) "getMySubParam") []
|
||||||
]
|
]
|
||||||
addCheck = map ((,) True)
|
addCheck = map ((,) True)
|
||||||
rrinst <- mkRenderRouteInstance (ConT ''MyApp) ress
|
rrinst <- mkRenderRouteInstance (ConT ''MyApp) ress
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user