Merge branch 'beta'

This commit is contained in:
Michael Snoyman 2011-12-29 12:01:21 +02:00
commit 8e623d04a6
56 changed files with 460 additions and 641 deletions

View File

@ -73,7 +73,7 @@ data Creds m = Creds
, credsExtra :: [(Text, Text)]
}
class (Yesod m, SinglePiece (AuthId m), RenderMessage m FormMessage) => YesodAuth m where
class (Yesod m, PathPiece (AuthId m), RenderMessage m FormMessage) => YesodAuth m where
type AuthId m
-- | Default destination on successful login, if no other
@ -133,7 +133,7 @@ setCreds doRedirects creds = do
Just ar -> do setMessageI Msg.InvalidLogin
redirect RedirectTemporary ar
Just aid -> do
setSession credsKey $ toSinglePiece aid
setSession credsKey $ toPathPiece aid
when doRedirects $ do
setMessageI Msg.NowLoggedIn
redirectUltDest RedirectTemporary $ loginDest y
@ -189,12 +189,12 @@ maybeAuthId = do
ms <- lookupSession credsKey
case ms of
Nothing -> return Nothing
Just s -> return $ fromSinglePiece s
Just s -> return $ fromPathPiece s
maybeAuth :: ( YesodAuth m
, b ~ YesodPersistBackend m
, Key b val ~ AuthId m
, PersistBackend b (GGHandler s m IO)
, PersistStore b (GHandler s m)
, PersistEntity val
, YesodPersist m
) => GHandler s m (Maybe (Key b val, val))
@ -209,7 +209,7 @@ requireAuthId = maybeAuthId >>= maybe redirectLogin return
requireAuth :: ( YesodAuth m
, b ~ YesodPersistBackend m
, Key b val ~ AuthId m
, PersistBackend b (GGHandler s m IO)
, PersistStore b (GHandler s m)
, PersistEntity val
, YesodPersist m
) => GHandler s m (Key b val, val)

View File

@ -60,7 +60,7 @@ data EmailCreds m = EmailCreds
, emailCredsVerkey :: Maybe VerKey
}
class (YesodAuth m, SinglePiece (AuthEmailId m)) => YesodAuthEmail m where
class (YesodAuth m, PathPiece (AuthEmailId m)) => YesodAuthEmail m where
type AuthEmailId m
addUnverified :: Email -> VerKey -> GHandler Auth m (AuthEmailId m)
@ -102,7 +102,7 @@ authEmail =
dispatch "GET" ["register"] = getRegisterR >>= sendResponse
dispatch "POST" ["register"] = postRegisterR >>= sendResponse
dispatch "GET" ["verify", eid, verkey] =
case fromSinglePiece eid of
case fromPathPiece eid of
Nothing -> notFound
Just eid' -> getVerifyR eid' verkey >>= sendResponse
dispatch "POST" ["login"] = postLoginR >>= sendResponse
@ -142,7 +142,7 @@ postRegisterR = do
return (lid, key)
render <- getUrlRender
tm <- getRouteToMaster
let verUrl = render $ tm $ verify (toSinglePiece lid) verKey
let verUrl = render $ tm $ verify (toPathPiece lid) verKey
sendVerifyEmail email verKey verUrl
defaultLayout $ do
setTitleI Msg.ConfirmationEmailSentTitle

View File

@ -49,7 +49,7 @@ facebookLogout = PluginR "facebook" ["logout"]
-- @Nothing@ if it's not found (probably because the user is not
-- logged in via Facebook). Note that the returned access token
-- may have expired.
getFacebookAccessToken :: MonadIO mo => GGHandler sub master mo (Maybe Facebook.AccessToken)
getFacebookAccessToken :: GHandler sub master (Maybe Facebook.AccessToken)
getFacebookAccessToken =
liftM (fmap Facebook.AccessToken) (lookupSession facebookAccessTokenKey)

View File

