support static page route parsing
This commit is contained in:
parent
010e9425b2
commit
60a6df9765
@ -17,6 +17,7 @@ module Yesod.Dispatch
|
|||||||
, mkYesodSubData
|
, mkYesodSubData
|
||||||
, mkYesodDispatch
|
, mkYesodDispatch
|
||||||
, mkYesodSubDispatch
|
, mkYesodSubDispatch
|
||||||
|
, mkYesodStaticPages
|
||||||
-- ** Path pieces
|
-- ** Path pieces
|
||||||
, PathPiece (..)
|
, PathPiece (..)
|
||||||
, PathMultiPiece (..)
|
, PathMultiPiece (..)
|
||||||
@ -53,9 +54,29 @@ import Network.HTTP.Types (status301)
|
|||||||
import Yesod.Routes.TH
|
import Yesod.Routes.TH
|
||||||
import Yesod.Content (chooseRep)
|
import Yesod.Content (chooseRep)
|
||||||
import Yesod.Routes.Parse
|
import Yesod.Routes.Parse
|
||||||
|
import Data.List (partition)
|
||||||
|
|
||||||
type Texts = [Text]
|
type Texts = [Text]
|
||||||
|
|
||||||
|
mkYesodStaticPages :: String -> [StaticPageRoute] -> Q [Dec]
|
||||||
|
mkYesodStaticPages name routes =
|
||||||
|
let (staticGets, staticResources) = partition isGet routes
|
||||||
|
gets = ListE (map (LitE . StringL . toString) staticGets)
|
||||||
|
getsD = ValD (VarP (mkName "staticPageRoutePaths")) (NormalB gets) []
|
||||||
|
in do yesod <- mkYesod name (map toResource staticResources)
|
||||||
|
return $ [getsD] ++ yesod
|
||||||
|
|
||||||
|
where
|
||||||
|
isGet (StaticGet _) = True
|
||||||
|
isGet (StaticResource _) = False
|
||||||
|
toResource (StaticResource r) = r
|
||||||
|
toResource (StaticGet _) = error "expected resource"
|
||||||
|
toString (StaticGet str) = dropSlashes str
|
||||||
|
toString (StaticResource _) = error "did not expect resource"
|
||||||
|
dropSlashes str | last str == '/' = dropSlashes $ init str
|
||||||
|
| head str == '/' = dropSlashes $ tail str
|
||||||
|
| otherwise = str
|
||||||
|
|
||||||
-- | Generates URL datatype and site function for the given 'Resource's. This
|
-- | Generates URL datatype and site function for the given 'Resource's. This
|
||||||
-- is used for creating sites, /not/ subsites. See 'mkYesodSub' for the latter.
|
-- is used for creating sites, /not/ subsites. See 'mkYesodSub' for the latter.
|
||||||
-- Use 'parseRoutes' to create the 'Resource's.
|
-- Use 'parseRoutes' to create the 'Resource's.
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import YesodCoreTest.Cache
|
|||||||
import qualified YesodCoreTest.WaiSubsite as WaiSubsite
|
import qualified YesodCoreTest.WaiSubsite as WaiSubsite
|
||||||
import qualified YesodCoreTest.Redirect as Redirect
|
import qualified YesodCoreTest.Redirect as Redirect
|
||||||
import qualified YesodCoreTest.JsLoader as JsLoader
|
import qualified YesodCoreTest.JsLoader as JsLoader
|
||||||
|
import qualified YesodCoreTest.StaticPages as StaticPages
|
||||||
|
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
|
|
||||||
@ -29,4 +30,5 @@ specs =
|
|||||||
, WaiSubsite.specs
|
, WaiSubsite.specs
|
||||||
, Redirect.specs
|
, Redirect.specs
|
||||||
, JsLoader.specs
|
, JsLoader.specs
|
||||||
|
, StaticPages.specs
|
||||||
]
|
]
|
||||||
|
|||||||
33
yesod-core/test/YesodCoreTest/StaticPages.hs
Normal file
33
yesod-core/test/YesodCoreTest/StaticPages.hs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{-# OPTIONS_GHC -fno-warn-unused-binds #-}
|
||||||
|
{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
module YesodCoreTest.StaticPages (specs) where
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import Test.Hspec.HUnit ()
|
||||||
|
import Test.HUnit
|
||||||
|
|
||||||
|
import Yesod.Core
|
||||||
|
import Yesod.Routes.Parse (staticPageRoutes)
|
||||||
|
|
||||||
|
data StaticPages = StaticPages
|
||||||
|
|
||||||
|
mkYesodStaticPages "StaticPages" [staticPageRoutes|
|
||||||
|
/pages/ PageR
|
||||||
|
/pages/
|
||||||
|
about
|
||||||
|
data
|
||||||
|
faq
|
||||||
|
|]
|
||||||
|
|
||||||
|
instance Yesod StaticPages where approot _ = ""
|
||||||
|
|
||||||
|
handlePageR :: Handler RepHtml
|
||||||
|
handlePageR = defaultLayout [whamlet|Hello World!|]
|
||||||
|
|
||||||
|
specs :: [Spec]
|
||||||
|
specs = describe "staticPageRoutePaths" [
|
||||||
|
it "lists static page routes" $
|
||||||
|
["pages","pages/about","pages/data","pages/faq"] @=? staticPageRoutePaths
|
||||||
|
]
|
||||||
@ -127,6 +127,7 @@ test-suite tests
|
|||||||
, random
|
, random
|
||||||
,HUnit
|
,HUnit
|
||||||
,QuickCheck >= 2 && < 3
|
,QuickCheck >= 2 && < 3
|
||||||
|
, yesod-routes >= 0.0 && < 0.1
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
|
|
||||||
source-repository head
|
source-repository head
|
||||||
|
|||||||
@ -7,11 +7,15 @@ module Yesod.Routes.Parse
|
|||||||
, parseRoutesNoCheck
|
, parseRoutesNoCheck
|
||||||
, parseRoutesFileNoCheck
|
, parseRoutesFileNoCheck
|
||||||
, parseType
|
, parseType
|
||||||
|
, staticPageRoutes
|
||||||
|
, staticPageRoutesFile
|
||||||
|
, StaticPageRoute (..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Language.Haskell.TH.Syntax
|
import Language.Haskell.TH.Syntax
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
import Data.Char (isUpper)
|
import Data.Char (isUpper, isSpace)
|
||||||
|
import Data.List (intercalate)
|
||||||
import Language.Haskell.TH.Quote
|
import Language.Haskell.TH.Quote
|
||||||
import qualified System.IO as SIO
|
import qualified System.IO as SIO
|
||||||
import Yesod.Routes.TH
|
import Yesod.Routes.TH
|
||||||
@ -49,24 +53,82 @@ readUtf8File fp = do
|
|||||||
-- | Same as 'parseRoutes', but performs no overlap checking.
|
-- | Same as 'parseRoutes', but performs no overlap checking.
|
||||||
parseRoutesNoCheck :: QuasiQuoter
|
parseRoutesNoCheck :: QuasiQuoter
|
||||||
parseRoutesNoCheck = QuasiQuoter
|
parseRoutesNoCheck = QuasiQuoter
|
||||||
{ quoteExp = lift . resourcesFromString
|
{ quoteExp = lift . resourcesFromString }
|
||||||
}
|
|
||||||
|
-- | QuasiQuoter for 'staticPageRoutesFromString'
|
||||||
|
staticPageRoutes :: QuasiQuoter
|
||||||
|
staticPageRoutes = QuasiQuoter
|
||||||
|
{ quoteExp = lift . staticPageRoutesFromString }
|
||||||
|
|
||||||
|
-- | parse a file with 'staticPageRoutesFromString'
|
||||||
|
staticPageRoutesFile :: FilePath -> Q Exp
|
||||||
|
staticPageRoutesFile fp = do
|
||||||
|
s <- qRunIO $ readUtf8File fp
|
||||||
|
quoteExp staticPageRoutes s
|
||||||
|
|
||||||
|
data StaticPageRoute = StaticGet String | StaticResource (Resource String)
|
||||||
|
instance Lift StaticPageRoute where
|
||||||
|
lift (StaticGet str) = [|StaticGet $(lift str)|]
|
||||||
|
lift (StaticResource r) = [|StaticResource $(lift r)|]
|
||||||
|
|
||||||
|
-- | Convert a multi-line string to a set of routes.
|
||||||
|
-- like normal route parsing, but there are just route paths, no route constructors
|
||||||
|
-- This is a partial function which calls 'error' on invalid input.
|
||||||
|
staticPageRoutesFromString :: String -> [StaticPageRoute]
|
||||||
|
staticPageRoutesFromString = parseRoutesFromString staticPageRoute
|
||||||
|
where
|
||||||
|
staticPageRoute r [] = Just (StaticGet r)
|
||||||
|
staticPageRoute r rest = fmap StaticResource $ resourceFromLine r rest
|
||||||
|
|
||||||
-- | Convert a multi-line string to a set of resources. See documentation for
|
-- | 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
|
-- the format of this string. This is a partial function which calls 'error' on
|
||||||
-- invalid input.
|
-- invalid input.
|
||||||
resourcesFromString :: String -> [Resource String]
|
resourcesFromString :: String -> [Resource String]
|
||||||
resourcesFromString =
|
resourcesFromString =
|
||||||
mapMaybe go . lines
|
parseRoutesFromString justResourceFromLine
|
||||||
|
|
||||||
|
resourceFromLine :: String -> [String] -> Maybe (Resource String)
|
||||||
|
resourceFromLine fullRoute (constr:rest) =
|
||||||
|
let (pieces, mmulti) = piecesFromString $ drop1Slash fullRoute
|
||||||
|
disp = dispatchFromString rest mmulti
|
||||||
|
in Just $ Resource constr pieces disp
|
||||||
|
resourceFromLine _ [] = Nothing -- an indenter: there should be indented routes afterwards
|
||||||
|
|
||||||
|
|
||||||
|
justResourceFromLine :: String -> [String] -> Maybe (Resource String)
|
||||||
|
justResourceFromLine x xs =
|
||||||
|
case resourceFromLine x xs of
|
||||||
|
Nothing -> error $ "Invalid resource line: " ++ (intercalate " " (x:xs))
|
||||||
|
r -> r
|
||||||
|
|
||||||
|
-- | used by 'resourcesFromString' and 'staticPageRoutesFromString'
|
||||||
|
parseRoutesFromString :: (String -- ^ route pattern
|
||||||
|
-> [String] -- ^ extra
|
||||||
|
-> Maybe a)
|
||||||
|
-> String -- ^ unparsed routes
|
||||||
|
-> [a]
|
||||||
|
parseRoutesFromString mkRoute =
|
||||||
|
catMaybes . (parseLines $ error "first route cannot be indented") . lines
|
||||||
where
|
where
|
||||||
go s =
|
indents :: String -> Int
|
||||||
case takeWhile (/= "--") $ words s of
|
indents = length . takeWhile isSpace
|
||||||
(pattern:constr:rest) ->
|
|
||||||
let (pieces, mmulti) = piecesFromString $ drop1Slash pattern
|
parseLines noIndent (l:ls) =
|
||||||
disp = dispatchFromString rest mmulti
|
case takeWhile (/= "--") $ words l of
|
||||||
in Just $ Resource constr pieces disp
|
(route:rest) ->
|
||||||
[] -> Nothing
|
let (newNoIndent, fullRoute) =
|
||||||
_ -> error $ "Invalid resource line: " ++ s
|
if indents l == 0
|
||||||
|
-- important: the check is done lazily
|
||||||
|
then (checkEndSlash route, route)
|
||||||
|
else (noIndent, noIndent ++ route)
|
||||||
|
in mkRoute fullRoute rest : parseLines newNoIndent ls
|
||||||
|
[] -> parseLines noIndent ls
|
||||||
|
parseLines _ [] = []
|
||||||
|
|
||||||
|
checkEndSlash route =
|
||||||
|
if last route /= '/'
|
||||||
|
then error "the route indenter was expected to have a slash: " ++ route
|
||||||
|
else route
|
||||||
|
|
||||||
dispatchFromString :: [String] -> Maybe String -> Dispatch String
|
dispatchFromString :: [String] -> Maybe String -> Dispatch String
|
||||||
dispatchFromString rest mmulti
|
dispatchFromString rest mmulti
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user