Merge branch 'master' of git@github.com:snoyberg/yesod

This commit is contained in:
Michael Snoyman 2010-01-24 23:32:55 +02:00
commit f8fab1c81e
13 changed files with 116 additions and 96 deletions

View File

@ -19,6 +19,7 @@ module Data.Object.Html
( -- * Data type ( -- * Data type
Html (..) Html (..)
, HtmlDoc (..) , HtmlDoc (..)
, HtmlFragment (..)
, HtmlObject , HtmlObject
-- * XML helpers -- * XML helpers
, XmlDoc (..) , XmlDoc (..)
@ -26,6 +27,8 @@ module Data.Object.Html
-- * Standard 'Object' functions -- * Standard 'Object' functions
, toHtmlObject , toHtmlObject
, fromHtmlObject , fromHtmlObject
-- * Re-export
, module Data.Object
#if TEST #if TEST
, testSuite , testSuite
#endif #endif
@ -35,11 +38,12 @@ import Data.Generics
import Data.Object.Text import Data.Object.Text
import Data.Object.Json import Data.Object.Json
import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy as TL
import Data.ByteString.Lazy (ByteString) import qualified Data.Text as TS
import Web.Encodings import Web.Encodings
import Text.StringTemplate.Classes import Text.StringTemplate.Classes
import Control.Arrow (second) import Control.Arrow (second)
import Data.Attempt import Data.Attempt
import Data.Object
#if TEST #if TEST
import Test.Framework (testGroup, Test) import Test.Framework (testGroup, Test)
@ -50,8 +54,8 @@ import Text.StringTemplate
-- | A single piece of HTML code. -- | A single piece of HTML code.
data Html = data Html =
Html Text -- ^ Already encoded HTML. Html TS.Text -- ^ Already encoded HTML.
| Text Text -- ^ Text which should be HTML escaped. | Text TS.Text -- ^ Text which should be HTML escaped.
| Tag String [(String, String)] Html -- ^ Tag which needs a closing tag. | Tag String [(String, String)] Html -- ^ Tag which needs a closing tag.
| EmptyTag String [(String, String)] -- ^ Tag without a closing tag. | EmptyTag String [(String, String)] -- ^ Tag without a closing tag.
| HtmlList [Html] | HtmlList [Html]
@ -70,57 +74,68 @@ fromHtmlObject = ca
instance ConvertSuccess String Html where instance ConvertSuccess String Html where
convertSuccess = Text . cs convertSuccess = Text . cs
instance ConvertSuccess Text Html where instance ConvertSuccess TS.Text Html where
convertSuccess = Text convertSuccess = Text
instance ConvertSuccess Text Html where
convertSuccess = Text . cs
$(deriveAttempts $(deriveAttempts
[ (''String, ''Html) [ (''String, ''Html)
, (''Text, ''Html) , (''Text, ''Html)
, (''TS.Text, ''Html)
]) ])
showAttribs :: [(String, String)] -> Text instance ConvertSuccess String HtmlObject where
showAttribs = TL.concat . map helper where convertSuccess = Scalar . cs
helper :: (String, String) -> Text instance ConvertSuccess Text HtmlObject where
helper (k, v) = TL.concat convertSuccess = Scalar . cs
[ cs " " instance ConvertSuccess TS.Text HtmlObject where
, encodeHtml $ cs k convertSuccess = Scalar . cs
, cs "=\"" instance ConvertSuccess [(String, String)] HtmlObject where
, encodeHtml $ cs v convertSuccess = omTO
, cs "\"" instance ConvertSuccess [(Text, Text)] HtmlObject where
] convertSuccess = omTO
instance ConvertSuccess [(TS.Text, TS.Text)] HtmlObject where
convertSuccess = omTO
showAttribs :: [(String, String)] -> String -> String
showAttribs pairs rest = foldr ($) rest $ map helper pairs where
helper :: (String, String) -> String -> String
helper (k, v) rest' =
' ' : encodeHtml k
++ '=' : '"' : encodeHtml v
++ '"' : rest'
htmlToText :: Bool -- ^ True to close empty tags like XML, False like HTML htmlToText :: Bool -- ^ True to close empty tags like XML, False like HTML
-> Html -> Html
-> Text -> ([TS.Text] -> [TS.Text])
htmlToText _ (Html t) = t htmlToText _ (Html t) = (:) t
htmlToText _ (Text t) = encodeHtml t htmlToText _ (Text t) = (:) $ encodeHtml t
htmlToText xml (Tag n as content) = TL.concat htmlToText xml (Tag n as content) = \rest ->
[ cs "<" (cs $ '<' : n)
, cs n : (cs $ showAttribs as ">")
, showAttribs as : (htmlToText xml content
, cs ">" $ (cs $ '<' : '/' : n)
, htmlToText xml content : cs ">"
, cs "</" : rest)
, cs n htmlToText xml (EmptyTag n as) = \rest ->
, cs ">" (cs $ '<' : n )
] : (cs $ showAttribs as (if xml then "/>" else ">"))
htmlToText xml (EmptyTag n as) = TL.concat : rest
[ cs "<" htmlToText xml (HtmlList l) = \rest ->
, cs n foldr ($) rest $ map (htmlToText xml) l
, showAttribs as
, cs $ if xml then "/>" else ">"
]
htmlToText xml (HtmlList l) = TL.concat $ map (htmlToText xml) l
instance ConvertSuccess Html Text where newtype HtmlFragment = HtmlFragment { unHtmlFragment :: Text }
convertSuccess = htmlToText False instance ConvertSuccess Html HtmlFragment where
convertSuccess h = HtmlFragment . TL.fromChunks . htmlToText False h $ []
instance ConvertSuccess HtmlFragment Html where
convertSuccess = HtmlList . map Html . TL.toChunks . unHtmlFragment
-- | Not fully typesafe. You must make sure that when converting to this, the -- | Not fully typesafe. You must make sure that when converting to this, the
-- 'Html' starts with a tag. -- 'Html' starts with a tag.
newtype XmlDoc = XmlDoc { unXmlDoc :: Text } newtype XmlDoc = XmlDoc { unXmlDoc :: Text }
instance ConvertSuccess Html XmlDoc where instance ConvertSuccess Html XmlDoc where
convertSuccess h = XmlDoc $ TL.concat convertSuccess h = XmlDoc $ TL.fromChunks $
[ cs "<?xml version='1.0' encoding='utf-8' ?>\n" cs "<?xml version='1.0' encoding='utf-8' ?>\n"
, htmlToText True h : htmlToText True h []
]
-- | Wrap an 'Html' in CDATA for XML output. -- | Wrap an 'Html' in CDATA for XML output.
cdata :: Html -> Html cdata :: Html -> Html
@ -130,18 +145,11 @@ cdata h = HtmlList
, Html $ cs "]]>" , Html $ cs "]]>"
] ]
instance ConvertSuccess Html String where
convertSuccess = cs . (cs :: Html -> Text)
instance ConvertSuccess Html ByteString where
convertSuccess = cs . (cs :: Html -> Text)
instance ConvertSuccess Html HtmlDoc where instance ConvertSuccess Html HtmlDoc where
convertSuccess h = HtmlDoc $ TL.concat convertSuccess h = HtmlDoc $ TL.fromChunks $
[ cs "<!DOCTYPE html><html><head><title>HtmlDoc (autogenerated)" cs "<!DOCTYPE html>\n<html><head><title>HtmlDoc (autogenerated)</title></head><body>"
, cs "</title></head><body>" : htmlToText False h
, cs h [cs "</body></html>"]
, cs "</body></html>"
]
instance ConvertSuccess HtmlObject Html where instance ConvertSuccess HtmlObject Html where
convertSuccess (Scalar h) = h convertSuccess (Scalar h) = h
@ -159,25 +167,24 @@ instance ConvertSuccess HtmlObject HtmlDoc where
convertSuccess = cs . (cs :: HtmlObject -> Html) convertSuccess = cs . (cs :: HtmlObject -> Html)
instance ConvertSuccess Html JsonScalar where instance ConvertSuccess Html JsonScalar where
convertSuccess = cs . (cs :: Html -> Text) convertSuccess = cs . unHtmlFragment . cs
instance ConvertSuccess HtmlObject JsonObject where instance ConvertSuccess HtmlObject JsonObject where
convertSuccess = mapKeysValues convertSuccess convertSuccess convertSuccess = mapKeysValues convertSuccess convertSuccess
instance ConvertSuccess HtmlObject JsonDoc where instance ConvertSuccess HtmlObject JsonDoc where
convertSuccess = cs . (cs :: HtmlObject -> JsonObject) convertSuccess = cs . (cs :: HtmlObject -> JsonObject)
$(deriveAttempts $(deriveAttempts
[ (''Html, ''String) [ (''Html, ''HtmlFragment)
, (''Html, ''Text)
, (''Html, ''HtmlDoc) , (''Html, ''HtmlDoc)
, (''Html, ''JsonScalar) , (''Html, ''JsonScalar)
]) ])
$(deriveSuccessConvs ''String ''Html $(deriveSuccessConvs ''String ''Html
[''String, ''Text] [''String, ''Text]
[''Html, ''String, ''Text]) [''Html, ''HtmlFragment])
instance ToSElem HtmlObject where instance ToSElem HtmlObject where
toSElem (Scalar h) = STR $ TL.unpack $ cs h toSElem (Scalar h) = STR $ TL.unpack $ unHtmlFragment $ cs h
toSElem (Sequence hs) = LI $ map toSElem hs toSElem (Sequence hs) = LI $ map toSElem hs
toSElem (Mapping pairs) = helper $ map (second toSElem) pairs where toSElem (Mapping pairs) = helper $ map (second toSElem) pairs where
helper :: [(String, SElem b)] -> SElem b helper :: [(String, SElem b)] -> SElem b