@ -137,7 +137,8 @@ setPassword pwd u = do salt <- randomSalt
-- the database values.
validateUser :: ( YesodPersist yesod
, b ~ YesodPersistBackend yesod
, PersistBackend b (GGHandler sub yesod IO)
, PersistStore b (GHandler sub yesod)
, PersistUnique b (GHandler sub yesod)
, PersistEntity user
, HashDBUser user
) =>
@ -163,7 +164,8 @@ login = PluginR "hashdb" ["login"]
postLoginR :: ( YesodAuth y, YesodPersist y
, b ~ YesodPersistBackend y
, HashDBUser user, PersistEntity user
, PersistBackend b (GGHandler Auth y IO))
, PersistStore b (GHandler Auth y)
, PersistUnique b (GHandler Auth y))
=> (Text -> Maybe (Unique user b))
-> GHandler Auth y ()
postLoginR uniq = do
@ -186,7 +188,8 @@ getAuthIdHashDB :: ( YesodAuth master, YesodPersist master
, HashDBUser user, PersistEntity user
, Key b user ~ AuthId master
, b ~ YesodPersistBackend master
, PersistBackend b (GGHandler sub master IO))
, PersistUnique b (GHandler sub master)
, PersistStore b (GHandler sub master))
=> (AuthRoute -> Route master) -- ^ your site's Auth Route
-> (Text -> Maybe (Unique user b)) -- ^ gets user ID
-> Creds master -- ^ the creds argument
@ -213,7 +216,8 @@ authHashDB :: ( YesodAuth m, YesodPersist m
, HashDBUser user
, PersistEntity user
, b ~ YesodPersistBackend m
, PersistBackend b (GGHandler Auth m IO))
, PersistStore b (GHandler Auth m)
, PersistUnique b (GHandler Auth m))
=> (Text -> Maybe (Unique user b)) -> AuthPlugin m
authHashDB uniq = AuthPlugin "hashdb" dispatch $ \tm -> addHamlet
[QQ(hamlet)|
@ -252,8 +256,8 @@ authHashDB uniq = AuthPlugin "hashdb" dispatch $ \tm -> addHamlet
----------------------------------------------------------------
-- | Generate data base instances for a valid user
share2 (mkPersist sqlSettings) (mkMigrate "migrateUsers")
[QQ(persist)|
share2 (mkPersist sqlMkSettings) (mkMigrate "migrateUsers")
[QQ(persistUpperCase)|
User
username Text Eq
password Text

View File

@ -1,5 +1,5 @@
name: yesod-auth
version: 0.7.9
version: 0.8.0
license: BSD3
license-file: LICENSE
author: Michael Snoyman, Patrick Brisbin
@ -21,10 +21,10 @@ library
cpp-options: -DGHC7
else
build-depends: base >= 4 && < 4.3
build-depends: authenticate >= 0.10.4 && < 0.11
build-depends: authenticate >= 0.11 && < 0.12
, bytestring >= 0.9.1.4 && < 0.10
, yesod-core >= 0.9.3.4 && < 0.10
, wai >= 0.4 && < 0.5
, yesod-core >= 0.10 && < 0.11
, wai >= 1.0 && < 1.1
, template-haskell
, pureMD5 >= 2.0 && < 2.2
, random >= 1.0.0.2 && < 1.1
@ -32,19 +32,19 @@ library
, text >= 0.7 && < 0.12
, mime-mail >= 0.3 && < 0.5
, blaze-html >= 0.4.1.3 && < 0.5
, yesod-persistent >= 0.2 && < 0.3
, yesod-persistent >= 0.3 && < 0.4
, hamlet >= 0.10 && < 0.11
, shakespeare-css >= 0.10 && < 0.11
, yesod-json >= 0.2 && < 0.3
, yesod-json >= 0.3 && < 0.4
, containers
, unordered-containers
, yesod-form >= 0.3 && < 0.4
, yesod-form >= 0.4 && < 0.5
, transformers >= 0.2.2 && < 0.3
, persistent >= 0.6 && < 0.7
, persistent-template >= 0.6 && < 0.7
, persistent >= 0.7 && < 0.8
, persistent-template >= 0.7 && < 0.8
, SHA >= 1.4.1.3 && < 1.6
, http-enumerator >= 0.6 && < 0.8
, aeson >= 0.3
, http-conduit >= 1.0 && < 1.1
, aeson >= 0.5
, pwstore-fast >= 2.2 && < 3
exposed-modules: Yesod.Auth

View File

@ -1,113 +0,0 @@
{-# LANGUAGE OverloadedStrings #-}
module Yesod.Config
{-# DEPRECATED "This code has been moved to yesod-default. This module will be removed in the next major version bump." #-}
( AppConfig(..)
, loadConfig
, withYamlEnvironment
) where
import Control.Monad (join)
import Data.Maybe (fromMaybe)
import Data.Object
import Data.Object.Yaml
import Data.Text (Text)
import qualified Data.Text as T
-- | Dynamic per-environment configuration which can be loaded at
-- run-time negating the need to recompile between environments.
data AppConfig e = AppConfig
{ appEnv :: e
, appPort :: Int
, appRoot :: Text
} deriving (Show)
-- | Load an @'AppConfig'@ from @config\/settings.yml@.
--
-- Some examples:
--
-- > -- typical local development
-- > Development:
-- > host: localhost
-- > port: 3000
-- >
-- > -- ssl: will default false
-- > -- approot: will default to "http://localhost:3000"
--
-- > -- typical outward-facing production box
-- > Production:
-- > host: www.example.com
-- >
-- > -- ssl: will default false
-- > -- port: will default 80
-- > -- approot: will default "http://www.example.com"
--
-- > -- maybe you're reverse proxying connections to the running app
-- > -- on some other port
-- > Production:
-- > port: 8080
-- > approot: "http://example.com"
-- >
-- > -- approot is specified so that the non-80 port is not appended
-- > -- automatically.
--
loadConfig :: Show e => e -> IO (AppConfig e)
loadConfig env = withYamlEnvironment "config/settings.yml" env $ \e' -> do
e <- maybe (fail "Expected map") return $ fromMapping e'
let mssl = lookupScalar "ssl" e
let mhost = lookupScalar "host" e
let mport = lookupScalar "port" e
let mapproot = lookupScalar "approot" e
-- set some default arguments
let ssl = maybe False toBool mssl
port <- safeRead "port" $ fromMaybe (if ssl then "443" else "80") mport
approot <- case (mhost, mapproot) of
(_ , Just ar) -> return ar
(Just host, _ ) -> return $ T.concat
[ if ssl then "https://" else "http://"
, host
, addPort ssl port
]
_ -> fail "You must supply either a host or approot"
return $ AppConfig
{ appEnv = env
, appPort = port
, appRoot = approot
}
where
toBool :: Text -> Bool
toBool = (`elem` ["true", "TRUE", "yes", "YES", "Y", "1"])
addPort :: Bool -> Int -> Text
addPort True 443 = ""
addPort False 80 = ""
addPort _ p = T.pack $ ':' : show p
-- | Loads the configuration block in the passed file named by the
-- passed environment, yeilds to the passed function as a mapping.
--
-- Errors in the case of a bad load or if your function returns
-- @Nothing@.
withYamlEnvironment :: Show e
=> FilePath -- ^ the yaml file
-> e -- ^ the environment you want to load
-> (TextObject -> IO a) -- ^ what to do with the mapping
-> IO a
withYamlEnvironment fp env f = do
obj <- join $ decodeFile fp
envs <- fromMapping obj
conf <- maybe (fail $ "Could not find environment: " ++ show env) return
$ lookup (T.pack $ show env) envs
f conf
-- | Returns 'fail' if read fails
safeRead :: Monad m => String -> Text -> m Int
safeRead name t = case reads s of
(i, _):_ -> return i
[] -> fail $ concat ["Invalid value for ", name, ": ", s]
where
s = T.unpack t

View File

@ -54,7 +54,6 @@ import System.Locale
import qualified Data.Text.Encoding
import qualified Data.Text.Lazy.Encoding
import Data.Enumerator (Enumerator)
import Blaze.ByteString.Builder (Builder, fromByteString, fromLazyByteString)
import Data.Monoid (mempty)
@ -62,9 +61,10 @@ import Text.Hamlet (Html)
import Text.Blaze.Renderer.Utf8 (renderHtmlBuilder)
import Data.String (IsString (fromString))
import Network.Wai (FilePart)
import Data.Conduit (Source)
data Content = ContentBuilder Builder (Maybe Int) -- ^ The content and optional content length.
| ContentEnum (forall a. Enumerator Builder IO a)
| ContentSource (Source IO Builder)
| ContentFile FilePath (Maybe FilePart)
-- | Zero-length enumerator.

View File

@ -34,7 +34,6 @@ module Yesod.Core
, module Yesod.Request
, module Yesod.Widget
, module Yesod.Message
, module Yesod.Config
) where
import Yesod.Internal.Core
@ -44,7 +43,6 @@ import Yesod.Handler
import Yesod.Request
import Yesod.Widget
import Yesod.Message
import Yesod.Config
import Language.Haskell.TH.Syntax
import Data.Text (Text)

View File

@ -15,8 +15,8 @@ module Yesod.Dispatch
, mkYesodDispatch
, mkYesodSubDispatch
-- ** Path pieces
, SinglePiece (..)
, MultiPiece (..)
, PathPiece (..)
, PathMultiPiece (..)
, Texts
-- * Convert to WAI
, toWaiApp
@ -31,7 +31,7 @@ import Yesod.Handler
import Yesod.Internal.Dispatch
import Yesod.Widget (GWidget)
import Web.PathPieces (SinglePiece (..), MultiPiece (..))
import Web.PathPieces
import Yesod.Internal.RouteParsing (THResource, Pieces (..), createRoutes, createRender, Resource (..), parseRoutes, parseRoutesNoCheck, parseRoutesFile, parseRoutesFileNoCheck)
import Language.Haskell.TH.Syntax

View File

@ -28,7 +28,7 @@ module Yesod.Handler
, YesodSubRoute (..)
-- * Handler monad
, GHandler
, GGHandler
, GHandlerT
-- ** Read information from handler
, getYesod
, getYesodSub
@ -148,8 +148,6 @@ import qualified Data.Text.Lazy as TL
import qualified Data.Map as Map
import qualified Data.ByteString as S
import Data.ByteString (ByteString)
import Data.Enumerator (Iteratee (..), run_, ($$))
import Network.Wai.Parse (parseHttpAccept)
import Yesod.Content
@ -160,17 +158,21 @@ import qualified Network.Wai.Parse as NWP
import Data.Monoid (mappend, mempty, Endo (..))
import qualified Data.ByteString.Char8 as S8
import Data.CaseInsensitive (CI)
import qualified Data.CaseInsensitive as CI
import Blaze.ByteString.Builder (toByteString)
import Data.Text (Text)
import Yesod.Message (RenderMessage (..))
import Text.Blaze (toHtml, preEscapedText)
import Yesod.Internal.TestApi (catchIter)
import qualified Yesod.Internal.Cache as Cache
import Yesod.Internal.Cache (mkCacheKey, CacheKey)
import Data.Typeable (Typeable)
import qualified Data.IORef as I
import Control.Monad.Trans.Resource (ResourceT)
import Control.Exception.Lifted (catch)
import Network.Wai (requestBody)
import Data.Conduit (($$))
-- | The type-safe URLs associated with a site argument.
type family Route a
@ -206,22 +208,22 @@ handlerSubDataMaybe tm ts route hd = hd
, handlerRoute = route
}
get :: MonadIO monad => GGHandler sub master monad GHState
get :: MonadIO monad => GHandlerT sub master monad GHState
get = do
hd <- ask
liftIO $ I.readIORef $ handlerState hd
put :: MonadIO monad => GHState -> GGHandler sub master monad ()
put :: MonadIO monad => GHState -> GHandlerT sub master monad ()
put g = do
hd <- ask
liftIO $ I.writeIORef (handlerState hd) g
modify :: MonadIO monad => (GHState -> GHState) -> GGHandler sub master monad ()
modify :: MonadIO monad => (GHState -> GHState) -> GHandlerT sub master monad ()
modify f = do
hd <- ask
liftIO $ I.atomicModifyIORef (handlerState hd) $ \g -> (f g, ())
tell :: MonadIO monad => Endo [Header] -> GGHandler sub master monad ()
tell :: MonadIO monad => Endo [Header] -> GHandlerT sub master monad ()
tell hs = modify $ \g -> g { ghsHeaders = ghsHeaders g `mappend` hs }
-- | Used internally for promoting subsite handler functions to master site
@ -229,16 +231,16 @@ tell hs = modify $ \g -> g { ghsHeaders = ghsHeaders g `mappend` hs }
toMasterHandler :: (Route sub -> Route master)
-> (master -> sub)
-> Route sub
-> GGHandler sub master mo a
-> GGHandler sub' master mo a
-> GHandlerT sub master mo a
-> GHandlerT sub' master mo a
toMasterHandler tm ts route = withReaderT (handlerSubData tm ts route)
toMasterHandlerDyn :: Monad mo
=> (Route sub -> Route master)
-> GGHandler sub' master mo sub
-> GHandlerT sub' master mo sub
-> Route sub
-> GGHandler sub master mo a
-> GGHandler sub' master mo a
-> GHandlerT sub master mo a
-> GHandlerT sub' master mo a
toMasterHandlerDyn tm getSub route h = do
sub <- getSub
withReaderT (handlerSubData tm (const sub) route) h
@ -258,8 +260,8 @@ instance (anySub ~ anySub'
toMasterHandlerMaybe :: (Route sub -> Route master)
-> (master -> sub)
-> Maybe (Route sub)
-> GGHandler sub master mo a
-> GGHandler sub' master mo a
-> GHandlerT sub master mo a
-> GHandlerT sub' master mo a
toMasterHandlerMaybe tm ts route = withReaderT (handlerSubDataMaybe tm ts route)
-- | A generic handler monad, which can have a different subsite and master
@ -267,9 +269,9 @@ toMasterHandlerMaybe tm ts route = withReaderT (handlerSubDataMaybe tm ts route)
-- 'WriterT' for headers and session, and an 'MEitherT' monad for handling
-- special responses. It is declared as a newtype to make compiler errors more
-- readable.
type GGHandler sub master = ReaderT (HandlerData sub master)
type GHandlerT sub master = ReaderT (HandlerData sub master)
type GHandler sub master = GGHandler sub master (Iteratee ByteString IO)
type GHandler sub master = GHandlerT sub master (ResourceT IO)
data GHState = GHState
{ ghsSession :: SessionMap
@ -290,7 +292,7 @@ newtype YesodApp = YesodApp
-> Request
-> [ContentType]
-> SessionMap
-> Iteratee ByteString IO YesodAppResult
-> ResourceT IO YesodAppResult
}
data YesodAppResult
@ -310,10 +312,10 @@ instance Show HandlerContents where
show _ = "Cannot show a HandlerContents"
instance Exception HandlerContents
getRequest :: Monad mo => GGHandler s m mo Request
getRequest :: Monad mo => GHandlerT s m mo Request
getRequest = handlerRequest `liftM` ask
instance MonadIO monad => Failure ErrorResponse (GGHandler sub master monad) where
instance MonadIO monad => Failure ErrorResponse (GHandlerT sub master monad) where
failure = liftIO . throwIO . HCError
runRequestBody :: GHandler s m RequestBodyContents
@ -327,9 +329,9 @@ runRequestBody = do
put x { ghsRBC = Just rbc }
return rbc
rbHelper :: W.Request -> Iteratee ByteString IO RequestBodyContents
rbHelper :: W.Request -> ResourceT IO RequestBodyContents
rbHelper req =
(map fix1 *** map fix2) <$> iter
(map fix1 *** map fix2) <$> (requestBody req $$ iter)
where
iter = NWP.parseRequestBody NWP.lbsSink req
fix1 = go *** go
@ -338,15 +340,15 @@ rbHelper req =
go = decodeUtf8With lenientDecode
-- | Get the sub application argument.
getYesodSub :: Monad m => GGHandler sub master m sub
getYesodSub :: Monad m => GHandlerT sub master m sub
getYesodSub = handlerSub `liftM` ask
-- | Get the master site appliation argument.
getYesod :: Monad m => GGHandler sub master m master
getYesod :: Monad m => GHandlerT sub master m master
getYesod = handlerMaster `liftM` ask
-- | Get the URL rendering function.
getUrlRender :: Monad m => GGHandler sub master m (Route master -> Text)
getUrlRender :: Monad m => GHandlerT sub master m (Route master -> Text)
getUrlRender = do
x <- handlerRender `liftM` ask
return $ flip x []
@ -354,17 +356,17 @@ getUrlRender = do
-- | The URL rendering function with query-string parameters.
getUrlRenderParams
:: Monad m
=> GGHandler sub master m (Route master -> [(Text, Text)] -> Text)
=> GHandlerT sub master m (Route master -> [(Text, Text)] -> Text)
getUrlRenderParams = handlerRender `liftM` ask
-- | Get the route requested by the user. If this is a 404 response- where the
-- user requested an invalid route- this function will return 'Nothing'.
getCurrentRoute :: Monad m => GGHandler sub master m (Maybe (Route sub))
getCurrentRoute :: Monad m => GHandlerT sub master m (Maybe (Route sub))
getCurrentRoute = handlerRoute `liftM` ask
-- | Get the function to promote a route for a subsite to a route for the
-- master site.
getRouteToMaster :: Monad m => GGHandler sub master m (Route sub -> Route master)
getRouteToMaster :: Monad m => GHandlerT sub master m (Route sub -> Route master)
getRouteToMaster = handlerToMaster `liftM` ask
-- | Function used internally by Yesod in the process of converting a
@ -399,7 +401,7 @@ runHandler handler mrender sroute tomr master sub =
, handlerToMaster = tomr
, handlerState = istate
}
contents' <- catchIter (fmap Right $ runReaderT handler hd)
contents' <- catch (fmap Right $ runReaderT handler hd)
(\e -> return $ Left $ maybe (HCError $ toErrorHandler e) id
$ fromException e)
state <- liftIO $ I.readIORef istate
@ -425,7 +427,7 @@ runHandler handler mrender sroute tomr master sub =
return $ YARPlain
(getRedirectStatus rt $ reqWaiRequest rr) hs typePlain emptyContent
finalSession
HCSendFile ct fp p -> catchIter
HCSendFile ct fp p -> catch
(sendFile' ct fp p)
(handleError . toErrorHandler)
HCCreated loc -> do
@ -449,19 +451,19 @@ safeEh er = YesodApp $ \_ _ _ session -> do
session
-- | Redirect to the given route.
redirect :: MonadIO mo => RedirectType -> Route master -> GGHandler sub master mo a
redirect :: MonadIO mo => RedirectType -> Route master -> GHandlerT sub master mo a
redirect rt url = redirectParams rt url []
-- | Redirects to the given route with the associated query-string parameters.
redirectParams :: MonadIO mo
=> RedirectType -> Route master -> [(Text, Text)]
-> GGHandler sub master mo a
-> GHandlerT sub master mo a
redirectParams rt url params = do
r <- getUrlRenderParams
redirectString rt $ r url params
-- | Redirect to the given URL.
redirectString, redirectText :: MonadIO mo => RedirectType -> Text -> GGHandler sub master mo a
redirectString, redirectText :: MonadIO mo => RedirectType -> Text -> GHandlerT sub master mo a
redirectText rt = liftIO . throwIO . HCRedirect rt
redirectString = redirectText
{-# DEPRECATED redirectString "Use redirectText instead" #-}
@ -473,16 +475,16 @@ ultDestKey = "_ULT"
--
-- An ultimate destination is stored in the user session and can be loaded
-- later by 'redirectUltDest'.
setUltDest :: MonadIO mo => Route master -> GGHandler sub master mo ()
setUltDest :: MonadIO mo => Route master -> GHandlerT sub master mo ()
setUltDest dest = do
render <- getUrlRender
setUltDestString $ render dest
-- | Same as 'setUltDest', but use the given string.
setUltDestText :: MonadIO mo => Text -> GGHandler sub master mo ()
setUltDestText :: MonadIO mo => Text -> GHandlerT sub master mo ()
setUltDestText = setSession ultDestKey
setUltDestString :: MonadIO mo => Text -> GGHandler sub master mo ()
setUltDestString :: MonadIO mo => Text -> GHandlerT sub master mo ()
setUltDestString = setSession ultDestKey
{-# DEPRECATED setUltDestString "Use setUltDestText instead" #-}
@ -490,7 +492,7 @@ setUltDestString = setSession ultDestKey
--
-- If this is a 404 handler, there is no current page, and then this call does
-- nothing.
setUltDest' :: MonadIO mo => GGHandler sub master mo ()
setUltDest' :: MonadIO mo => GHandlerT sub master mo ()
setUltDest' = do
route <- getCurrentRoute
case route of
@ -504,7 +506,7 @@ setUltDest' = do
-- | Sets the ultimate destination to the referer request header, if present.
--
-- This function will not overwrite an existing ultdest.
setUltDestReferer :: MonadIO mo => GGHandler sub master mo ()
setUltDestReferer :: MonadIO mo => GHandlerT sub master mo ()
setUltDestReferer = do
mdest <- lookupSession ultDestKey
maybe
@ -521,14 +523,14 @@ setUltDestReferer = do
redirectUltDest :: MonadIO mo
=> RedirectType
-> Route master -- ^ default destination if nothing in session
-> GGHandler sub master mo a
-> GHandlerT sub master mo a
redirectUltDest rt def = do
mdest <- lookupSession ultDestKey
deleteSession ultDestKey
maybe (redirect rt def) (redirectText rt) mdest
-- | Remove a previously set ultimate destination. See 'setUltDest'.
clearUltDest :: MonadIO mo => GGHandler sub master mo ()
clearUltDest :: MonadIO mo => GHandlerT sub master mo ()
clearUltDest = deleteSession ultDestKey
msgKey :: Text
@ -537,13 +539,13 @@ msgKey = "_MSG"
-- | Sets a message in the user's session.
--
-- See 'getMessage'.
setMessage :: MonadIO mo => Html -> GGHandler sub master mo ()
setMessage :: MonadIO mo => Html -> GHandlerT sub master mo ()
setMessage = setSession msgKey . T.concat . TL.toChunks . Text.Blaze.Renderer.Text.renderHtml
-- | Sets a message in the user's session.
--
-- See 'getMessage'.
setMessageI :: (RenderMessage y msg, MonadIO mo) => msg -> GGHandler sub y mo ()
setMessageI :: (RenderMessage y msg, MonadIO mo) => msg -> GHandlerT sub y mo ()
setMessageI msg = do
mr <- getMessageRender
setMessage $ toHtml $ mr msg
@ -552,7 +554,7 @@ setMessageI msg = do
-- variable.
--
-- See 'setMessage'.
getMessage :: MonadIO mo => GGHandler sub master mo (Maybe Html)
getMessage :: MonadIO mo => GHandlerT sub master mo (Maybe Html)
getMessage = do
mmsg <- liftM (fmap preEscapedText) $ lookupSession msgKey
deleteSession msgKey
@ -562,7 +564,7 @@ getMessage = do
--
-- For some backends, this is more efficient than reading in the file to
-- memory, since they can optimize file sending via a system call to sendfile.
sendFile :: MonadIO mo => ContentType -> FilePath -> GGHandler sub master mo a
sendFile :: MonadIO mo => ContentType -> FilePath -> GHandlerT sub master mo a
sendFile ct fp = liftIO . throwIO $ HCSendFile ct fp Nothing
-- | Same as 'sendFile', but only sends part of a file.
@ -571,25 +573,25 @@ sendFilePart :: MonadIO mo
-> FilePath
-> Integer -- ^ offset
-> Integer -- ^ count
-> GGHandler sub master mo a
-> GHandlerT sub master mo a
sendFilePart ct fp off count =
liftIO . throwIO $ HCSendFile ct fp $ Just $ W.FilePart off count
-- | Bypass remaining handler code and output the given content with a 200
-- status code.
sendResponse :: (MonadIO mo, HasReps c) => c -> GGHandler sub master mo a
sendResponse :: (MonadIO mo, HasReps c) => c -> GHandlerT sub master mo a
sendResponse = liftIO . throwIO . HCContent H.status200
. chooseRep
-- | Bypass remaining handler code and output the given content with the given
-- status code.
sendResponseStatus :: (MonadIO mo, HasReps c) => H.Status -> c -> GGHandler s m mo a
sendResponseStatus :: (MonadIO mo, HasReps c) => H.Status -> c -> GHandlerT s m mo a
sendResponseStatus s = liftIO . throwIO . HCContent s
. chooseRep
-- | Send a 201 "Created" response with the given route as the Location
-- response header.
sendResponseCreated :: MonadIO mo => Route m -> GGHandler s m mo a
sendResponseCreated :: MonadIO mo => Route m -> GHandlerT s m mo a
sendResponseCreated url = do
r <- getUrlRender
liftIO . throwIO $ HCCreated $ r url
@ -599,7 +601,7 @@ sendResponseCreated url = do
-- that you have already specified. This function short-circuits. It should be
-- considered only for very specific needs. If you are not sure if you need it,
-- you don't.
sendWaiResponse :: MonadIO mo => W.Response -> GGHandler s m mo b
sendWaiResponse :: MonadIO mo => W.Response -> GHandlerT s m mo b
sendWaiResponse = liftIO . throwIO . HCWai
-- | Return a 404 not found page. Also denotes no handler available.
@ -607,7 +609,7 @@ notFound :: Failure ErrorResponse m => m a
notFound = failure NotFound
-- | Return a 405 method not supported page.
badMethod :: MonadIO mo => GGHandler s m mo a
badMethod :: MonadIO mo => GHandlerT s m mo a
badMethod = do
w <- waiRequest
failure $ BadMethod $ W.requestMethod w
@ -617,7 +619,7 @@ permissionDenied :: Failure ErrorResponse m => Text -> m a
permissionDenied = failure . PermissionDenied
-- | Return a 403 permission denied page.
permissionDeniedI :: (RenderMessage y msg, MonadIO mo) => msg -> GGHandler s y mo a
permissionDeniedI :: (RenderMessage y msg, MonadIO mo) => msg -> GHandlerT s y mo a
permissionDeniedI msg = do
mr <- getMessageRender
permissionDenied $ mr msg
@ -627,56 +629,62 @@ invalidArgs :: Failure ErrorResponse m => [Text] -> m a
invalidArgs = failure . InvalidArgs
-- | Return a 400 invalid arguments page.
invalidArgsI :: (RenderMessage y msg, MonadIO mo) => [msg] -> GGHandler s y mo a
invalidArgsI :: (RenderMessage y msg, MonadIO mo) => [msg] -> GHandlerT s y mo a
invalidArgsI msg = do
mr <- getMessageRender
invalidArgs $ map mr msg
------- Headers
-- | Set the cookie on the client.
--
-- Note: although the value used for key and value is 'Text', you should only
-- use ASCII values to be HTTP compliant.
setCookie :: MonadIO mo
=> Int -- ^ minutes to timeout
-> H.Ascii -- ^ key
-> H.Ascii -- ^ value
-> GGHandler sub master mo ()
setCookie a b = addHeader . AddCookie a b
-> Text -- ^ key
-> Text -- ^ value
-> GHandlerT sub master mo ()
setCookie a b = addHeader . AddCookie a (encodeUtf8 b) . encodeUtf8
-- | Unset the cookie on the client.
deleteCookie :: MonadIO mo => H.Ascii -> GGHandler sub master mo ()
deleteCookie = addHeader . DeleteCookie
deleteCookie :: MonadIO mo => Text -> GHandlerT sub master mo ()
deleteCookie = addHeader . DeleteCookie . encodeUtf8
-- | Set the language in the user session. Will show up in 'languages' on the
-- next request.
setLanguage :: MonadIO mo => Text -> GGHandler sub master mo ()
setLanguage :: MonadIO mo => Text -> GHandlerT sub master mo ()
setLanguage = setSession langKey
-- | Set an arbitrary response header.
--
-- Note that, while the data type used here is 'Text', you must provide only
-- ASCII value to be HTTP compliant.
setHeader :: MonadIO mo
=> CI H.Ascii -> H.Ascii -> GGHandler sub master mo ()
setHeader a = addHeader . Header a
=> Text -> Text -> GHandlerT sub master mo ()
setHeader a = addHeader . Header (encodeUtf8 a) . encodeUtf8
-- | Set the Cache-Control header to indicate this response should be cached
-- for the given number of seconds.
cacheSeconds :: MonadIO mo => Int -> GGHandler s m mo ()
cacheSeconds i = setHeader "Cache-Control" $ S8.pack $ concat
cacheSeconds :: MonadIO mo => Int -> GHandlerT s m mo ()
cacheSeconds i = setHeader "Cache-Control" $ T.concat
[ "max-age="
, show i
, T.pack $ show i
, ", public"
]
-- | Set the Expires header to some date in 2037. In other words, this content
-- is never (realistically) expired.
neverExpires :: MonadIO mo => GGHandler s m mo ()
neverExpires :: MonadIO mo => GHandlerT s m mo ()
neverExpires = setHeader "Expires" "Thu, 31 Dec 2037 23:55:55 GMT"
-- | Set an Expires header in the past, meaning this content should not be
-- cached.
alreadyExpired :: MonadIO mo => GGHandler s m mo ()
alreadyExpired :: MonadIO mo => GHandlerT s m mo ()
alreadyExpired = setHeader "Expires" "Thu, 01 Jan 1970 05:05:05 GMT"
-- | Set an Expires header to the given date.
expiresAt :: MonadIO mo => UTCTime -> GGHandler s m mo ()
expiresAt = setHeader "Expires" . encodeUtf8 . formatRFC1123
expiresAt :: MonadIO mo => UTCTime -> GHandlerT s m mo ()
expiresAt = setHeader "Expires" . formatRFC1123
-- | Set a variable in the user's session.
--
@ -686,18 +694,18 @@ expiresAt = setHeader "Expires" . encodeUtf8 . formatRFC1123
setSession :: MonadIO mo
=> Text -- ^ key
-> Text -- ^ value
-> GGHandler sub master mo ()
-> GHandlerT sub master mo ()
setSession k = modify . modSession . Map.insert k
-- | Unsets a session variable. See 'setSession'.
deleteSession :: MonadIO mo => Text -> GGHandler sub master mo ()
deleteSession :: MonadIO mo => Text -> GHandlerT sub master mo ()
deleteSession = modify . modSession . Map.delete
modSession :: (SessionMap -> SessionMap) -> GHState -> GHState
modSession f x = x { ghsSession = f $ ghsSession x }
-- | Internal use only, not to be confused with 'setHeader'.
addHeader :: MonadIO mo => Header -> GGHandler sub master mo ()
addHeader :: MonadIO mo => Header -> GHandlerT sub master mo ()
addHeader = tell . Endo . (:)
getStatus :: ErrorResponse -> H.Status
@ -720,18 +728,18 @@ data RedirectType = RedirectPermanent
| RedirectSeeOther
deriving (Show, Eq)
localNoCurrent :: Monad mo => GGHandler s m mo a -> GGHandler s m mo a
localNoCurrent :: Monad mo => GHandlerT s m mo a -> GHandlerT s m mo a
localNoCurrent =
local (\hd -> hd { handlerRoute = Nothing })
-- | Lookup for session data.
lookupSession :: MonadIO mo => Text -> GGHandler s m mo (Maybe Text)
lookupSession :: MonadIO mo => Text -> GHandlerT s m mo (Maybe Text)
lookupSession n = do
m <- liftM ghsSession get
return $ Map.lookup n m
-- | Get all session variables.
getSession :: MonadIO mo => GGHandler s m mo SessionMap
getSession :: MonadIO mo => GHandlerT s m mo SessionMap
getSession = liftM ghsSession get
handlerToYAR :: (HasReps a, HasReps b)
@ -744,7 +752,7 @@ handlerToYAR :: (HasReps a, HasReps b)
-> Maybe (Route s)
-> SessionMap
-> GHandler s m b
-> Iteratee ByteString IO YesodAppResult
-> ResourceT IO YesodAppResult
handlerToYAR y s toMasterRoute render errorHandler rr murl sessionMap h =
unYesodApp ya eh' rr types sessionMap
where
@ -766,8 +774,7 @@ yarToResponse renderHeaders (YARPlain s hs ct c sessionFinal) =
let hs' = maybe finalHeaders finalHeaders' mlen
in W.ResponseBuilder s hs' b
ContentFile fp p -> W.ResponseFile s finalHeaders fp p
ContentEnum e ->
W.ResponseEnumerator $ \iter -> run_ $ e $$ iter s finalHeaders
ContentSource body -> W.ResponseSource s finalHeaders body
where
finalHeaders = renderHeaders hs ct sessionFinal
finalHeaders' len = ("Content-Length", S8.pack $ show len)
@ -817,12 +824,17 @@ headerToPair cp getExpires (AddCookie minutes key value) =
})
headerToPair cp _ (DeleteCookie key) =
( "Set-Cookie"
, key `mappend` "=; path=" `mappend` cp `mappend` "; expires=Thu, 01-Jan-1970 00:00:00 GMT"
, S.concat
[ key
, "=; path="
, cp
, "; expires=Thu, 01-Jan-1970 00:00:00 GMT"
]
)
headerToPair _ _ (Header key value) = (key, value)
headerToPair _ _ (Header key value) = (CI.mk key, value)
-- | Get a unique identifier.
newIdent :: MonadIO mo => GGHandler sub master mo String -- FIXME use Text
newIdent :: MonadIO mo => GHandlerT sub master mo String -- FIXME use Text
newIdent = do
x <- get
let i' = ghsIdent x + 1
@ -830,8 +842,8 @@ newIdent = do
return $ 'h' : show i'
liftIOHandler :: MonadIO mo
=> GGHandler sub master IO a
-> GGHandler sub master mo a
=> GHandlerT sub master IO a
-> GHandlerT sub master mo a
liftIOHandler (ReaderT m) = ReaderT $ \r -> liftIO $ m r
-- | Redirect to a POST resource.
@ -840,7 +852,7 @@ liftIOHandler (ReaderT m) = ReaderT $ \r -> liftIO $ m r
-- POST form, and some Javascript to automatically submit the form. This can be
-- useful when you need to post a plain link somewhere that needs to cause
-- changes on the server.
redirectToPost :: MonadIO mo => Route master -> GGHandler sub master mo a
redirectToPost :: MonadIO mo => Route master -> GHandlerT sub master mo a
redirectToPost dest = hamletToRepHtml
#if GHC7
[hamlet|
@ -862,35 +874,35 @@ redirectToPost dest = hamletToRepHtml
-- | Converts the given Hamlet template into 'Content', which can be used in a
-- Yesod 'Response'.
hamletToContent :: Monad mo
=> HtmlUrl (Route master) -> GGHandler sub master mo Content
=> HtmlUrl (Route master) -> GHandlerT sub master mo Content
hamletToContent h = do
render <- getUrlRenderParams
return $ toContent $ h render
-- | Wraps the 'Content' generated by 'hamletToContent' in a 'RepHtml'.
hamletToRepHtml :: Monad mo
=> HtmlUrl (Route master) -> GGHandler sub master mo RepHtml
=> HtmlUrl (Route master) -> GHandlerT sub master mo RepHtml
hamletToRepHtml = liftM RepHtml . hamletToContent
-- | Get the request\'s 'W.Request' value.
waiRequest :: Monad mo => GGHandler sub master mo W.Request
waiRequest :: Monad mo => GHandlerT sub master mo W.Request
waiRequest = reqWaiRequest `liftM` getRequest
getMessageRender :: (Monad mo, RenderMessage master message) => GGHandler s master mo (message -> Text)
getMessageRender :: (Monad mo, RenderMessage master message) => GHandlerT s master mo (message -> Text)
getMessageRender = do
m <- getYesod
l <- reqLangs `liftM` getRequest
return $ renderMessage m l
cacheLookup :: MonadIO mo => CacheKey a -> GGHandler sub master mo (Maybe a)
cacheLookup :: MonadIO mo => CacheKey a -> GHandlerT sub master mo (Maybe a)
cacheLookup k = do
gs <- get
return $ Cache.lookup k $ ghsCache gs
cacheInsert :: MonadIO mo => CacheKey a -> a -> GGHandler sub master mo ()
cacheInsert :: MonadIO mo => CacheKey a -> a -> GHandlerT sub master mo ()
cacheInsert k v = modify $ \gs ->
gs { ghsCache = Cache.insert k v $ ghsCache gs }
cacheDelete :: MonadIO mo => CacheKey a -> GGHandler sub master mo ()
cacheDelete :: MonadIO mo => CacheKey a -> GHandlerT sub master mo ()
cacheDelete k = modify $ \gs ->
gs { ghsCache = Cache.delete k $ ghsCache gs }

View File

@ -39,11 +39,10 @@ import Data.Typeable (Typeable)
import Control.Exception (Exception)
import qualified Network.HTTP.Types as H
import qualified Network.HTTP.Types as A
import Data.CaseInsensitive (CI)
import Data.String (IsString)
import qualified Data.Map as Map
import Data.Text.Lazy.Builder (Builder)
import Network.HTTP.Types (Ascii)
#if GHC7
#define HAMLET hamlet
@ -65,9 +64,9 @@ instance Exception ErrorResponse
----- header stuff
-- | Headers to be added to a 'Result'.
data Header =
AddCookie Int A.Ascii A.Ascii
| DeleteCookie A.Ascii
| Header (CI A.Ascii) A.Ascii
AddCookie Int Ascii Ascii
| DeleteCookie Ascii
| Header Ascii Ascii
deriving (Eq, Show)
langKey :: IsString a => a

View File

@ -281,7 +281,7 @@ class RenderRoute (Route a) => Yesod a where
yepnopeJs _ = Nothing
messageLoggerHandler :: (Yesod m, MonadIO mo)
=> Loc -> LogLevel -> Text -> GGHandler s m mo ()
=> Loc -> LogLevel -> Text -> GHandlerT s m mo ()
messageLoggerHandler loc level msg = do
y <- getYesod
liftIO $ messageLogger y loc level msg

View File

@ -30,7 +30,7 @@ import qualified Data.Text
Alright, let's explain how routing works. We want to take a [String] and found
out which route it applies to. For static pieces, we need to ensure an exact
match against the segment. For a single or multi piece, we need to check the
result of fromSinglePiece/fromMultiPiece, respectively.
result of fromPathPiece/fromMultiPathPiece, respectively.
We want to create a tree of case statements basically resembling:
@ -51,7 +51,7 @@ case segments of
case as of
[] -> Nothing
b:bs ->
case fromSinglePiece b of
case fromPathPiece b of
Left _ -> Nothing
Right name ->
case bs of
@ -59,7 +59,7 @@ case segments of
case cs of
[] -> Nothing
d:ds ->
case fromSinglePiece d of
case fromPathPiece d of
Left _ -> Nothing
Right age ->
case ds of
@ -219,7 +219,7 @@ mkSimpleExp segments (SinglePiece _:pieces) frontVars x = do
innerExp <- mkSimpleExp (VarE srest) pieces (frontVars . (:) (VarE next')) x
nothing <- [|Nothing|]
next <- newName "next"
fsp <- [|fromSinglePiece|]
fsp <- [|fromPathPiece|]
let exp' = CaseE (fsp `AppE` VarE next)
[ Match
(ConP (mkName "Nothing") [])
@ -243,7 +243,7 @@ mkSimpleExp segments [MultiPiece _] frontVars x = do
srest <- [|[]|]
innerExp <- mkSimpleExp srest [] (frontVars . (:) (VarE next')) x
nothing <- [|Nothing|]
fmp <- [|fromMultiPiece|]
fmp <- [|fromPathMultiPiece|]
let exp = CaseE (fmp `AppE` segments)
[ Match
(ConP (mkName "Nothing") [])
@ -301,7 +301,7 @@ mkSubsiteExp segments (SinglePiece _:pieces) frontVars x = do
innerExp <- mkSubsiteExp srest pieces (frontVars . (:) (VarE next')) x
nothing <- [|Nothing|]
next <- newName "next"
fsp <- [|fromSinglePiece|]
fsp <- [|fromPathPiece|]
let exp' = CaseE (fsp `AppE` VarE next)
[ Match
(ConP (mkName "Nothing") [])

View File

@ -85,7 +85,7 @@ createParse res = do
mkPat' :: Exp -> [Piece] -> Exp -> Q ([Pat], Exp)
mkPat' be [MultiPiece s] parse = do
v <- newName $ "var" ++ s
fmp <- [|fromMultiPiece|]
fmp <- [|fromPathMultiPiece|]
let parse' = InfixE (Just parse) be $ Just $ fmp `AppE` VarE v
return ([VarP v], parse')
mkPat' _ (MultiPiece _:_) _parse = error "MultiPiece must be last"
@ -94,7 +94,7 @@ createParse res = do
let sp = LitP $ StringL s
return (sp : x, parse')
mkPat' be (SinglePiece s:rest) parse = do
fsp <- [|fromSinglePiece|]
fsp <- [|fromPathPiece|]
v <- newName $ "var" ++ s
let parse' = InfixE (Just parse) be $ Just $ fsp `AppE` VarE v
(x, parse'') <- mkPat' be rest parse'
@ -137,13 +137,13 @@ createRender = mapM go
return $ ConE (mkName ":") `AppE` (pack `AppE` x') `AppE` xs'
mkBod ((i, SinglePiece _):xs) = do
let x' = VarE $ mkName $ "var" ++ show i
tsp <- [|toSinglePiece|]
tsp <- [|toPathPiece|]
let x'' = tsp `AppE` x'
xs' <- mkBod xs
return $ ConE (mkName ":") `AppE` x'' `AppE` xs'
mkBod ((i, MultiPiece _):_) = do
let x' = VarE $ mkName $ "var" ++ show i
tmp <- [|toMultiPiece|]
tmp <- [|toPathMultiPiece|]
return $ tmp `AppE` x'
-- | Whether the set of resources cover all possible URLs.

View File

@ -6,22 +6,6 @@
--
module Yesod.Internal.TestApi
( randomString, parseWaiRequest'
, catchIter
) where
import Yesod.Internal.Request (randomString, parseWaiRequest')
import Control.Exception (Exception, catch)
import Data.Enumerator (Iteratee (..), Step (..))
import Data.ByteString (ByteString)
import Prelude hiding (catch)
catchIter :: Exception e
=> Iteratee ByteString IO a
-> (e -> Iteratee ByteString IO a)
-> Iteratee ByteString IO a
catchIter (Iteratee mstep) f = Iteratee $ do
step <- mstep `catch` (runIteratee . f)
return $ case step of
Continue k -> Continue $ \s -> catchIter (k s) f
Yield b s -> Yield b s
Error e -> Error e

View File

@ -52,20 +52,20 @@ import Data.Text (Text)
-- * Accept-Language HTTP header.
--
-- This is handled by parseWaiRequest (not exposed).
languages :: Monad mo => GGHandler s m mo [Text]
languages :: Monad mo => GHandlerT s m mo [Text]
languages = reqLangs `liftM` getRequest
lookup' :: Eq a => a -> [(a, b)] -> [b]
lookup' a = map snd . filter (\x -> a == fst x)
-- | Lookup for GET parameters.
lookupGetParams :: Monad mo => Text -> GGHandler s m mo [Text]
lookupGetParams :: Monad mo => Text -> GHandlerT s m mo [Text]
lookupGetParams pn = do
rr <- getRequest
return $ lookup' pn $ reqGetParams rr
-- | Lookup for GET parameters.
lookupGetParam :: Monad mo => Text -> GGHandler s m mo (Maybe Text)
lookupGetParam :: Monad mo => Text -> GHandlerT s m mo (Maybe Text)
lookupGetParam = liftM listToMaybe . lookupGetParams
-- | Lookup for POST parameters.
@ -91,11 +91,11 @@ lookupFiles pn = do
return $ lookup' pn files
-- | Lookup for cookie data.
lookupCookie :: Monad mo => Text -> GGHandler s m mo (Maybe Text)
lookupCookie :: Monad mo => Text -> GHandlerT s m mo (Maybe Text)
lookupCookie = liftM listToMaybe . lookupCookies
-- | Lookup for cookie data.
lookupCookies :: Monad mo => Text -> GGHandler s m mo [Text]
lookupCookies :: Monad mo => Text -> GHandlerT s m mo [Text]
lookupCookies pn = do
rr <- getRequest
return $ lookup' pn $ reqCookies rr

View File

@ -64,7 +64,7 @@ import Text.Cassius
import Text.Julius
import Text.Coffee
import Yesod.Handler
(Route, GHandler, GGHandler, YesodSubRoute(..), toMasterHandlerMaybe, getYesod
(Route, GHandler, GHandlerT, YesodSubRoute(..), toMasterHandlerMaybe, getYesod
, getMessageRender, getUrlRenderParams
)
import Yesod.Message (RenderMessage)
@ -189,7 +189,7 @@ setTitle x = GWidget $ tell $ GWData mempty (Last $ Just $ Title x) mempty mempt
-- | Set the page title. Calling 'setTitle' multiple times overrides previously
-- set values.
setTitleI :: (RenderMessage master msg, Monad m) => msg -> GGWidget master (GGHandler sub master m) ()
setTitleI :: (RenderMessage master msg, Monad m) => msg -> GGWidget master (GHandlerT sub master m) ()
setTitleI msg = do
mr <- lift getMessageRender
setTitle $ toHtml $ mr msg
@ -280,7 +280,7 @@ addJuliusBody j = addHamlet $ \r -> H.script $ preEscapedLazyText $ renderJavasc
-- | Add Coffesscript to the page's script tag. Requires the coffeescript
-- executable to be present at runtime.
addCoffee :: MonadIO m => CoffeeUrl (Route master) -> GGWidget master (GGHandler sub master m) ()
addCoffee :: MonadIO m => CoffeeUrl (Route master) -> GGWidget master (GHandlerT sub master m) ()
addCoffee c = do
render <- lift getUrlRenderParams
t <- liftIO $ renderCoffee render c
@ -288,7 +288,7 @@ addCoffee c = do
-- | Add a new script tag to the body with the contents of this Coffesscript
-- template. Requires the coffeescript executable to be present at runtime.
addCoffeeBody :: MonadIO m => CoffeeUrl (Route master) -> GGWidget master (GGHandler sub master m) ()
addCoffeeBody :: MonadIO m => CoffeeUrl (Route master) -> GGWidget master (GHandlerT sub master m) ()
addCoffeeBody c = do
render <- lift getUrlRenderParams
t <- liftIO $ renderCoffee render c
@ -338,7 +338,7 @@ rules = do
-- | Wraps the 'Content' generated by 'hamletToContent' in a 'RepHtml'.
ihamletToRepHtml :: (Monad mo, RenderMessage master message)
=> HtmlUrlI18n message (Route master)
-> GGHandler sub master mo RepHtml
-> GHandlerT sub master mo RepHtml
ihamletToRepHtml ih = do
urender <- getUrlRenderParams
mrender <- getMessageRender

View File

@ -11,10 +11,6 @@ import Network.Wai.Test
import Text.Hamlet (hamlet)
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Char8 as S8
import Yesod.Internal.TestApi
import qualified Data.Enumerator as E
import qualified Data.Enumerator.List as EL
import Control.Exception (SomeException)
data App = App
@ -61,7 +57,6 @@ errorHandlingTest = describe "Test.ErrorHandling"
[ it "says not found" caseNotFound
, it "says 'There was an error' before runRequestBody" caseBefore
, it "says 'There was an error' after runRequestBody" caseAfter
, it "catchIter handles internal exceptions" caseCatchIter
]
runner :: Session () -> IO ()
@ -101,11 +96,3 @@ caseAfter = runner $ do
}
assertStatus 500 res
assertBodyContains "bin12345" res
caseCatchIter :: IO ()
caseCatchIter = E.run_ $ E.enumList 8 (replicate 1000 "foo") E.$$ flip catchIter ignorer $ do
_ <- EL.consume
error "foo"
where
ignorer :: SomeException -> E.Iteratee a IO ()
ignorer _ = return ()

View File

@ -6,7 +6,6 @@ module YesodCoreTest.YesodTest
, module Network.Wai
, module Network.Wai.Test
, module Test.Hspec
, module Test.Hspec.HUnit
) where
import Yesod.Core hiding (Request)

View File

@ -1,5 +1,5 @@
name: yesod-core
version: 0.9.4
version: 0.10.0
license: BSD3
license-file: LICENSE
author: Michael Snoyman <michael@snoyman.com>
@ -46,12 +46,12 @@ library
build-depends: wai-test
build-depends: time >= 1.1.4
, wai >= 0.4 && < 0.5
, wai-extra >= 0.4.1 && < 0.5
, wai >= 1.0 && < 1.1
, wai-extra >= 1.0 && < 1.1
, bytestring >= 0.9.1.4 && < 0.10
, text >= 0.7 && < 0.12
, template-haskell
, path-pieces >= 0.0 && < 0.1
, path-pieces >= 0.1 && < 0.2
, hamlet >= 0.10.6 && < 0.11
, shakespeare >= 0.10 && < 0.11
, shakespeare-js >= 0.10.4 && < 0.11
@ -65,21 +65,20 @@ library
, old-locale >= 1.0.0.2 && < 1.1
, failure >= 0.1 && < 0.2
, containers >= 0.2 && < 0.5
, monad-control >= 0.2 && < 0.4
, monad-control >= 0.3 && < 0.4
, transformers-base >= 0.4
, enumerator >= 0.4.8 && < 0.5
, cookie >= 0.3 && < 0.4
, blaze-html >= 0.4.1.3 && < 0.5
, http-types >= 0.6.5 && < 0.7
, case-insensitive >= 0.2
, parsec >= 2 && < 3.2
, directory >= 1 && < 1.2
, data-object >= 0.3 && < 0.4
, data-object-yaml >= 0.3 && < 0.4
, vector >= 0.9 && < 0.10
, aeson >= 0.3
, aeson >= 0.5
, fast-logger >= 0.0.1
, wai-logger >= 0.0.1
, conduit >= 0.0 && < 0.1
, lifted-base >= 0.1 && < 0.2
exposed-modules: Yesod.Content
Yesod.Core
@ -89,7 +88,6 @@ library
Yesod.Request
Yesod.Widget
Yesod.Message
Yesod.Config
Yesod.Internal.TestApi
other-modules: Yesod.Internal
Yesod.Internal.Cache
@ -117,7 +115,7 @@ test-suite tests
main-is: test.hs
cpp-options: -DTEST
build-depends: hspec >= 0.8 && < 0.10
,wai-test >= 0.1.2 && < 0.2
,wai-test
,wai
,yesod-core
,bytestring
@ -129,7 +127,6 @@ test-suite tests
, random
,HUnit
,QuickCheck >= 2 && < 3
, enumerator
ghc-options: -Wall
source-repository head

View File

@ -1,5 +1,6 @@
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternGuards #-}
module Yesod.Default.Config
( DefaultEnv (..)
, fromArgs
@ -18,10 +19,9 @@ import Data.Char (toUpper, toLower)
import System.Console.CmdArgs hiding (args)
import Data.Text (Text)
import qualified Data.Text as T
import Control.Monad (join)
import Data.Object
import Data.Object.Yaml
import Data.Yaml
import Data.Maybe (fromMaybe)
import qualified Data.HashMap.Strict as M
-- | A yesod-provided @'AppEnv'@, allows for Development, Testing, and
-- Production environments
@ -55,13 +55,13 @@ fromArgs = fromArgsExtra (const $ const $ return ())
-- | Same as 'fromArgs', but allows you to specify how to parse the 'appExtra'
-- record.
fromArgsExtra :: (DefaultEnv -> TextObject -> IO extra)
fromArgsExtra :: (DefaultEnv -> Value -> IO extra)
-> IO (AppConfig DefaultEnv extra)
fromArgsExtra = fromArgsWith defaultArgConfig
fromArgsWith :: (Read env, Show env)
=> ArgConfig
-> (env -> TextObject -> IO extra)
-> (env -> Value -> IO extra)
-> IO (AppConfig env extra)
fromArgsWith argConfig getExtra = do
args <- cmdArgs argConfig
@ -103,12 +103,12 @@ data ConfigSettings environment extra = ConfigSettings
-- environment. Usually, you will use 'DefaultEnv' for this type.
csEnv :: environment
-- | Load any extra data, to be used by the application.
, csLoadExtra :: environment -> TextObject -> IO extra
, csLoadExtra :: environment -> Value -> IO extra
-- | Return the path to the YAML config file.
, csFile :: environment -> IO FilePath
-- | Get the sub-object (if relevant) from the given YAML source which
-- contains the specific settings for the current environment.
, csGetObject :: environment -> TextObject -> IO TextObject
, csGetObject :: environment -> Value -> IO Value
}
-- | Default config settings.
@ -117,14 +117,17 @@ configSettings env0 = ConfigSettings
{ csEnv = env0
, csLoadExtra = \_ _ -> return ()
, csFile = \_ -> return "config/settings.yml"
, csGetObject = \env obj -> do
envs <- fromMapping obj
, csGetObject = \env v -> do
envs <-
case v of
Object obj -> return obj
_ -> fail "Expected Object"
let senv = show env
tenv = T.pack senv
maybe
(error $ "Could not find environment: " ++ senv)
return
(lookup tenv envs)
(M.lookup tenv envs)
}
-- | Load an @'AppConfig'@.
@ -160,10 +163,14 @@ loadConfig :: ConfigSettings environment extra
-> IO (AppConfig environment extra)
loadConfig (ConfigSettings env loadExtra getFile getObject) = do
fp <- getFile env
topObj <- join $ decodeFile fp
mtopObj <- decodeFile fp
topObj <- maybe (fail "Invalid YAML file") return mtopObj
obj <- getObject env topObj
m <-
case obj of
Object m -> return m
_ -> fail "Expected map"
m <- maybe (fail "Expected map") return $ fromMapping obj
let mssl = lookupScalar "ssl" m
let mhost = lookupScalar "host" m
let mport = lookupScalar "port" m
@ -192,6 +199,11 @@ loadConfig (ConfigSettings env loadExtra getFile getObject) = do
}
where
lookupScalar k m =
case M.lookup k m of
Just (String t) -> return t
Just _ -> fail $ "Invalid value for: " ++ show k
Nothing -> fail $ "Not found: " ++ show k
toBool :: Text -> Bool
toBool = (`elem` ["true", "TRUE", "yes", "YES", "Y", "1"])
@ -216,11 +228,12 @@ safeRead name' t = case reads s of
withYamlEnvironment :: Show e
=> FilePath -- ^ the yaml file
-> e -- ^ the environment you want to load
-> (TextObject -> IO a) -- ^ what to do with the mapping
-> (Value -> IO a) -- ^ what to do with the mapping
-> IO a
withYamlEnvironment fp env f = do
obj <- join $ decodeFile fp
envs <- fromMapping obj
conf <- maybe (fail $ "Could not find environment: " ++ show env) return
$ lookup (T.pack $ show env) envs
f conf
mval <- decodeFile fp
case mval of
Nothing -> fail $ "Invalid YAML file: " ++ show fp
Just (Object obj)
| Just v <- M.lookup (T.pack $ show env) obj -> f v
_ -> fail $ "Could not find environment: " ++ show env

View File

@ -7,7 +7,7 @@ module Yesod.Default.Main
, defaultDevelAppWith
) where
import Yesod.Core hiding (AppConfig (..))
import Yesod.Core
import Yesod.Default.Config
import Yesod.Logger (Logger, makeDefaultLogger, logString, flushLogger)
import Network.Wai (Application)

View File

@ -1,5 +1,5 @@
name: yesod-default
version: 0.5.0
version: 0.6.0
license: BSD3
license-file: LICENSE
author: Patrick Brisbin
@ -18,11 +18,11 @@ library
cpp-options: -DWINDOWS
build-depends: base >= 4 && < 5
, yesod-core >= 0.9.4 && < 0.10
, yesod-core >= 0.10 && < 0.11
, cmdargs >= 0.8
, warp >= 0.4 && < 0.5
, wai >= 0.4 && < 0.5
, wai-extra >= 0.4.4 && < 0.5
, warp >= 1.0 && < 1.1
, wai >= 1.0 && < 1.1
, wai-extra >= 1.0 && < 1.1
, bytestring >= 0.9.1.4
, transformers >= 0.2.2 && < 0.3
, text >= 0.9
@ -30,8 +30,8 @@ library
, shakespeare-css >= 0.10.5 && < 0.11
, shakespeare-js >= 0.10.4 && < 0.11
, template-haskell
, data-object >= 0.3 && < 0.4
, data-object-yaml >= 0.3 && < 0.4
, yaml >= 0.5 && < 0.6
, unordered-containers
if !os(windows)
build-depends: unix

View File

@ -67,6 +67,7 @@ import Text.Blaze.Renderer.String (renderHtml)
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import Data.Text (Text, unpack, pack)
import qualified Data.Text as T
import qualified Data.Text.Read
import Control.Monad.Trans.Class (lift)
@ -75,8 +76,8 @@ import qualified Data.Map as Map
import Yesod.Handler (newIdent, liftIOHandler)
import Yesod.Request (FileInfo)
import Yesod.Core (toSinglePiece, GGHandler, SinglePiece)
import Yesod.Persist (selectList, runDB, Filter, SelectOpt, YesodPersistBackend, Key, YesodPersist, PersistEntity, PersistBackend)
import Yesod.Core (toPathPiece, GHandler, GHandlerT, PathPiece)
import Yesod.Persist (selectList, runDB, Filter, SelectOpt, YesodPersistBackend, Key, YesodPersist, PersistEntity, PersistQuery)
import Control.Arrow ((&&&))
#if __GLASGOW_HASKELL__ >= 700
@ -110,9 +111,9 @@ intField = Field
Right (a, "") -> Right a
_ -> Left $ MsgInvalidInteger s
, fieldView = \theId name val isReq -> addHamlet
, fieldView = \theId name theClass val isReq -> addHamlet
[HAMLET|\
<input id="#{theId}" name="#{name}" type="number" :isReq:required="" value="#{showVal val}">
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="number" :isReq:required="" value="#{showVal val}">
|]
}
where
@ -126,9 +127,9 @@ doubleField = Field
Right (a, "") -> Right a
_ -> Left $ MsgInvalidNumber s
, fieldView = \theId name val isReq -> addHamlet
, fieldView = \theId name theClass val isReq -> addHamlet
[HAMLET|\
<input id="#{theId}" name="#{name}" type="text" :isReq:required="" value="#{showVal val}">
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="text" :isReq:required="" value="#{showVal val}">
|]
}
where showVal = either id (pack . show)
@ -136,9 +137,9 @@ doubleField = Field
dayField :: RenderMessage master FormMessage => Field sub master Day
dayField = Field
{ fieldParse = blank $ parseDate . unpack
, fieldView = \theId name val isReq -> addHamlet
, fieldView = \theId name theClass val isReq -> addHamlet
[HAMLET|\
<input id="#{theId}" name="#{name}" type="date" :isReq:required="" value="#{showVal val}">
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="date" :isReq:required="" value="#{showVal val}">
|]
}
where showVal = either id (pack . show)
@ -146,9 +147,9 @@ dayField = Field
timeField :: RenderMessage master FormMessage => Field sub master TimeOfDay
timeField = Field
{ fieldParse = blank $ parseTime . unpack
, fieldView = \theId name val isReq -> addHamlet
, fieldView = \theId name theClass val isReq -> addHamlet
[HAMLET|\
<input id="#{theId}" name="#{name}" :isReq:required="" value="#{showVal val}">
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate "" theClass}" :isReq:required="" value="#{showVal val}">
|]
}
where
@ -161,9 +162,10 @@ timeField = Field
htmlField :: RenderMessage master FormMessage => Field sub master Html
htmlField = Field
{ fieldParse = blank $ Right . preEscapedText . sanitizeBalance
, fieldView = \theId name val _isReq -> addHamlet
, fieldView = \theId name theClass val _isReq -> addHamlet
-- FIXME: There was a class="html" attribute, for what purpose?
[HAMLET|\
<textarea id="#{theId}" name="#{name}" .html>#{showVal val}
<textarea id="#{theId}" name="#{name}" :not (null theClass):class=#{T.intercalate " " theClass}>#{showVal val}
|]
}
where showVal = either id (pack . renderHtml)
@ -189,36 +191,36 @@ instance ToHtml Textarea where
textareaField :: RenderMessage master FormMessage => Field sub master Textarea
textareaField = Field
{ fieldParse = blank $ Right . Textarea
, fieldView = \theId name val _isReq -> addHamlet
, fieldView = \theId name theClass val _isReq -> addHamlet
[HAMLET|\
<textarea id="#{theId}" name="#{name}">#{either id unTextarea val}
<textarea id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}">#{either id unTextarea val}
|]
}
hiddenField :: RenderMessage master FormMessage => Field sub master Text
hiddenField = Field
{ fieldParse = blank $ Right
, fieldView = \theId name val _isReq -> addHamlet
, fieldView = \theId name theClass val _isReq -> addHamlet
[HAMLET|\
<input type="hidden" id="#{theId}" name="#{name}" value="#{either id id val}">
<input type="hidden" id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" value="#{either id id val}">
|]
}
textField :: RenderMessage master FormMessage => Field sub master Text
textField = Field
{ fieldParse = blank $ Right
, fieldView = \theId name val isReq ->
, fieldView = \theId name theClass val isReq ->
[WHAMLET|
<input id="#{theId}" name="#{name}" type="text" :isReq:required value="#{either id id val}">
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="text" :isReq:required value="#{either id id val}">
|]
}
passwordField :: RenderMessage master FormMessage => Field sub master Text
passwordField = Field
{ fieldParse = blank $ Right
, fieldView = \theId name val isReq -> addHamlet
, fieldView = \theId name theClass val isReq -> addHamlet
[HAMLET|\
<input id="#{theId}" name="#{name}" type="password" :isReq:required="" value="#{either id id val}">
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="password" :isReq:required="" value="#{either id id val}">
|]
}
@ -266,9 +268,9 @@ emailField = Field
\s -> if Email.isValid (unpack s)
then Right s
else Left $ MsgInvalidEmail s
, fieldView = \theId name val isReq -> addHamlet
, fieldView = \theId name theClass val isReq -> addHamlet
[HAMLET|\
<input id="#{theId}" name="#{name}" type="email" :isReq:required="" value="#{either id id val}">
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="email" :isReq:required="" value="#{either id id val}">
|]
}
@ -276,9 +278,9 @@ type AutoFocus = Bool
searchField :: RenderMessage master FormMessage => AutoFocus -> Field sub master Text
searchField autoFocus = Field
{ fieldParse = blank Right
, fieldView = \theId name val isReq -> do
, fieldView = \theId name theClass val isReq -> do
[WHAMLET|\
<input id="#{theId}" name="#{name}" type="search" :isReq:required="" :autoFocus:autofocus="" value="#{either id id val}">
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="search" :isReq:required="" :autoFocus:autofocus="" value="#{either id id val}">
|]
when autoFocus $ do
-- we want this javascript to be placed immediately after the field
@ -296,30 +298,30 @@ urlField = Field
case parseURI $ unpack s of
Nothing -> Left $ MsgInvalidUrl s
Just _ -> Right s
, fieldView = \theId name val isReq ->
, fieldView = \theId name theClass val isReq ->
[WHAMLET|
<input ##{theId} name=#{name} type=url :isReq:required value=#{either id id val}>
<input ##{theId} name=#{name} :not (null theClass):class="#{T.intercalate " " theClass}" type=url :isReq:required value=#{either id id val}>
|]
}
selectField :: (Eq a, RenderMessage master FormMessage) => [(Text, a)] -> Field sub master a
selectField = selectField' . optionsPairs
selectField' :: (Eq a, RenderMessage master FormMessage) => GGHandler sub master IO (OptionList a) -> Field sub master a
selectField' :: (Eq a, RenderMessage master FormMessage) => GHandlerT sub master IO (OptionList a) -> Field sub master a
selectField' = selectFieldHelper
(\theId name inside -> [WHAMLET|<select ##{theId} name=#{name}>^{inside}|]) -- outside
(\_theId _name isSel -> [WHAMLET|<option value=none :isSel:selected>_{MsgSelectNone}|]) -- onOpt
(\_theId _name value isSel text -> [WHAMLET|<option value=#{value} :isSel:selected>#{text}|]) -- inside
(\_theId _name theClass value isSel text -> [WHAMLET|<option value=#{value} :isSel:selected :not (null theClass):class="#{T.intercalate " " theClass}">#{text}|]) -- inside
multiSelectField :: (Show a, Eq a, RenderMessage master FormMessage) => [(Text, a)] -> Field sub master [a]
multiSelectField = multiSelectFieldHelper
(\theId name inside -> [WHAMLET|<select ##{theId} multiple name=#{name}>^{inside}|])
(\_theId _name value isSel text -> [WHAMLET|<option value=#{value} :isSel:selected>#{text}|])
(\_theId _name theClass value isSel text -> [WHAMLET|<option value=#{value} :isSel:selected :not (null theClass):class="#{T.intercalate " " theClass}">#{text}|])
radioField :: (Eq a, RenderMessage master FormMessage) => [(Text, a)] -> Field sub master a
radioField = radioField' . optionsPairs
radioField' :: (Eq a, RenderMessage master FormMessage) => GGHandler sub master IO (OptionList a) -> Field sub master a
radioField' :: (Eq a, RenderMessage master FormMessage) => GHandlerT sub master IO (OptionList a) -> Field sub master a
radioField' = selectFieldHelper
(\theId _name inside -> [WHAMLET|<div ##{theId}>^{inside}|])
(\theId name isSel -> [WHAMLET|
@ -327,25 +329,25 @@ radioField' = selectFieldHelper
<input id=#{theId}-none type=radio name=#{name} value=none :isSel:checked>
<label for=#{theId}-none>_{MsgSelectNone}
|])
(\theId name value isSel text -> [WHAMLET|
(\theId name theClass value isSel text -> [WHAMLET|
<div>
<input id=#{theId}-#{value} type=radio name=#{name} value=#{value} :isSel:checked>
<input id=#{theId}-#{value} type=radio name=#{name} value=#{value} :isSel:checked :not (null theClass):class="#{T.intercalate " " theClass}">
<label for=#{theId}-#{value}>#{text}
|])
boolField :: RenderMessage master FormMessage => Field sub master Bool
boolField = Field
{ fieldParse = return . boolParser
, fieldView = \theId name val isReq -> [WHAMLET|
, fieldView = \theId name theClass val isReq -> [WHAMLET|
$if not isReq
<input id=#{theId}-none type=radio name=#{name} value=none checked>
<input id=#{theId}-none :not (null theClass):class="#{T.intercalate " " theClass}" type=radio name=#{name} value=none checked>
<label for=#{theId}-none>_{MsgSelectNone}
<input id=#{theId}-yes type=radio name=#{name} value=yes :showVal id val:checked>
<input id=#{theId}-yes :not (null theClass):class="#{T.intercalate " " theClass}" type=radio name=#{name} value=yes :showVal id val:checked>
<label for=#{theId}-yes>_{MsgBoolYes}
<input id=#{theId}-no type=radio name=#{name} value=no :showVal not val:checked>
<input id=#{theId}-no :not (null theClass):class="#{T.intercalate " " theClass}" type=radio name=#{name} value=no :showVal not val:checked>
<label for=#{theId}-no>_{MsgBoolNo}
|]
}
@ -361,15 +363,16 @@ boolField = Field
multiSelectFieldHelper :: (Show a, Eq a)
=> (Text -> Text -> GWidget sub master () -> GWidget sub master ())
-> (Text -> Text -> Text -> Bool -> Text -> GWidget sub master ())
-> (Text -> Text -> [Text] -> Text -> Bool -> Text -> GWidget sub master ())
-> [(Text, a)] -> Field sub master [a]
multiSelectFieldHelper outside inside opts = Field
{ fieldParse = return . selectParser
, fieldView = \theId name vals _ ->
, fieldView = \theId name theClass vals _ ->
outside theId name $ do
flip mapM_ pairs $ \pair -> inside
theId
name
theClass
(pack $ show $ fst pair)
((fst pair) `elem` (either (\_ -> []) selectedVals vals)) -- We are presuming that select fields can't hold invalid values
(fst $ snd pair)
@ -399,45 +402,47 @@ data Option a = Option
, optionExternalValue :: Text
}
optionsPairs :: [(Text, a)] -> GGHandler sub master IO (OptionList a)
optionsPairs :: [(Text, a)] -> GHandlerT sub master IO (OptionList a)
optionsPairs = return . mkOptionList . zipWith (\external (display, internal) -> Option
{ optionDisplay = display
, optionInternalValue = internal
, optionExternalValue = pack $ show external
}) [1 :: Int ..]
optionsEnum :: (Show a, Enum a, Bounded a) => GGHandler sub master IO (OptionList a)
optionsEnum :: (Show a, Enum a, Bounded a) => GHandlerT sub master IO (OptionList a)
optionsEnum = optionsPairs $ map (\x -> (pack $ show x, x)) [minBound..maxBound]
optionsPersist :: ( YesodPersist master, PersistEntity a, PersistBackend (YesodPersistBackend master) (GGHandler sub master IO)
, SinglePiece (Key (YesodPersistBackend master) a)
optionsPersist :: ( YesodPersist master, PersistEntity a
, PersistQuery (YesodPersistBackend master) (GHandler sub master)
, PathPiece (Key (YesodPersistBackend master) a)
)
=> [Filter a] -> [SelectOpt a] -> (a -> Text) -> GGHandler sub master IO (OptionList (Key (YesodPersistBackend master) a, a))
=> [Filter a] -> [SelectOpt a] -> (a -> Text) -> GHandler sub master (OptionList (Key (YesodPersistBackend master) a, a))
optionsPersist filts ords toDisplay = fmap mkOptionList $ do
pairs <- runDB $ selectList filts ords
return $ map (\(key, value) -> Option
{ optionDisplay = toDisplay value
, optionInternalValue = (key, value)
, optionExternalValue = toSinglePiece key
, optionExternalValue = toPathPiece key
}) pairs
selectFieldHelper
:: (Eq a, RenderMessage master FormMessage)
=> (Text -> Text -> GWidget sub master () -> GWidget sub master ())
-> (Text -> Text -> Bool -> GWidget sub master ())
-> (Text -> Text -> Text -> Bool -> Text -> GWidget sub master ())
-> GGHandler sub master IO (OptionList a) -> Field sub master a
-> (Text -> Text -> [Text] -> Text -> Bool -> Text -> GWidget sub master ())
-> GHandlerT sub master IO (OptionList a) -> Field sub master a
selectFieldHelper outside onOpt inside opts' = Field
{ fieldParse = \x -> do
opts <- opts'
return $ selectParser opts x
, fieldView = \theId name val isReq -> do
, fieldView = \theId name theClass val isReq -> do
opts <- fmap olOptions $ lift $ liftIOHandler opts'
outside theId name $ do
unless isReq $ onOpt theId name $ not $ render opts val `elem` map optionExternalValue opts
flip mapM_ opts $ \opt -> inside
theId
name
theClass
(optionExternalValue opt)
((render opts val) == optionExternalValue opt)
(optionDisplay opt)
@ -471,12 +476,13 @@ fileAFormReq fs = AForm $ \(master, langs) menvs ints -> do
let t = renderMessage master langs MsgValueRequired
in (FormFailure [t], Just $ toHtml t)
Just fi -> (FormSuccess fi, Nothing)
let theClass = fsClass fs
let fv = FieldView
{ fvLabel = toHtml $ renderMessage master langs $ fsLabel fs
, fvTooltip = fmap (toHtml . renderMessage master langs) $ fsTooltip fs
, fvId = id'
, fvInput = [WHAMLET|
<input type=file name=#{name} ##{id'}>
<input type=file name=#{name} ##{id'} :not (null theClass):class="#{T.intercalate " " theClass}">
|]
, fvErrors = errs
, fvRequired = True
@ -499,12 +505,13 @@ fileAFormOpt fs = AForm $ \(master, langs) menvs ints -> do
case Map.lookup name fenv of
Nothing -> (FormSuccess Nothing, Nothing)
Just fi -> (FormSuccess $ Just fi, Nothing)
let theClass = fsClass fs
let fv = FieldView
{ fvLabel = toHtml $ renderMessage master langs $ fsLabel fs
, fvTooltip = fmap (toHtml . renderMessage master langs) $ fsTooltip fs
, fvId = id'
, fvInput = [WHAMLET|
<input type=file name=#{name} ##{id'}>
<input type=file name=#{name} ##{id'} :not (null theClass):class="#{T.intercalate " " theClass}">
|]
, fvErrors = errs
, fvRequired = False

View File

@ -40,7 +40,7 @@ import Control.Monad.Trans.RWS (ask, get, put, runRWST, tell, evalRWST)
import Control.Monad.Trans.Class (lift)
import Control.Monad (liftM, join)
import Text.Blaze (Html, toHtml)
import Yesod.Handler (GHandler, GGHandler, getRequest, runRequestBody, newIdent, getYesod)
import Yesod.Handler (GHandler, GHandlerT, getRequest, runRequestBody, newIdent, getYesod)
import Yesod.Core (RenderMessage, liftIOHandler, SomeMessage (..))
import Yesod.Widget (GWidget, whamlet)
import Yesod.Request (reqNonce, reqWaiRequest, reqGetParams, languages, FileInfo (..))
@ -137,7 +137,7 @@ mhelper Field {..} FieldSettings {..} mdef onMissing onFound isReq = do
{ fvLabel = toHtml $ mr2 fsLabel
, fvTooltip = fmap toHtml $ fmap mr2 fsTooltip
, fvId = theId
, fvInput = fieldView theId name val isReq
, fvInput = fieldView theId name fsClass val isReq
, fvErrors =
case res of
FormFailure [e] -> Just $ toHtml e
@ -157,7 +157,7 @@ aopt :: RenderMessage master msg
-> AForm sub master (Maybe a)
aopt a b = formToAForm . mopt a b
runFormGeneric :: MonadIO m => MForm sub master a -> master -> [Text] -> Maybe (Env, FileEnv) -> GGHandler sub master m (a, Enctype)
runFormGeneric :: MonadIO m => MForm sub master a -> master -> [Text] -> Maybe (Env, FileEnv) -> GHandlerT sub master m (a, Enctype)
runFormGeneric form master langs env = liftIOHandler $ evalRWST form (env, master, langs) (IntSingle 1)
-- | This function is used to both initially render a form and to later extract
@ -299,7 +299,7 @@ checkBool :: RenderMessage master msg
checkBool b s = check $ \x -> if b x then Right x else Left s
checkM :: RenderMessage master msg
=> (a -> GGHandler sub master IO (Either msg a))
=> (a -> GHandlerT sub master IO (Either msg a))
-> Field sub master a
-> Field sub master a
checkM f field = field

View File

@ -11,7 +11,7 @@ module Yesod.Form.Input
import Yesod.Form.Types
import Data.Text (Text)
import Control.Applicative (Applicative (..))
import Yesod.Handler (GHandler, GGHandler, invalidArgs, runRequestBody, getRequest, getYesod, liftIOHandler)
import Yesod.Handler (GHandler, GHandlerT, invalidArgs, runRequestBody, getRequest, getYesod, liftIOHandler)
import Yesod.Request (reqGetParams, languages)
import Control.Monad (liftM)
import Yesod.Message (RenderMessage (..), SomeMessage (..))
@ -19,7 +19,7 @@ import qualified Data.Map as Map
import Data.Maybe (fromMaybe)
type DText = [Text] -> [Text]
newtype FormInput sub master a = FormInput { unFormInput :: master -> [Text] -> Env -> GGHandler sub master IO (Either DText a) }
newtype FormInput sub master a = FormInput { unFormInput :: master -> [Text] -> Env -> GHandlerT sub master IO (Either DText a) }
instance Functor (FormInput sub master) where
fmap a (FormInput f) = FormInput $ \c d e -> fmap (either Left (Right . a)) $ f c d e
instance Applicative (FormInput sub master) where

View File

@ -20,6 +20,7 @@ import Yesod.Form
import Yesod.Widget
import Data.Time (UTCTime (..), Day, TimeOfDay (..), timeOfDayToTime,
timeToTimeOfDay)
import qualified Data.Text as T
import Data.Char (isSpace)
import Data.Default
import Text.Hamlet (shamlet)
@ -78,9 +79,9 @@ jqueryDayField jds = Field
Right
. readMay
. unpack
, fieldView = \theId name val isReq -> do
, fieldView = \theId name theClass val isReq -> do
addHtml [HTML|\
<input id="#{theId}" name="#{name}" type="date" :isReq:required="" value="#{showVal val}">
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="date" :isReq:required="" value="#{showVal val}">
|]
addScript' urlJqueryJs
addScript' urlJqueryUiJs
@ -129,9 +130,9 @@ jqueryDayTimeUTCTime (UTCTime day utcTime) =
jqueryDayTimeField :: (RenderMessage master FormMessage, YesodJquery master) => Field sub master UTCTime
jqueryDayTimeField = Field
{ fieldParse = blank $ parseUTCTime . unpack
, fieldView = \theId name val isReq -> do
, fieldView = \theId name theClass val isReq -> do
addHtml [HTML|\
<input id="#{theId}" name="#{name}" :isReq:required="" value="#{showVal val}">
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" :isReq:required="" value="#{showVal val}">
|]
addScript' urlJqueryJs
addScript' urlJqueryUiJs
@ -159,9 +160,9 @@ jqueryAutocompleteField :: (RenderMessage master FormMessage, YesodJquery master
=> Route master -> Field sub master Text
jqueryAutocompleteField src = Field
{ fieldParse = blank $ Right
, fieldView = \theId name val isReq -> do
, fieldView = \theId name theClass val isReq -> do
addHtml [HTML|\
<input id="#{theId}" name="#{name}" type="text" :isReq:required="" value="#{either id id val}" .autocomplete>
<input id="#{theId}" name="#{name}" :not (null theClass):class="#{T.intercalate " " theClass}" type="text" :isReq:required="" value="#{either id id val}" .autocomplete>
|]
addScript' urlJqueryJs
addScript' urlJqueryUiJs
@ -171,7 +172,7 @@ $(function(){$("##{theId}").autocomplete({source:"@{src}",minLength:2})});
|]
}
addScript' :: Monad m => (t -> Either (Route master) Text) -> GGWidget master (GGHandler sub t m) ()
addScript' :: (master -> Either (Route master) Text) -> GWidget sub master ()
addScript' f = do
y <- lift getYesod
addScriptEither $ f y

View File

@ -14,7 +14,7 @@ import Yesod.Form.Functions
import Yesod.Form.Fields (boolField)
import Yesod.Widget (GWidget, whamlet)
import Yesod.Message (RenderMessage)
import Yesod.Handler (newIdent, GGHandler)
import Yesod.Handler (newIdent, GHandler)
import Text.Blaze (Html)
import Control.Monad.Trans.Class (lift)
import Data.Text (pack)
@ -53,7 +53,7 @@ up i = do
IntCons _ is' -> put is' >> newFormIdent >> return ()
up $ i - 1
inputList :: (m ~ GGHandler sub master IO, xml ~ GWidget sub master (), RenderMessage master FormMessage)
inputList :: (m ~ GHandler sub master, xml ~ GWidget sub master (), RenderMessage master FormMessage)
=> Html
-> ([[FieldView sub master]] -> xml)
-> (Maybe a -> AForm sub master a)
@ -111,6 +111,7 @@ withDelete af = do
, fsTooltip = Nothing
, fsName = Just deleteName
, fsId = Nothing
, fsClass = []
} $ Just False
(res, xml) <- aFormToForm af
return $ Right (res, xml $ xml2 [])

View File

@ -20,6 +20,7 @@ import Text.Blaze.Renderer.String (renderHtml)
import Text.Blaze (preEscapedText)
import Control.Monad.Trans.Class (lift)
import Data.Text (Text, pack)
import qualified Data.Text as T
import Data.Maybe (listToMaybe)
class YesodNic a where
@ -30,14 +31,14 @@ class YesodNic a where
nicHtmlField :: YesodNic master => Field sub master Html
nicHtmlField = Field
{ fieldParse = return . Right . fmap (preEscapedText . sanitizeBalance) . listToMaybe
, fieldView = \theId name val _isReq -> do
, fieldView = \theId name theClass val _isReq -> do
addHtml
#if __GLASGOW_HASKELL__ >= 700
[shamlet|
#else
[$shamlet|
#endif
<textarea id="#{theId}" name="#{name}" .html>#{showVal val}
<textarea id="#{theId}" :not (null theClass):class="#{T.intercalate " " theClass}" name="#{name}" .html>#{showVal val}
|]
addScript' urlNicEdit
addJulius

View File

@ -10,7 +10,6 @@ module Yesod.Form.Types
, FileEnv
, Ints (..)
-- * Form
, Form
, MForm
, AForm (..)
-- * Build forms
@ -27,7 +26,7 @@ import Text.Blaze (Html, ToHtml (toHtml))
import Control.Applicative ((<$>), Applicative (..))
import Control.Monad (liftM)
import Data.String (IsString (..))
import Yesod.Core (GGHandler, GWidget, SomeMessage)
import Yesod.Core (GHandlerT, GWidget, SomeMessage)
import qualified Data.Map as Map
-- | A form can produce three different results: there was no data available,
@ -75,12 +74,10 @@ type Env = Map.Map Text [Text]
type FileEnv = Map.Map Text FileInfo
type Lang = Text
type Form sub master a = RWST (Maybe (Env, FileEnv), master, [Lang]) Enctype Ints (GGHandler sub master IO) a
{-# DEPRECATED Form "Use MForm instead" #-}
type MForm sub master a = RWST (Maybe (Env, FileEnv), master, [Lang]) Enctype Ints (GGHandler sub master IO) a
type MForm sub master a = RWST (Maybe (Env, FileEnv), master, [Lang]) Enctype Ints (GHandlerT sub master IO) a
newtype AForm sub master a = AForm
{ unAForm :: (master, [Text]) -> Maybe (Env, FileEnv) -> Ints -> GGHandler sub master IO (FormResult a, [FieldView sub master] -> [FieldView sub master], Ints, Enctype)
{ unAForm :: (master, [Text]) -> Maybe (Env, FileEnv) -> Ints -> GHandlerT sub master IO (FormResult a, [FieldView sub master] -> [FieldView sub master], Ints, Enctype)
}
instance Functor (AForm sub master) where
fmap f (AForm a) =
@ -102,10 +99,11 @@ data FieldSettings msg = FieldSettings
, fsTooltip :: Maybe msg
, fsId :: Maybe Text
, fsName :: Maybe Text
, fsClass :: [Text]
}
instance (a ~ Text) => IsString (FieldSettings a) where
fromString s = FieldSettings (fromString s) Nothing Nothing Nothing
fromString s = FieldSettings (fromString s) Nothing Nothing Nothing []
data FieldView sub master = FieldView
{ fvLabel :: Html
@ -117,10 +115,11 @@ data FieldView sub master = FieldView
}
data Field sub master a = Field
{ fieldParse :: [Text] -> GGHandler sub master IO (Either (SomeMessage master) (Maybe a))
-- | ID, name, (invalid text OR legimiate result), required?
{ fieldParse :: [Text] -> GHandlerT sub master IO (Either (SomeMessage master) (Maybe a))
-- | ID, name, class, (invalid text OR legimiate result), required?
, fieldView :: Text
-> Text
-> [Text]
-> Either Text a
-> Bool
-> GWidget sub master ()

View File

@ -1,5 +1,5 @@
name: yesod-form
version: 0.3.4.2
version: 0.4.0
license: BSD3
license-file: LICENSE
author: Michael Snoyman <michael@snoyman.com>
@ -14,25 +14,24 @@ description: Form handling support for Yesod Web Framework
library
build-depends: base >= 4 && < 5
, yesod-core >= 0.9 && < 0.10
, yesod-persistent >= 0.2 && < 0.3
, yesod-core >= 0.10 && < 0.11
, yesod-persistent >= 0.3 && < 0.4
, time >= 1.1.4
, hamlet >= 0.10 && < 0.11
, shakespeare-css >= 0.10 && < 0.11
, shakespeare-js >= 0.10 && < 0.11
, persistent >= 0.6 && < 0.7
, yesod-persistent >= 0.2 && < 0.3
, persistent >= 0.7 && < 0.8
, template-haskell
, transformers >= 0.2.2 && < 0.3
, data-default >= 0.3 && < 0.4
, xss-sanitize >= 0.3.0.1 && < 0.4
, blaze-builder >= 0.2.1.4 && < 0.4
, blaze-builder >= 0.2.1.4 && < 0.4
, network >= 2.2 && < 2.4
, email-validate >= 0.2.6 && < 0.3
, blaze-html >= 0.4.1.3 && < 0.5
, bytestring >= 0.9.1.4 && < 0.10
, text >= 0.9 && < 0.12
, wai >= 0.4 && < 0.5
, text >= 0.9 && < 1.0
, wai >= 1.0 && < 1.1
, containers >= 0.2 && < 0.5
exposed-modules: Yesod.Form
Yesod.Form.Class

View File

@ -7,14 +7,13 @@ module Yesod.Json
, jsonToRepJson
-- * Convert to a JSON value
, parseJsonBody
-- * Compatibility wrapper for old API
, Json
, jsonScalar
, jsonList
, jsonMap
-- * Produce JSON values
, J.Value (..)
, object
, array
) where
import Yesod.Handler (GHandler)
import Yesod.Handler (GHandler, waiRequest)
import Yesod.Content
( ToContent (toContent), RepHtmlJson (RepHtmlJson), RepHtml (RepHtml)
, RepJson (RepJson), Content (ContentBuilder)
@ -24,33 +23,23 @@ import Yesod.Widget (GWidget)
import qualified Data.Aeson as J
import qualified Data.Aeson.Encode as JE
import Data.Aeson.Encode (fromValue)
import Data.Attoparsec.Enumerator (iterParser)
import Data.Text (pack)
import Control.Arrow (first)
import Data.Conduit.Attoparsec (sinkParser)
import Data.Text (Text)
import Control.Monad.Trans.Class (lift)
#if MIN_VERSION_aeson(0, 4, 0)
import Data.HashMap.Strict (fromList)
#else
import Data.Map (fromList)
#endif
import qualified Data.Vector as V
import Text.Julius (ToJavascript (..))
import Data.Text.Lazy.Builder (fromLazyText)
import Data.Text.Lazy.Encoding (decodeUtf8)
#if MIN_VERSION_aeson(0, 5, 0)
import Data.Text.Lazy.Builder (toLazyText)
import qualified Blaze.ByteString.Builder.Char.Utf8 as Blaze
#endif
import Data.Conduit (($$))
import Network.Wai (requestBody)
instance ToContent J.Value where
#if MIN_VERSION_aeson(0, 5, 0)
toContent = flip ContentBuilder Nothing
. Blaze.fromLazyText
. toLazyText
. fromValue
#else
toContent = flip ContentBuilder Nothing . fromValue
#endif
-- | Provide both an HTML and JSON representation for a piece of data, using
-- the default layout for the HTML output ('defaultLayout').
@ -70,19 +59,17 @@ jsonToRepJson = return . RepJson . toContent
--
-- /Since: 0.2.3/
parseJsonBody :: GHandler sub master J.Value
parseJsonBody = lift $ iterParser J.json'
type Json = J.Value
jsonScalar :: String -> Json
jsonScalar = J.String . pack
jsonList :: [Json] -> Json
jsonList = J.Array . V.fromList
jsonMap :: [(String, Json)] -> Json
jsonMap = J.Object . fromList . map (first pack)
parseJsonBody = do
req <- waiRequest
lift $ requestBody req $$ sinkParser J.json'
instance ToJavascript J.Value where
toJavascript = fromLazyText . decodeUtf8 . JE.encode
-- | Convert a list of pairs to an 'J.Object'.
object :: [(Text, J.Value)] -> J.Value
object = J.object
-- | Convert a list of values to an 'J.Array'.
array :: [J.Value] -> J.Value
array = J.Array . V.fromList

View File

@ -1,5 +1,5 @@
name: yesod-json
version: 0.2.3
version: 0.3.0
license: BSD3
license-file: LICENSE
author: Michael Snoyman <michael@snoyman.com>
@ -14,16 +14,17 @@ description: Generate content for Yesod using the aeson package.
library
build-depends: base >= 4 && < 5
, yesod-core >= 0.9 && < 0.10
, aeson >= 0.3
, text >= 0.8 && < 0.12
, yesod-core >= 0.10 && < 0.11
, aeson >= 0.5
, text >= 0.8 && < 1.0
, shakespeare-js >= 0.10 && < 0.11
, vector >= 0.9
, containers >= 0.2 && < 0.5
, unordered-containers
, containers >= 0.2
, blaze-builder
, attoparsec-enumerator >= 0.3 && < 0.4
, attoparsec-conduit >= 0.0 && < 0.1
, conduit >= 0.0 && < 0.1
, transformers >= 0.2.2 && < 0.3
, wai >= 1.0 && < 1.1
exposed-modules: Yesod.Json
ghc-options: -Wall

View File

@ -34,7 +34,7 @@ newtype RepAtom = RepAtom Content
instance HasReps RepAtom where
chooseRep (RepAtom c) _ = return (typeAtom, c)
atomFeed :: Monad mo => Feed (Route master) -> GGHandler sub master mo RepAtom
atomFeed :: Feed (Route master) -> GHandler sub master RepAtom
atomFeed = liftM RepAtom . hamletToContent . template
template :: Feed url -> HtmlUrl url

View File

@ -25,7 +25,7 @@ import Yesod.FeedTypes
import Yesod.AtomFeed
import Yesod.RssFeed
import Yesod.Content (HasReps (chooseRep), typeAtom, typeRss)
import Yesod.Handler (Route, GGHandler)
import Yesod.Handler (Route, GHandler)
data RepAtomRss = RepAtomRss RepAtom RepRss
instance HasReps RepAtomRss where
@ -33,7 +33,7 @@ instance HasReps RepAtomRss where
[ (typeAtom, a)
, (typeRss, r)
]
newsFeed :: Monad mo => Feed (Route master) -> GGHandler sub master mo RepAtomRss
newsFeed :: Feed (Route master) -> GHandler sub master RepAtomRss
newsFeed f = do
a <- atomFeed f
r <- rssFeed f

View File

@ -31,7 +31,7 @@ instance HasReps RepRss where
chooseRep (RepRss c) _ = return (typeRss, c)
-- | Generate the feed
rssFeed :: Monad mo => Feed (Route master) -> GGHandler sub master mo RepRss
rssFeed :: Feed (Route master) -> GHandler sub master RepRss
rssFeed = liftM RepRss . hamletToContent . template
template :: Feed url -> HtmlUrl url

View File

@ -1,5 +1,5 @@
name: yesod-newsfeed
version: 0.3.2
version: 0.4.0
license: BSD3
license-file: LICENSE
author: Michael Snoyman, Patrick Brisbin
@ -14,7 +14,7 @@ description: Helper functions and data types for producing News feeds.
library
build-depends: base >= 4 && < 5
, yesod-core >= 0.9 && < 0.10
, yesod-core >= 0.10 && < 0.11
, time >= 1.1.4
, hamlet >= 0.10 && < 0.11
, bytestring >= 0.9.1.4 && < 0.10

View File

@ -6,25 +6,26 @@ module Yesod.Persist
, get404
, getBy404
, module Database.Persist
, module Database.Persist.Query
, module Database.Persist.TH
) where
import Database.Persist
import Database.Persist.Query
import Database.Persist.TH
import Control.Monad.Trans.Class (MonadTrans (..))
import Control.Monad.IO.Class (MonadIO)
import Control.Failure (Failure)
import Yesod.Handler
type YesodDB sub master = YesodPersistBackend master (GGHandler sub master IO)
type YesodDB sub master = YesodPersistBackend master (GHandler sub master)
class YesodPersist master where
type YesodPersistBackend master :: (* -> *) -> * -> *
runDB :: MonadIO monad => YesodDB sub master a -> GGHandler sub master monad a
runDB :: YesodDB sub master a -> GHandler sub master a
-- | Get the given entity by ID, or return a 404 not found if it doesn't exist.
get404 :: (PersistBackend t m, PersistEntity val, Monad (t m),
get404 :: (PersistStore t m, PersistEntity val, Monad (t m),
Failure ErrorResponse m, MonadTrans t)
=> Key t val -> t m val
get404 key = do
@ -35,7 +36,7 @@ get404 key = do
-- | Get the given entity by unique key, or return a 404 not found if it doesn't
-- exist.
getBy404 :: (PersistBackend t m, PersistEntity val, Monad (t m),
getBy404 :: (PersistUnique t m, PersistEntity val, Monad (t m),
Failure ErrorResponse m, MonadTrans t)
=> Unique val t -> t m (Key t val, val)
getBy404 key = do

View File

@ -1,5 +1,5 @@
name: yesod-persistent
version: 0.2.2
version: 0.3.0
license: BSD3
license-file: LICENSE
author: Michael Snoyman <michael@snoyman.com>
@ -14,9 +14,9 @@ description: Some helpers for using Persistent from Yesod.
library
build-depends: base >= 4 && < 5
, yesod-core >= 0.8 && < 0.10
, persistent >= 0.6 && < 0.7
, persistent-template >= 0.6 && < 0.7
, yesod-core >= 0.10 && < 0.11
, persistent >= 0.7 && < 0.8
, persistent-template >= 0.7 && < 0.8
, failure >= 0.1 && < 0.2
, transformers >= 0.2.2 && < 0.3
exposed-modules: Yesod.Persist

View File

@ -1,83 +0,0 @@
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
---------------------------------------------------------
--
-- Module : Yesod.Helpers.Sitemap
-- Copyright : Michael Snoyman
-- License : BSD3
--
-- Maintainer : Michael Snoyman <michael@snoyman.com>
-- Stability : Stable
-- Portability : portable
--
-- Generating Google sitemap files.
--
---------------------------------------------------------
-- | Generates XML sitemap files.
--
-- See <http://www.sitemaps.org/>.
module Yesod.Helpers.Sitemap
( sitemap
, robots
, SitemapUrl (..)
, SitemapChangeFreq (..)
) where
import Yesod.Content (RepXml (..), RepPlain (..), toContent, formatW3)
import Yesod.Handler (Route, GHandler, getUrlRender)
import Yesod.Handler (hamletToContent)
import Text.Hamlet (Hamlet, xhamlet)
import Data.Time (UTCTime)
import Data.Monoid (mappend)
data SitemapChangeFreq = Always
| Hourly
| Daily
| Weekly
| Monthly
| Yearly
| Never
showFreq :: SitemapChangeFreq -> String
showFreq Always = "always"
showFreq Hourly = "hourly"
showFreq Daily = "daily"
showFreq Weekly = "weekly"
showFreq Monthly = "monthly"
showFreq Yearly = "yearly"
showFreq Never = "never"
data SitemapUrl url = SitemapUrl
{ sitemapLoc :: url
, sitemapLastMod :: UTCTime
, sitemapChangeFreq :: SitemapChangeFreq
, priority :: Double
}
template :: [SitemapUrl url] -> Hamlet url
template urls =
#if __GLASGOW_HASKELL__ >= 700
[xhamlet|
#else
[$xhamlet|
#endif
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
$forall url <- urls
<url>
<loc>@{sitemapLoc url}
<lastmod>#{formatW3 (sitemapLastMod url)}
<changefreq>#{showFreq (sitemapChangeFreq url)}
<priority>#{show (priority url)}
|]
sitemap :: [SitemapUrl (Route master)] -> GHandler sub master RepXml
sitemap = fmap RepXml . hamletToContent . template
-- | A basic robots file which just lists the "Sitemap: " line.
robots :: Route master -- ^ sitemap url
-> GHandler sub master RepPlain
robots smurl = do
render <- getUrlRender
return $ RepPlain $ toContent $ "Sitemap: " `mappend` render smurl

View File

@ -1,5 +1,5 @@
name: yesod-sitemap
version: 0.2.2
version: 0.3.0
license: BSD3
license-file: LICENSE
author: Michael Snoyman <michael@snoyman.com>
@ -14,7 +14,7 @@ description: Generate XML sitemaps.
library
build-depends: base >= 4 && < 5
, yesod-core >= 0.9 && < 0.10
, yesod-core >= 0.10 && < 0.11
, time >= 1.1.4
, hamlet >= 0.10 && < 0.11
exposed-modules: Yesod.Sitemap

View File

@ -68,9 +68,9 @@ import qualified Data.ByteString as S
import Network.HTTP.Types (status301)
import System.PosixCompat.Files (getFileStatus, modificationTime)
import System.Posix.Types (EpochTime)
import qualified Data.Enumerator as E
import qualified Data.Enumerator.List as EL
import qualified Data.Enumerator.Binary as EB
import qualified Data.Conduit as C
import qualified Data.Conduit.Binary as CB
import qualified Data.Conduit.List as CL
import Network.Wai.Application.Static
( StaticSettings (..)
@ -282,7 +282,7 @@ mkStaticFilesList fp fs routeConName makeHash = do
-- descriptors
base64md5File :: Prelude.FilePath -> IO String
base64md5File file = do
bss <- E.run_ $ EB.enumFile file E.$$ EL.consume
bss <- C.runResourceT $ CB.sourceFile file C.$$ CL.consume
return $ base64md5 $ L.fromChunks bss
-- FIXME I'd like something streaming instead
{-

View File

@ -1,5 +1,5 @@
name: yesod-static
version: 0.3.2.1
version: 0.10.0
license: BSD3
license-file: LICENSE
author: Michael Snoyman <michael@snoyman.com>
@ -26,7 +26,7 @@ library
build-depends: base >= 4 && < 5
, containers >= 0.2 && < 0.5
, old-time >= 1.0
, yesod-core >= 0.9 && < 0.10
, yesod-core >= 0.10 && < 0.11
, base64-bytestring >= 0.1.0.1 && < 0.2
, pureMD5 >= 2.1.0.3 && < 2.2
, cereal >= 0.3 && < 0.4
@ -34,43 +34,43 @@ library
, template-haskell
, directory >= 1.0 && < 1.2
, transformers >= 0.2.2 && < 0.3
, wai-app-static >= 0.3.2.1 && < 0.4
, wai >= 0.4 && < 0.5
, text >= 0.9 && < 0.12
, wai-app-static >= 1.0 && < 1.1
, wai >= 1.0 && < 1.1
, text >= 0.9 && < 1.0
, file-embed >= 0.0.4.1 && < 0.5
, http-types >= 0.6.5 && < 0.7
, unix-compat >= 0.2
, enumerator >= 0.4.8 && < 0.5
, conduit >= 0.0
exposed-modules: Yesod.Static
ghc-options: -Wall
test-suite tests
hs-source-dirs: ., test
hs-source-dirs: test
main-is: tests.hs
type: exitcode-stdio-1.0
cpp-options: -DTEST
build-depends:
hspec >= 0.8 && < 0.10
, HUnit
, yesod-static
-- copy from above
, base >= 4 && < 5
, containers >= 0.2 && < 0.5
, old-time >= 1.0
, yesod-core >= 0.9 && < 0.10
, base64-bytestring >= 0.1.0.1 && < 0.2
, pureMD5 >= 2.1.0.3 && < 2.2
, cereal >= 0.3 && < 0.4
, bytestring >= 0.9.1.4 && < 0.10
, base
, containers
, old-time
, yesod-core
, base64-bytestring
, pureMD5
, cereal
, bytestring
, template-haskell
, directory >= 1.0 && < 1.2
, transformers >= 0.2.2 && < 0.3
, wai-app-static >= 0.3.2.1 && < 0.4
, wai >= 0.4 && < 0.5
, text >= 0.9 && < 0.12
, file-embed >= 0.0.4.1 && < 0.5
, http-types >= 0.6.5 && < 0.7
, unix-compat >= 0.2
, enumerator >= 0.4.8 && < 0.5
, directory
, transformers
, wai-app-static
, wai
, text
, file-embed
, http-types
, unix-compat
ghc-options: -Wall

View File

@ -66,8 +66,8 @@ scaffold = do
backendC <- prompt $ flip elem $ map (return . toLower . head . show) backends
let (backend, importGenericDB, dbMonad, importPersist, mkPersistSettings) =
case backendC of
"s" -> (Sqlite, "GenericSql", "SqlPersist", "Sqlite", "sqlSettings")
"p" -> (Postgresql, "GenericSql", "SqlPersist", "Postgresql", "sqlSettings")
"s" -> (Sqlite, "GenericSql", "SqlPersist", "Sqlite", "sqlMkSettings")
"p" -> (Postgresql, "GenericSql", "SqlPersist", "Postgresql", "sqlMkSettings")
"m" -> (MongoDB, "MongoDB", "Action", "MongoDB", "MkPersistSettings { mpsBackend = ConT ''Action }")
"t" -> (Tiny, "","","",undefined)
_ -> error $ "Invalid backend: " ++ backendC

View File

@ -1,5 +1,4 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE CPP #-}
-- | This module simply re-exports from other modules for your convenience.
module Yesod
( -- * Re-exports from yesod-core
@ -15,11 +14,7 @@ module Yesod
, Application
, lift
, liftIO
#if MIN_VERSION_monad_control(0, 3, 0)
, MonadBaseControl
#else
, MonadControlIO
#endif
-- * Utilities
, showIntegral
, readIntegral
@ -54,11 +49,7 @@ import Network.Wai (Application)
import Network.Wai.Middleware.RequestLogger
import Control.Monad.Trans.Class (lift)
import Control.Monad.IO.Class (liftIO)
#if MIN_VERSION_monad_control(0, 3, 0)
import Control.Monad.Trans.Control (MonadBaseControl)
#else
import Control.Monad.IO.Control (MonadControlIO)
#endif
import Network.Wai.Handler.Warp (run)
import System.IO (stderr, hPutStrLn)

View File

@ -19,7 +19,7 @@ import Network.Wai.Middleware.RequestLogger (logHandleDev)
import Yesod.Logger (Logger)
import Network.Wai.Middleware.RequestLogger (logStdout)
#endif
import qualified Database.Persist.Base~importMigration~
import qualified Database.Persist.Store~importMigration~
-- Import all relevant handler modules here.
import Handler.Root
@ -33,12 +33,12 @@ mkYesodDispatch "~sitearg~" resources~sitearg~
-- performs initialization and creates a WAI application. This is also the
-- place to put your migrate statements to have automatic database
-- migrations handled by Yesod.
with~sitearg~ :: AppConfig DefaultEnv () -> Logger -> (Application -> IO ()) -> IO ()
with~sitearg~ :: AppConfig DefaultEnv Extra -> Logger -> (Application -> IO ()) -> IO ()
with~sitearg~ conf logger f = do
s <- staticSite
dbconf <- withYamlEnvironment "config/~dbConfigFile~.yml" (appEnv conf)
$ either error return . Database.Persist.Base.loadConfig
Database.Persist.Base.withPool (dbconf :: Settings.PersistConfig) $ \p -> do~runMigration~
$ either error return . Database.Persist.Store.loadConfig
Database.Persist.Store.withPool (dbconf :: Settings.PersistConfig) $ \p -> do~runMigration~
let h = ~sitearg~ conf logger s p
defaultRunner (f . logWare) h
where
@ -50,4 +50,10 @@ with~sitearg~ conf logger f = do
-- for yesod devel
withDevelAppPort :: Dynamic
withDevelAppPort = toDyn $ defaultDevelApp with~sitearg~
withDevelAppPort =
toDyn $ defaultDevelAppWith loader with~sitearg~
where
loader = loadConfig (configSettings Development)
{ csLoadExtra = loadExtra
}

View File

@ -16,7 +16,7 @@ module Foundation
) where
import Prelude
import Yesod hiding (Form, AppConfig (..), withYamlEnvironment)
import Yesod
import Yesod.Static (Static, base64md5, StaticRoute(..))
import Settings.StaticFiles
import Yesod.Auth
@ -29,9 +29,9 @@ import Yesod.Logger (logLazyText)
#endif
import qualified Settings
import qualified Data.ByteString.Lazy as L
import qualified Database.Persist.Base
import qualified Database.Persist.Store
import Database.Persist.~importGenericDB~
import Settings (widgetFile)
import Settings (widgetFile, Extra)
import Model
import Text.Jasmine (minifym)
import Web.ClientSession (getKey)
@ -47,10 +47,10 @@ import Network.Mail.Mime (sendmail)
-- starts running, such as database connections. Every handler will have
-- access to the data present here.
data ~sitearg~ = ~sitearg~
{ settings :: AppConfig DefaultEnv ()
{ settings :: AppConfig DefaultEnv Extra
, getLogger :: Logger
, getStatic :: Static -- ^ Settings for static file serving.
, connPool :: Database.Persist.Base.PersistConfigPool Settings.PersistConfig -- ^ Database connection pool.
, connPool :: Database.Persist.Store.PersistConfigPool Settings.PersistConfig -- ^ Database connection pool.
}
-- Set up i18n messages. See the message folder.
@ -125,8 +125,7 @@ instance Yesod ~sitearg~ where
-- How to run database actions.
instance YesodPersist ~sitearg~ where
type YesodPersistBackend ~sitearg~ = ~dbMonad~
runDB f = liftIOHandler
$ fmap connPool getYesod >>= Database.Persist.Base.runPool (undefined :: Settings.PersistConfig) f
runDB f = fmap connPool getYesod >>= Database.Persist.Store.runPool (undefined :: Settings.PersistConfig) f
instance YesodAuth ~sitearg~ where
type AuthId ~sitearg~ = UserId

View File

@ -3,11 +3,13 @@ module Model where
import Prelude
import Yesod
import Data.Text (Text)
import Database.Persist.Quasi
~modelImports~
-- You can define all of your database entities in the entities file.
-- You can find more information on persistent and how to declare entities
-- at:
-- http://www.yesodweb.com/book/persistent/
share [mkPersist ~mkPersistSettings~, mkMigrate "migrateAll"] $(persistFile "config/models")
share [mkPersist ~mkPersistSettings~, mkMigrate "migrateAll"]
$(persistFile upperCaseSettings "config/models")

View File

@ -8,15 +8,18 @@ module Settings
, PersistConfig
, staticRoot
, staticDir
, Extra (..)
, loadExtra
) where
import Prelude (FilePath, String)
import Prelude
import Text.Shakespeare.Text (st)
import Language.Haskell.TH.Syntax
import Database.Persist.~importPersist~ (~configPersist~)
import Yesod.Default.Config
import qualified Yesod.Default.Util
import Data.Text (Text)
import Data.Yaml
-- | Which Persistent backend this site is using.
type PersistConfig = ~configPersist~
@ -54,3 +57,9 @@ widgetFile = Yesod.Default.Util.widgetFileReload
#else
widgetFile = Yesod.Default.Util.widgetFileNoReload
#endif
data Extra = Extra
loadExtra :: DefaultEnv -> Value -> IO Extra
loadExtra _ _ = return Extra

View File

@ -1,7 +1,8 @@
import Yesod.Default.Config (fromArgs)
import Yesod.Default.Main (defaultMain)
import Application (with~sitearg~)
import Prelude (IO)
import Yesod.Default.Config (fromArgsExtra)
import Yesod.Default.Main (defaultMain)
import Settings (loadExtra)
import Application (with~sitearg~)
main :: IO ()
main = defaultMain fromArgs with~sitearg~
main = defaultMain (fromArgsExtra loadExtra) with~sitearg~

View File

@ -74,24 +74,25 @@ executable ~project~
FlexibleContexts
build-depends: base >= 4 && < 5
, yesod >= 0.9.3.4 && < 0.10
, yesod-core >= 0.9.3 && < 0.10
, yesod-auth >= 0.7.3 && < 0.8
, yesod-static >= 0.3.1 && < 0.4
, yesod-default >= 0.5 && < 0.6
, yesod-form >= 0.3.4 && < 0.4
, yesod >= 0.10 && < 0.11
, yesod-core >= 0.10 && < 0.11
, yesod-auth >= 0.8 && < 0.9
, yesod-static >= 0.10 && < 0.11
, yesod-default >= 0.6 && < 0.7
, yesod-form >= 0.4 && < 0.5
, mime-mail >= 0.3.0.3 && < 0.5
, clientsession >= 0.7.3 && < 0.8
, bytestring >= 0.9 && < 0.10
, text >= 0.11 && < 0.12
, persistent >= 0.6.2 && < 0.7
, persistent-~backendLower~ >= 0.6 && < 0.7
, persistent >= 0.7 && < 0.8
, persistent-~backendLower~ >= 0.7 && < 0.8
, template-haskell
, hamlet >= 0.10 && < 0.11
, shakespeare-css >= 0.10 && < 0.11
, shakespeare-js >= 0.10 && < 0.11
, shakespeare-text >= 0.10 && < 0.11
, hjsmin >= 0.0.14 && < 0.1
, monad-control ~monadControlVersion~
, wai-extra >= 0.4.6 && < 0.5
, monad-control >= 0.3 && < 0.4
, wai-extra >= 1.0 && < 1.1
, yaml >= 0.5 && < 0.6

View File

@ -5,9 +5,10 @@ module Application
) where
import Import
import Settings (loadExtra)
import Settings.StaticFiles (staticSite)
import Yesod.Default.Config
import Yesod.Default.Main (defaultDevelApp, defaultRunner)
import Yesod.Default.Main (defaultDevelAppWith, defaultRunner)
import Yesod.Default.Handlers (getFaviconR, getRobotsR)
import Yesod.Logger (Logger)
import Network.Wai (Application)
@ -25,7 +26,7 @@ mkYesodDispatch "~sitearg~" resources~sitearg~
-- performs initialization and creates a WAI application. This is also the
-- place to put your migrate statements to have automatic database
-- migrations handled by Yesod.
with~sitearg~ :: AppConfig DefaultEnv () -> Logger -> (Application -> IO ()) -> IO ()
with~sitearg~ :: AppConfig DefaultEnv Extra -> Logger -> (Application -> IO ()) -> IO ()
with~sitearg~ conf logger f = do
s <- staticSite
let h = ~sitearg~ conf logger s
@ -33,4 +34,10 @@ with~sitearg~ conf logger f = do
-- for yesod devel
withDevelAppPort :: Dynamic
withDevelAppPort = toDyn $ defaultDevelApp with~sitearg~
withDevelAppPort =
toDyn $ defaultDevelAppWith loader with~sitearg~
where
loader = loadConfig (configSettings Development)
{ csLoadExtra = loadExtra
}

View File

@ -13,14 +13,14 @@ module Foundation
) where
import Prelude
import Yesod.Core hiding (AppConfig (..))
import Yesod.Core
import Yesod.Default.Config
import Yesod.Default.Util (addStaticContentExternal)
import Yesod.Static (Static, base64md5, StaticRoute(..))
import Settings.StaticFiles
import Yesod.Logger (Logger, logMsg, formatLogText)
import qualified Settings
import Settings (widgetFile)
import Settings (Extra, widgetFile)
import Control.Monad.Trans.Class (lift)
import Control.Monad.IO.Class (liftIO)
import Web.ClientSession (getKey)
@ -31,7 +31,7 @@ import Text.Hamlet (hamletFile)
-- starts running, such as database connections. Every handler will have
-- access to the data present here.
data ~sitearg~ = ~sitearg~
{ settings :: AppConfig DefaultEnv ()
{ settings :: AppConfig DefaultEnv Extra
, getLogger :: Logger
, getStatic :: Static -- ^ Settings for static file serving.
}

View File

@ -7,14 +7,17 @@ module Settings
( widgetFile
, staticRoot
, staticDir
, Extra (..)
, loadExtra
) where
import Prelude (FilePath, String)
import Prelude
import Text.Shakespeare.Text (st)
import Language.Haskell.TH.Syntax
import Yesod.Default.Config
import qualified Yesod.Default.Util
import Data.Text (Text)
import Data.Yaml
-- | The location of static files on your system. This is a file system
-- path. The default value works properly with your scaffolded site.
@ -43,3 +46,8 @@ widgetFile = Yesod.Default.Util.widgetFileReload
#else
widgetFile = Yesod.Default.Util.widgetFileNoReload
#endif
data Extra = Extra
loadExtra :: DefaultEnv -> Value -> IO Extra
loadExtra _ _ = return Extra

View File

@ -66,16 +66,17 @@ executable ~project~
TypeFamilies
build-depends: base >= 4 && < 5
, yesod-core >= 0.9.3 && < 0.10
, yesod-static >= 0.3.1 && < 0.4
, yesod-default >= 0.5 && < 0.6
, yesod-core >= 0.10 && < 0.11
, yesod-static >= 0.10 && < 0.11
, yesod-default >= 0.6 && < 0.7
, clientsession >= 0.7.3 && < 0.8
, bytestring >= 0.9 && < 0.10
, text >= 0.11 && < 0.12
, template-haskell
, hamlet >= 0.10 && < 0.11
, shakespeare-text >= 0.10 && < 0.11
, wai >= 0.4.2 && < 0.5
, wai >= 1.0 && < 1.1
, transformers >= 0.2 && < 0.3
, monad-control >= 0.3 && < 0.4
, yaml >= 0.5 && < 0.6

View File

@ -1,5 +1,5 @@
name: yesod
version: 0.9.4.1
version: 0.10.0
license: BSD3
license-file: LICENSE
author: Michael Snoyman <michael@snoyman.com>
@ -71,19 +71,19 @@ library
cpp-options: -DGHC7
else
build-depends: base >= 4 && < 4.3
build-depends: yesod-core >= 0.9.3.4 && < 0.10
, yesod-auth >= 0.7 && < 0.8
, yesod-json >= 0.2.2 && < 0.3
, yesod-persistent >= 0.2 && < 0.3
, yesod-form >= 0.3 && < 0.4
, monad-control >= 0.2 && < 0.4
build-depends: yesod-core >= 0.10 && < 0.11
, yesod-auth >= 0.8 && < 0.9
, yesod-json >= 0.3 && < 0.4
, yesod-persistent >= 0.3 && < 0.4
, yesod-form >= 0.4 && < 0.5
, monad-control >= 0.3 && < 0.4
, transformers >= 0.2.2 && < 0.3
, wai >= 0.4 && < 0.5
, wai-extra >= 0.4.6 && < 0.5
, wai >= 1.0 && < 1.1
, wai-extra >= 1.0 && < 1.1
, hamlet >= 0.10 && < 0.11
, shakespeare-js >= 0.10 && < 0.11
, shakespeare-css >= 0.10 && < 0.11
, warp >= 0.4 && < 0.5
, warp >= 1.0 && < 1.1
, blaze-html >= 0.4.1.3 && < 0.5
exposed-modules: Yesod
ghc-options: -Wall