support static page route parsing

This commit is contained in:
Greg Weber 2012-02-04 18:24:17 -06:00 committed by gregwebs
parent 010e9425b2
commit 60a6df9765
5 changed files with 131 additions and 12 deletions

View File

@ -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.

View File

@ -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
] ]

View 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
]

View File

@ -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

View File

@ -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