View File

@ -24,7 +24,6 @@ module Yesod
, module Yesod.Parameter , module Yesod.Parameter
, module Yesod.Rep , module Yesod.Rep
, module Yesod.Template , module Yesod.Template
, module Data.Convertible.Text
, Application , Application
) where ) where
@ -48,4 +47,3 @@ import Yesod.Definitions
import Yesod.Handler import Yesod.Handler
import Hack (Application) import Hack (Application)
import Yesod.Template import Yesod.Template
import Data.Convertible.Text

View File

@ -17,7 +17,7 @@
module Yesod.Definitions module Yesod.Definitions
( Verb (..) ( Verb (..)
, Resource , Resource
, Approot (..) , Approot
, Language , Language
, Location (..) , Location (..)
, showLocation , showLocation
@ -55,7 +55,7 @@ type Resource = [String]
-- | An absolute URL to the base of this application. This can almost be done -- | An absolute URL to the base of this application. This can almost be done
-- programatically, but due to ambiguities in different ways of doing URL -- programatically, but due to ambiguities in different ways of doing URL
-- rewriting for (fast)cgi applications, it should be supplied by the user. -- rewriting for (fast)cgi applications, it should be supplied by the user.
newtype Approot = Approot { unApproot :: String } -- FIXME make type syn? type Approot = String
type Language = String type Language = String
@ -66,6 +66,6 @@ data Location = AbsLoc String | RelLoc String
-- | Display a 'Location' in absolute form. -- | Display a 'Location' in absolute form.
showLocation :: Approot -> Location -> String showLocation :: Approot -> Location -> String
showLocation _ (AbsLoc s) = s showLocation _ (AbsLoc s) = s
showLocation (Approot ar) (RelLoc s) = ar ++ s showLocation ar (RelLoc s) = ar ++ s
type PathInfo = [String] type PathInfo = [String]

View File

@ -49,7 +49,7 @@ class YesodApproot a => YesodAuth a where
getFullAuthRoot :: YesodAuth y => Handler y String getFullAuthRoot :: YesodAuth y => Handler y String
getFullAuthRoot = do getFullAuthRoot = do
y <- getYesod y <- getYesod
let (Approot ar) = approot y ar <- getApproot
return $ ar ++ authRoot y return $ ar ++ authRoot y
data AuthResource = data AuthResource =
@ -169,15 +169,14 @@ authCheck = do
authLogout :: YesodAuth y => Handler y HtmlObject authLogout :: YesodAuth y => Handler y HtmlObject
authLogout = do authLogout = do
deleteCookie authCookieName deleteCookie authCookieName
y <- getYesod ar <- getApproot
let (Approot ar) = approot y
redirect ar redirect ar
-- FIXME check the DEST information -- FIXME check the DEST information
authIdentifier :: YesodAuth y => Handler y String authIdentifier :: YesodAuth y => Handler y String
authIdentifier = do authIdentifier = do
mi <- identifier mi <- identifier
Approot ar <- getApproot ar <- getApproot
case mi of case mi of
Nothing -> do Nothing -> do
rp <- requestPath rp <- requestPath

View File

@ -25,19 +25,25 @@ module Yesod.Helpers.Static
import qualified Data.ByteString.Lazy as B import qualified Data.ByteString.Lazy as B
import System.Directory (doesFileExist) import System.Directory (doesFileExist)
import Control.Applicative ((<$>)) import Control.Applicative ((<$>))
import Control.Monad
import Yesod import Yesod
import Data.List (intercalate) import Data.List (intercalate)
type FileLookup = FilePath -> IO (Maybe B.ByteString) type FileLookup = FilePath -> IO (Maybe B.ByteString)
-- | A 'FileLookup' for files in a directory. -- | A 'FileLookup' for files in a directory. Note that this function does not
-- check if the requested path does unsafe things, eg expose hidden files. You
-- should provide this checking elsewhere.
--
-- If you are just using this in combination with serveStatic, serveStatic
-- provides this checking.
fileLookupDir :: FilePath -> FileLookup fileLookupDir :: FilePath -> FileLookup
fileLookupDir dir fp = do fileLookupDir dir fp = do
let fp' = dir ++ '/' : fp -- FIXME incredibly insecure... let fp' = dir ++ '/' : fp
exists <- doesFileExist fp' exists <- doesFileExist fp'
if exists if exists
then Just <$> B.readFile fp' then Just <$> B.readFile fp' -- FIXME replace lazy I/O when possible
else return Nothing else return Nothing
serveStatic :: FileLookup -> Verb -> [String] serveStatic :: FileLookup -> Verb -> [String]
@ -47,11 +53,16 @@ serveStatic _ _ _ = notFound
getStatic :: FileLookup -> [String] -> Handler y [(ContentType, Content)] getStatic :: FileLookup -> [String] -> Handler y [(ContentType, Content)]
getStatic fl fp' = do getStatic fl fp' = do
let fp = intercalate "/" fp' -- FIXME check for . or .. when (any isUnsafe fp') $ notFound
let fp = intercalate "/" fp'
content <- liftIO $ fl fp content <- liftIO $ fl fp
case content of case content of
Nothing -> notFound Nothing -> notFound
Just bs -> return [(mimeType $ ext fp, Content bs)] Just bs -> return [(mimeType $ ext fp, Content bs)]
where
isUnsafe [] = True
isUnsafe ('.':_) = True
isUnsafe _ = False
mimeType :: String -> ContentType mimeType :: String -> ContentType
mimeType "jpg" = TypeJpeg mimeType "jpg" = TypeJpeg

View File

@ -57,7 +57,6 @@ import Data.Object.Html
#endif #endif
import Data.Object.Json import Data.Object.Json
import Data.Convertible.Text
import Text.StringTemplate import Text.StringTemplate
#if TEST #if TEST
@ -109,8 +108,8 @@ instance ConvertSuccess ByteString Content where
convertSuccess = Content convertSuccess = Content
instance ConvertSuccess String Content where instance ConvertSuccess String Content where
convertSuccess = Content . cs convertSuccess = Content . cs
instance ConvertSuccess Html Content where instance ConvertSuccess HtmlDoc Content where
convertSuccess = Content . cs convertSuccess = cs . unHtmlDoc
instance ConvertSuccess XmlDoc Content where instance ConvertSuccess XmlDoc Content where
convertSuccess = cs . unXmlDoc convertSuccess = cs . unXmlDoc

View File

@ -216,8 +216,6 @@ data RPNode = RPNode RP VerbMap
deriving (Show, Eq) deriving (Show, Eq)
data VerbMap = AllVerbs String | Verbs [(Verb, String)] data VerbMap = AllVerbs String | Verbs [(Verb, String)]
deriving (Show, Eq) deriving (Show, Eq)
instance ConvertAttempt YamlDoc [RPNode] where
convertAttempt = fromTextObject <=< ca
instance ConvertAttempt TextObject [RPNode] where instance ConvertAttempt TextObject [RPNode] where
convertAttempt = mapM helper <=< fromMapping where convertAttempt = mapM helper <=< fromMapping where
helper :: (Text, TextObject) -> Attempt RPNode helper :: (Text, TextObject) -> Attempt RPNode
@ -246,7 +244,7 @@ checkRPNodes :: (MonadFailure OverlappingPatterns m,
=> [RPNode] => [RPNode]
-> m [RPNode] -> m [RPNode]
checkRPNodes nodes = do checkRPNodes nodes = do
_ <- checkPatterns $ map (\(RPNode r _) -> cs r) nodes -- FIXME ugly _ <- checkPatterns $ map (\(RPNode r _) -> cs r) nodes
mapM_ (\(RPNode _ v) -> checkVerbMap v) nodes mapM_ (\(RPNode _ v) -> checkVerbMap v) nodes
return nodes return nodes
where where
@ -384,7 +382,7 @@ liftVerbMap (Verbs vs) r rp = do
strToExp :: Bool -> String -> Q Exp strToExp :: Bool -> String -> Q Exp
strToExp toCheck s = do strToExp toCheck s = do
rpnodes <- runIO $ convertAttemptWrap $ YamlDoc $ cs s rpnodes <- runIO $ decode (cs s) >>= \to -> convertAttemptWrap (to :: TextObject)
(if toCheck then rpnodesTHCheck else rpnodesTH) rpnodes (if toCheck then rpnodesTHCheck else rpnodesTH) rpnodes
#if TEST #if TEST

View File

@ -1,6 +1,6 @@
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeSynonymInstances #-} -- FIXME remove {-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
@ -97,7 +97,6 @@ toPair (DeleteCookie key) = return
key ++ "=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT") key ++ "=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT")
toPair (Header key value) = return (key, value) toPair (Header key value) = return (key, value)
-- FIXME add test
responseToHackResponse :: [String] -- ^ language list responseToHackResponse :: [String] -- ^ language list
-> Response -> IO Hack.Response -> Response -> IO Hack.Response
responseToHackResponse _FIXMEls (Response sc hs ct c) = do responseToHackResponse _FIXMEls (Response sc hs ct c) = do

