diff --git a/Data/Object/Html.hs b/Data/Object/Html.hs
index 32938049..369e4a23 100644
--- a/Data/Object/Html.hs
+++ b/Data/Object/Html.hs
@@ -4,7 +4,6 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE TemplateHaskell #-}
-- | An 'Html' data type and associated 'ConvertSuccess' instances. This has
-- useful conversions in web development:
--
@@ -66,6 +65,17 @@ newtype HtmlDoc = HtmlDoc { unHtmlDoc :: Text }
type HtmlObject = Object String Html
+instance ConvertSuccess Html HtmlObject where
+ convertSuccess = Scalar
+instance ConvertSuccess [Html] HtmlObject where
+ convertSuccess = Sequence . map cs
+instance ConvertSuccess [HtmlObject] HtmlObject where
+ convertSuccess = Sequence
+instance ConvertSuccess [(String, HtmlObject)] HtmlObject where
+ convertSuccess = Mapping
+instance ConvertSuccess [(String, Html)] HtmlObject where
+ convertSuccess = Mapping . map (second cs)
+
toHtmlObject :: ConvertSuccess x HtmlObject => x -> HtmlObject
toHtmlObject = cs
@@ -78,11 +88,6 @@ instance ConvertSuccess TS.Text Html where
convertSuccess = Text
instance ConvertSuccess Text Html where
convertSuccess = Text . cs
-$(deriveAttempts
- [ (''String, ''Html)
- , (''Text, ''Html)
- , (''TS.Text, ''Html)
- ])
instance ConvertSuccess String HtmlObject where
convertSuccess = Scalar . cs
@@ -151,11 +156,19 @@ cdata h = HtmlList
, Html $ cs "]]>"
]
-instance ConvertSuccess Html HtmlDoc where
- convertSuccess h = HtmlDoc $ TL.fromChunks $
- cs "\n
HtmlDoc (autogenerated)"
- : htmlToText False h
- [cs ""]
+instance ConvertSuccess (Html, Html) HtmlDoc where
+ convertSuccess (h, b) = HtmlDoc $ TL.fromChunks $
+ cs "\n"
+ : htmlToText False (Tag "html" [] $ HtmlList
+ [ Tag "head" [] h
+ , Tag "body" [] b
+ ]
+ ) []
+instance ConvertSuccess (HtmlObject, HtmlObject) HtmlDoc where
+ convertSuccess (x, y) = cs (cs' x :: Html, cs' y) where
+ cs' = cs
+instance ConvertSuccess (HtmlObject, HtmlObject) JsonDoc where
+ convertSuccess (_, y) = cs y
instance ConvertSuccess HtmlObject Html where
convertSuccess (Scalar h) = h
@@ -169,25 +182,20 @@ instance ConvertSuccess HtmlObject Html where
, Tag "dd" [] $ cs v
]
-instance ConvertSuccess HtmlObject HtmlDoc where
- convertSuccess = cs . (cs :: HtmlObject -> Html)
-
instance ConvertSuccess Html JsonScalar where
convertSuccess = cs . unHtmlFragment . cs
+instance ConvertAttempt Html JsonScalar where
+ convertAttempt = return . cs
+
instance ConvertSuccess HtmlObject JsonObject where
convertSuccess = mapKeysValues convertSuccess convertSuccess
+instance ConvertAttempt HtmlObject JsonObject where
+ convertAttempt = return . cs
+
instance ConvertSuccess HtmlObject JsonDoc where
convertSuccess = cs . (cs :: HtmlObject -> JsonObject)
-
-$(deriveAttempts
- [ (''Html, ''HtmlFragment)
- , (''Html, ''HtmlDoc)
- , (''Html, ''JsonScalar)
- ])
-
-$(deriveSuccessConvs ''String ''Html
- [''String, ''Text]
- [''Html, ''HtmlFragment])
+instance ConvertAttempt HtmlObject JsonDoc where
+ convertAttempt = return . cs
instance ToSElem HtmlObject where
toSElem (Scalar h) = STR $ TL.unpack $ unHtmlFragment $ cs h
diff --git a/TODO b/TODO
index a44ffdf5..6a6c07d0 100644
--- a/TODO
+++ b/TODO
@@ -1,6 +1 @@
-Some form of i18n.
Cleanup Parameter stuff. Own module? Interface with formlets?
-Authentication via e-mail address built in. (eaut.org)
-OpenID 2 stuff (for direct Google login).
-Languages (read languages header, set language cookie)
-Approot and trailing slash missing
diff --git a/Test/Errors.hs b/Test/Errors.hs
index 0861a5de..ddb07346 100644
--- a/Test/Errors.hs
+++ b/Test/Errors.hs
@@ -9,7 +9,6 @@ import Data.List
import Test.Framework (testGroup, Test)
import Test.Framework.Providers.HUnit
import Test.HUnit hiding (Test)
-import Control.Applicative
data Errors = Errors
instance Yesod Errors where
@@ -28,19 +27,19 @@ instance YesodAuth Errors
denied :: Handler Errors ()
denied = permissionDenied
-needsIdent :: Handler Errors HtmlObject
+needsIdent :: Handler Errors (HtmlObject, HtmlObject)
needsIdent = do
i <- authIdentifier
- return $ toHtmlObject i
+ return $ (toHtmlObject "", toHtmlObject i)
-hasArgs :: Handler Errors HtmlObject
+hasArgs :: Handler Errors (HtmlObject, HtmlObject)
hasArgs = do
{- FIXME wait for new request API
(a, b) <- runRequest $ (,) <$> getParam "firstParam"
<*> getParam "secondParam"
-}
let (a, b) = ("foo", "bar")
- return $ toHtmlObject [a :: String, b]
+ return (toHtmlObject "", toHtmlObject [a :: String, b])
caseErrorMessages :: Assertion
caseErrorMessages = do
diff --git a/Test/QuasiResource.hs b/Test/QuasiResource.hs
index 43ac1495..4a4439d3 100644
--- a/Test/QuasiResource.hs
+++ b/Test/QuasiResource.hs
@@ -14,24 +14,30 @@ data MyYesod = MyYesod
instance Show (Handler MyYesod ChooseRep) where show _ = "Another handler"
-getStatic :: Verb -> [String] -> Handler MyYesod HtmlObject
-getStatic v p = return $ toHtmlObject ["getStatic", show v, show p]
-pageIndex :: Handler MyYesod HtmlObject
-pageIndex = return $ toHtmlObject ["pageIndex"]
+addHead' :: HtmlObject -> (HtmlObject, HtmlObject)
+addHead' x = (cs "", x)
+
+addHead :: Monad m => HtmlObject -> m (HtmlObject, HtmlObject)
+addHead = return . addHead'
+
+getStatic :: Verb -> [String] -> Handler MyYesod (HtmlObject, HtmlObject)
+getStatic v p = addHead $ toHtmlObject ["getStatic", show v, show p]
+pageIndex :: Handler MyYesod (HtmlObject, HtmlObject)
+pageIndex = addHead $ toHtmlObject ["pageIndex"]
pageAdd :: Handler MyYesod ChooseRep
-pageAdd = return $ chooseRep $ toHtmlObject ["pageAdd"]
+pageAdd = return $ chooseRep $ addHead' $ toHtmlObject ["pageAdd"]
pageDetail :: String -> Handler MyYesod ChooseRep
-pageDetail s = return $ chooseRep $ toHtmlObject ["pageDetail", s]
-pageDelete :: String -> Handler MyYesod HtmlObject
-pageDelete s = return $ toHtmlObject ["pageDelete", s]
+pageDetail s = return $ chooseRep $ addHead' $ toHtmlObject ["pageDetail", s]
+pageDelete :: String -> Handler MyYesod (HtmlObject, HtmlObject)
+pageDelete s = addHead $ toHtmlObject ["pageDelete", s]
pageUpdate :: String -> Handler MyYesod ChooseRep
-pageUpdate s = return $ chooseRep $ toHtmlObject ["pageUpdate", s]
-userInfo :: Int -> Handler MyYesod HtmlObject
-userInfo i = return $ toHtmlObject ["userInfo", show i]
-userVariable :: Int -> String -> Handler MyYesod HtmlObject
-userVariable i s = return $ toHtmlObject ["userVariable", show i, s]
-userPage :: Int -> [String] -> Handler MyYesod HtmlObject
-userPage i p = return $ toHtmlObject ["userPage", show i, show p]
+pageUpdate s = return $ chooseRep $ addHead' $ toHtmlObject ["pageUpdate", s]
+userInfo :: Int -> Handler MyYesod (HtmlObject, HtmlObject)
+userInfo i = addHead $ toHtmlObject ["userInfo", show i]
+userVariable :: Int -> String -> Handler MyYesod (HtmlObject, HtmlObject)
+userVariable i s = addHead $ toHtmlObject ["userVariable", show i, s]
+userPage :: Int -> [String] -> Handler MyYesod (HtmlObject, HtmlObject)
+userPage i p = addHead $ toHtmlObject ["userPage", show i, show p]
instance Show (Verb -> Handler MyYesod ChooseRep) where
show _ = "verb -> handler"
@@ -57,7 +63,7 @@ handler = [$resources|
ph :: [String] -> Handler MyYesod ChooseRep -> Assertion
ph ss h = do
- let eh = return . chooseRep . toHtmlObject . show
+ let eh = return . chooseRep . addHead' . toHtmlObject . show
rr = error "No raw request"
y = MyYesod
cts = [TypeHtml]
diff --git a/Yesod/Handler.hs b/Yesod/Handler.hs
index c1a6bb9d..68f7d32c 100644
--- a/Yesod/Handler.hs
+++ b/Yesod/Handler.hs
@@ -101,23 +101,28 @@ runHandler :: Handler yesod ChooseRep
-> [ContentType]
-> IO Response
runHandler (Handler handler) eh rr y tg cts = do
+ let toErrorHandler =
+ InternalError
+ . (show :: Control.Exception.SomeException -> String)
(headers, contents) <- Control.Exception.catch
(handler (rr, y, tg))
- (\e -> return ([], HCError $ InternalError $ show
- (e :: Control.Exception.SomeException)))
- case contents of
- HCError e -> do
+ (\e -> return ([], HCError $ toErrorHandler e))
+ let handleError e = do
Response _ hs ct c <- runHandler (eh e) safeEh rr y tg cts
let hs' = headers ++ hs
return $ Response (getStatus e) hs' ct c
+ let sendFile' ct fp = do
+ -- avoid lazy I/O by switching to WAI
+ c <- BL.readFile fp
+ return $ Response 200 headers ct $ cs c
+ case contents of
+ HCError e -> handleError e
HCSpecial (Redirect rt loc) -> do
let hs = Header "Location" loc : headers
return $ Response (getRedirectStatus rt) hs TypePlain $ cs ""
- HCSpecial (SendFile ct fp) -> do
- -- FIXME do error handling on this, or leave it to the app?
- -- FIXME avoid lazy I/O by switching to WAI
- c <- BL.readFile fp
- return $ Response 200 headers ct $ cs c
+ HCSpecial (SendFile ct fp) -> Control.Exception.catch
+ (sendFile' ct fp)
+ (handleError . toErrorHandler)
HCContent a -> do
(ct, c) <- a cts
return $ Response 200 headers ct c
@@ -125,7 +130,10 @@ runHandler (Handler handler) eh rr y tg cts = do
safeEh :: ErrorResponse -> Handler yesod ChooseRep
safeEh er = do
liftIO $ hPutStrLn stderr $ "Error handler errored out: " ++ show er
- return $ chooseRep $ toHtmlObject "Internal server error"
+ return $ chooseRep $
+ ( toHtmlObject $ Tag "title" [] $ cs "Internal Server Error"
+ , toHtmlObject "Internal server error"
+ )
------ Special handlers
specialResponse :: SpecialResponse -> Handler yesod a
diff --git a/Yesod/Helpers/Auth.hs b/Yesod/Helpers/Auth.hs
index 332185ae..9fcefe62 100644
--- a/Yesod/Helpers/Auth.hs
+++ b/Yesod/Helpers/Auth.hs
@@ -105,7 +105,7 @@ getParam :: (Monad m, RequestReader m)
-> m ParamValue
getParam = someParam GetParam getParams
-authOpenidForm :: Handler y HtmlObject
+authOpenidForm :: Handler y (HtmlObject, HtmlObject)
authOpenidForm = do
rr <- getRawRequest
case getParams rr "dest" of
@@ -124,9 +124,9 @@ authOpenidForm = do
, EmptyTag "input" [("type", "submit"), ("value", "Login")]
]
]
- return $ cs html
+ return $ (justTitle "Log in via OpenID", cs html)
-authOpenidForward :: YesodAuth y => Handler y HtmlObject
+authOpenidForward :: YesodAuth y => Handler y ()
authOpenidForward = do
oid <- getParam "openid"
authroot <- getFullAuthRoot
@@ -138,7 +138,7 @@ authOpenidForward = do
(redirect RedirectTemporary)
res
-authOpenidComplete :: YesodApproot y => Handler y HtmlObject
+authOpenidComplete :: YesodApproot y => Handler y ()
authOpenidComplete = do
ar <- getApproot
rr <- getRawRequest
@@ -156,7 +156,7 @@ authOpenidComplete = do
redirect RedirectTemporary dest
attempt onFailure onSuccess res
-rpxnowLogin :: YesodAuth y => Handler y HtmlObject
+rpxnowLogin :: YesodAuth y => Handler y ()
rpxnowLogin = do
ay <- getYesod
let ar = approot ay
@@ -192,21 +192,30 @@ getDisplayName (Rpxnow.Identifier ident extra) = helper choices where
Nothing -> helper xs
Just y -> y
-authCheck :: Handler y HtmlObject
+-- FIXME use templates for all of the following
+
+justTitle :: String -> HtmlObject
+justTitle = cs . Tag "title" [] . cs
+
+authCheck :: Handler y (HtmlObject, HtmlObject)
authCheck = do
ident <- maybeIdentifier
dn <- displayName
- return $ toHtmlObject
+ return $ (justTitle "Authentication Status", toHtmlObject
[ ("identifier", fromMaybe "" ident)
, ("displayName", fromMaybe "" dn)
- ]
+ ])
-authLogout :: YesodAuth y => Handler y HtmlObject
+authLogout :: YesodAuth y => Handler y ()
authLogout = do
deleteCookie authCookieName
+ rr <- getRawRequest
ar <- getApproot
- redirect RedirectTemporary ar
- -- FIXME check the DEST information
+ let dest = case cookies rr "DEST" of
+ [] -> ar
+ (x:_) -> x
+ deleteCookie "DEST"
+ redirect RedirectTemporary dest
-- | Gets the identifier for a user if available.
maybeIdentifier :: (Functor m, Monad m, RequestReader m) => m (Maybe String)
diff --git a/Yesod/Helpers/Static.hs b/Yesod/Helpers/Static.hs
index 83a98fc0..647ae240 100644
--- a/Yesod/Helpers/Static.hs
+++ b/Yesod/Helpers/Static.hs
@@ -22,15 +22,13 @@ module Yesod.Helpers.Static
, fileLookupDir
) where
-import qualified Data.ByteString.Lazy as B
import System.Directory (doesFileExist)
import Control.Monad
import Yesod
import Data.List (intercalate)
--- FIXME this type is getting ugly...
-type FileLookup = FilePath -> IO (Maybe (Either FilePath B.ByteString))
+type FileLookup = FilePath -> IO (Maybe (Either FilePath Content))
-- | 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
diff --git a/Yesod/Request.hs b/Yesod/Request.hs
index cbe02d6f..6b730a55 100644
--- a/Yesod/Request.hs
+++ b/Yesod/Request.hs
@@ -74,7 +74,7 @@ parseEnv = rawEnv `fmap` getRawRequest
data RawRequest = RawRequest
{ rawGetParams :: [(ParamName, ParamValue)]
, rawCookies :: [(ParamName, ParamValue)]
- -- FIXME when we switch to WAI, the following two should be combined and
+ -- when we switch to WAI, the following two should be combined and
-- wrapped in the IO monad
, rawPostParams :: [(ParamName, ParamValue)]
, rawFiles :: [(ParamName, FileInfo String BL.ByteString)]
diff --git a/Yesod/Response.hs b/Yesod/Response.hs
index d2bdbc3d..23a27fb1 100644
--- a/Yesod/Response.hs
+++ b/Yesod/Response.hs
@@ -124,8 +124,7 @@ instance HasReps [(ContentType, Content)] where
(x:_) -> x
_ -> error "chooseRep [(ContentType, Content)] of empty"
--- FIXME remove this instance? only good for debugging, maybe special debugging newtype?
-instance HasReps HtmlObject where
+instance HasReps (HtmlObject, HtmlObject) where
chooseRep = defChooseRep
[ (TypeHtml, return . cs . unHtmlDoc . cs)
, (TypeJson, return . cs . unJsonDoc . cs)
diff --git a/Yesod/Template.hs b/Yesod/Template.hs
index b145d95d..a13db2fa 100644
--- a/Yesod/Template.hs
+++ b/Yesod/Template.hs
@@ -1,3 +1,4 @@
+-- FIXME this whole module needs to be rethought
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
module Yesod.Template
@@ -24,7 +25,6 @@ type TemplateGroup = STGroup Text
class HasTemplateGroup a where
getTemplateGroup :: a TemplateGroup
--- FIXME better home
template :: (MonadFailure NoSuchTemplate t, HasTemplateGroup t)
=> String -- ^ template name
-> String -- ^ object name
@@ -58,7 +58,6 @@ instance HasReps Template where
return $ cs $ unJsonDoc $ cs ho)
]
--- FIXME
data TemplateFile = TemplateFile FilePath HtmlObject
instance HasReps TemplateFile where
chooseRep = defChooseRep [ (TypeHtml,
diff --git a/Yesod/Yesod.hs b/Yesod/Yesod.hs
index e77e7864..f6cb4bf2 100644
--- a/Yesod/Yesod.hs
+++ b/Yesod/Yesod.hs
@@ -6,7 +6,7 @@ module Yesod.Yesod
, toHackApp
) where
-import Data.Object.Html (toHtmlObject)
+import Data.Object.Html
import Yesod.Response
import Yesod.Request
import Yesod.Definitions
@@ -14,7 +14,6 @@ import Yesod.Handler
import Yesod.Template (TemplateGroup)
import Data.Maybe (fromMaybe)
-import Data.Convertible.Text
import Text.StringTemplate
import Web.Mime
import Web.Encodings (parseHttpAccept)
@@ -55,23 +54,32 @@ class Yesod a => YesodApproot a where
getApproot :: YesodApproot y => Handler y Approot
getApproot = approot `fmap` getYesod
+justTitle :: String -> HtmlObject
+justTitle = cs . Tag "title" [] . cs
+
defaultErrorHandler :: Yesod y
=> ErrorResponse
-> Handler y ChooseRep
defaultErrorHandler NotFound = do
rr <- getRawRequest
- return $ chooseRep $ toHtmlObject $ "Not found: " ++ show rr
+ return $ chooseRep
+ ( justTitle "Not Found"
+ , toHtmlObject [("Not found", show rr)]
+ )
defaultErrorHandler PermissionDenied =
- return $ chooseRep $ toHtmlObject "Permission denied"
+ return $ chooseRep
+ ( justTitle "Permission Denied"
+ , toHtmlObject "Permission denied"
+ )
defaultErrorHandler (InvalidArgs ia) =
- return $ chooseRep $ toHtmlObject
+ return $ chooseRep (justTitle "Invalid Arguments", toHtmlObject
[ ("errorMsg", toHtmlObject "Invalid arguments")
, ("messages", toHtmlObject ia)
- ]
+ ])
defaultErrorHandler (InternalError e) =
- return $ chooseRep $ toHtmlObject
+ return $ chooseRep (justTitle "Internal Server Error", toHtmlObject
[ ("Internal server error", e)
- ]
+ ])
toHackApp :: Yesod y => y -> IO Hack.Application
toHackApp a = do