View File

@ -14,6 +14,7 @@ import Yesod.Constants
import Yesod.Definitions import Yesod.Definitions
import Yesod.Handler import Yesod.Handler
import Yesod.Utils import Yesod.Utils
import Yesod.Template (TemplateGroup)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.Convertible.Text import Data.Convertible.Text
@ -27,7 +28,8 @@ import Hack.Middleware.Jsonp
import Hack.Middleware.MethodOverride import Hack.Middleware.MethodOverride
class Yesod a where class Yesod a where
-- | Please use the Quasi-Quoter, you\'ll be happier. FIXME more info. -- | Please use the Quasi-Quoter, you\'ll be happier. For more information,
-- see the examples/fact.lhs sample.
handlers :: Resource -> Verb -> Handler a RepChooser handlers :: Resource -> Verb -> Handler a RepChooser
-- | The encryption key to be used for encrypting client sessions. -- | The encryption key to be used for encrypting client sessions.
@ -74,26 +76,33 @@ defaultErrorHandler (InternalError e) =
[ ("Internal server error", e) [ ("Internal server error", e)
] ]
toHackApp :: Yesod y => y -> Hack.Application toHackApp :: Yesod y => y -> IO Hack.Application
toHackApp a env = do toHackApp a = do
key <- encryptKey a key <- encryptKey a
let app' = toHackApp' a app' <- toHackApp' a
let mins = clientSessionDuration a let mins = clientSessionDuration a
(gzip $ cleanPath $ jsonp $ methodOverride return $ gzip
$ clientsession encryptedCookies key mins $ app') env $ cleanPath
$ jsonp
$ methodOverride
$ clientsession encryptedCookies key mins
$ app'
toHackApp' :: Yesod y => y -> Hack.Application toHackApp' :: Yesod y => y -> IO Hack.Application
toHackApp' y env = do toHackApp' y = do
let td = templateDir y
tg <- if null td
then return nullGroup
else directoryGroupRecursiveLazy td
return $ toHackApp'' y tg
toHackApp'' :: Yesod y => y -> TemplateGroup -> Hack.Env -> IO Hack.Response
toHackApp'' y tg env = do
let (Right resource) = splitPath $ Hack.pathInfo env let (Right resource) = splitPath $ Hack.pathInfo env
types = httpAccept env types = httpAccept env
verb = cs $ Hack.requestMethod env verb = cs $ Hack.requestMethod env
handler = handlers resource verb handler = handlers resource verb
rr = cs env rr = cs env
-- FIXME don't do the templateDir thing for each request
let td = templateDir y
tg <- if null td
then return nullGroup
else directoryGroupRecursiveLazy td
res <- runHandler handler errorHandler rr y tg types res <- runHandler handler errorHandler rr y tg types
let langs = ["en"] -- FIXME let langs = ["en"] -- FIXME
responseToHackResponse langs res responseToHackResponse langs res

View File

@ -89,7 +89,7 @@ one piece of data.
> factRedirect :: Handler y () > factRedirect :: Handler y ()
> factRedirect = do > factRedirect = do
> i <- getParam "num" > i <- runRequest $ getParam "num"
> redirect $ "../" ++ i ++ "/" > redirect $ "../" ++ i ++ "/"
The following line would be unnecesary if we had a type signature on The following line would be unnecesary if we had a type signature on
@ -102,4 +102,4 @@ you could use CGI, FastCGI or a more powerful server. Just check out Hackage
for options (any package starting hack-handler- should suffice). for options (any package starting hack-handler- should suffice).
> main :: IO () > main :: IO ()
> main = putStrLn "Running..." >> run 3000 (toHackApp Fact) > main = putStrLn "Running..." >> toHackApp Fact >>= run 3000

View File

@ -26,5 +26,5 @@ helloGroup = template "real-template" "foo" (cs "bar") $ return []
main :: IO () main :: IO ()
main = do main = do
putStrLn "Running..." putStrLn "Running..."
run 3000 $ toHackApp HelloWorld toHackApp HelloWorld >>= run 3000
\end{code} \end{code}

View File

@ -15,5 +15,5 @@ helloWorld :: Handler HelloWorld HtmlObject
helloWorld = return $ cs "Hello world!" helloWorld = return $ cs "Hello world!"
main :: IO () main :: IO ()
main = putStrLn "Running..." >> run 3000 (toHackApp HelloWorld) main = putStrLn "Running..." >> toHackApp HelloWorld >>= run 3000
\end{code} \end{code}

View File

@ -39,7 +39,7 @@ library
bytestring >= 0.9.1.4 && < 0.10, bytestring >= 0.9.1.4 && < 0.10,
web-encodings >= 0.2.0 && < 0.3, web-encodings >= 0.2.0 && < 0.3,
data-object >= 0.2.0 && < 0.3, data-object >= 0.2.0 && < 0.3,
data-object-yaml >= 0.0.0 && < 0.1, data-object-yaml >= 0.2.0 && < 0.3,
directory >= 1 && < 1.1, directory >= 1 && < 1.1,
transformers >= 0.1.4.0 && < 0.2, transformers >= 0.1.4.0 && < 0.2,
control-monad-attempt >= 0.0.0 && < 0.1, control-monad-attempt >= 0.0.0 && < 0.1,