Merge branch 'master' of github.com:yesodweb/yesod
This commit is contained in:
commit
430e724eeb
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,7 @@
|
|||||||
*.o
|
*.o
|
||||||
|
*.o_p
|
||||||
*.hi
|
*.hi
|
||||||
dist
|
dist
|
||||||
*.swp
|
*.swp
|
||||||
client_session_key.aes
|
client_session_key.aes
|
||||||
|
cabal-dev/
|
||||||
|
|||||||
92
README.md
92
README.md
@ -1,5 +1,4 @@
|
|||||||
A next generation web framework using the Haskell programming language,
|
An advanced web framework using the Haskell programming language. Featuring:
|
||||||
featuring:
|
|
||||||
|
|
||||||
* safety & security guaranteed at compile time
|
* safety & security guaranteed at compile time
|
||||||
* performance
|
* performance
|
||||||
@ -12,25 +11,108 @@ featuring:
|
|||||||
|
|
||||||
## Installation: http://www.yesodweb.com/page/five-minutes
|
## Installation: http://www.yesodweb.com/page/five-minutes
|
||||||
|
|
||||||
|
cabal update && cabal install yesod
|
||||||
|
|
||||||
## Create a new project after installing
|
## Create a new project after installing
|
||||||
|
|
||||||
yesod init
|
yesod init
|
||||||
|
|
||||||
|
|
||||||
|
## Using cabal-dev
|
||||||
|
|
||||||
|
cabal-dev creates a sandboxed environment for an individual cabal package.
|
||||||
|
Your application is a cabal package and you should use cabal-dev with your Yesod application.
|
||||||
|
Instead of using the `cabal` command, use the `cabal-dev` command.
|
||||||
|
|
||||||
|
Use `yesod-devel --dev` when developing your application.
|
||||||
|
|
||||||
## Installing the latest development version from github
|
## Installing the latest development version from github
|
||||||
|
|
||||||
Yesod is built upon many smaller packages, all of which can be installed
|
Yesod is broken up into 4 separate code repositories each built upon many smaller packages.
|
||||||
with:
|
|
||||||
|
Install conflicts are unfortunately common in Haskell development.
|
||||||
|
However, we can prevent most of them by using some extra tools.
|
||||||
|
This will require a little up-front reading and learning, but save you from a lot of misery in the long-run.
|
||||||
|
See the above explanation of cabal-dev, and below of virthualenv.
|
||||||
|
|
||||||
|
Please note that cabal-dev will not work in a virthualenv shell - you can't use both at the same time.
|
||||||
|
|
||||||
|
### virthualenv
|
||||||
|
|
||||||
|
To just install Yesod from github, we only need cabal-dev. However, cabal-dev may be more hassle than it is worth when hacking on Yesod.
|
||||||
|
|
||||||
|
We recommend using [virthualenv](http://hackage.haskell.org/package/virthualenv) when hacking on Yesod.
|
||||||
|
This is optional, but prevents your custom build of Yesod from interfering with your currently installed cabal packages.
|
||||||
|
virthualenv creates an isolated environment like cabal-dev.
|
||||||
|
cabal-dev isolates a single cabal package, but virthualenv isolates multiple packages together.
|
||||||
|
|
||||||
|
virthualenv works at the shell level, so every shell must activate the virthualenv.
|
||||||
|
|
||||||
|
### cabal-src
|
||||||
|
|
||||||
|
Michael just released the cabal-src tool. Whenever you would use `cabal install` for a local package, use `cabal-src-install` instead.
|
||||||
|
Our installer script now uses cabal-src-install when it is available.
|
||||||
|
|
||||||
|
### Building Yesod
|
||||||
|
|
||||||
~~~ { .bash }
|
~~~ { .bash }
|
||||||
|
# update your package database if you haven't recently
|
||||||
cabal update
|
cabal update
|
||||||
|
# install required libraries
|
||||||
|
cabal install Cabal cabal-install cabal-src virthualenv
|
||||||
|
|
||||||
|
# clone and install all repos
|
||||||
|
# see below about first using virthualenv before running ./scripts/install
|
||||||
for repo in hamlet persistent wai yesod; do
|
for repo in hamlet persistent wai yesod; do
|
||||||
git clone http://github.com/yesodweb/$repo
|
git clone http://github.com/yesodweb/$repo
|
||||||
(
|
(
|
||||||
cd $repo
|
cd $repo
|
||||||
git submodule update --init
|
git submodule update --init
|
||||||
./script/install
|
./scripts/install
|
||||||
)
|
)
|
||||||
done
|
done
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
### Hacking on Yesod
|
||||||
|
|
||||||
|
To prevent Yesod from conflicting with your other installs, you should use virthualenv, although it is optional.
|
||||||
|
|
||||||
|
#### virthualenv
|
||||||
|
|
||||||
|
~~~ { .bash }
|
||||||
|
cabal update
|
||||||
|
cabal install virthualenv
|
||||||
|
cd yesodweb
|
||||||
|
virthualenv --name=yesod
|
||||||
|
. .virthualenv/bin/activate
|
||||||
|
~~~
|
||||||
|
|
||||||
|
#### individual cabal packages
|
||||||
|
|
||||||
|
~~~ { .bash }
|
||||||
|
# install and test all packages
|
||||||
|
./scripts/install
|
||||||
|
|
||||||
|
# move to the individual package you are working on
|
||||||
|
cd shakespeare-text
|
||||||
|
|
||||||
|
# build and test the individual package
|
||||||
|
cabal configure -ftest --enable-tests
|
||||||
|
cabal build
|
||||||
|
cabal test
|
||||||
|
~~~
|
||||||
|
|
||||||
|
#### cabal-dev
|
||||||
|
|
||||||
|
cabal-dev works very well if you are working on a single package, but it can be very cumbersome to work on multiple packages at once.
|
||||||
|
|
||||||
|
### Use your development version of Yesod in your application
|
||||||
|
|
||||||
|
Note that we have told you to install Yesod into a sandboxed virthualenv environment.
|
||||||
|
This means it is not available through your user/global cabal database for your application.
|
||||||
|
Instead you should use `cabal-dev install` to retrieve these packages.
|
||||||
|
cd to your application directory, and the reference the source list.
|
||||||
|
|
||||||
|
~~~ { .bash }
|
||||||
|
cabal-dev install /path/to/yesodweb/yesod/*(/)
|
||||||
|
~~~
|
||||||
|
|||||||
12
package-list.sh
Normal file
12
package-list.sh
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
pkgs=( ./yesod-core
|
||||||
|
./yesod-json
|
||||||
|
./yesod-static
|
||||||
|
./yesod-persistent
|
||||||
|
./yesod-newsfeed
|
||||||
|
./yesod-form
|
||||||
|
./yesod-auth
|
||||||
|
./yesod-sitemap
|
||||||
|
./yesod-default
|
||||||
|
./yesod )
|
||||||
2
scripts
2
scripts
@ -1 +1 @@
|
|||||||
Subproject commit f56426fada59012329f23c928a2d7f9c3a515d75
|
Subproject commit 713588bcf3526aad8a809215fb34c314334a5ffd
|
||||||
10
sources.txt
Normal file
10
sources.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
yesod-core
|
||||||
|
yesod-json
|
||||||
|
yesod-static
|
||||||
|
yesod-persistent
|
||||||
|
yesod-newsfeed
|
||||||
|
yesod-form
|
||||||
|
yesod-auth
|
||||||
|
yesod-sitemap
|
||||||
|
yesod-default
|
||||||
|
yesod
|
||||||
1
test/en.msg
Symbolic link
1
test/en.msg
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../yesod-core/test/en.msg
|
||||||
9
test/main.hs
Normal file
9
test/main.hs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import Test.Hspec
|
||||||
|
import qualified YesodCoreTest
|
||||||
|
import qualified YesodStaticTest
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = hspecX $ descriptions [
|
||||||
|
concat YesodCoreTest.specs
|
||||||
|
, concat YesodStaticTest.specs
|
||||||
|
]
|
||||||
@ -34,7 +34,11 @@ import Data.Text.Encoding (decodeUtf8With)
|
|||||||
import Data.Text.Encoding.Error (lenientDecode)
|
import Data.Text.Encoding.Error (lenientDecode)
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
|
#if MIN_VERSION_aeson(0, 4, 0)
|
||||||
|
import qualified Data.HashMap.Lazy as Map
|
||||||
|
#else
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
|
#endif
|
||||||
|
|
||||||
import Language.Haskell.TH.Syntax hiding (lift)
|
import Language.Haskell.TH.Syntax hiding (lift)
|
||||||
|
|
||||||
@ -96,6 +100,11 @@ class (Yesod m, SinglePiece (AuthId m), RenderMessage m FormMessage) => YesodAut
|
|||||||
-> AuthMessage -> Text
|
-> AuthMessage -> Text
|
||||||
renderAuthMessage _ _ = defaultMessage
|
renderAuthMessage _ _ = defaultMessage
|
||||||
|
|
||||||
|
-- | After login and logout, redirect to the referring page, instead of
|
||||||
|
-- 'loginDest' and 'logoutDest'. Default is 'False'.
|
||||||
|
redirectToReferer :: m -> Bool
|
||||||
|
redirectToReferer _ = False
|
||||||
|
|
||||||
mkYesodSub "Auth"
|
mkYesodSub "Auth"
|
||||||
[ ClassP ''YesodAuth [VarT $ mkName "master"]
|
[ ClassP ''YesodAuth [VarT $ mkName "master"]
|
||||||
]
|
]
|
||||||
@ -134,7 +143,7 @@ getCheckR = do
|
|||||||
creds <- maybeAuthId
|
creds <- maybeAuthId
|
||||||
defaultLayoutJson (do
|
defaultLayoutJson (do
|
||||||
setTitle "Authentication Status"
|
setTitle "Authentication Status"
|
||||||
addHtml $ html' creds) (json' creds)
|
addHtml $ html' creds) (jsonCreds creds)
|
||||||
where
|
where
|
||||||
html' creds =
|
html' creds =
|
||||||
[QQ(shamlet)|
|
[QQ(shamlet)|
|
||||||
@ -144,16 +153,21 @@ $maybe _ <- creds
|
|||||||
$nothing
|
$nothing
|
||||||
<p>Not logged in.
|
<p>Not logged in.
|
||||||
|]
|
|]
|
||||||
json' creds =
|
jsonCreds creds =
|
||||||
Object $ Map.fromList
|
Object $ Map.fromList
|
||||||
[ (T.pack "logged_in", Bool $ maybe False (const True) creds)
|
[ (T.pack "logged_in", Bool $ maybe False (const True) creds)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
setUltDestReferer' :: YesodAuth master => GHandler sub master ()
|
||||||
|
setUltDestReferer' = do
|
||||||
|
m <- getYesod
|
||||||
|
when (redirectToReferer m) setUltDestReferer
|
||||||
|
|
||||||
getLoginR :: YesodAuth m => GHandler Auth m RepHtml
|
getLoginR :: YesodAuth m => GHandler Auth m RepHtml
|
||||||
getLoginR = setUltDestReferer >> loginHandler
|
getLoginR = setUltDestReferer' >> loginHandler
|
||||||
|
|
||||||
getLogoutR :: YesodAuth m => GHandler Auth m ()
|
getLogoutR :: YesodAuth m => GHandler Auth m ()
|
||||||
getLogoutR = setUltDestReferer >> postLogoutR -- FIXME redirect to post
|
getLogoutR = setUltDestReferer' >> postLogoutR -- FIXME redirect to post
|
||||||
|
|
||||||
postLogoutR :: YesodAuth m => GHandler Auth m ()
|
postLogoutR :: YesodAuth m => GHandler Auth m ()
|
||||||
postLogoutR = do
|
postLogoutR = do
|
||||||
|
|||||||
@ -203,7 +203,7 @@ getPasswordR = do
|
|||||||
Just _ -> return ()
|
Just _ -> return ()
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
setMessageI Msg.BadSetPass
|
setMessageI Msg.BadSetPass
|
||||||
redirect RedirectTemporary $ toMaster loginR
|
redirect RedirectTemporary $ toMaster LoginR
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
setTitleI Msg.SetPassTitle
|
setTitleI Msg.SetPassTitle
|
||||||
addWidget
|
addWidget
|
||||||
@ -238,7 +238,7 @@ postPasswordR = do
|
|||||||
aid <- case maid of
|
aid <- case maid of
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
setMessageI Msg.BadSetPass
|
setMessageI Msg.BadSetPass
|
||||||
redirect RedirectTemporary $ toMaster loginR
|
redirect RedirectTemporary $ toMaster LoginR
|
||||||
Just aid -> return aid
|
Just aid -> return aid
|
||||||
salted <- liftIO $ saltPass new
|
salted <- liftIO $ saltPass new
|
||||||
setPassword aid salted
|
setPassword aid salted
|
||||||
|
|||||||
@ -3,7 +3,10 @@
|
|||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
module Yesod.Auth.Facebook
|
module Yesod.Auth.Facebook
|
||||||
( authFacebook
|
( authFacebook
|
||||||
|
, facebookLogin
|
||||||
, facebookUrl
|
, facebookUrl
|
||||||
|
, facebookLogout
|
||||||
|
, getFacebookAccessToken
|
||||||
) where
|
) where
|
||||||
|
|
||||||
#include "qq.h"
|
#include "qq.h"
|
||||||
@ -17,20 +20,48 @@ import Data.Maybe (fromMaybe)
|
|||||||
import Yesod.Form
|
import Yesod.Form
|
||||||
import Yesod.Handler
|
import Yesod.Handler
|
||||||
import Yesod.Widget
|
import Yesod.Widget
|
||||||
import Control.Monad.IO.Class (liftIO)
|
import Control.Monad.IO.Class (MonadIO, liftIO)
|
||||||
import Control.Monad.Trans.Class (lift)
|
import Control.Monad.Trans.Class (lift)
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Control.Monad (mzero)
|
import Control.Monad (liftM, mzero, when)
|
||||||
import Data.Monoid (mappend)
|
import Data.Monoid (mappend)
|
||||||
import qualified Data.Aeson.Types
|
import qualified Data.Aeson.Types
|
||||||
import qualified Yesod.Auth.Message as Msg
|
import qualified Yesod.Auth.Message as Msg
|
||||||
|
|
||||||
facebookUrl :: AuthRoute
|
-- | Route for login using this authentication plugin.
|
||||||
facebookUrl = PluginR "facebook" ["forward"]
|
facebookLogin :: AuthRoute
|
||||||
|
facebookLogin = PluginR "facebook" ["forward"]
|
||||||
|
|
||||||
|
-- | This is just a synonym of 'facebookLogin'. Deprecated since
|
||||||
|
-- @yesod-auth 0.7.8@, please use 'facebookLogin' instead.
|
||||||
|
facebookUrl :: AuthRoute
|
||||||
|
facebookUrl = facebookLogin
|
||||||
|
{-# DEPRECATED facebookUrl "Please use facebookLogin instead." #-}
|
||||||
|
|
||||||
|
-- | Route for logout using this authentication plugin. Per
|
||||||
|
-- Facebook's policies
|
||||||
|
-- (<https://developers.facebook.com/policy/>), the user needs to
|
||||||
|
-- logout from Facebook itself as well.
|
||||||
|
facebookLogout :: AuthRoute
|
||||||
|
facebookLogout = PluginR "facebook" ["logout"]
|
||||||
|
|
||||||
|
-- | Get Facebook's access token from the session. Returns
|
||||||
|
-- @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 =
|
||||||
|
liftM (fmap Facebook.AccessToken) (lookupSession facebookAccessTokenKey)
|
||||||
|
|
||||||
|
-- | Key used to store Facebook's access token in the client
|
||||||
|
-- session.
|
||||||
|
facebookAccessTokenKey :: Text
|
||||||
|
facebookAccessTokenKey = "_FB"
|
||||||
|
|
||||||
|
-- | Authentication plugin using Facebook.
|
||||||
authFacebook :: YesodAuth m
|
authFacebook :: YesodAuth m
|
||||||
=> Text -- ^ Application ID
|
=> Text -- ^ Application ID
|
||||||
-> Text -- ^ Application secret
|
-> Text -- ^ Application secret
|
||||||
-> [Text] -- ^ Requested permissions
|
-> [Text] -- ^ Requested permissions
|
||||||
-> AuthPlugin m
|
-> AuthPlugin m
|
||||||
authFacebook cid secret perms =
|
authFacebook cid secret perms =
|
||||||
@ -49,10 +80,24 @@ authFacebook cid secret perms =
|
|||||||
code <- runInputGet $ ireq textField "code"
|
code <- runInputGet $ ireq textField "code"
|
||||||
at <- liftIO $ Facebook.getAccessToken fb code
|
at <- liftIO $ Facebook.getAccessToken fb code
|
||||||
let Facebook.AccessToken at' = at
|
let Facebook.AccessToken at' = at
|
||||||
|
setSession facebookAccessTokenKey at'
|
||||||
so <- liftIO $ Facebook.getGraphData at "me"
|
so <- liftIO $ Facebook.getGraphData at "me"
|
||||||
let c = fromMaybe (error "Invalid response from Facebook")
|
let c = fromMaybe (error "Invalid response from Facebook")
|
||||||
$ parseMaybe (parseCreds at') $ either error id so
|
$ parseMaybe (parseCreds at') $ either error id so
|
||||||
setCreds True c
|
setCreds True c
|
||||||
|
dispatch "GET" ["logout"] = do
|
||||||
|
m <- getYesod
|
||||||
|
tm <- getRouteToMaster
|
||||||
|
mtoken <- getFacebookAccessToken
|
||||||
|
when (redirectToReferer m) setUltDestReferer
|
||||||
|
case mtoken of
|
||||||
|
Nothing -> do
|
||||||
|
-- Well... then just logout from our app.
|
||||||
|
redirect RedirectTemporary (tm LogoutR)
|
||||||
|
Just at -> do
|
||||||
|
render <- getUrlRender
|
||||||
|
let logout = Facebook.getLogoutUrl at (render $ tm LogoutR)
|
||||||
|
redirectText RedirectTemporary logout
|
||||||
dispatch _ _ = notFound
|
dispatch _ _ = notFound
|
||||||
login tm = do
|
login tm = do
|
||||||
render <- lift getUrlRender
|
render <- lift getUrlRender
|
||||||
@ -67,8 +112,8 @@ parseCreds :: Text -> Value -> Data.Aeson.Types.Parser (Creds m)
|
|||||||
parseCreds at' (Object m) = do
|
parseCreds at' (Object m) = do
|
||||||
id' <- m .: "id"
|
id' <- m .: "id"
|
||||||
let id'' = "http://graph.facebook.com/" `mappend` id'
|
let id'' = "http://graph.facebook.com/" `mappend` id'
|
||||||
name <- m .: "name"
|
name <- m .:? "name"
|
||||||
email <- m .: "email"
|
email <- m .:? "email"
|
||||||
return
|
return
|
||||||
$ Creds "facebook" id''
|
$ Creds "facebook" id''
|
||||||
$ maybe id (\x -> (:) ("verifiedEmail", x)) email
|
$ maybe id (\x -> (:) ("verifiedEmail", x)) email
|
||||||
|
|||||||
99
yesod-auth/Yesod/Auth/GoogleEmail.hs
Normal file
99
yesod-auth/Yesod/Auth/GoogleEmail.hs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
-- | Use an email address as an identifier via Google's OpenID login system.
|
||||||
|
--
|
||||||
|
-- This backend will not use the OpenID identifier at all. It only uses OpenID
|
||||||
|
-- as a login system. By using this plugin, you are trusting Google to validate
|
||||||
|
-- an email address, and requiring users to have a Google account. On the plus
|
||||||
|
-- side, you get to use email addresses as the identifier, many users have
|
||||||
|
-- existing Google accounts, the login system has been long tested (as opposed
|
||||||
|
-- to BrowserID), and it requires no credential managing or setup (as opposed
|
||||||
|
-- to Email).
|
||||||
|
module Yesod.Auth.GoogleEmail
|
||||||
|
( authGoogleEmail
|
||||||
|
, forwardUrl
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Yesod.Auth
|
||||||
|
import qualified Web.Authenticate.OpenId as OpenId
|
||||||
|
import Control.Monad.Attempt
|
||||||
|
|
||||||
|
import Yesod.Form
|
||||||
|
import Yesod.Handler
|
||||||
|
import Yesod.Widget
|
||||||
|
import Yesod.Request
|
||||||
|
import Text.Blaze (toHtml)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Yesod.Auth.Message as Msg
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
forwardUrl :: AuthRoute
|
||||||
|
forwardUrl = PluginR "googleemail" ["forward"]
|
||||||
|
|
||||||
|
authGoogleEmail :: YesodAuth m => AuthPlugin m
|
||||||
|
authGoogleEmail =
|
||||||
|
AuthPlugin "googleemail" dispatch login
|
||||||
|
where
|
||||||
|
complete = PluginR "googleemail" ["complete"]
|
||||||
|
name = "openid_identifier"
|
||||||
|
login tm = do
|
||||||
|
[whamlet|
|
||||||
|
<form method=get action=@{tm forwardUrl}>
|
||||||
|
<input type=hidden name=openid_identifier value=https://www.google.com/accounts/o8/id>
|
||||||
|
<input type=submit value=_{Msg.LoginTitle}>
|
||||||
|
|]
|
||||||
|
dispatch "GET" ["forward"] = do
|
||||||
|
roid <- runInputGet $ iopt textField name
|
||||||
|
case roid of
|
||||||
|
Just oid -> do
|
||||||
|
render <- getUrlRender
|
||||||
|
toMaster <- getRouteToMaster
|
||||||
|
let complete' = render $ toMaster complete
|
||||||
|
res <- runAttemptT $ OpenId.getForwardUrl oid complete' Nothing
|
||||||
|
[ ("openid.ax.type.email", "http://schema.openid.net/contact/email")
|
||||||
|
, ("openid.ns.ax", "http://openid.net/srv/ax/1.0")
|
||||||
|
, ("openid.ns.ax.required", "email")
|
||||||
|
, ("openid.ax.mode", "fetch_request")
|
||||||
|
, ("openid.ax.required", "email")
|
||||||
|
, ("openid.ui.icon", "true")
|
||||||
|
]
|
||||||
|
attempt
|
||||||
|
(\err -> do
|
||||||
|
setMessage $ toHtml $ show err
|
||||||
|
redirect RedirectTemporary $ toMaster LoginR
|
||||||
|
)
|
||||||
|
(redirectText RedirectTemporary)
|
||||||
|
res
|
||||||
|
Nothing -> do
|
||||||
|
toMaster <- getRouteToMaster
|
||||||
|
setMessageI Msg.NoOpenID
|
||||||
|
redirect RedirectTemporary $ toMaster LoginR
|
||||||
|
dispatch "GET" ["complete", ""] = dispatch "GET" ["complete"] -- compatibility issues
|
||||||
|
dispatch "GET" ["complete"] = do
|
||||||
|
rr <- getRequest
|
||||||
|
completeHelper $ reqGetParams rr
|
||||||
|
dispatch "POST" ["complete", ""] = dispatch "POST" ["complete"] -- compatibility issues
|
||||||
|
dispatch "POST" ["complete"] = do
|
||||||
|
(posts, _) <- runRequestBody
|
||||||
|
completeHelper posts
|
||||||
|
dispatch _ _ = notFound
|
||||||
|
|
||||||
|
completeHelper :: YesodAuth m => [(Text, Text)] -> GHandler Auth m ()
|
||||||
|
completeHelper gets' = do
|
||||||
|
res <- runAttemptT $ OpenId.authenticate gets'
|
||||||
|
toMaster <- getRouteToMaster
|
||||||
|
let onFailure err = do
|
||||||
|
setMessage $ toHtml $ show err
|
||||||
|
redirect RedirectTemporary $ toMaster LoginR
|
||||||
|
let onSuccess (OpenId.Identifier ident, _) = do
|
||||||
|
memail <- lookupGetParam "openid.ext1.value.email"
|
||||||
|
case (memail, "https://www.google.com/accounts/o8/id" `T.isPrefixOf` ident) of
|
||||||
|
(Just email, True) -> setCreds True $ Creds "openid" email []
|
||||||
|
(_, False) -> do
|
||||||
|
setMessage "Only Google login is supported"
|
||||||
|
redirect RedirectTemporary $ toMaster LoginR
|
||||||
|
(Nothing, _) -> do
|
||||||
|
setMessage "No email address provided"
|
||||||
|
redirect RedirectTemporary $ toMaster LoginR
|
||||||
|
attempt onFailure onSuccess res
|
||||||
@ -98,10 +98,18 @@ class HashDBUser user where
|
|||||||
userPasswordHash :: user -> Maybe Text
|
userPasswordHash :: user -> Maybe Text
|
||||||
-- | Retrieve salt for password
|
-- | Retrieve salt for password
|
||||||
userPasswordSalt :: user -> Maybe Text
|
userPasswordSalt :: user -> Maybe Text
|
||||||
-- | Set hash and password
|
|
||||||
|
-- | Deprecated for the better named setSaltAndPasswordHash
|
||||||
setUserHashAndSalt :: Text -- ^ Salt
|
setUserHashAndSalt :: Text -- ^ Salt
|
||||||
-> Text -- ^ Password hash
|
-> Text -- ^ Password hash
|
||||||
-> user -> user
|
-> user -> user
|
||||||
|
setUserHashAndSalt = setSaltAndPasswordHash
|
||||||
|
|
||||||
|
-- | a callback for setPassword
|
||||||
|
setSaltAndPasswordHash :: Text -- ^ Salt
|
||||||
|
-> Text -- ^ Password hash
|
||||||
|
-> user -> user
|
||||||
|
setSaltAndPasswordHash = setUserHashAndSalt
|
||||||
|
|
||||||
-- | Generate random salt. Length of 8 is chosen arbitrarily
|
-- | Generate random salt. Length of 8 is chosen arbitrarily
|
||||||
randomSalt :: MonadIO m => m Text
|
randomSalt :: MonadIO m => m Text
|
||||||
@ -118,7 +126,7 @@ saltedHash salt =
|
|||||||
-- passwords. It generates random salt and calculates proper hashes.
|
-- passwords. It generates random salt and calculates proper hashes.
|
||||||
setPassword :: (MonadIO m, HashDBUser user) => Text -> user -> m user
|
setPassword :: (MonadIO m, HashDBUser user) => Text -> user -> m user
|
||||||
setPassword pwd u = do salt <- randomSalt
|
setPassword pwd u = do salt <- randomSalt
|
||||||
return $ setUserHashAndSalt salt (saltedHash salt pwd) u
|
return $ setSaltAndPasswordHash salt (saltedHash salt pwd) u
|
||||||
|
|
||||||
|
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
@ -256,6 +264,6 @@ User
|
|||||||
instance HashDBUser (UserGeneric backend) where
|
instance HashDBUser (UserGeneric backend) where
|
||||||
userPasswordHash = Just . userPassword
|
userPasswordHash = Just . userPassword
|
||||||
userPasswordSalt = Just . userSalt
|
userPasswordSalt = Just . userSalt
|
||||||
setUserHashAndSalt s h u = u { userSalt = s
|
setSaltAndPasswordHash s h u = u { userSalt = s
|
||||||
, userPassword = h
|
, userPassword = h
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import Data.Text (Text)
|
|||||||
data AuthMessage =
|
data AuthMessage =
|
||||||
NoOpenID
|
NoOpenID
|
||||||
| LoginOpenID
|
| LoginOpenID
|
||||||
|
| LoginGoogle
|
||||||
|
| LoginYahoo
|
||||||
| Email
|
| Email
|
||||||
| Password
|
| Password
|
||||||
| Register
|
| Register
|
||||||
@ -37,6 +39,8 @@ data AuthMessage =
|
|||||||
defaultMessage :: AuthMessage -> Text
|
defaultMessage :: AuthMessage -> Text
|
||||||
defaultMessage NoOpenID = "No OpenID identifier found"
|
defaultMessage NoOpenID = "No OpenID identifier found"
|
||||||
defaultMessage LoginOpenID = "Login via OpenID"
|
defaultMessage LoginOpenID = "Login via OpenID"
|
||||||
|
defaultMessage LoginGoogle = "Login via Google"
|
||||||
|
defaultMessage LoginYahoo = "Login via Yahoo"
|
||||||
defaultMessage Email = "Email"
|
defaultMessage Email = "Email"
|
||||||
defaultMessage Password = "Password"
|
defaultMessage Password = "Password"
|
||||||
defaultMessage Register = "Register"
|
defaultMessage Register = "Register"
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
module Yesod.Auth.OpenId
|
module Yesod.Auth.OpenId
|
||||||
( authOpenId
|
( authOpenId
|
||||||
|
, authOpenIdExtended
|
||||||
, forwardUrl
|
, forwardUrl
|
||||||
) where
|
) where
|
||||||
|
|
||||||
@ -26,7 +27,10 @@ forwardUrl :: AuthRoute
|
|||||||
forwardUrl = PluginR "openid" ["forward"]
|
forwardUrl = PluginR "openid" ["forward"]
|
||||||
|
|
||||||
authOpenId :: YesodAuth m => AuthPlugin m
|
authOpenId :: YesodAuth m => AuthPlugin m
|
||||||
authOpenId =
|
authOpenId = authOpenIdExtended []
|
||||||
|
|
||||||
|
authOpenIdExtended :: YesodAuth m => [(Text, Text)] -> AuthPlugin m
|
||||||
|
authOpenIdExtended extensionFields =
|
||||||
AuthPlugin "openid" dispatch login
|
AuthPlugin "openid" dispatch login
|
||||||
where
|
where
|
||||||
complete = PluginR "openid" ["complete"]
|
complete = PluginR "openid" ["complete"]
|
||||||
@ -39,6 +43,12 @@ authOpenId =
|
|||||||
padding-left: 18px;
|
padding-left: 18px;
|
||||||
|]
|
|]
|
||||||
[QQ(whamlet)|
|
[QQ(whamlet)|
|
||||||
|
<form method="get" action="@{tm forwardUrl}">
|
||||||
|
<input type="hidden" name="openid_identifier" value="https://www.google.com/accounts/o8/id">
|
||||||
|
<button .openid-google>_{Msg.LoginGoogle}
|
||||||
|
<form method="get" action="@{tm forwardUrl}">
|
||||||
|
<input type="hidden" name="openid_identifier" value="http://me.yahoo.com">
|
||||||
|
<button .openid-yahoo>_{Msg.LoginYahoo}
|
||||||
<form method="get" action="@{tm forwardUrl}">
|
<form method="get" action="@{tm forwardUrl}">
|
||||||
<label for="#{ident}">OpenID: #
|
<label for="#{ident}">OpenID: #
|
||||||
<input id="#{ident}" type="text" name="#{name}" value="http://">
|
<input id="#{ident}" type="text" name="#{name}" value="http://">
|
||||||
@ -51,7 +61,7 @@ authOpenId =
|
|||||||
render <- getUrlRender
|
render <- getUrlRender
|
||||||
toMaster <- getRouteToMaster
|
toMaster <- getRouteToMaster
|
||||||
let complete' = render $ toMaster complete
|
let complete' = render $ toMaster complete
|
||||||
res <- runAttemptT $ OpenId.getForwardUrl oid complete' Nothing []
|
res <- runAttemptT $ OpenId.getForwardUrl oid complete' Nothing extensionFields
|
||||||
attempt
|
attempt
|
||||||
(\err -> do
|
(\err -> do
|
||||||
setMessage $ toHtml $ show err
|
setMessage $ toHtml $ show err
|
||||||
@ -81,5 +91,5 @@ completeHelper gets' = do
|
|||||||
setMessage $ toHtml $ show err
|
setMessage $ toHtml $ show err
|
||||||
redirect RedirectTemporary $ toMaster LoginR
|
redirect RedirectTemporary $ toMaster LoginR
|
||||||
let onSuccess (OpenId.Identifier ident, _) =
|
let onSuccess (OpenId.Identifier ident, _) =
|
||||||
setCreds True $ Creds "openid" ident []
|
setCreds True $ Creds "openid" ident gets'
|
||||||
attempt onFailure onSuccess res
|
attempt onFailure onSuccess res
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import Web.Authenticate.Facebook
|
|||||||
import Yesod.Form
|
import Yesod.Form
|
||||||
|
|
||||||
data FB = FB Facebook
|
data FB = FB Facebook
|
||||||
type Handler = GHandler FB FB
|
|
||||||
|
|
||||||
fb :: FB
|
fb :: FB
|
||||||
fb = FB Facebook
|
fb = FB Facebook
|
||||||
|
|||||||
54
yesod-auth/openid.hs
Normal file
54
yesod-auth/openid.hs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
import Yesod.Core
|
||||||
|
import Yesod.Auth
|
||||||
|
import Yesod.Auth.OpenId
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Text.Hamlet (hamlet)
|
||||||
|
import Control.Monad.IO.Class (liftIO)
|
||||||
|
import Yesod.Form
|
||||||
|
import Network.Wai.Handler.Warp (run)
|
||||||
|
|
||||||
|
data BID = BID
|
||||||
|
|
||||||
|
mkYesod "BID" [parseRoutes|
|
||||||
|
/ RootR GET
|
||||||
|
/after AfterLoginR GET
|
||||||
|
/auth AuthR Auth getAuth
|
||||||
|
|]
|
||||||
|
|
||||||
|
getRootR :: Handler RepHtml
|
||||||
|
getRootR = getAfterLoginR
|
||||||
|
|
||||||
|
getAfterLoginR :: Handler RepHtml
|
||||||
|
getAfterLoginR = do
|
||||||
|
mauth <- maybeAuthId
|
||||||
|
defaultLayout $ addHamlet [hamlet|
|
||||||
|
<p>Auth: #{show mauth}
|
||||||
|
$maybe _ <- mauth
|
||||||
|
<p>
|
||||||
|
<a href=@{AuthR LogoutR}>Logout
|
||||||
|
$nothing
|
||||||
|
<p>
|
||||||
|
<a href=@{AuthR LoginR}>Login
|
||||||
|
|]
|
||||||
|
|
||||||
|
instance Yesod BID where
|
||||||
|
approot _ = "http://localhost:3000"
|
||||||
|
|
||||||
|
instance YesodAuth BID where
|
||||||
|
type AuthId BID = Text
|
||||||
|
loginDest _ = AfterLoginR
|
||||||
|
logoutDest _ = AuthR LoginR
|
||||||
|
getAuthId = return . Just . credsIdent
|
||||||
|
authPlugins = [authOpenId]
|
||||||
|
|
||||||
|
instance RenderMessage BID FormMessage where
|
||||||
|
renderMessage _ _ = defaultFormMessage
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = toWaiApp BID >>= run 3000
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-auth
|
name: yesod-auth
|
||||||
version: 0.7.3
|
version: 0.7.8
|
||||||
license: BSD3
|
license: BSD3
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Michael Snoyman, Patrick Brisbin
|
author: Michael Snoyman, Patrick Brisbin
|
||||||
@ -17,33 +17,34 @@ flag ghc7
|
|||||||
|
|
||||||
library
|
library
|
||||||
if flag(ghc7)
|
if flag(ghc7)
|
||||||
build-depends: base >= 4.3 && < 5
|
build-depends: base >= 4.3 && < 5
|
||||||
cpp-options: -DGHC7
|
cpp-options: -DGHC7
|
||||||
else
|
else
|
||||||
build-depends: base >= 4 && < 4.3
|
build-depends: base >= 4 && < 4.3
|
||||||
build-depends: authenticate >= 0.10 && < 0.11
|
build-depends: authenticate >= 0.10.4 && < 0.11
|
||||||
, bytestring >= 0.9.1.4 && < 0.10
|
, bytestring >= 0.9.1.4 && < 0.10
|
||||||
, yesod-core >= 0.9 && < 0.10
|
, yesod-core >= 0.9.3.4 && < 0.10
|
||||||
, wai >= 0.4 && < 0.5
|
, wai >= 0.4 && < 0.5
|
||||||
, template-haskell
|
, template-haskell
|
||||||
, pureMD5 >= 1.1 && < 2.2
|
, pureMD5 >= 2.0 && < 2.2
|
||||||
, random >= 1.0 && < 1.1
|
, random >= 1.0.0.2 && < 1.1
|
||||||
, control-monad-attempt >= 0.3.0 && < 0.4
|
, control-monad-attempt >= 0.3.0 && < 0.4
|
||||||
, text >= 0.7 && < 0.12
|
, text >= 0.7 && < 0.12
|
||||||
, mime-mail >= 0.3 && < 0.4
|
, mime-mail >= 0.3 && < 0.5
|
||||||
, blaze-html >= 0.4 && < 0.5
|
, blaze-html >= 0.4.1.3 && < 0.5
|
||||||
, yesod-persistent >= 0.2 && < 0.3
|
, yesod-persistent >= 0.2 && < 0.3
|
||||||
, hamlet >= 0.10 && < 0.11
|
, hamlet >= 0.10 && < 0.11
|
||||||
, shakespeare-css >= 0.10 && < 0.11
|
, shakespeare-css >= 0.10 && < 0.11
|
||||||
, yesod-json >= 0.2 && < 0.3
|
, yesod-json >= 0.2 && < 0.3
|
||||||
, containers >= 0.2 && < 0.5
|
, containers
|
||||||
|
, unordered-containers
|
||||||
, yesod-form >= 0.3 && < 0.4
|
, yesod-form >= 0.3 && < 0.4
|
||||||
, transformers >= 0.2 && < 0.3
|
, transformers >= 0.2.2 && < 0.3
|
||||||
, persistent >= 0.6 && < 0.7
|
, persistent >= 0.6 && < 0.7
|
||||||
, persistent-template >= 0.6 && < 0.7
|
, persistent-template >= 0.6 && < 0.7
|
||||||
, SHA >= 1.4.1.3 && < 1.6
|
, SHA >= 1.4.1.3 && < 1.6
|
||||||
, http-enumerator >= 0.6 && < 0.8
|
, http-enumerator >= 0.6 && < 0.8
|
||||||
, aeson-native >= 0.3.2.11 && < 0.4
|
, aeson >= 0.3
|
||||||
, pwstore-fast >= 2.2 && < 3
|
, pwstore-fast >= 2.2 && < 3
|
||||||
|
|
||||||
exposed-modules: Yesod.Auth
|
exposed-modules: Yesod.Auth
|
||||||
@ -57,9 +58,10 @@ library
|
|||||||
Yesod.Auth.HashDB
|
Yesod.Auth.HashDB
|
||||||
Yesod.Auth.Message
|
Yesod.Auth.Message
|
||||||
Yesod.Auth.Kerberos
|
Yesod.Auth.Kerberos
|
||||||
|
Yesod.Auth.GoogleEmail
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
include-dirs: include
|
include-dirs: include
|
||||||
|
|
||||||
source-repository head
|
source-repository head
|
||||||
type: git
|
type: git
|
||||||
location: git://github.com/snoyberg/yesod-auth.git
|
location: git://github.com/yesodweb/yesod.git
|
||||||
|
|||||||
113
yesod-core/Yesod/Config.hs
Normal file
113
yesod-core/Yesod/Config.hs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
{-# 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
|
||||||
@ -1,4 +1,5 @@
|
|||||||
{-# LANGUAGE TemplateHaskell #-}
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
|
||||||
module Yesod.Core
|
module Yesod.Core
|
||||||
( -- * Type classes
|
( -- * Type classes
|
||||||
Yesod (..)
|
Yesod (..)
|
||||||
@ -33,6 +34,7 @@ module Yesod.Core
|
|||||||
, module Yesod.Request
|
, module Yesod.Request
|
||||||
, module Yesod.Widget
|
, module Yesod.Widget
|
||||||
, module Yesod.Message
|
, module Yesod.Message
|
||||||
|
, module Yesod.Config
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Yesod.Internal.Core
|
import Yesod.Internal.Core
|
||||||
@ -42,6 +44,7 @@ import Yesod.Handler
|
|||||||
import Yesod.Request
|
import Yesod.Request
|
||||||
import Yesod.Widget
|
import Yesod.Widget
|
||||||
import Yesod.Message
|
import Yesod.Message
|
||||||
|
import Yesod.Config
|
||||||
|
|
||||||
import Language.Haskell.TH.Syntax
|
import Language.Haskell.TH.Syntax
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
|
|||||||
@ -4,7 +4,9 @@
|
|||||||
module Yesod.Dispatch
|
module Yesod.Dispatch
|
||||||
( -- * Quasi-quoted routing
|
( -- * Quasi-quoted routing
|
||||||
parseRoutes
|
parseRoutes
|
||||||
|
, parseRoutesNoCheck
|
||||||
, parseRoutesFile
|
, parseRoutesFile
|
||||||
|
, parseRoutesFileNoCheck
|
||||||
, mkYesod
|
, mkYesod
|
||||||
, mkYesodSub
|
, mkYesodSub
|
||||||
-- ** More fine-grained
|
-- ** More fine-grained
|
||||||
@ -30,11 +32,10 @@ import Yesod.Internal.Dispatch
|
|||||||
import Yesod.Widget (GWidget)
|
import Yesod.Widget (GWidget)
|
||||||
|
|
||||||
import Web.PathPieces (SinglePiece (..), MultiPiece (..))
|
import Web.PathPieces (SinglePiece (..), MultiPiece (..))
|
||||||
import Yesod.Internal.RouteParsing (THResource, Pieces (..), createRoutes, createRender, Resource (..), parseRoutes, parseRoutesFile)
|
import Yesod.Internal.RouteParsing (THResource, Pieces (..), createRoutes, createRender, Resource (..), parseRoutes, parseRoutesNoCheck, parseRoutesFile, parseRoutesFileNoCheck)
|
||||||
import Language.Haskell.TH.Syntax
|
import Language.Haskell.TH.Syntax
|
||||||
|
|
||||||
import qualified Network.Wai as W
|
import qualified Network.Wai as W
|
||||||
import Network.Wai.Middleware.Jsonp
|
|
||||||
import Network.Wai.Middleware.Gzip
|
import Network.Wai.Middleware.Gzip
|
||||||
import Network.Wai.Middleware.Autohead
|
import Network.Wai.Middleware.Autohead
|
||||||
|
|
||||||
@ -172,11 +173,11 @@ thResourceFromResource (Resource n _ _) =
|
|||||||
error $ "Invalid attributes for resource: " ++ n
|
error $ "Invalid attributes for resource: " ++ n
|
||||||
|
|
||||||
-- | Convert the given argument into a WAI application, executable with any WAI
|
-- | Convert the given argument into a WAI application, executable with any WAI
|
||||||
-- handler. This is the same as 'toWaiAppPlain', except it includes three
|
-- handler. This is the same as 'toWaiAppPlain', except it includes two
|
||||||
-- middlewares: GZIP compression, JSON-P and autohead. This is the
|
-- middlewares: GZIP compression and autohead. This is the
|
||||||
-- recommended approach for most users.
|
-- recommended approach for most users.
|
||||||
toWaiApp :: (Yesod y, YesodDispatch y y) => y -> IO W.Application
|
toWaiApp :: (Yesod y, YesodDispatch y y) => y -> IO W.Application
|
||||||
toWaiApp y = gzip (gzipCompressFiles y) . jsonp . autohead <$> toWaiAppPlain y
|
toWaiApp y = gzip (gzipCompressFiles y) . autohead <$> toWaiAppPlain y
|
||||||
|
|
||||||
-- | Convert the given argument into a WAI application, executable with any WAI
|
-- | Convert the given argument into a WAI application, executable with any WAI
|
||||||
-- handler. This differs from 'toWaiApp' in that it uses no middlewares.
|
-- handler. This differs from 'toWaiApp' in that it uses no middlewares.
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
{-# LANGUAGE FunctionalDependencies #-}
|
{-# LANGUAGE FunctionalDependencies #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE QuasiQuotes #-}
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
{-# LANGUAGE DeriveDataTypeable #-}
|
||||||
---------------------------------------------------------
|
---------------------------------------------------------
|
||||||
--
|
--
|
||||||
-- Module : Yesod.Handler
|
-- Module : Yesod.Handler
|
||||||
@ -97,6 +98,12 @@ module Yesod.Handler
|
|||||||
, liftIOHandler
|
, liftIOHandler
|
||||||
-- * i18n
|
-- * i18n
|
||||||
, getMessageRender
|
, getMessageRender
|
||||||
|
-- * Per-request caching
|
||||||
|
, CacheKey
|
||||||
|
, mkCacheKey
|
||||||
|
, cacheLookup
|
||||||
|
, cacheInsert
|
||||||
|
, cacheDelete
|
||||||
-- * Internal Yesod
|
-- * Internal Yesod
|
||||||
, runHandler
|
, runHandler
|
||||||
, YesodApp (..)
|
, YesodApp (..)
|
||||||
@ -119,17 +126,13 @@ import Yesod.Internal
|
|||||||
import Data.Time (UTCTime)
|
import Data.Time (UTCTime)
|
||||||
|
|
||||||
import Control.Exception hiding (Handler, catch, finally)
|
import Control.Exception hiding (Handler, catch, finally)
|
||||||
import qualified Control.Exception as E
|
|
||||||
import Control.Applicative
|
import Control.Applicative
|
||||||
|
|
||||||
import Control.Monad (liftM, join, MonadPlus)
|
import Control.Monad (liftM)
|
||||||
|
|
||||||
import Control.Monad.IO.Class
|
import Control.Monad.IO.Class
|
||||||
import Control.Monad.Trans.Class
|
import Control.Monad.Trans.Class
|
||||||
import Control.Monad.Trans.Writer
|
|
||||||
import Control.Monad.Trans.Reader
|
import Control.Monad.Trans.Reader
|
||||||
import Control.Monad.Trans.State
|
|
||||||
import Control.Monad.Trans.Error (throwError, ErrorT (..), Error (..))
|
|
||||||
|
|
||||||
import System.IO
|
import System.IO
|
||||||
import qualified Network.Wai as W
|
import qualified Network.Wai as W
|
||||||
@ -143,8 +146,6 @@ import Data.Text.Encoding (encodeUtf8, decodeUtf8With)
|
|||||||
import Data.Text.Encoding.Error (lenientDecode)
|
import Data.Text.Encoding.Error (lenientDecode)
|
||||||
import qualified Data.Text.Lazy as TL
|
import qualified Data.Text.Lazy as TL
|
||||||
|
|
||||||
import Control.Monad.IO.Control (MonadControlIO)
|
|
||||||
import Control.Monad.Trans.Control (MonadTransControl, liftControl)
|
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
import qualified Data.ByteString as S
|
import qualified Data.ByteString as S
|
||||||
import Data.ByteString (ByteString)
|
import Data.ByteString (ByteString)
|
||||||
@ -154,7 +155,7 @@ import Network.Wai.Parse (parseHttpAccept)
|
|||||||
import Yesod.Content
|
import Yesod.Content
|
||||||
import Data.Maybe (fromMaybe)
|
import Data.Maybe (fromMaybe)
|
||||||
import Web.Cookie (SetCookie (..), renderSetCookie)
|
import Web.Cookie (SetCookie (..), renderSetCookie)
|
||||||
import Control.Arrow (second, (***))
|
import Control.Arrow ((***))
|
||||||
import qualified Network.Wai.Parse as NWP
|
import qualified Network.Wai.Parse as NWP
|
||||||
import Data.Monoid (mappend, mempty, Endo (..))
|
import Data.Monoid (mappend, mempty, Endo (..))
|
||||||
import qualified Data.ByteString.Char8 as S8
|
import qualified Data.ByteString.Char8 as S8
|
||||||
@ -164,6 +165,12 @@ import Data.Text (Text)
|
|||||||
import Yesod.Message (RenderMessage (..))
|
import Yesod.Message (RenderMessage (..))
|
||||||
|
|
||||||
import Text.Blaze (toHtml, preEscapedText)
|
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
|
||||||
|
|
||||||
-- | The type-safe URLs associated with a site argument.
|
-- | The type-safe URLs associated with a site argument.
|
||||||
type family Route a
|
type family Route a
|
||||||
@ -178,6 +185,7 @@ data HandlerData sub master = HandlerData
|
|||||||
, handlerRoute :: Maybe (Route sub)
|
, handlerRoute :: Maybe (Route sub)
|
||||||
, handlerRender :: Route master -> [(Text, Text)] -> Text
|
, handlerRender :: Route master -> [(Text, Text)] -> Text
|
||||||
, handlerToMaster :: Route sub -> Route master
|
, handlerToMaster :: Route sub -> Route master
|
||||||
|
, handlerState :: I.IORef GHState
|
||||||
}
|
}
|
||||||
|
|
||||||
handlerSubData :: (Route sub -> Route master)
|
handlerSubData :: (Route sub -> Route master)
|
||||||
@ -198,6 +206,24 @@ handlerSubDataMaybe tm ts route hd = hd
|
|||||||
, handlerRoute = route
|
, handlerRoute = route
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get :: MonadIO monad => GGHandler sub master monad GHState
|
||||||
|
get = do
|
||||||
|
hd <- ask
|
||||||
|
liftIO $ I.readIORef $ handlerState hd
|
||||||
|
|
||||||
|
put :: MonadIO monad => GHState -> GGHandler sub master monad ()
|
||||||
|
put g = do
|
||||||
|
hd <- ask
|
||||||
|
liftIO $ I.writeIORef (handlerState hd) g
|
||||||
|
|
||||||
|
modify :: MonadIO monad => (GHState -> GHState) -> GGHandler 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 hs = modify $ \g -> g { ghsHeaders = ghsHeaders g `mappend` hs }
|
||||||
|
|
||||||
-- | Used internally for promoting subsite handler functions to master site
|
-- | Used internally for promoting subsite handler functions to master site
|
||||||
-- handler functions. Should not be needed by users.
|
-- handler functions. Should not be needed by users.
|
||||||
toMasterHandler :: (Route sub -> Route master)
|
toMasterHandler :: (Route sub -> Route master)
|
||||||
@ -205,8 +231,7 @@ toMasterHandler :: (Route sub -> Route master)
|
|||||||
-> Route sub
|
-> Route sub
|
||||||
-> GGHandler sub master mo a
|
-> GGHandler sub master mo a
|
||||||
-> GGHandler sub' master mo a
|
-> GGHandler sub' master mo a
|
||||||
toMasterHandler tm ts route (GHandler h) =
|
toMasterHandler tm ts route = withReaderT (handlerSubData tm ts route)
|
||||||
GHandler $ withReaderT (handlerSubData tm ts route) h
|
|
||||||
|
|
||||||
toMasterHandlerDyn :: Monad mo
|
toMasterHandlerDyn :: Monad mo
|
||||||
=> (Route sub -> Route master)
|
=> (Route sub -> Route master)
|
||||||
@ -214,9 +239,9 @@ toMasterHandlerDyn :: Monad mo
|
|||||||
-> Route sub
|
-> Route sub
|
||||||
-> GGHandler sub master mo a
|
-> GGHandler sub master mo a
|
||||||
-> GGHandler sub' master mo a
|
-> GGHandler sub' master mo a
|
||||||
toMasterHandlerDyn tm getSub route (GHandler h) = do
|
toMasterHandlerDyn tm getSub route h = do
|
||||||
sub <- getSub
|
sub <- getSub
|
||||||
GHandler $ withReaderT (handlerSubData tm (const sub) route) h
|
withReaderT (handlerSubData tm (const sub) route) h
|
||||||
|
|
||||||
class SubsiteGetter g m s | g -> s where
|
class SubsiteGetter g m s | g -> s where
|
||||||
runSubsiteGetter :: g -> m s
|
runSubsiteGetter :: g -> m s
|
||||||
@ -235,22 +260,14 @@ toMasterHandlerMaybe :: (Route sub -> Route master)
|
|||||||
-> Maybe (Route sub)
|
-> Maybe (Route sub)
|
||||||
-> GGHandler sub master mo a
|
-> GGHandler sub master mo a
|
||||||
-> GGHandler sub' master mo a
|
-> GGHandler sub' master mo a
|
||||||
toMasterHandlerMaybe tm ts route (GHandler h) =
|
toMasterHandlerMaybe tm ts route = withReaderT (handlerSubDataMaybe tm ts route)
|
||||||
GHandler $ withReaderT (handlerSubDataMaybe tm ts route) h
|
|
||||||
|
|
||||||
-- | A generic handler monad, which can have a different subsite and master
|
-- | A generic handler monad, which can have a different subsite and master
|
||||||
-- site. This monad is a combination of 'ReaderT' for basic arguments, a
|
-- site. This monad is a combination of 'ReaderT' for basic arguments, a
|
||||||
-- 'WriterT' for headers and session, and an 'MEitherT' monad for handling
|
-- 'WriterT' for headers and session, and an 'MEitherT' monad for handling
|
||||||
-- special responses. It is declared as a newtype to make compiler errors more
|
-- special responses. It is declared as a newtype to make compiler errors more
|
||||||
-- readable.
|
-- readable.
|
||||||
newtype GGHandler sub master m a =
|
type GGHandler sub master = ReaderT (HandlerData sub master)
|
||||||
GHandler
|
|
||||||
{ unGHandler :: GHInner sub master m a
|
|
||||||
}
|
|
||||||
deriving (Functor, Applicative, Monad, MonadIO, MonadControlIO, MonadPlus)
|
|
||||||
|
|
||||||
instance MonadTrans (GGHandler s m) where
|
|
||||||
lift = GHandler . lift . lift . lift . lift
|
|
||||||
|
|
||||||
type GHandler sub master = GGHandler sub master (Iteratee ByteString IO)
|
type GHandler sub master = GGHandler sub master (Iteratee ByteString IO)
|
||||||
|
|
||||||
@ -258,16 +275,10 @@ data GHState = GHState
|
|||||||
{ ghsSession :: SessionMap
|
{ ghsSession :: SessionMap
|
||||||
, ghsRBC :: Maybe RequestBodyContents
|
, ghsRBC :: Maybe RequestBodyContents
|
||||||
, ghsIdent :: Int
|
, ghsIdent :: Int
|
||||||
|
, ghsCache :: Cache.Cache
|
||||||
|
, ghsHeaders :: Endo [Header]
|
||||||
}
|
}
|
||||||
|
|
||||||
type GHInner s m monad = -- FIXME collapse the stack
|
|
||||||
ReaderT (HandlerData s m) (
|
|
||||||
ErrorT HandlerContents (
|
|
||||||
WriterT (Endo [Header]) (
|
|
||||||
StateT GHState (
|
|
||||||
monad
|
|
||||||
))))
|
|
||||||
|
|
||||||
type SessionMap = Map.Map Text Text
|
type SessionMap = Map.Map Text Text
|
||||||
|
|
||||||
-- | An extension of the basic WAI 'W.Application' datatype to provide extra
|
-- | An extension of the basic WAI 'W.Application' datatype to provide extra
|
||||||
@ -293,25 +304,27 @@ data HandlerContents =
|
|||||||
| HCRedirect RedirectType Text
|
| HCRedirect RedirectType Text
|
||||||
| HCCreated Text
|
| HCCreated Text
|
||||||
| HCWai W.Response
|
| HCWai W.Response
|
||||||
|
deriving Typeable
|
||||||
|
|
||||||
instance Error HandlerContents where
|
instance Show HandlerContents where
|
||||||
strMsg = HCError . InternalError . T.pack
|
show _ = "Cannot show a HandlerContents"
|
||||||
|
instance Exception HandlerContents
|
||||||
|
|
||||||
getRequest :: Monad mo => GGHandler s m mo Request
|
getRequest :: Monad mo => GGHandler s m mo Request
|
||||||
getRequest = handlerRequest `liftM` GHandler ask
|
getRequest = handlerRequest `liftM` ask
|
||||||
|
|
||||||
instance Monad monad => Failure ErrorResponse (GGHandler sub master monad) where
|
instance MonadIO monad => Failure ErrorResponse (GGHandler sub master monad) where
|
||||||
failure = GHandler . lift . throwError . HCError
|
failure = liftIO . throwIO . HCError
|
||||||
|
|
||||||
runRequestBody :: GHandler s m RequestBodyContents
|
runRequestBody :: GHandler s m RequestBodyContents
|
||||||
runRequestBody = do
|
runRequestBody = do
|
||||||
x <- GHandler $ lift $ lift $ lift get
|
x <- get
|
||||||
case ghsRBC x of
|
case ghsRBC x of
|
||||||
Just rbc -> return rbc
|
Just rbc -> return rbc
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
rr <- waiRequest
|
rr <- waiRequest
|
||||||
rbc <- lift $ rbHelper rr
|
rbc <- lift $ rbHelper rr
|
||||||
GHandler $ lift $ lift $ lift $ put x { ghsRBC = Just rbc }
|
put x { ghsRBC = Just rbc }
|
||||||
return rbc
|
return rbc
|
||||||
|
|
||||||
rbHelper :: W.Request -> Iteratee ByteString IO RequestBodyContents
|
rbHelper :: W.Request -> Iteratee ByteString IO RequestBodyContents
|
||||||
@ -326,33 +339,33 @@ rbHelper req =
|
|||||||
|
|
||||||
-- | Get the sub application argument.
|
-- | Get the sub application argument.
|
||||||
getYesodSub :: Monad m => GGHandler sub master m sub
|
getYesodSub :: Monad m => GGHandler sub master m sub
|
||||||
getYesodSub = handlerSub `liftM` GHandler ask
|
getYesodSub = handlerSub `liftM` ask
|
||||||
|
|
||||||
-- | Get the master site appliation argument.
|
-- | Get the master site appliation argument.
|
||||||
getYesod :: Monad m => GGHandler sub master m master
|
getYesod :: Monad m => GGHandler sub master m master
|
||||||
getYesod = handlerMaster `liftM` GHandler ask
|
getYesod = handlerMaster `liftM` ask
|
||||||
|
|
||||||
-- | Get the URL rendering function.
|
-- | Get the URL rendering function.
|
||||||
getUrlRender :: Monad m => GGHandler sub master m (Route master -> Text)
|
getUrlRender :: Monad m => GGHandler sub master m (Route master -> Text)
|
||||||
getUrlRender = do
|
getUrlRender = do
|
||||||
x <- handlerRender `liftM` GHandler ask
|
x <- handlerRender `liftM` ask
|
||||||
return $ flip x []
|
return $ flip x []
|
||||||
|
|
||||||
-- | The URL rendering function with query-string parameters.
|
-- | The URL rendering function with query-string parameters.
|
||||||
getUrlRenderParams
|
getUrlRenderParams
|
||||||
:: Monad m
|
:: Monad m
|
||||||
=> GGHandler sub master m (Route master -> [(Text, Text)] -> Text)
|
=> GGHandler sub master m (Route master -> [(Text, Text)] -> Text)
|
||||||
getUrlRenderParams = handlerRender `liftM` GHandler ask
|
getUrlRenderParams = handlerRender `liftM` ask
|
||||||
|
|
||||||
-- | Get the route requested by the user. If this is a 404 response- where the
|
-- | 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'.
|
-- user requested an invalid route- this function will return 'Nothing'.
|
||||||
getCurrentRoute :: Monad m => GGHandler sub master m (Maybe (Route sub))
|
getCurrentRoute :: Monad m => GGHandler sub master m (Maybe (Route sub))
|
||||||
getCurrentRoute = handlerRoute `liftM` GHandler ask
|
getCurrentRoute = handlerRoute `liftM` ask
|
||||||
|
|
||||||
-- | Get the function to promote a route for a subsite to a route for the
|
-- | Get the function to promote a route for a subsite to a route for the
|
||||||
-- master site.
|
-- master site.
|
||||||
getRouteToMaster :: Monad m => GGHandler sub master m (Route sub -> Route master)
|
getRouteToMaster :: Monad m => GGHandler sub master m (Route sub -> Route master)
|
||||||
getRouteToMaster = handlerToMaster `liftM` GHandler ask
|
getRouteToMaster = handlerToMaster `liftM` ask
|
||||||
|
|
||||||
-- | Function used internally by Yesod in the process of converting a
|
-- | Function used internally by Yesod in the process of converting a
|
||||||
-- 'GHandler' into an 'W.Application'. Should not be needed by users.
|
-- 'GHandler' into an 'W.Application'. Should not be needed by users.
|
||||||
@ -370,6 +383,13 @@ runHandler handler mrender sroute tomr ma sa =
|
|||||||
case fromException e of
|
case fromException e of
|
||||||
Just x -> x
|
Just x -> x
|
||||||
Nothing -> InternalError $ T.pack $ show e
|
Nothing -> InternalError $ T.pack $ show e
|
||||||
|
istate <- liftIO $ I.newIORef GHState
|
||||||
|
{ ghsSession = initSession
|
||||||
|
, ghsRBC = Nothing
|
||||||
|
, ghsIdent = 1
|
||||||
|
, ghsCache = mempty
|
||||||
|
, ghsHeaders = mempty
|
||||||
|
}
|
||||||
let hd = HandlerData
|
let hd = HandlerData
|
||||||
{ handlerRequest = rr
|
{ handlerRequest = rr
|
||||||
, handlerSub = sa
|
, handlerSub = sa
|
||||||
@ -377,16 +397,14 @@ runHandler handler mrender sroute tomr ma sa =
|
|||||||
, handlerRoute = sroute
|
, handlerRoute = sroute
|
||||||
, handlerRender = mrender
|
, handlerRender = mrender
|
||||||
, handlerToMaster = tomr
|
, handlerToMaster = tomr
|
||||||
|
, handlerState = istate
|
||||||
}
|
}
|
||||||
let initSession' = GHState initSession Nothing 1
|
contents' <- catchIter (fmap Right $ runReaderT handler hd)
|
||||||
((contents', headers), finalSession) <- catchIter (
|
(\e -> return $ Left $ maybe (HCError $ toErrorHandler e) id
|
||||||
fmap (second ghsSession)
|
$ fromException e)
|
||||||
$ flip runStateT initSession'
|
state <- liftIO $ I.readIORef istate
|
||||||
$ runWriterT
|
let finalSession = ghsSession state
|
||||||
$ runErrorT
|
let headers = ghsHeaders state
|
||||||
$ flip runReaderT hd
|
|
||||||
$ unGHandler handler
|
|
||||||
) (\e -> return ((Left $ HCError $ toErrorHandler e, mempty), initSession))
|
|
||||||
let contents = either id (HCContent H.status200 . chooseRep) contents'
|
let contents = either id (HCContent H.status200 . chooseRep) contents'
|
||||||
let handleError e = do
|
let handleError e = do
|
||||||
yar <- unYesodApp (eh e) safeEh rr cts finalSession
|
yar <- unYesodApp (eh e) safeEh rr cts finalSession
|
||||||
@ -420,12 +438,6 @@ runHandler handler mrender sroute tomr ma sa =
|
|||||||
finalSession
|
finalSession
|
||||||
HCWai r -> return $ YARWai r
|
HCWai r -> return $ YARWai r
|
||||||
|
|
||||||
catchIter :: Exception e
|
|
||||||
=> Iteratee ByteString IO a
|
|
||||||
-> (e -> Iteratee ByteString IO a)
|
|
||||||
-> Iteratee ByteString IO a
|
|
||||||
catchIter (Iteratee mstep) f = Iteratee $ mstep `E.catch` (runIteratee . f)
|
|
||||||
|
|
||||||
safeEh :: ErrorResponse -> YesodApp
|
safeEh :: ErrorResponse -> YesodApp
|
||||||
safeEh er = YesodApp $ \_ _ _ session -> do
|
safeEh er = YesodApp $ \_ _ _ session -> do
|
||||||
liftIO $ hPutStrLn stderr $ "Error handler errored out: " ++ show er
|
liftIO $ hPutStrLn stderr $ "Error handler errored out: " ++ show er
|
||||||
@ -437,11 +449,11 @@ safeEh er = YesodApp $ \_ _ _ session -> do
|
|||||||
session
|
session
|
||||||
|
|
||||||
-- | Redirect to the given route.
|
-- | Redirect to the given route.
|
||||||
redirect :: Monad mo => RedirectType -> Route master -> GGHandler sub master mo a
|
redirect :: MonadIO mo => RedirectType -> Route master -> GGHandler sub master mo a
|
||||||
redirect rt url = redirectParams rt url []
|
redirect rt url = redirectParams rt url []
|
||||||
|
|
||||||
-- | Redirects to the given route with the associated query-string parameters.
|
-- | Redirects to the given route with the associated query-string parameters.
|
||||||
redirectParams :: Monad mo
|
redirectParams :: MonadIO mo
|
||||||
=> RedirectType -> Route master -> [(Text, Text)]
|
=> RedirectType -> Route master -> [(Text, Text)]
|
||||||
-> GGHandler sub master mo a
|
-> GGHandler sub master mo a
|
||||||
redirectParams rt url params = do
|
redirectParams rt url params = do
|
||||||
@ -449,8 +461,8 @@ redirectParams rt url params = do
|
|||||||
redirectString rt $ r url params
|
redirectString rt $ r url params
|
||||||
|
|
||||||
-- | Redirect to the given URL.
|
-- | Redirect to the given URL.
|
||||||
redirectString, redirectText :: Monad mo => RedirectType -> Text -> GGHandler sub master mo a
|
redirectString, redirectText :: MonadIO mo => RedirectType -> Text -> GGHandler sub master mo a
|
||||||
redirectText rt = GHandler . lift . throwError . HCRedirect rt
|
redirectText rt = liftIO . throwIO . HCRedirect rt
|
||||||
redirectString = redirectText
|
redirectString = redirectText
|
||||||
{-# DEPRECATED redirectString "Use redirectText instead" #-}
|
{-# DEPRECATED redirectString "Use redirectText instead" #-}
|
||||||
|
|
||||||
@ -461,16 +473,16 @@ ultDestKey = "_ULT"
|
|||||||
--
|
--
|
||||||
-- An ultimate destination is stored in the user session and can be loaded
|
-- An ultimate destination is stored in the user session and can be loaded
|
||||||
-- later by 'redirectUltDest'.
|
-- later by 'redirectUltDest'.
|
||||||
setUltDest :: Monad mo => Route master -> GGHandler sub master mo ()
|
setUltDest :: MonadIO mo => Route master -> GGHandler sub master mo ()
|
||||||
setUltDest dest = do
|
setUltDest dest = do
|
||||||
render <- getUrlRender
|
render <- getUrlRender
|
||||||
setUltDestString $ render dest
|
setUltDestString $ render dest
|
||||||
|
|
||||||
-- | Same as 'setUltDest', but use the given string.
|
-- | Same as 'setUltDest', but use the given string.
|
||||||
setUltDestText :: Monad mo => Text -> GGHandler sub master mo ()
|
setUltDestText :: MonadIO mo => Text -> GGHandler sub master mo ()
|
||||||
setUltDestText = setSession ultDestKey
|
setUltDestText = setSession ultDestKey
|
||||||
|
|
||||||
setUltDestString :: Monad mo => Text -> GGHandler sub master mo ()
|
setUltDestString :: MonadIO mo => Text -> GGHandler sub master mo ()
|
||||||
setUltDestString = setSession ultDestKey
|
setUltDestString = setSession ultDestKey
|
||||||
{-# DEPRECATED setUltDestString "Use setUltDestText instead" #-}
|
{-# DEPRECATED setUltDestString "Use setUltDestText instead" #-}
|
||||||
|
|
||||||
@ -478,21 +490,21 @@ setUltDestString = setSession ultDestKey
|
|||||||
--
|
--
|
||||||
-- If this is a 404 handler, there is no current page, and then this call does
|
-- If this is a 404 handler, there is no current page, and then this call does
|
||||||
-- nothing.
|
-- nothing.
|
||||||
setUltDest' :: Monad mo => GGHandler sub master mo ()
|
setUltDest' :: MonadIO mo => GGHandler sub master mo ()
|
||||||
setUltDest' = do
|
setUltDest' = do
|
||||||
route <- getCurrentRoute
|
route <- getCurrentRoute
|
||||||
case route of
|
case route of
|
||||||
Nothing -> return ()
|
Nothing -> return ()
|
||||||
Just r -> do
|
Just r -> do
|
||||||
tm <- getRouteToMaster
|
tm <- getRouteToMaster
|
||||||
gets' <- reqGetParams `liftM` handlerRequest `liftM` GHandler ask
|
gets' <- reqGetParams `liftM` handlerRequest `liftM` ask
|
||||||
render <- getUrlRenderParams
|
render <- getUrlRenderParams
|
||||||
setUltDestString $ render (tm r) gets'
|
setUltDestString $ render (tm r) gets'
|
||||||
|
|
||||||
-- | Sets the ultimate destination to the referer request header, if present.
|
-- | Sets the ultimate destination to the referer request header, if present.
|
||||||
--
|
--
|
||||||
-- This function will not overwrite an existing ultdest.
|
-- This function will not overwrite an existing ultdest.
|
||||||
setUltDestReferer :: Monad mo => GGHandler sub master mo ()
|
setUltDestReferer :: MonadIO mo => GGHandler sub master mo ()
|
||||||
setUltDestReferer = do
|
setUltDestReferer = do
|
||||||
mdest <- lookupSession ultDestKey
|
mdest <- lookupSession ultDestKey
|
||||||
maybe
|
maybe
|
||||||
@ -506,7 +518,7 @@ setUltDestReferer = do
|
|||||||
-- value from the session.
|
-- value from the session.
|
||||||
--
|
--
|
||||||
-- The ultimate destination is set with 'setUltDest'.
|
-- The ultimate destination is set with 'setUltDest'.
|
||||||
redirectUltDest :: Monad mo
|
redirectUltDest :: MonadIO mo
|
||||||
=> RedirectType
|
=> RedirectType
|
||||||
-> Route master -- ^ default destination if nothing in session
|
-> Route master -- ^ default destination if nothing in session
|
||||||
-> GGHandler sub master mo a
|
-> GGHandler sub master mo a
|
||||||
@ -516,7 +528,7 @@ redirectUltDest rt def = do
|
|||||||
maybe (redirect rt def) (redirectText rt) mdest
|
maybe (redirect rt def) (redirectText rt) mdest
|
||||||
|
|
||||||
-- | Remove a previously set ultimate destination. See 'setUltDest'.
|
-- | Remove a previously set ultimate destination. See 'setUltDest'.
|
||||||
clearUltDest :: Monad mo => GGHandler sub master mo ()
|
clearUltDest :: MonadIO mo => GGHandler sub master mo ()
|
||||||
clearUltDest = deleteSession ultDestKey
|
clearUltDest = deleteSession ultDestKey
|
||||||
|
|
||||||
msgKey :: Text
|
msgKey :: Text
|
||||||
@ -525,13 +537,13 @@ msgKey = "_MSG"
|
|||||||
-- | Sets a message in the user's session.
|
-- | Sets a message in the user's session.
|
||||||
--
|
--
|
||||||
-- See 'getMessage'.
|
-- See 'getMessage'.
|
||||||
setMessage :: Monad mo => Html -> GGHandler sub master mo ()
|
setMessage :: MonadIO mo => Html -> GGHandler sub master mo ()
|
||||||
setMessage = setSession msgKey . T.concat . TL.toChunks . Text.Blaze.Renderer.Text.renderHtml
|
setMessage = setSession msgKey . T.concat . TL.toChunks . Text.Blaze.Renderer.Text.renderHtml
|
||||||
|
|
||||||
-- | Sets a message in the user's session.
|
-- | Sets a message in the user's session.
|
||||||
--
|
--
|
||||||
-- See 'getMessage'.
|
-- See 'getMessage'.
|
||||||
setMessageI :: (RenderMessage y msg, Monad mo) => msg -> GGHandler sub y mo ()
|
setMessageI :: (RenderMessage y msg, MonadIO mo) => msg -> GGHandler sub y mo ()
|
||||||
setMessageI msg = do
|
setMessageI msg = do
|
||||||
mr <- getMessageRender
|
mr <- getMessageRender
|
||||||
setMessage $ toHtml $ mr msg
|
setMessage $ toHtml $ mr msg
|
||||||
@ -540,7 +552,7 @@ setMessageI msg = do
|
|||||||
-- variable.
|
-- variable.
|
||||||
--
|
--
|
||||||
-- See 'setMessage'.
|
-- See 'setMessage'.
|
||||||
getMessage :: Monad mo => GGHandler sub master mo (Maybe Html)
|
getMessage :: MonadIO mo => GGHandler sub master mo (Maybe Html)
|
||||||
getMessage = do
|
getMessage = do
|
||||||
mmsg <- liftM (fmap preEscapedText) $ lookupSession msgKey
|
mmsg <- liftM (fmap preEscapedText) $ lookupSession msgKey
|
||||||
deleteSession msgKey
|
deleteSession msgKey
|
||||||
@ -550,52 +562,52 @@ getMessage = do
|
|||||||
--
|
--
|
||||||
-- For some backends, this is more efficient than reading in the file to
|
-- 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.
|
-- memory, since they can optimize file sending via a system call to sendfile.
|
||||||
sendFile :: Monad mo => ContentType -> FilePath -> GGHandler sub master mo a
|
sendFile :: MonadIO mo => ContentType -> FilePath -> GGHandler sub master mo a
|
||||||
sendFile ct fp = GHandler . lift . throwError $ HCSendFile ct fp Nothing
|
sendFile ct fp = liftIO . throwIO $ HCSendFile ct fp Nothing
|
||||||
|
|
||||||
-- | Same as 'sendFile', but only sends part of a file.
|
-- | Same as 'sendFile', but only sends part of a file.
|
||||||
sendFilePart :: Monad mo
|
sendFilePart :: MonadIO mo
|
||||||
=> ContentType
|
=> ContentType
|
||||||
-> FilePath
|
-> FilePath
|
||||||
-> Integer -- ^ offset
|
-> Integer -- ^ offset
|
||||||
-> Integer -- ^ count
|
-> Integer -- ^ count
|
||||||
-> GGHandler sub master mo a
|
-> GGHandler sub master mo a
|
||||||
sendFilePart ct fp off count =
|
sendFilePart ct fp off count =
|
||||||
GHandler . lift . throwError $ HCSendFile ct fp $ Just $ W.FilePart off count
|
liftIO . throwIO $ HCSendFile ct fp $ Just $ W.FilePart off count
|
||||||
|
|
||||||
-- | Bypass remaining handler code and output the given content with a 200
|
-- | Bypass remaining handler code and output the given content with a 200
|
||||||
-- status code.
|
-- status code.
|
||||||
sendResponse :: (Monad mo, HasReps c) => c -> GGHandler sub master mo a
|
sendResponse :: (MonadIO mo, HasReps c) => c -> GGHandler sub master mo a
|
||||||
sendResponse = GHandler . lift . throwError . HCContent H.status200
|
sendResponse = liftIO . throwIO . HCContent H.status200
|
||||||
. chooseRep
|
. chooseRep
|
||||||
|
|
||||||
-- | Bypass remaining handler code and output the given content with the given
|
-- | Bypass remaining handler code and output the given content with the given
|
||||||
-- status code.
|
-- status code.
|
||||||
sendResponseStatus :: (Monad mo, HasReps c) => H.Status -> c -> GGHandler s m mo a
|
sendResponseStatus :: (MonadIO mo, HasReps c) => H.Status -> c -> GGHandler s m mo a
|
||||||
sendResponseStatus s = GHandler . lift . throwError . HCContent s
|
sendResponseStatus s = liftIO . throwIO . HCContent s
|
||||||
. chooseRep
|
. chooseRep
|
||||||
|
|
||||||
-- | Send a 201 "Created" response with the given route as the Location
|
-- | Send a 201 "Created" response with the given route as the Location
|
||||||
-- response header.
|
-- response header.
|
||||||
sendResponseCreated :: Monad mo => Route m -> GGHandler s m mo a
|
sendResponseCreated :: MonadIO mo => Route m -> GGHandler s m mo a
|
||||||
sendResponseCreated url = do
|
sendResponseCreated url = do
|
||||||
r <- getUrlRender
|
r <- getUrlRender
|
||||||
GHandler $ lift $ throwError $ HCCreated $ r url
|
liftIO . throwIO $ HCCreated $ r url
|
||||||
|
|
||||||
-- | Send a 'W.Response'. Please note: this function is rarely
|
-- | Send a 'W.Response'. Please note: this function is rarely
|
||||||
-- necessary, and will /disregard/ any changes to response headers and session
|
-- necessary, and will /disregard/ any changes to response headers and session
|
||||||
-- that you have already specified. This function short-circuits. It should be
|
-- 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,
|
-- considered only for very specific needs. If you are not sure if you need it,
|
||||||
-- you don't.
|
-- you don't.
|
||||||
sendWaiResponse :: Monad mo => W.Response -> GGHandler s m mo b
|
sendWaiResponse :: MonadIO mo => W.Response -> GGHandler s m mo b
|
||||||
sendWaiResponse = GHandler . lift . throwError . HCWai
|
sendWaiResponse = liftIO . throwIO . HCWai
|
||||||
|
|
||||||
-- | Return a 404 not found page. Also denotes no handler available.
|
-- | Return a 404 not found page. Also denotes no handler available.
|
||||||
notFound :: Failure ErrorResponse m => m a
|
notFound :: Failure ErrorResponse m => m a
|
||||||
notFound = failure NotFound
|
notFound = failure NotFound
|
||||||
|
|
||||||
-- | Return a 405 method not supported page.
|
-- | Return a 405 method not supported page.
|
||||||
badMethod :: Monad mo => GGHandler s m mo a
|
badMethod :: MonadIO mo => GGHandler s m mo a
|
||||||
badMethod = do
|
badMethod = do
|
||||||
w <- waiRequest
|
w <- waiRequest
|
||||||
failure $ BadMethod $ W.requestMethod w
|
failure $ BadMethod $ W.requestMethod w
|
||||||
@ -605,7 +617,7 @@ permissionDenied :: Failure ErrorResponse m => Text -> m a
|
|||||||
permissionDenied = failure . PermissionDenied
|
permissionDenied = failure . PermissionDenied
|
||||||
|
|
||||||
-- | Return a 403 permission denied page.
|
-- | Return a 403 permission denied page.
|
||||||
permissionDeniedI :: (RenderMessage y msg, Monad mo) => msg -> GGHandler s y mo a
|
permissionDeniedI :: (RenderMessage y msg, MonadIO mo) => msg -> GGHandler s y mo a
|
||||||
permissionDeniedI msg = do
|
permissionDeniedI msg = do
|
||||||
mr <- getMessageRender
|
mr <- getMessageRender
|
||||||
permissionDenied $ mr msg
|
permissionDenied $ mr msg
|
||||||
@ -615,14 +627,14 @@ invalidArgs :: Failure ErrorResponse m => [Text] -> m a
|
|||||||
invalidArgs = failure . InvalidArgs
|
invalidArgs = failure . InvalidArgs
|
||||||
|
|
||||||
-- | Return a 400 invalid arguments page.
|
-- | Return a 400 invalid arguments page.
|
||||||
invalidArgsI :: (RenderMessage y msg, Monad mo) => [msg] -> GGHandler s y mo a
|
invalidArgsI :: (RenderMessage y msg, MonadIO mo) => [msg] -> GGHandler s y mo a
|
||||||
invalidArgsI msg = do
|
invalidArgsI msg = do
|
||||||
mr <- getMessageRender
|
mr <- getMessageRender
|
||||||
invalidArgs $ map mr msg
|
invalidArgs $ map mr msg
|
||||||
|
|
||||||
------- Headers
|
------- Headers
|
||||||
-- | Set the cookie on the client.
|
-- | Set the cookie on the client.
|
||||||
setCookie :: Monad mo
|
setCookie :: MonadIO mo
|
||||||
=> Int -- ^ minutes to timeout
|
=> Int -- ^ minutes to timeout
|
||||||
-> H.Ascii -- ^ key
|
-> H.Ascii -- ^ key
|
||||||
-> H.Ascii -- ^ value
|
-> H.Ascii -- ^ value
|
||||||
@ -630,22 +642,22 @@ setCookie :: Monad mo
|
|||||||
setCookie a b = addHeader . AddCookie a b
|
setCookie a b = addHeader . AddCookie a b
|
||||||
|
|
||||||
-- | Unset the cookie on the client.
|
-- | Unset the cookie on the client.
|
||||||
deleteCookie :: Monad mo => H.Ascii -> GGHandler sub master mo ()
|
deleteCookie :: MonadIO mo => H.Ascii -> GGHandler sub master mo ()
|
||||||
deleteCookie = addHeader . DeleteCookie
|
deleteCookie = addHeader . DeleteCookie
|
||||||
|
|
||||||
-- | Set the language in the user session. Will show up in 'languages' on the
|
-- | Set the language in the user session. Will show up in 'languages' on the
|
||||||
-- next request.
|
-- next request.
|
||||||
setLanguage :: Monad mo => Text -> GGHandler sub master mo ()
|
setLanguage :: MonadIO mo => Text -> GGHandler sub master mo ()
|
||||||
setLanguage = setSession langKey
|
setLanguage = setSession langKey
|
||||||
|
|
||||||
-- | Set an arbitrary response header.
|
-- | Set an arbitrary response header.
|
||||||
setHeader :: Monad mo
|
setHeader :: MonadIO mo
|
||||||
=> CI H.Ascii -> H.Ascii -> GGHandler sub master mo ()
|
=> CI H.Ascii -> H.Ascii -> GGHandler sub master mo ()
|
||||||
setHeader a = addHeader . Header a
|
setHeader a = addHeader . Header a
|
||||||
|
|
||||||
-- | Set the Cache-Control header to indicate this response should be cached
|
-- | Set the Cache-Control header to indicate this response should be cached
|
||||||
-- for the given number of seconds.
|
-- for the given number of seconds.
|
||||||
cacheSeconds :: Monad mo => Int -> GGHandler s m mo ()
|
cacheSeconds :: MonadIO mo => Int -> GGHandler s m mo ()
|
||||||
cacheSeconds i = setHeader "Cache-Control" $ S8.pack $ concat
|
cacheSeconds i = setHeader "Cache-Control" $ S8.pack $ concat
|
||||||
[ "max-age="
|
[ "max-age="
|
||||||
, show i
|
, show i
|
||||||
@ -654,16 +666,16 @@ cacheSeconds i = setHeader "Cache-Control" $ S8.pack $ concat
|
|||||||
|
|
||||||
-- | Set the Expires header to some date in 2037. In other words, this content
|
-- | Set the Expires header to some date in 2037. In other words, this content
|
||||||
-- is never (realistically) expired.
|
-- is never (realistically) expired.
|
||||||
neverExpires :: Monad mo => GGHandler s m mo ()
|
neverExpires :: MonadIO mo => GGHandler s m mo ()
|
||||||
neverExpires = setHeader "Expires" "Thu, 31 Dec 2037 23:55:55 GMT"
|
neverExpires = setHeader "Expires" "Thu, 31 Dec 2037 23:55:55 GMT"
|
||||||
|
|
||||||
-- | Set an Expires header in the past, meaning this content should not be
|
-- | Set an Expires header in the past, meaning this content should not be
|
||||||
-- cached.
|
-- cached.
|
||||||
alreadyExpired :: Monad mo => GGHandler s m mo ()
|
alreadyExpired :: MonadIO mo => GGHandler s m mo ()
|
||||||
alreadyExpired = setHeader "Expires" "Thu, 01 Jan 1970 05:05:05 GMT"
|
alreadyExpired = setHeader "Expires" "Thu, 01 Jan 1970 05:05:05 GMT"
|
||||||
|
|
||||||
-- | Set an Expires header to the given date.
|
-- | Set an Expires header to the given date.
|
||||||
expiresAt :: Monad mo => UTCTime -> GGHandler s m mo ()
|
expiresAt :: MonadIO mo => UTCTime -> GGHandler s m mo ()
|
||||||
expiresAt = setHeader "Expires" . encodeUtf8 . formatRFC1123
|
expiresAt = setHeader "Expires" . encodeUtf8 . formatRFC1123
|
||||||
|
|
||||||
-- | Set a variable in the user's session.
|
-- | Set a variable in the user's session.
|
||||||
@ -671,22 +683,22 @@ expiresAt = setHeader "Expires" . encodeUtf8 . formatRFC1123
|
|||||||
-- The session is handled by the clientsession package: it sets an encrypted
|
-- The session is handled by the clientsession package: it sets an encrypted
|
||||||
-- and hashed cookie on the client. This ensures that all data is secure and
|
-- and hashed cookie on the client. This ensures that all data is secure and
|
||||||
-- not tampered with.
|
-- not tampered with.
|
||||||
setSession :: Monad mo
|
setSession :: MonadIO mo
|
||||||
=> Text -- ^ key
|
=> Text -- ^ key
|
||||||
-> Text -- ^ value
|
-> Text -- ^ value
|
||||||
-> GGHandler sub master mo ()
|
-> GGHandler sub master mo ()
|
||||||
setSession k = GHandler . lift . lift . lift . modify . modSession . Map.insert k
|
setSession k = modify . modSession . Map.insert k
|
||||||
|
|
||||||
-- | Unsets a session variable. See 'setSession'.
|
-- | Unsets a session variable. See 'setSession'.
|
||||||
deleteSession :: Monad mo => Text -> GGHandler sub master mo ()
|
deleteSession :: MonadIO mo => Text -> GGHandler sub master mo ()
|
||||||
deleteSession = GHandler . lift . lift . lift . modify . modSession . Map.delete
|
deleteSession = modify . modSession . Map.delete
|
||||||
|
|
||||||
modSession :: (SessionMap -> SessionMap) -> GHState -> GHState
|
modSession :: (SessionMap -> SessionMap) -> GHState -> GHState
|
||||||
modSession f x = x { ghsSession = f $ ghsSession x }
|
modSession f x = x { ghsSession = f $ ghsSession x }
|
||||||
|
|
||||||
-- | Internal use only, not to be confused with 'setHeader'.
|
-- | Internal use only, not to be confused with 'setHeader'.
|
||||||
addHeader :: Monad mo => Header -> GGHandler sub master mo ()
|
addHeader :: MonadIO mo => Header -> GGHandler sub master mo ()
|
||||||
addHeader = GHandler . lift . lift . tell . Endo . (:)
|
addHeader = tell . Endo . (:)
|
||||||
|
|
||||||
getStatus :: ErrorResponse -> H.Status
|
getStatus :: ErrorResponse -> H.Status
|
||||||
getStatus NotFound = H.status404
|
getStatus NotFound = H.status404
|
||||||
@ -708,17 +720,17 @@ data RedirectType = RedirectPermanent
|
|||||||
|
|
||||||
localNoCurrent :: Monad mo => GGHandler s m mo a -> GGHandler s m mo a
|
localNoCurrent :: Monad mo => GGHandler s m mo a -> GGHandler s m mo a
|
||||||
localNoCurrent =
|
localNoCurrent =
|
||||||
GHandler . local (\hd -> hd { handlerRoute = Nothing }) . unGHandler
|
local (\hd -> hd { handlerRoute = Nothing })
|
||||||
|
|
||||||
-- | Lookup for session data.
|
-- | Lookup for session data.
|
||||||
lookupSession :: Monad mo => Text -> GGHandler s m mo (Maybe Text)
|
lookupSession :: MonadIO mo => Text -> GGHandler s m mo (Maybe Text)
|
||||||
lookupSession n = GHandler $ do
|
lookupSession n = do
|
||||||
m <- liftM ghsSession $ lift $ lift $ lift get
|
m <- liftM ghsSession get
|
||||||
return $ Map.lookup n m
|
return $ Map.lookup n m
|
||||||
|
|
||||||
-- | Get all session variables.
|
-- | Get all session variables.
|
||||||
getSession :: Monad mo => GGHandler s m mo SessionMap
|
getSession :: MonadIO mo => GGHandler s m mo SessionMap
|
||||||
getSession = liftM ghsSession $ GHandler $ lift $ lift $ lift get
|
getSession = liftM ghsSession get
|
||||||
|
|
||||||
handlerToYAR :: (HasReps a, HasReps b)
|
handlerToYAR :: (HasReps a, HasReps b)
|
||||||
=> m -- ^ master site foundation
|
=> m -- ^ master site foundation
|
||||||
@ -808,8 +820,8 @@ headerToPair cp _ (DeleteCookie key) =
|
|||||||
headerToPair _ _ (Header key value) = (key, value)
|
headerToPair _ _ (Header key value) = (key, value)
|
||||||
|
|
||||||
-- | Get a unique identifier.
|
-- | Get a unique identifier.
|
||||||
newIdent :: Monad mo => GGHandler sub master mo String -- FIXME use Text
|
newIdent :: MonadIO mo => GGHandler sub master mo String -- FIXME use Text
|
||||||
newIdent = GHandler $ lift $ lift $ lift $ do
|
newIdent = do
|
||||||
x <- get
|
x <- get
|
||||||
let i' = ghsIdent x + 1
|
let i' = ghsIdent x + 1
|
||||||
put x { ghsIdent = i' }
|
put x { ghsIdent = i' }
|
||||||
@ -818,42 +830,7 @@ newIdent = GHandler $ lift $ lift $ lift $ do
|
|||||||
liftIOHandler :: MonadIO mo
|
liftIOHandler :: MonadIO mo
|
||||||
=> GGHandler sub master IO a
|
=> GGHandler sub master IO a
|
||||||
-> GGHandler sub master mo a
|
-> GGHandler sub master mo a
|
||||||
liftIOHandler m = GHandler $
|
liftIOHandler (ReaderT m) = ReaderT $ \r -> liftIO $ m r
|
||||||
ReaderT $ \r ->
|
|
||||||
ErrorT $
|
|
||||||
WriterT $
|
|
||||||
StateT $ \s ->
|
|
||||||
liftIO $ runGGHandler m r s
|
|
||||||
|
|
||||||
runGGHandler :: GGHandler sub master m a
|
|
||||||
-> HandlerData sub master
|
|
||||||
-> GHState
|
|
||||||
-> m ( ( Either HandlerContents a
|
|
||||||
, Endo [Header]
|
|
||||||
)
|
|
||||||
, GHState
|
|
||||||
)
|
|
||||||
runGGHandler m r s = runStateT
|
|
||||||
(runWriterT
|
|
||||||
(runErrorT
|
|
||||||
(runReaderT
|
|
||||||
(unGHandler m) r))) s
|
|
||||||
|
|
||||||
instance MonadTransControl (GGHandler s m) where
|
|
||||||
liftControl f =
|
|
||||||
GHandler $
|
|
||||||
liftControl $ \runRdr ->
|
|
||||||
liftControl $ \runErr ->
|
|
||||||
liftControl $ \runWrt ->
|
|
||||||
liftControl $ \runSt ->
|
|
||||||
f ( liftM ( GHandler
|
|
||||||
. join . lift
|
|
||||||
. join . lift
|
|
||||||
. join . lift
|
|
||||||
)
|
|
||||||
. runSt . runWrt . runErr . runRdr
|
|
||||||
. unGHandler
|
|
||||||
)
|
|
||||||
|
|
||||||
-- | Redirect to a POST resource.
|
-- | Redirect to a POST resource.
|
||||||
--
|
--
|
||||||
@ -861,7 +838,7 @@ instance MonadTransControl (GGHandler s m) where
|
|||||||
-- POST form, and some Javascript to automatically submit the form. This can be
|
-- 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
|
-- useful when you need to post a plain link somewhere that needs to cause
|
||||||
-- changes on the server.
|
-- changes on the server.
|
||||||
redirectToPost :: Monad mo => Route master -> GGHandler sub master mo a
|
redirectToPost :: MonadIO mo => Route master -> GGHandler sub master mo a
|
||||||
redirectToPost dest = hamletToRepHtml
|
redirectToPost dest = hamletToRepHtml
|
||||||
#if GHC7
|
#if GHC7
|
||||||
[hamlet|
|
[hamlet|
|
||||||
@ -902,3 +879,16 @@ getMessageRender = do
|
|||||||
m <- getYesod
|
m <- getYesod
|
||||||
l <- reqLangs `liftM` getRequest
|
l <- reqLangs `liftM` getRequest
|
||||||
return $ renderMessage m l
|
return $ renderMessage m l
|
||||||
|
|
||||||
|
cacheLookup :: MonadIO mo => CacheKey a -> GGHandler 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 k v = modify $ \gs ->
|
||||||
|
gs { ghsCache = Cache.insert k v $ ghsCache gs }
|
||||||
|
|
||||||
|
cacheDelete :: MonadIO mo => CacheKey a -> GGHandler sub master mo ()
|
||||||
|
cacheDelete k = modify $ \gs ->
|
||||||
|
gs { ghsCache = Cache.delete k $ ghsCache gs }
|
||||||
|
|||||||
@ -29,7 +29,6 @@ module Yesod.Internal
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import Text.Hamlet (HtmlUrl, hamlet, Html)
|
import Text.Hamlet (HtmlUrl, hamlet, Html)
|
||||||
import Text.Cassius (CssUrl)
|
|
||||||
import Text.Julius (JavascriptUrl)
|
import Text.Julius (JavascriptUrl)
|
||||||
import Data.Monoid (Monoid (..), Last)
|
import Data.Monoid (Monoid (..), Last)
|
||||||
import Data.List (nub)
|
import Data.List (nub)
|
||||||
@ -44,6 +43,7 @@ import qualified Network.HTTP.Types as A
|
|||||||
import Data.CaseInsensitive (CI)
|
import Data.CaseInsensitive (CI)
|
||||||
import Data.String (IsString)
|
import Data.String (IsString)
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
|
import Data.Text.Lazy.Builder (Builder)
|
||||||
|
|
||||||
#if GHC7
|
#if GHC7
|
||||||
#define HAMLET hamlet
|
#define HAMLET hamlet
|
||||||
@ -107,12 +107,14 @@ nonceKey = "_NONCE"
|
|||||||
sessionName :: IsString a => a
|
sessionName :: IsString a => a
|
||||||
sessionName = "_SESSION"
|
sessionName = "_SESSION"
|
||||||
|
|
||||||
|
type CssBuilderUrl a = (a -> [(Text, Text)] -> Text) -> Builder
|
||||||
|
|
||||||
data GWData a = GWData
|
data GWData a = GWData
|
||||||
!(Body a)
|
!(Body a)
|
||||||
!(Last Title)
|
!(Last Title)
|
||||||
!(UniqueList (Script a))
|
!(UniqueList (Script a))
|
||||||
!(UniqueList (Stylesheet a))
|
!(UniqueList (Stylesheet a))
|
||||||
!(Map.Map (Maybe Text) (CssUrl a)) -- media type
|
!(Map.Map (Maybe Text) (CssBuilderUrl a)) -- media type
|
||||||
!(Maybe (JavascriptUrl a))
|
!(Maybe (JavascriptUrl a))
|
||||||
!(Head a)
|
!(Head a)
|
||||||
instance Monoid (GWData a) where
|
instance Monoid (GWData a) where
|
||||||
|
|||||||
38
yesod-core/Yesod/Internal/Cache.hs
Normal file
38
yesod-core/Yesod/Internal/Cache.hs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
module Yesod.Internal.Cache
|
||||||
|
( Cache
|
||||||
|
, CacheKey
|
||||||
|
, mkCacheKey
|
||||||
|
, lookup
|
||||||
|
, insert
|
||||||
|
, delete
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Prelude hiding (lookup)
|
||||||
|
import qualified Data.IntMap as Map
|
||||||
|
import Language.Haskell.TH.Syntax (Q, Exp, runIO, Exp (LitE), Lit (IntegerL))
|
||||||
|
import Language.Haskell.TH (appE)
|
||||||
|
import Data.Unique (hashUnique, newUnique)
|
||||||
|
import GHC.Exts (Any)
|
||||||
|
import Unsafe.Coerce (unsafeCoerce)
|
||||||
|
import Data.Monoid (Monoid)
|
||||||
|
import Control.Applicative ((<$>))
|
||||||
|
|
||||||
|
newtype Cache = Cache (Map.IntMap Any)
|
||||||
|
deriving Monoid
|
||||||
|
|
||||||
|
newtype CacheKey a = CacheKey Int
|
||||||
|
|
||||||
|
-- | Generate a new 'CacheKey'. Be sure to give a full type signature.
|
||||||
|
mkCacheKey :: Q Exp
|
||||||
|
mkCacheKey = [|CacheKey|] `appE` (LitE . IntegerL . fromIntegral . hashUnique <$> runIO newUnique)
|
||||||
|
|
||||||
|
lookup :: CacheKey a -> Cache -> Maybe a
|
||||||
|
lookup (CacheKey i) (Cache m) = unsafeCoerce <$> Map.lookup i m
|
||||||
|
|
||||||
|
insert :: CacheKey a -> a -> Cache -> Cache
|
||||||
|
insert (CacheKey k) v (Cache m) = Cache (Map.insert k (unsafeCoerce v) m)
|
||||||
|
|
||||||
|
delete :: CacheKey a -> Cache -> Cache
|
||||||
|
delete (CacheKey k) (Cache m) = Cache (Map.delete k m)
|
||||||
@ -35,8 +35,6 @@ import Yesod.Handler
|
|||||||
|
|
||||||
import Control.Arrow ((***))
|
import Control.Arrow ((***))
|
||||||
import Control.Monad (forM)
|
import Control.Monad (forM)
|
||||||
import qualified Paths_yesod_core
|
|
||||||
import Data.Version (showVersion)
|
|
||||||
import Yesod.Widget
|
import Yesod.Widget
|
||||||
import Yesod.Request
|
import Yesod.Request
|
||||||
import qualified Network.Wai as W
|
import qualified Network.Wai as W
|
||||||
@ -48,11 +46,10 @@ import qualified Web.ClientSession as CS
|
|||||||
import qualified Data.ByteString.Char8 as S8
|
import qualified Data.ByteString.Char8 as S8
|
||||||
import qualified Data.ByteString.Lazy as L
|
import qualified Data.ByteString.Lazy as L
|
||||||
import Data.Monoid
|
import Data.Monoid
|
||||||
import Control.Monad.Trans.RWS
|
import Control.Monad.Trans.Writer (runWriterT)
|
||||||
import Text.Hamlet
|
import Text.Hamlet
|
||||||
import Text.Cassius
|
|
||||||
import Text.Julius
|
import Text.Julius
|
||||||
import Text.Blaze ((!), customAttribute, textTag, toValue)
|
import Text.Blaze ((!), customAttribute, textTag, toValue, unsafeLazyByteString)
|
||||||
import qualified Text.Blaze.Html5 as TBH
|
import qualified Text.Blaze.Html5 as TBH
|
||||||
import Data.Text.Lazy.Builder (toLazyText)
|
import Data.Text.Lazy.Builder (toLazyText)
|
||||||
import Data.Text.Lazy.Encoding (encodeUtf8)
|
import Data.Text.Lazy.Encoding (encodeUtf8)
|
||||||
@ -75,6 +72,20 @@ import qualified Data.Text.Lazy.IO
|
|||||||
import qualified Data.Text.Lazy.Builder as TB
|
import qualified Data.Text.Lazy.Builder as TB
|
||||||
import Language.Haskell.TH.Syntax (Loc (..), Lift (..))
|
import Language.Haskell.TH.Syntax (Loc (..), Lift (..))
|
||||||
import Text.Blaze (preEscapedLazyText)
|
import Text.Blaze (preEscapedLazyText)
|
||||||
|
import Data.Aeson (Value (Array, String))
|
||||||
|
import Data.Aeson.Encode (encode)
|
||||||
|
import qualified Data.Vector as Vector
|
||||||
|
|
||||||
|
-- mega repo can't access this
|
||||||
|
#ifndef MEGA
|
||||||
|
import qualified Paths_yesod_core
|
||||||
|
import Data.Version (showVersion)
|
||||||
|
yesodVersion :: String
|
||||||
|
yesodVersion = showVersion Paths_yesod_core.version
|
||||||
|
#else
|
||||||
|
yesodVersion :: String
|
||||||
|
yesodVersion = "0.9.3.2"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if GHC7
|
#if GHC7
|
||||||
#define HAMLET hamlet
|
#define HAMLET hamlet
|
||||||
@ -159,9 +170,9 @@ class RenderRoute (Route a) => Yesod a where
|
|||||||
|
|
||||||
-- | Determine if a request is authorized or not.
|
-- | Determine if a request is authorized or not.
|
||||||
--
|
--
|
||||||
-- Return 'Nothing' is the request is authorized, 'Just' a message if
|
-- Return 'Authorized' if the request is authorized,
|
||||||
-- unauthorized. If authentication is required, you should use a redirect;
|
-- 'Unauthorized' a message if unauthorized.
|
||||||
-- the Auth helper provides this functionality automatically.
|
-- If authentication is required, return 'AuthenticationRequired'.
|
||||||
isAuthorized :: Route a
|
isAuthorized :: Route a
|
||||||
-> Bool -- ^ is this a write request?
|
-> Bool -- ^ is this a write request?
|
||||||
-> GHandler s a AuthResult
|
-> GHandler s a AuthResult
|
||||||
@ -264,6 +275,11 @@ class RenderRoute (Route a) => Yesod a where
|
|||||||
gzipCompressFiles :: a -> Bool
|
gzipCompressFiles :: a -> Bool
|
||||||
gzipCompressFiles _ = False
|
gzipCompressFiles _ = False
|
||||||
|
|
||||||
|
-- | Location of yepnope.js, if any. If one is provided, then all
|
||||||
|
-- Javascript files will be loaded asynchronously.
|
||||||
|
yepnopeJs :: a -> Maybe (Either Text (Route a))
|
||||||
|
yepnopeJs _ = Nothing
|
||||||
|
|
||||||
messageLoggerHandler :: (Yesod m, MonadIO mo)
|
messageLoggerHandler :: (Yesod m, MonadIO mo)
|
||||||
=> Loc -> LogLevel -> Text -> GGHandler s m mo ()
|
=> Loc -> LogLevel -> Text -> GGHandler s m mo ()
|
||||||
messageLoggerHandler loc level msg = do
|
messageLoggerHandler loc level msg = do
|
||||||
@ -327,12 +343,12 @@ defaultYesodRunner _ m toMaster _ murl _ req
|
|||||||
[] -> Nothing
|
[] -> Nothing
|
||||||
(x, _):_ -> Just x
|
(x, _):_ -> Just x
|
||||||
defaultYesodRunner s master toMasterRoute mkey murl handler req = do
|
defaultYesodRunner s master toMasterRoute mkey murl handler req = do
|
||||||
now <- liftIO getCurrentTime
|
now <- {-# SCC "getCurrentTime" #-} liftIO getCurrentTime
|
||||||
let getExpires m = fromIntegral (m * 60) `addUTCTime` now
|
let getExpires m = {-# SCC "getExpires" #-} fromIntegral (m * 60) `addUTCTime` now
|
||||||
let exp' = getExpires $ clientSessionDuration master
|
let exp' = {-# SCC "exp'" #-} getExpires $ clientSessionDuration master
|
||||||
let rh = takeWhile (/= ':') $ show $ W.remoteHost req
|
let rh = {-# SCC "rh" #-} takeWhile (/= ':') $ show $ W.remoteHost req
|
||||||
let host = if sessionIpAddress master then S8.pack rh else ""
|
let host = if sessionIpAddress master then S8.pack rh else ""
|
||||||
let session' =
|
let session' = {-# SCC "session'" #-}
|
||||||
case mkey of
|
case mkey of
|
||||||
Nothing -> []
|
Nothing -> []
|
||||||
Just key -> fromMaybe [] $ do
|
Just key -> fromMaybe [] $ do
|
||||||
@ -340,7 +356,7 @@ defaultYesodRunner s master toMasterRoute mkey murl handler req = do
|
|||||||
val <- lookup sessionName $ parseCookies raw
|
val <- lookup sessionName $ parseCookies raw
|
||||||
decodeSession key now host val
|
decodeSession key now host val
|
||||||
rr <- liftIO $ parseWaiRequest req session' mkey
|
rr <- liftIO $ parseWaiRequest req session' mkey
|
||||||
let h = do
|
let h = {-# SCC "h" #-} do
|
||||||
case murl of
|
case murl of
|
||||||
Nothing -> handler
|
Nothing -> handler
|
||||||
Just url -> do
|
Just url -> do
|
||||||
@ -361,7 +377,8 @@ defaultYesodRunner s master toMasterRoute mkey murl handler req = do
|
|||||||
$ filter (\(x, _) -> x /= nonceKey) session'
|
$ filter (\(x, _) -> x /= nonceKey) session'
|
||||||
yar <- handlerToYAR master s toMasterRoute (yesodRender master) errorHandler rr murl sessionMap h
|
yar <- handlerToYAR master s toMasterRoute (yesodRender master) errorHandler rr murl sessionMap h
|
||||||
let mnonce = reqNonce rr
|
let mnonce = reqNonce rr
|
||||||
iv <- liftIO CS.randomIV
|
-- FIXME should we be caching this IV value and reusing it for efficiency?
|
||||||
|
iv <- {-# SCC "iv" #-} maybe (return $ error "Should not be used") (const $ liftIO CS.randomIV) mkey
|
||||||
return $ yarToResponse (hr iv mnonce getExpires host exp') yar
|
return $ yarToResponse (hr iv mnonce getExpires host exp') yar
|
||||||
where
|
where
|
||||||
hr iv mnonce getExpires host exp' hs ct sm =
|
hr iv mnonce getExpires host exp' hs ct sm =
|
||||||
@ -472,18 +489,22 @@ maybeAuthorized r isWrite = do
|
|||||||
x <- isAuthorized r isWrite
|
x <- isAuthorized r isWrite
|
||||||
return $ if x == Authorized then Just r else Nothing
|
return $ if x == Authorized then Just r else Nothing
|
||||||
|
|
||||||
|
jsToHtml :: Javascript -> Html
|
||||||
|
jsToHtml (Javascript b) = preEscapedLazyText $ toLazyText b
|
||||||
|
|
||||||
|
jelper :: JavascriptUrl url -> HtmlUrl url
|
||||||
|
jelper = fmap jsToHtml
|
||||||
|
|
||||||
-- | Convert a widget to a 'PageContent'.
|
-- | Convert a widget to a 'PageContent'.
|
||||||
widgetToPageContent :: (Eq (Route master), Yesod master)
|
widgetToPageContent :: (Eq (Route master), Yesod master)
|
||||||
=> GWidget sub master ()
|
=> GWidget sub master ()
|
||||||
-> GHandler sub master (PageContent (Route master))
|
-> GHandler sub master (PageContent (Route master))
|
||||||
widgetToPageContent (GWidget w) = do
|
widgetToPageContent (GWidget w) = do
|
||||||
((), _, GWData (Body body) (Last mTitle) scripts' stylesheets' style jscript (Head head')) <- runRWST w () 0
|
master <- getYesod
|
||||||
|
((), GWData (Body body) (Last mTitle) scripts' stylesheets' style jscript (Head head')) <- runWriterT w
|
||||||
let title = maybe mempty unTitle mTitle
|
let title = maybe mempty unTitle mTitle
|
||||||
let scripts = runUniqueList scripts'
|
let scripts = runUniqueList scripts'
|
||||||
let stylesheets = runUniqueList stylesheets'
|
let stylesheets = runUniqueList stylesheets'
|
||||||
let jsToHtml (Javascript b) = preEscapedLazyText $ toLazyText b
|
|
||||||
jelper :: JavascriptUrl url -> HtmlUrl url
|
|
||||||
jelper = fmap jsToHtml
|
|
||||||
|
|
||||||
render <- getUrlRenderParams
|
render <- getUrlRenderParams
|
||||||
let renderLoc x =
|
let renderLoc x =
|
||||||
@ -492,7 +513,7 @@ widgetToPageContent (GWidget w) = do
|
|||||||
Just (Left s) -> Just s
|
Just (Left s) -> Just s
|
||||||
Just (Right (u, p)) -> Just $ render u p
|
Just (Right (u, p)) -> Just $ render u p
|
||||||
css <- forM (Map.toList style) $ \(mmedia, content) -> do
|
css <- forM (Map.toList style) $ \(mmedia, content) -> do
|
||||||
let rendered = renderCssUrl render content
|
let rendered = toLazyText $ content render
|
||||||
x <- addStaticContent "css" "text/css; charset=utf-8"
|
x <- addStaticContent "css" "text/css; charset=utf-8"
|
||||||
$ encodeUtf8 rendered
|
$ encodeUtf8 rendered
|
||||||
return (mmedia,
|
return (mmedia,
|
||||||
@ -536,19 +557,54 @@ $forall s <- css
|
|||||||
<style media=#{media}>#{content}
|
<style media=#{media}>#{content}
|
||||||
$nothing
|
$nothing
|
||||||
<style>#{content}
|
<style>#{content}
|
||||||
$forall s <- scripts
|
$maybe _ <- yepnopeJs master
|
||||||
^{mkScriptTag s}
|
$nothing
|
||||||
$maybe j <- jscript
|
$forall s <- scripts
|
||||||
$maybe s <- jsLoc
|
^{mkScriptTag s}
|
||||||
<script src="#{s}">
|
$maybe j <- jscript
|
||||||
$nothing
|
$maybe s <- jsLoc
|
||||||
<script>^{jelper j}
|
<script src="#{s}">
|
||||||
|
$nothing
|
||||||
|
<script>^{jelper j}
|
||||||
\^{head'}
|
\^{head'}
|
||||||
|]
|
|]
|
||||||
return $ PageContent title head'' body
|
let (mcomplete, ynscripts) = ynHelper render scripts jscript jsLoc
|
||||||
|
let bodyYN = [HAMLET|
|
||||||
|
^{body}
|
||||||
|
$maybe eyn <- yepnopeJs master
|
||||||
|
$maybe yn <- left eyn
|
||||||
|
<script src=#{yn}>
|
||||||
|
$maybe yn <- right eyn
|
||||||
|
<script src=@{yn}>
|
||||||
|
$maybe complete <- mcomplete
|
||||||
|
<script>yepnope({load:#{ynscripts},complete:function(){^{complete}}})
|
||||||
|
$nothing
|
||||||
|
<script>yepnope({load:#{ynscripts}})
|
||||||
|
|]
|
||||||
|
return $ PageContent title head'' bodyYN
|
||||||
|
|
||||||
yesodVersion :: String
|
ynHelper :: (url -> [x] -> Text)
|
||||||
yesodVersion = showVersion Paths_yesod_core.version
|
-> [Script (url)]
|
||||||
|
-> Maybe (JavascriptUrl (url))
|
||||||
|
-> Maybe Text
|
||||||
|
-> (Maybe (HtmlUrl (url)), Html)
|
||||||
|
ynHelper render scripts jscript jsLoc =
|
||||||
|
(mcomplete, unsafeLazyByteString $ encode $ Array $ Vector.fromList $ map String scripts'')
|
||||||
|
where
|
||||||
|
scripts' = map goScript scripts
|
||||||
|
scripts'' =
|
||||||
|
case jsLoc of
|
||||||
|
Just s -> scripts' ++ [s]
|
||||||
|
Nothing -> scripts'
|
||||||
|
goScript (Script (Local url) _) = render url []
|
||||||
|
goScript (Script (Remote s) _) = s
|
||||||
|
mcomplete =
|
||||||
|
case jsLoc of
|
||||||
|
Just{} -> Nothing
|
||||||
|
Nothing ->
|
||||||
|
case jscript of
|
||||||
|
Nothing -> Nothing
|
||||||
|
Just j -> Just $ jelper j
|
||||||
|
|
||||||
yesodRender :: Yesod y
|
yesodRender :: Yesod y
|
||||||
=> y
|
=> y
|
||||||
|
|||||||
@ -60,9 +60,9 @@ parseWaiRequest' env session' key' gen = Request gets'' cookies' env langs' nonc
|
|||||||
, lookup langKey cookies' -- Cookie _LANG
|
, lookup langKey cookies' -- Cookie _LANG
|
||||||
, lookup langKey session' -- Session _LANG
|
, lookup langKey session' -- Session _LANG
|
||||||
] ++ langs -- Accept-Language(s)
|
] ++ langs -- Accept-Language(s)
|
||||||
-- If the session is not secure a nonce should not be
|
-- If sessions are disabled nonces should not be used (any
|
||||||
-- used (any nonce present in the session is ignored).
|
-- nonceKey present in the session is ignored). If sessions
|
||||||
-- If a secure session has no nonceKey a new one is
|
-- are enabled and a session has no nonceKey a new one is
|
||||||
-- generated.
|
-- generated.
|
||||||
nonce = case (key', lookup nonceKey session') of
|
nonce = case (key', lookup nonceKey session') of
|
||||||
(Nothing, _) -> Nothing
|
(Nothing, _) -> Nothing
|
||||||
@ -75,7 +75,10 @@ parseWaiRequest' env session' key' gen = Request gets'' cookies' env langs' nonc
|
|||||||
randomString :: RandomGen g => Int -> g -> String
|
randomString :: RandomGen g => Int -> g -> String
|
||||||
randomString len = take len . map toChar . randomRs (0, 61)
|
randomString len = take len . map toChar . randomRs (0, 61)
|
||||||
where
|
where
|
||||||
toChar i = (['A'..'Z'] ++ ['a'..'z'] ++ ['0'..'9']) !! i
|
toChar i
|
||||||
|
| i < 26 = toEnum $ i + fromEnum 'A'
|
||||||
|
| i < 52 = toEnum $ i + fromEnum 'a' - 26
|
||||||
|
| otherwise = toEnum $ i + fromEnum '0' - 52
|
||||||
|
|
||||||
-- | A tuple containing both the POST parameters and submitted files.
|
-- | A tuple containing both the POST parameters and submitted files.
|
||||||
type RequestBodyContents =
|
type RequestBodyContents =
|
||||||
|
|||||||
@ -331,18 +331,24 @@ pieceFromString ('#':x) = SinglePiece x
|
|||||||
pieceFromString ('*':x) = MultiPiece x
|
pieceFromString ('*':x) = MultiPiece x
|
||||||
pieceFromString x = StaticPiece x
|
pieceFromString x = StaticPiece x
|
||||||
|
|
||||||
|
-- n^2, should be a way to speed it up
|
||||||
findOverlaps :: [Resource] -> [(Resource, Resource)]
|
findOverlaps :: [Resource] -> [(Resource, Resource)]
|
||||||
findOverlaps = gos . map justPieces
|
findOverlaps = go . map justPieces
|
||||||
where
|
where
|
||||||
|
justPieces :: Resource -> ([Piece], Resource)
|
||||||
justPieces r@(Resource _ ps _) = (ps, r)
|
justPieces r@(Resource _ ps _) = (ps, r)
|
||||||
gos [] = []
|
|
||||||
gos (x:xs) = mapMaybe (go x) xs ++ gos xs
|
go [] = []
|
||||||
go (StaticPiece x:xs, xr) (StaticPiece y:ys, yr)
|
go (x:xs) = mapMaybe (mOverlap x) xs ++ go xs
|
||||||
| x == y = go (xs, xr) (ys, yr)
|
|
||||||
|
mOverlap :: ([Piece], Resource) -> ([Piece], Resource) ->
|
||||||
|
Maybe (Resource, Resource)
|
||||||
|
mOverlap (StaticPiece x:xs, xr) (StaticPiece y:ys, yr)
|
||||||
|
| x == y = mOverlap (xs, xr) (ys, yr)
|
||||||
| otherwise = Nothing
|
| otherwise = Nothing
|
||||||
go (MultiPiece _:_, xr) (_, yr) = Just (xr, yr)
|
mOverlap (MultiPiece _:_, xr) (_, yr) = Just (xr, yr)
|
||||||
go (_, xr) (MultiPiece _:_, yr) = Just (xr, yr)
|
mOverlap (_, xr) (MultiPiece _:_, yr) = Just (xr, yr)
|
||||||
go ([], xr) ([], yr) = Just (xr, yr)
|
mOverlap ([], xr) ([], yr) = Just (xr, yr)
|
||||||
go ([], _) (_, _) = Nothing
|
mOverlap ([], _) (_, _) = Nothing
|
||||||
go (_, _) ([], _) = Nothing
|
mOverlap (_, _) ([], _) = Nothing
|
||||||
go (_:xs, xr) (_:ys, yr) = go (xs, xr) (ys, yr)
|
mOverlap (_:xs, xr) (_:ys, yr) = mOverlap (xs, xr) (ys, yr)
|
||||||
|
|||||||
@ -6,6 +6,22 @@
|
|||||||
--
|
--
|
||||||
module Yesod.Internal.TestApi
|
module Yesod.Internal.TestApi
|
||||||
( randomString, parseWaiRequest'
|
( randomString, parseWaiRequest'
|
||||||
|
, catchIter
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Yesod.Internal.Request (randomString, parseWaiRequest')
|
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
|
||||||
|
|||||||
@ -1,259 +1,5 @@
|
|||||||
{-# LANGUAGE TemplateHaskell #-}
|
|
||||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
|
||||||
{-# LANGUAGE TypeSynonymInstances #-}
|
|
||||||
{-# LANGUAGE ExistentialQuantification #-}
|
|
||||||
module Yesod.Message
|
module Yesod.Message
|
||||||
( mkMessage
|
( module Text.Shakespeare.I18N
|
||||||
, RenderMessage (..)
|
|
||||||
, ToMessage (..)
|
|
||||||
, SomeMessage (..)
|
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Language.Haskell.TH.Syntax
|
import Text.Shakespeare.I18N
|
||||||
import Data.Text (Text, pack, unpack)
|
|
||||||
import System.Directory
|
|
||||||
import Data.Maybe (catMaybes)
|
|
||||||
import Data.List (isSuffixOf, sortBy, foldl')
|
|
||||||
import qualified Data.ByteString as S
|
|
||||||
import Data.Text.Encoding (decodeUtf8)
|
|
||||||
import Data.Char (isSpace, toLower, toUpper)
|
|
||||||
import Data.Ord (comparing)
|
|
||||||
import Text.Shakespeare.Base (Deref (..), Ident (..), parseHash, derefToExp)
|
|
||||||
import Text.ParserCombinators.Parsec (parse, many, eof, many1, noneOf, (<|>))
|
|
||||||
import Control.Arrow ((***))
|
|
||||||
import Data.Monoid (mempty, mappend)
|
|
||||||
import qualified Data.Text as T
|
|
||||||
import Data.String (IsString (fromString))
|
|
||||||
|
|
||||||
class ToMessage a where
|
|
||||||
toMessage :: a -> Text
|
|
||||||
instance ToMessage Text where
|
|
||||||
toMessage = id
|
|
||||||
instance ToMessage String where
|
|
||||||
toMessage = Data.Text.pack
|
|
||||||
|
|
||||||
class RenderMessage master message where
|
|
||||||
renderMessage :: master
|
|
||||||
-> [Text] -- ^ languages
|
|
||||||
-> message
|
|
||||||
-> Text
|
|
||||||
|
|
||||||
instance RenderMessage master Text where
|
|
||||||
renderMessage _ _ = id
|
|
||||||
|
|
||||||
type Lang = Text
|
|
||||||
|
|
||||||
mkMessage :: String
|
|
||||||
-> FilePath
|
|
||||||
-> Lang
|
|
||||||
-> Q [Dec]
|
|
||||||
mkMessage dt folder lang = do
|
|
||||||
files <- qRunIO $ getDirectoryContents folder
|
|
||||||
contents <- qRunIO $ fmap catMaybes $ mapM (loadLang folder) files
|
|
||||||
sdef <-
|
|
||||||
case lookup lang contents of
|
|
||||||
Nothing -> error $ "Did not find main language file: " ++ unpack lang
|
|
||||||
Just def -> toSDefs def
|
|
||||||
mapM_ (checkDef sdef) $ map snd contents
|
|
||||||
let dt' = ConT $ mkName dt
|
|
||||||
let mname = mkName $ dt ++ "Message"
|
|
||||||
c1 <- fmap concat $ mapM (toClauses dt) contents
|
|
||||||
c2 <- mapM (sToClause dt) sdef
|
|
||||||
c3 <- defClause
|
|
||||||
return
|
|
||||||
[ DataD [] mname [] (map (toCon dt) sdef) []
|
|
||||||
, InstanceD
|
|
||||||
[]
|
|
||||||
(ConT ''RenderMessage `AppT` dt' `AppT` ConT mname)
|
|
||||||
[ FunD (mkName "renderMessage") $ c1 ++ c2 ++ [c3]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
toClauses :: String -> (Lang, [Def]) -> Q [Clause]
|
|
||||||
toClauses dt (lang, defs) =
|
|
||||||
mapM go defs
|
|
||||||
where
|
|
||||||
go def = do
|
|
||||||
a <- newName "lang"
|
|
||||||
(pat, bod) <- mkBody dt (constr def) (map fst $ vars def) (content def)
|
|
||||||
guard <- fmap NormalG [|$(return $ VarE a) == pack $(lift $ unpack lang)|]
|
|
||||||
return $ Clause
|
|
||||||
[WildP, ConP (mkName ":") [VarP a, WildP], pat]
|
|
||||||
(GuardedB [(guard, bod)])
|
|
||||||
[]
|
|
||||||
|
|
||||||
mkBody :: String -- ^ datatype
|
|
||||||
-> String -- ^ constructor
|
|
||||||
-> [String] -- ^ variable names
|
|
||||||
-> [Content]
|
|
||||||
-> Q (Pat, Exp)
|
|
||||||
mkBody dt cs vs ct = do
|
|
||||||
vp <- mapM go vs
|
|
||||||
let pat = RecP (mkName $ "Msg" ++ cs) (map (varName dt *** VarP) vp)
|
|
||||||
let ct' = map (fixVars vp) ct
|
|
||||||
pack' <- [|Data.Text.pack|]
|
|
||||||
tomsg <- [|toMessage|]
|
|
||||||
let ct'' = map (toH pack' tomsg) ct'
|
|
||||||
mapp <- [|mappend|]
|
|
||||||
let app a b = InfixE (Just a) mapp (Just b)
|
|
||||||
e <-
|
|
||||||
case ct'' of
|
|
||||||
[] -> [|mempty|]
|
|
||||||
[x] -> return x
|
|
||||||
(x:xs) -> return $ foldl' app x xs
|
|
||||||
return (pat, e)
|
|
||||||
where
|
|
||||||
toH pack' _ (Raw s) = pack' `AppE` SigE (LitE (StringL s)) (ConT ''String)
|
|
||||||
toH _ tomsg (Var d) = tomsg `AppE` derefToExp [] d
|
|
||||||
go x = do
|
|
||||||
let y = mkName $ '_' : x
|
|
||||||
return (x, y)
|
|
||||||
fixVars vp (Var d) = Var $ fixDeref vp d
|
|
||||||
fixVars _ (Raw s) = Raw s
|
|
||||||
fixDeref vp (DerefIdent (Ident i)) = DerefIdent $ Ident $ fixIdent vp i
|
|
||||||
fixDeref vp (DerefBranch a b) = DerefBranch (fixDeref vp a) (fixDeref vp b)
|
|
||||||
fixDeref _ d = d
|
|
||||||
fixIdent vp i =
|
|
||||||
case lookup i vp of
|
|
||||||
Nothing -> i
|
|
||||||
Just y -> nameBase y
|
|
||||||
|
|
||||||
sToClause :: String -> SDef -> Q Clause
|
|
||||||
sToClause dt sdef = do
|
|
||||||
(pat, bod) <- mkBody dt (sconstr sdef) (map fst $ svars sdef) (scontent sdef)
|
|
||||||
return $ Clause
|
|
||||||
[WildP, ConP (mkName "[]") [], pat]
|
|
||||||
(NormalB bod)
|
|
||||||
[]
|
|
||||||
|
|
||||||
defClause :: Q Clause
|
|
||||||
defClause = do
|
|
||||||
a <- newName "sub"
|
|
||||||
c <- newName "langs"
|
|
||||||
d <- newName "msg"
|
|
||||||
rm <- [|renderMessage|]
|
|
||||||
return $ Clause
|
|
||||||
[VarP a, ConP (mkName ":") [WildP, VarP c], VarP d]
|
|
||||||
(NormalB $ rm `AppE` VarE a `AppE` VarE c `AppE` VarE d)
|
|
||||||
[]
|
|
||||||
|
|
||||||
toCon :: String -> SDef -> Con
|
|
||||||
toCon dt (SDef c vs _) =
|
|
||||||
RecC (mkName $ "Msg" ++ c) $ map go vs
|
|
||||||
where
|
|
||||||
go (n, t) = (varName dt n, NotStrict, ConT $ mkName t)
|
|
||||||
|
|
||||||
varName :: String -> String -> Name
|
|
||||||
varName a y =
|
|
||||||
mkName $ concat [lower a, "Message", upper y]
|
|
||||||
where
|
|
||||||
lower (x:xs) = toLower x : xs
|
|
||||||
lower [] = []
|
|
||||||
upper (x:xs) = toUpper x : xs
|
|
||||||
upper [] = []
|
|
||||||
|
|
||||||
checkDef :: [SDef] -> [Def] -> Q ()
|
|
||||||
checkDef x y =
|
|
||||||
go (sortBy (comparing sconstr) x) (sortBy (comparing constr) y)
|
|
||||||
where
|
|
||||||
go _ [] = return ()
|
|
||||||
go [] (b:_) = error $ "Extra message constructor: " ++ constr b
|
|
||||||
go (a:as) (b:bs)
|
|
||||||
| sconstr a < constr b = go as (b:bs)
|
|
||||||
| sconstr a > constr b = error $ "Extra message constructor: " ++ constr b
|
|
||||||
| otherwise = do
|
|
||||||
go' (svars a) (vars b)
|
|
||||||
go as bs
|
|
||||||
go' ((an, at):as) ((bn, mbt):bs)
|
|
||||||
| an /= bn = error "Mismatched variable names"
|
|
||||||
| otherwise =
|
|
||||||
case mbt of
|
|
||||||
Nothing -> go' as bs
|
|
||||||
Just bt
|
|
||||||
| at == bt -> go' as bs
|
|
||||||
| otherwise -> error "Mismatched variable types"
|
|
||||||
go' [] [] = return ()
|
|
||||||
go' _ _ = error "Mistmached variable count"
|
|
||||||
|
|
||||||
toSDefs :: [Def] -> Q [SDef]
|
|
||||||
toSDefs = mapM toSDef
|
|
||||||
|
|
||||||
toSDef :: Def -> Q SDef
|
|
||||||
toSDef d = do
|
|
||||||
vars' <- mapM go $ vars d
|
|
||||||
return $ SDef (constr d) vars' (content d)
|
|
||||||
where
|
|
||||||
go (a, Just b) = return (a, b)
|
|
||||||
go (a, Nothing) = error $ "Main language missing type for " ++ show (constr d, a)
|
|
||||||
|
|
||||||
data SDef = SDef
|
|
||||||
{ sconstr :: String
|
|
||||||
, svars :: [(String, String)]
|
|
||||||
, scontent :: [Content]
|
|
||||||
}
|
|
||||||
|
|
||||||
data Def = Def
|
|
||||||
{ constr :: String
|
|
||||||
, vars :: [(String, Maybe String)]
|
|
||||||
, content :: [Content]
|
|
||||||
}
|
|
||||||
|
|
||||||
loadLang :: FilePath -> FilePath -> IO (Maybe (Lang, [Def]))
|
|
||||||
loadLang folder file = do
|
|
||||||
let file' = folder ++ '/' : file
|
|
||||||
e <- doesFileExist file'
|
|
||||||
if e && ".msg" `isSuffixOf` file
|
|
||||||
then do
|
|
||||||
let lang = pack $ reverse $ drop 4 $ reverse file
|
|
||||||
bs <- S.readFile file'
|
|
||||||
let s = unpack $ decodeUtf8 bs
|
|
||||||
defs <- fmap catMaybes $ mapM parseDef $ lines s
|
|
||||||
return $ Just (lang, defs)
|
|
||||||
else return Nothing
|
|
||||||
|
|
||||||
parseDef :: String -> IO (Maybe Def)
|
|
||||||
parseDef "" = return Nothing
|
|
||||||
parseDef ('#':_) = return Nothing
|
|
||||||
parseDef s =
|
|
||||||
case end of
|
|
||||||
':':end' -> do
|
|
||||||
content' <- fmap compress $ parseContent $ dropWhile isSpace end'
|
|
||||||
case words begin of
|
|
||||||
[] -> error $ "Missing constructor: " ++ s
|
|
||||||
(w:ws) -> return $ Just Def
|
|
||||||
{ constr = w
|
|
||||||
, vars = map parseVar ws
|
|
||||||
, content = content'
|
|
||||||
}
|
|
||||||
_ -> error $ "Missing colon: " ++ s
|
|
||||||
where
|
|
||||||
(begin, end) = break (== ':') s
|
|
||||||
|
|
||||||
data Content = Var Deref | Raw String
|
|
||||||
|
|
||||||
compress :: [Content] -> [Content]
|
|
||||||
compress [] = []
|
|
||||||
compress (Raw a:Raw b:rest) = compress $ Raw (a ++ b) : rest
|
|
||||||
compress (x:y) = x : compress y
|
|
||||||
|
|
||||||
parseContent :: String -> IO [Content]
|
|
||||||
parseContent s =
|
|
||||||
either (error . show) return $ parse go s s
|
|
||||||
where
|
|
||||||
go = do
|
|
||||||
x <- many go'
|
|
||||||
eof
|
|
||||||
return x
|
|
||||||
go' = (Raw `fmap` many1 (noneOf "#")) <|> (fmap (either Raw Var) parseHash)
|
|
||||||
|
|
||||||
parseVar :: String -> (String, Maybe String)
|
|
||||||
parseVar s =
|
|
||||||
case break (== '@') s of
|
|
||||||
(x, '@':y) -> (x, Just y)
|
|
||||||
_ -> (s, Nothing)
|
|
||||||
|
|
||||||
data SomeMessage master = forall msg. RenderMessage master msg => SomeMessage msg
|
|
||||||
|
|
||||||
instance IsString (SomeMessage master) where
|
|
||||||
fromString = SomeMessage . T.pack
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE TypeSynonymInstances #-}
|
{-# LANGUAGE TypeSynonymInstances #-}
|
||||||
{-# LANGUAGE UndecidableInstances #-}
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
-- | Widgets combine HTML with JS and CSS dependencies with a unique identifier
|
-- | Widgets combine HTML with JS and CSS dependencies with a unique identifier
|
||||||
-- generator, allowing you to create truly modular HTML components.
|
-- generator, allowing you to create truly modular HTML components.
|
||||||
module Yesod.Widget
|
module Yesod.Widget
|
||||||
@ -56,7 +57,7 @@ module Yesod.Widget
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Monoid
|
import Data.Monoid
|
||||||
import Control.Monad.Trans.RWS
|
import Control.Monad.Trans.Writer
|
||||||
import qualified Text.Blaze.Html5 as H
|
import qualified Text.Blaze.Html5 as H
|
||||||
import Text.Hamlet
|
import Text.Hamlet
|
||||||
import Text.Cassius
|
import Text.Cassius
|
||||||
@ -78,22 +79,48 @@ import qualified Data.Map as Map
|
|||||||
import Language.Haskell.TH.Quote (QuasiQuoter)
|
import Language.Haskell.TH.Quote (QuasiQuoter)
|
||||||
import Language.Haskell.TH.Syntax (Q, Exp (InfixE, VarE, LamE), Pat (VarP), newName)
|
import Language.Haskell.TH.Syntax (Q, Exp (InfixE, VarE, LamE), Pat (VarP), newName)
|
||||||
|
|
||||||
|
#if MIN_VERSION_monad_control(0, 3, 0)
|
||||||
|
import Control.Monad.Trans.Control (MonadTransControl (..), MonadBaseControl (..), defaultLiftBaseWith, defaultRestoreM, ComposeSt)
|
||||||
|
#else
|
||||||
import Control.Monad.IO.Control (MonadControlIO)
|
import Control.Monad.IO.Control (MonadControlIO)
|
||||||
|
#endif
|
||||||
import qualified Text.Hamlet as NP
|
import qualified Text.Hamlet as NP
|
||||||
import Data.Text.Lazy.Builder (fromLazyText)
|
import Data.Text.Lazy.Builder (fromLazyText)
|
||||||
import Text.Blaze (toHtml, preEscapedLazyText)
|
import Text.Blaze (toHtml, preEscapedLazyText)
|
||||||
|
import Control.Monad.Base (MonadBase (liftBase))
|
||||||
|
|
||||||
-- | A generic widget, allowing specification of both the subsite and master
|
-- | A generic widget, allowing specification of both the subsite and master
|
||||||
-- site datatypes. This is basically a large 'WriterT' stack keeping track of
|
-- site datatypes. This is basically a large 'WriterT' stack keeping track of
|
||||||
-- dependencies along with a 'StateT' to track unique identifiers.
|
-- dependencies along with a 'StateT' to track unique identifiers.
|
||||||
newtype GGWidget m monad a = GWidget { unGWidget :: GWInner m monad a }
|
newtype GGWidget m monad a = GWidget { unGWidget :: GWInner m monad a }
|
||||||
deriving (Functor, Applicative, Monad, MonadIO, MonadControlIO)
|
deriving (Functor, Applicative, Monad, MonadIO
|
||||||
|
#if !MIN_VERSION_monad_control(0, 3, 0)
|
||||||
|
, MonadControlIO
|
||||||
|
#endif
|
||||||
|
)
|
||||||
|
|
||||||
|
instance MonadBase b m => MonadBase b (GGWidget master m) where
|
||||||
|
liftBase = lift . liftBase
|
||||||
|
#if MIN_VERSION_monad_control(0, 3, 0)
|
||||||
|
instance MonadTransControl (GGWidget master) where
|
||||||
|
newtype StT (GGWidget master) a =
|
||||||
|
StWidget {unStWidget :: StT (GWInner master) a}
|
||||||
|
liftWith f = GWidget $ liftWith $ \run ->
|
||||||
|
f $ liftM StWidget . run . unGWidget
|
||||||
|
restoreT = GWidget . restoreT . liftM unStWidget
|
||||||
|
{-# INLINE liftWith #-}
|
||||||
|
{-# INLINE restoreT #-}
|
||||||
|
instance MonadBaseControl b m => MonadBaseControl b (GGWidget master m) where
|
||||||
|
newtype StM (GGWidget master m) a = StMT {unStMT :: ComposeSt (GGWidget master) m a}
|
||||||
|
liftBaseWith = defaultLiftBaseWith StMT
|
||||||
|
restoreM = defaultRestoreM unStMT
|
||||||
|
#endif
|
||||||
|
|
||||||
instance MonadTrans (GGWidget m) where
|
instance MonadTrans (GGWidget m) where
|
||||||
lift = GWidget . lift
|
lift = GWidget . lift
|
||||||
|
|
||||||
type GWidget s m = GGWidget m (GHandler s m)
|
type GWidget s m = GGWidget m (GHandler s m)
|
||||||
type GWInner master = RWST () (GWData (Route master)) Int
|
type GWInner master = WriterT (GWData (Route master))
|
||||||
|
|
||||||
instance (Monad monad, a ~ ()) => Monoid (GGWidget master monad a) where
|
instance (Monad monad, a ~ ()) => Monoid (GGWidget master monad a) where
|
||||||
mempty = return ()
|
mempty = return ()
|
||||||
@ -103,9 +130,7 @@ addSubWidget :: (YesodSubRoute sub master) => sub -> GWidget sub master a -> GWi
|
|||||||
addSubWidget sub (GWidget w) = do
|
addSubWidget sub (GWidget w) = do
|
||||||
master <- lift getYesod
|
master <- lift getYesod
|
||||||
let sr = fromSubRoute sub master
|
let sr = fromSubRoute sub master
|
||||||
s <- GWidget get
|
(a, w') <- lift $ toMasterHandlerMaybe sr (const sub) Nothing $ runWriterT w
|
||||||
(a, s', w') <- lift $ toMasterHandlerMaybe sr (const sub) Nothing $ runRWST w () s
|
|
||||||
GWidget $ put s'
|
|
||||||
GWidget $ tell w'
|
GWidget $ tell w'
|
||||||
return a
|
return a
|
||||||
|
|
||||||
@ -192,7 +217,7 @@ addWidget = id
|
|||||||
|
|
||||||
-- | Add some raw CSS to the style tag. Applies to all media types.
|
-- | Add some raw CSS to the style tag. Applies to all media types.
|
||||||
addCassius :: Monad m => CssUrl (Route master) -> GGWidget master m ()
|
addCassius :: Monad m => CssUrl (Route master) -> GGWidget master m ()
|
||||||
addCassius x = GWidget $ tell $ GWData mempty mempty mempty mempty (Map.singleton Nothing x) mempty mempty
|
addCassius x = GWidget $ tell $ GWData mempty mempty mempty mempty (Map.singleton Nothing $ \r -> fromLazyText $ renderCss $ x r) mempty mempty
|
||||||
|
|
||||||
-- | Identical to 'addCassius'.
|
-- | Identical to 'addCassius'.
|
||||||
addLucius :: Monad m => CssUrl (Route master) -> GGWidget master m ()
|
addLucius :: Monad m => CssUrl (Route master) -> GGWidget master m ()
|
||||||
@ -200,7 +225,7 @@ addLucius = addCassius
|
|||||||
|
|
||||||
-- | Add some raw CSS to the style tag, for a specific media type.
|
-- | Add some raw CSS to the style tag, for a specific media type.
|
||||||
addCassiusMedia :: Monad m => Text -> CssUrl (Route master) -> GGWidget master m ()
|
addCassiusMedia :: Monad m => Text -> CssUrl (Route master) -> GGWidget master m ()
|
||||||
addCassiusMedia m x = GWidget $ tell $ GWData mempty mempty mempty mempty (Map.singleton (Just m) x) mempty mempty
|
addCassiusMedia m x = GWidget $ tell $ GWData mempty mempty mempty mempty (Map.singleton (Just m) $ \r -> fromLazyText $ renderCss $ x r) mempty mempty
|
||||||
|
|
||||||
-- | Identical to 'addCassiusMedia'.
|
-- | Identical to 'addCassiusMedia'.
|
||||||
addLuciusMedia :: Monad m => Text -> CssUrl (Route master) -> GGWidget master m ()
|
addLuciusMedia :: Monad m => Text -> CssUrl (Route master) -> GGWidget master m ()
|
||||||
@ -273,9 +298,9 @@ addCoffeeBody c = do
|
|||||||
-- manipulations. It can be easier to use this sometimes than 'wrapWidget'.
|
-- manipulations. It can be easier to use this sometimes than 'wrapWidget'.
|
||||||
extractBody :: Monad mo => GGWidget m mo () -> GGWidget m mo (HtmlUrl (Route m))
|
extractBody :: Monad mo => GGWidget m mo () -> GGWidget m mo (HtmlUrl (Route m))
|
||||||
extractBody (GWidget w) =
|
extractBody (GWidget w) =
|
||||||
GWidget $ mapRWST (liftM go) w
|
GWidget $ mapWriterT (liftM go) w
|
||||||
where
|
where
|
||||||
go ((), s, GWData (Body h) b c d e f g) = (h, s, GWData (Body mempty) b c d e f g)
|
go ((), GWData (Body h) b c d e f g) = (h, GWData (Body mempty) b c d e f g)
|
||||||
|
|
||||||
-- | Content for a web page. By providing this datatype, we can easily create
|
-- | Content for a web page. By providing this datatype, we can easily create
|
||||||
-- generic site templates, which would have the type signature:
|
-- generic site templates, which would have the type signature:
|
||||||
|
|||||||
8
yesod-core/bench.sh
Executable file
8
yesod-core/bench.sh
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash -ex
|
||||||
|
|
||||||
|
ghc --make bench/pong.hs
|
||||||
|
ghc --make bench/pong.hs -prof -osuf o_p -caf-all -auto-all -rtsopts
|
||||||
|
./bench/pong +RTS -p &
|
||||||
|
sleep 2
|
||||||
|
ab -n 1000 -c 5 http://localhost:3000/ 2>&1 | grep 'Time taken'
|
||||||
|
curl http://localhost:3000/kill
|
||||||
32
yesod-core/bench/pong.hs
Normal file
32
yesod-core/bench/pong.hs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings, QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-}
|
||||||
|
import Yesod.Dispatch
|
||||||
|
import Yesod.Content
|
||||||
|
import Yesod.Internal.Core
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import Network.Wai.Handler.Warp (run)
|
||||||
|
import Control.Concurrent.MVar
|
||||||
|
import Control.Concurrent
|
||||||
|
import Network.Wai
|
||||||
|
import Control.Monad.IO.Class
|
||||||
|
|
||||||
|
data Pong = Pong
|
||||||
|
mkYesod "Pong" [$parseRoutes|
|
||||||
|
/ PongR GET
|
||||||
|
|]
|
||||||
|
|
||||||
|
instance Yesod Pong where
|
||||||
|
approot _ = ""
|
||||||
|
encryptKey _ = return Nothing
|
||||||
|
|
||||||
|
getPongR = return $ RepPlain $ toContent ("PONG" :: ByteString)
|
||||||
|
|
||||||
|
main = do
|
||||||
|
app <- toWaiAppPlain Pong
|
||||||
|
flag <- newEmptyMVar
|
||||||
|
forkIO $ run 3000 $ \req ->
|
||||||
|
if pathInfo req == ["kill"]
|
||||||
|
then do
|
||||||
|
liftIO $ putMVar flag ()
|
||||||
|
error "done"
|
||||||
|
else app req
|
||||||
|
takeMVar flag
|
||||||
@ -5,10 +5,13 @@
|
|||||||
import Yesod.Core
|
import Yesod.Core
|
||||||
import Network.Wai.Handler.Warp (run)
|
import Network.Wai.Handler.Warp (run)
|
||||||
import Data.Text (unpack)
|
import Data.Text (unpack)
|
||||||
|
import Text.Julius (julius)
|
||||||
|
|
||||||
data Subsite = Subsite String
|
data Subsite = Subsite String
|
||||||
|
|
||||||
mkYesodSub "Subsite" [] [$parseRoutes|
|
type Strings = [String]
|
||||||
|
|
||||||
|
mkYesodSub "Subsite" [] [parseRoutes|
|
||||||
/ SubRootR GET
|
/ SubRootR GET
|
||||||
/multi/*Strings SubMultiR
|
/multi/*Strings SubMultiR
|
||||||
|]
|
|]
|
||||||
@ -32,9 +35,15 @@ mkYesod "HelloWorld" [$parseRoutes|
|
|||||||
/ RootR GET
|
/ RootR GET
|
||||||
/subsite/#String SubsiteR Subsite getSubsite
|
/subsite/#String SubsiteR Subsite getSubsite
|
||||||
|]
|
|]
|
||||||
instance Yesod HelloWorld where approot _ = ""
|
instance Yesod HelloWorld where
|
||||||
-- getRootR :: GHandler HelloWorld HelloWorld RepPlain -- FIXME remove type sig
|
approot _ = ""
|
||||||
|
yepnopeJs _ = Just $ Left "http://cdnjs.cloudflare.com/ajax/libs/modernizr/2.0.6/modernizr.min.js"
|
||||||
|
|
||||||
getRootR = do
|
getRootR = do
|
||||||
$(logOther "HAHAHA") "Here I am"
|
$(logOther "HAHAHA") "Here I am"
|
||||||
return $ RepPlain "Hello World"
|
defaultLayout $ do
|
||||||
|
addScriptRemote "https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"
|
||||||
|
toWidget [julius|$(function(){$("#mypara").css("color", "red")});|]
|
||||||
|
[whamlet|<p #mypara>Hello World|]
|
||||||
|
|
||||||
main = toWaiApp (HelloWorld Subsite) >>= run 3000
|
main = toWaiApp (HelloWorld Subsite) >>= run 3000
|
||||||
|
|||||||
4
yesod-core/test.hs
Normal file
4
yesod-core/test.hs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import Test.Hspec
|
||||||
|
import qualified YesodCoreTest
|
||||||
|
|
||||||
|
main = hspecX $ descriptions $ YesodCoreTest.specs
|
||||||
26
yesod-core/test/YesodCoreTest.hs
Normal file
26
yesod-core/test/YesodCoreTest.hs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
module YesodCoreTest (specs) where
|
||||||
|
|
||||||
|
import YesodCoreTest.CleanPath
|
||||||
|
import YesodCoreTest.Exceptions
|
||||||
|
import YesodCoreTest.Widget
|
||||||
|
import YesodCoreTest.Media
|
||||||
|
import YesodCoreTest.Links
|
||||||
|
import YesodCoreTest.NoOverloadedStrings
|
||||||
|
import YesodCoreTest.InternalRequest
|
||||||
|
import YesodCoreTest.ErrorHandling
|
||||||
|
import YesodCoreTest.Cache
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
|
||||||
|
specs :: [Specs]
|
||||||
|
specs =
|
||||||
|
[ cleanPathTest
|
||||||
|
, exceptionsTest
|
||||||
|
, widgetTest
|
||||||
|
, mediaTest
|
||||||
|
, linksTest
|
||||||
|
, noOverloadedTest
|
||||||
|
, internalRequestTest
|
||||||
|
, errorHandlingTest
|
||||||
|
, cacheTest
|
||||||
|
]
|
||||||
50
yesod-core/test/YesodCoreTest/Cache.hs
Normal file
50
yesod-core/test/YesodCoreTest/Cache.hs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
module YesodCoreTest.Cache (cacheTest, Widget) where
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import Test.Hspec.HUnit()
|
||||||
|
|
||||||
|
import Network.Wai
|
||||||
|
import Network.Wai.Test
|
||||||
|
|
||||||
|
import Yesod.Core
|
||||||
|
|
||||||
|
data C = C
|
||||||
|
|
||||||
|
key :: CacheKey Int
|
||||||
|
key = $(mkCacheKey)
|
||||||
|
|
||||||
|
key2 :: CacheKey Int
|
||||||
|
key2 = $(mkCacheKey)
|
||||||
|
|
||||||
|
mkYesod "C" [parseRoutes|/ RootR GET|]
|
||||||
|
|
||||||
|
instance Yesod C where approot _ = ""
|
||||||
|
|
||||||
|
getRootR :: Handler ()
|
||||||
|
getRootR = do
|
||||||
|
Nothing <- cacheLookup key
|
||||||
|
cacheInsert key 5
|
||||||
|
Just 5 <- cacheLookup key
|
||||||
|
cacheInsert key 7
|
||||||
|
Just 7 <- cacheLookup key
|
||||||
|
Nothing <- cacheLookup key2
|
||||||
|
cacheDelete key
|
||||||
|
Nothing <- cacheLookup key
|
||||||
|
return ()
|
||||||
|
|
||||||
|
cacheTest :: [Spec]
|
||||||
|
cacheTest =
|
||||||
|
describe "Test.Cache"
|
||||||
|
[ it "works" works
|
||||||
|
]
|
||||||
|
|
||||||
|
runner :: Session () -> IO ()
|
||||||
|
runner f = toWaiApp C >>= runSession f
|
||||||
|
|
||||||
|
works :: IO ()
|
||||||
|
works = runner $ do
|
||||||
|
res <- request defaultRequest { pathInfo = [] }
|
||||||
|
assertStatus 200 res
|
||||||
@ -1,7 +1,7 @@
|
|||||||
{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-}
|
{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
module Test.CleanPath (cleanPathTest, Widget) where
|
module YesodCoreTest.CleanPath (cleanPathTest, Widget) where
|
||||||
|
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
import Test.Hspec.HUnit()
|
import Test.Hspec.HUnit()
|
||||||
111
yesod-core/test/YesodCoreTest/ErrorHandling.hs
Normal file
111
yesod-core/test/YesodCoreTest/ErrorHandling.hs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
{-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings #-}
|
||||||
|
module YesodCoreTest.ErrorHandling
|
||||||
|
( errorHandlingTest
|
||||||
|
, Widget
|
||||||
|
) where
|
||||||
|
import Yesod.Core
|
||||||
|
import Test.Hspec
|
||||||
|
import Test.Hspec.HUnit()
|
||||||
|
import Network.Wai
|
||||||
|
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
|
||||||
|
|
||||||
|
mkYesod "App" [parseRoutes|
|
||||||
|
/ HomeR GET
|
||||||
|
/not_found NotFoundR POST
|
||||||
|
/first_thing FirstThingR POST
|
||||||
|
/after_runRequestBody AfterRunRequestBodyR POST
|
||||||
|
|]
|
||||||
|
|
||||||
|
instance Yesod App where approot _ = ""
|
||||||
|
|
||||||
|
getHomeR :: Handler RepHtml
|
||||||
|
getHomeR = defaultLayout $ toWidget [hamlet|
|
||||||
|
!!!
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<form method=post action=@{NotFoundR}>
|
||||||
|
<input type=submit value="Not found">
|
||||||
|
<form method=post action=@{FirstThingR}>
|
||||||
|
<input type=submit value="Error is thrown first thing in handler">
|
||||||
|
<form method=post action=@{AfterRunRequestBodyR}>
|
||||||
|
<input type=submit value="BUGGY: Error thrown after runRequestBody">
|
||||||
|
|]
|
||||||
|
|
||||||
|
postNotFoundR, postFirstThingR, postAfterRunRequestBodyR :: Handler RepHtml
|
||||||
|
postNotFoundR = do
|
||||||
|
(_, _files) <- runRequestBody
|
||||||
|
_ <- notFound
|
||||||
|
getHomeR
|
||||||
|
|
||||||
|
postFirstThingR = do
|
||||||
|
_ <- error "There was an error 3.14159"
|
||||||
|
getHomeR
|
||||||
|
|
||||||
|
postAfterRunRequestBodyR = do
|
||||||
|
x <- runRequestBody
|
||||||
|
_ <- error $ show x
|
||||||
|
getHomeR
|
||||||
|
|
||||||
|
errorHandlingTest :: [Spec]
|
||||||
|
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 ()
|
||||||
|
runner f = toWaiApp App >>= runSession f
|
||||||
|
|
||||||
|
caseNotFound :: IO ()
|
||||||
|
caseNotFound = runner $ do
|
||||||
|
res <- request defaultRequest
|
||||||
|
{ pathInfo = ["not_found"]
|
||||||
|
, requestMethod = "POST"
|
||||||
|
}
|
||||||
|
assertStatus 404 res
|
||||||
|
assertBodyContains "Not Found" res
|
||||||
|
|
||||||
|
caseBefore :: IO ()
|
||||||
|
caseBefore = runner $ do
|
||||||
|
res <- request defaultRequest
|
||||||
|
{ pathInfo = ["first_thing"]
|
||||||
|
, requestMethod = "POST"
|
||||||
|
}
|
||||||
|
assertStatus 500 res
|
||||||
|
assertBodyContains "There was an error 3.14159" res
|
||||||
|
|
||||||
|
caseAfter :: IO ()
|
||||||
|
caseAfter = runner $ do
|
||||||
|
let content = "foo=bar&baz=bin12345"
|
||||||
|
res <- srequest SRequest
|
||||||
|
{ simpleRequest = defaultRequest
|
||||||
|
{ pathInfo = ["after_runRequestBody"]
|
||||||
|
, requestMethod = "POST"
|
||||||
|
, requestHeaders =
|
||||||
|
[ ("content-type", "application/x-www-form-urlencoded")
|
||||||
|
, ("content-length", S8.pack $ show $ L.length content)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
, simpleRequestBody = content
|
||||||
|
}
|
||||||
|
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 ()
|
||||||
@ -1,17 +1,19 @@
|
|||||||
{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-}
|
{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
module Test.Exceptions (exceptionsTest, Widget) where
|
module YesodCoreTest.Exceptions (exceptionsTest, Widget) where
|
||||||
|
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
import Test.Hspec.HUnit ()
|
import Test.Hspec.HUnit ()
|
||||||
|
|
||||||
import Yesod.Core hiding (Request)
|
import Yesod.Core hiding (Request)
|
||||||
|
import Network.Wai
|
||||||
import Network.Wai.Test
|
import Network.Wai.Test
|
||||||
|
|
||||||
data Y = Y
|
data Y = Y
|
||||||
mkYesod "Y" [parseRoutes|
|
mkYesod "Y" [parseRoutes|
|
||||||
/ RootR GET
|
/ RootR GET
|
||||||
|
/redirect RedirR GET
|
||||||
|]
|
|]
|
||||||
|
|
||||||
instance Yesod Y where
|
instance Yesod Y where
|
||||||
@ -22,9 +24,15 @@ instance Yesod Y where
|
|||||||
getRootR :: Handler ()
|
getRootR :: Handler ()
|
||||||
getRootR = error "FOOBAR" >> return ()
|
getRootR = error "FOOBAR" >> return ()
|
||||||
|
|
||||||
|
getRedirR :: Handler ()
|
||||||
|
getRedirR = do
|
||||||
|
setHeader "foo" "bar"
|
||||||
|
redirect RedirectPermanent RootR
|
||||||
|
|
||||||
exceptionsTest :: [Spec]
|
exceptionsTest :: [Spec]
|
||||||
exceptionsTest = describe "Test.Exceptions"
|
exceptionsTest = describe "Test.Exceptions"
|
||||||
[ it "500" case500
|
[ it "500" case500
|
||||||
|
, it "redirect keeps headers" caseRedirect
|
||||||
]
|
]
|
||||||
|
|
||||||
runner :: Session () -> IO ()
|
runner :: Session () -> IO ()
|
||||||
@ -35,3 +43,9 @@ case500 = runner $ do
|
|||||||
res <- request defaultRequest
|
res <- request defaultRequest
|
||||||
assertStatus 500 res
|
assertStatus 500 res
|
||||||
assertBody "FOOBAR" res
|
assertBody "FOOBAR" res
|
||||||
|
|
||||||
|
caseRedirect :: IO ()
|
||||||
|
caseRedirect = runner $ do
|
||||||
|
res <- request defaultRequest { pathInfo = ["redirect"] }
|
||||||
|
assertStatus 301 res
|
||||||
|
assertHeader "foo" "bar" res
|
||||||
@ -1,5 +1,5 @@
|
|||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
module Test.InternalRequest (internalRequestTest) where
|
module YesodCoreTest.InternalRequest (internalRequestTest) where
|
||||||
|
|
||||||
import Data.List (nub)
|
import Data.List (nub)
|
||||||
import System.Random (StdGen, mkStdGen)
|
import System.Random (StdGen, mkStdGen)
|
||||||
@ -30,16 +30,16 @@ g = undefined
|
|||||||
|
|
||||||
nonceSpecs :: [Spec]
|
nonceSpecs :: [Spec]
|
||||||
nonceSpecs = describe "Yesod.Internal.Request.parseWaiRequest (reqNonce)"
|
nonceSpecs = describe "Yesod.Internal.Request.parseWaiRequest (reqNonce)"
|
||||||
[ it "is Nothing for unsecure sessions" noUnsecureNonce
|
[ it "is Nothing if sessions are disabled" noDisabledNonce
|
||||||
, it "ignores pre-existing nonce for unsecure sessions" ignoreUnsecureNonce
|
, it "ignores pre-existing nonce if sessions are disabled" ignoreDisabledNonce
|
||||||
, it "uses preexisting nonce for secure sessions" useOldNonce
|
, it "uses preexisting nonce in session" useOldNonce
|
||||||
, it "generates a new nonce for secure sessions without nonce" generateNonce
|
, it "generates a new nonce for sessions without nonce" generateNonce
|
||||||
]
|
]
|
||||||
|
|
||||||
noUnsecureNonce = reqNonce r == Nothing where
|
noDisabledNonce = reqNonce r == Nothing where
|
||||||
r = parseWaiRequest' defaultRequest [] Nothing g
|
r = parseWaiRequest' defaultRequest [] Nothing g
|
||||||
|
|
||||||
ignoreUnsecureNonce = reqNonce r == Nothing where
|
ignoreDisabledNonce = reqNonce r == Nothing where
|
||||||
r = parseWaiRequest' defaultRequest [("_NONCE", "old")] Nothing g
|
r = parseWaiRequest' defaultRequest [("_NONCE", "old")] Nothing g
|
||||||
|
|
||||||
useOldNonce = reqNonce r == Just "old" where
|
useOldNonce = reqNonce r == Just "old" where
|
||||||
@ -1,7 +1,7 @@
|
|||||||
{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-}
|
{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
module Test.Links (linksTest, Widget) where
|
module YesodCoreTest.Links (linksTest, Widget) where
|
||||||
|
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
import Test.Hspec.HUnit ()
|
import Test.Hspec.HUnit ()
|
||||||
@ -1,7 +1,7 @@
|
|||||||
{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-}
|
{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
module Test.Media (mediaTest, Widget) where
|
module YesodCoreTest.Media (mediaTest, Widget) where
|
||||||
|
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
import Test.Hspec.HUnit ()
|
import Test.Hspec.HUnit ()
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-}
|
{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
module Test.NoOverloadedStrings (noOverloadedTest, Widget) where
|
module YesodCoreTest.NoOverloadedStrings (noOverloadedTest, Widget) where
|
||||||
|
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
import Test.Hspec.HUnit ()
|
import Test.Hspec.HUnit ()
|
||||||
@ -1,7 +1,7 @@
|
|||||||
{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-}
|
{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
module Test.Widget (widgetTest) where
|
module YesodCoreTest.Widget (widgetTest) where
|
||||||
|
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
import Test.Hspec.HUnit ()
|
import Test.Hspec.HUnit ()
|
||||||
@ -1,20 +0,0 @@
|
|||||||
import Test.Hspec
|
|
||||||
|
|
||||||
import Test.CleanPath
|
|
||||||
import Test.Exceptions
|
|
||||||
import Test.Widget
|
|
||||||
import Test.Media
|
|
||||||
import Test.Links
|
|
||||||
import Test.NoOverloadedStrings
|
|
||||||
import Test.InternalRequest
|
|
||||||
|
|
||||||
main :: IO ()
|
|
||||||
main = hspecX $ descriptions $
|
|
||||||
[ cleanPathTest
|
|
||||||
, exceptionsTest
|
|
||||||
, widgetTest
|
|
||||||
, mediaTest
|
|
||||||
, linksTest
|
|
||||||
, noOverloadedTest
|
|
||||||
, internalRequestTest
|
|
||||||
]
|
|
||||||
1
yesod-core/test/test.hs
Symbolic link
1
yesod-core/test/test.hs
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../test.hs
|
||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-core
|
name: yesod-core
|
||||||
version: 0.9.3
|
version: 0.9.4
|
||||||
license: BSD3
|
license: BSD3
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Michael Snoyman <michael@snoyman.com>
|
author: Michael Snoyman <michael@snoyman.com>
|
||||||
@ -14,6 +14,18 @@ stability: Stable
|
|||||||
cabal-version: >= 1.8
|
cabal-version: >= 1.8
|
||||||
build-type: Simple
|
build-type: Simple
|
||||||
homepage: http://www.yesodweb.com/
|
homepage: http://www.yesodweb.com/
|
||||||
|
extra-source-files:
|
||||||
|
test/en.msg
|
||||||
|
test/YesodCoreTest/NoOverloadedStrings.hs
|
||||||
|
test/YesodCoreTest/Media.hs
|
||||||
|
test/YesodCoreTest/Exceptions.hs
|
||||||
|
test/YesodCoreTest/Widget.hs
|
||||||
|
test/YesodCoreTest/CleanPath.hs
|
||||||
|
test/YesodCoreTest/Links.hs
|
||||||
|
test/YesodCoreTest/InternalRequest.hs
|
||||||
|
test/YesodCoreTest/ErrorHandling.hs
|
||||||
|
test/YesodCoreTest/Cache.hs
|
||||||
|
test.hs
|
||||||
|
|
||||||
flag test
|
flag test
|
||||||
description: Build the executable to run unit tests
|
description: Build the executable to run unit tests
|
||||||
@ -23,39 +35,51 @@ flag ghc7
|
|||||||
|
|
||||||
library
|
library
|
||||||
if flag(ghc7)
|
if flag(ghc7)
|
||||||
build-depends: base >= 4.3 && < 5
|
build-depends: base >= 4.3 && < 5
|
||||||
cpp-options: -DGHC7
|
cpp-options: -DGHC7
|
||||||
else
|
else
|
||||||
build-depends: base >= 4 && < 4.3
|
build-depends: base >= 4 && < 4.3
|
||||||
build-depends: time >= 1.1.4 && < 1.4
|
|
||||||
, wai >= 0.4 && < 0.5
|
-- Work around a bug in cabal. Without this, wai-test doesn't get built and
|
||||||
, wai-extra >= 0.4.1 && < 0.5
|
-- we have a missing dependency during --enable-tests builds.
|
||||||
, bytestring >= 0.9.1.4 && < 0.10
|
if flag(test)
|
||||||
, text >= 0.5 && < 0.12
|
build-depends: wai-test
|
||||||
|
|
||||||
|
build-depends: time >= 1.1.4
|
||||||
|
, wai >= 0.4 && < 0.5
|
||||||
|
, wai-extra >= 0.4.1 && < 0.5
|
||||||
|
, bytestring >= 0.9.1.4 && < 0.10
|
||||||
|
, text >= 0.7 && < 0.12
|
||||||
, template-haskell
|
, template-haskell
|
||||||
, path-pieces >= 0.0 && < 0.1
|
, path-pieces >= 0.0 && < 0.1
|
||||||
, hamlet >= 0.10 && < 0.11
|
, hamlet >= 0.10 && < 0.11
|
||||||
, shakespeare >= 0.10 && < 0.11
|
, shakespeare >= 0.10 && < 0.11
|
||||||
, shakespeare-js >= 0.10 && < 0.11
|
, shakespeare-js >= 0.10.4 && < 0.11
|
||||||
, shakespeare-css >= 0.10 && < 0.11
|
, shakespeare-css >= 0.10.5 && < 0.11
|
||||||
, blaze-builder >= 0.2.1 && < 0.4
|
, shakespeare-i18n >= 0.0 && < 0.1
|
||||||
, transformers >= 0.2 && < 0.3
|
, blaze-builder >= 0.2.1.4 && < 0.4
|
||||||
, clientsession >= 0.7.2 && < 0.8
|
, transformers >= 0.2.2 && < 0.3
|
||||||
, random >= 1.0.0.2 && < 1.1
|
, clientsession >= 0.7.3.1 && < 0.8
|
||||||
, cereal >= 0.2 && < 0.4
|
, random >= 1.0.0.2 && < 1.1
|
||||||
, old-locale >= 1.0.0.2 && < 1.1
|
, cereal >= 0.3 && < 0.4
|
||||||
, failure >= 0.1 && < 0.2
|
, old-locale >= 1.0.0.2 && < 1.1
|
||||||
, containers >= 0.2 && < 0.5
|
, failure >= 0.1 && < 0.2
|
||||||
, monad-control >= 0.2 && < 0.3
|
, containers >= 0.2 && < 0.5
|
||||||
, enumerator >= 0.4.7 && < 0.5
|
, monad-control >= 0.2 && < 0.4
|
||||||
, cookie >= 0.3 && < 0.4
|
, transformers-base >= 0.4
|
||||||
, blaze-html >= 0.4 && < 0.5
|
, enumerator >= 0.4.8 && < 0.5
|
||||||
, http-types >= 0.6.5 && < 0.7
|
, cookie >= 0.3 && < 0.4
|
||||||
, case-insensitive >= 0.2 && < 0.4
|
, blaze-html >= 0.4.1.3 && < 0.5
|
||||||
, parsec >= 2 && < 3.2
|
, http-types >= 0.6.5 && < 0.7
|
||||||
, directory >= 1 && < 1.2
|
, 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
|
||||||
-- for logger. Probably logger should be a separate package
|
-- for logger. Probably logger should be a separate package
|
||||||
, strict-concurrency >= 0.2.4 && < 0.2.5
|
, strict-concurrency >= 0.2.4 && < 0.2.5
|
||||||
|
, vector >= 0.9 && < 0.10
|
||||||
|
, aeson >= 0.3
|
||||||
|
|
||||||
exposed-modules: Yesod.Content
|
exposed-modules: Yesod.Content
|
||||||
Yesod.Core
|
Yesod.Core
|
||||||
@ -65,8 +89,10 @@ library
|
|||||||
Yesod.Request
|
Yesod.Request
|
||||||
Yesod.Widget
|
Yesod.Widget
|
||||||
Yesod.Message
|
Yesod.Message
|
||||||
|
Yesod.Config
|
||||||
Yesod.Internal.TestApi
|
Yesod.Internal.TestApi
|
||||||
other-modules: Yesod.Internal
|
other-modules: Yesod.Internal
|
||||||
|
Yesod.Internal.Cache
|
||||||
Yesod.Internal.Core
|
Yesod.Internal.Core
|
||||||
Yesod.Internal.Session
|
Yesod.Internal.Session
|
||||||
Yesod.Internal.Request
|
Yesod.Internal.Request
|
||||||
@ -74,26 +100,24 @@ library
|
|||||||
Yesod.Internal.RouteParsing
|
Yesod.Internal.RouteParsing
|
||||||
Paths_yesod_core
|
Paths_yesod_core
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
if flag(test)
|
|
||||||
Buildable: False
|
|
||||||
|
|
||||||
test-suite runtests
|
test-suite tests
|
||||||
type: exitcode-stdio-1.0
|
type: exitcode-stdio-1.0
|
||||||
main-is: main.hs
|
main-is: test.hs
|
||||||
hs-source-dirs: test
|
hs-source-dirs: test
|
||||||
|
|
||||||
if flag(ghc7)
|
if flag(ghc7)
|
||||||
type: exitcode-stdio-1.0
|
type: exitcode-stdio-1.0
|
||||||
build-depends: base >= 4.3 && < 5
|
build-depends: base >= 4.3 && < 5
|
||||||
cpp-options: -DGHC7
|
cpp-options: -DGHC7
|
||||||
main-is: main.hs
|
main-is: test.hs
|
||||||
else
|
else
|
||||||
type: exitcode-stdio-1.0
|
type: exitcode-stdio-1.0
|
||||||
build-depends: base >= 4 && < 4.3
|
build-depends: base >= 4 && < 4.3
|
||||||
main-is: main.hs
|
main-is: test.hs
|
||||||
cpp-options: -DTEST
|
cpp-options: -DTEST
|
||||||
build-depends: hspec >= 0.8 && < 0.9
|
build-depends: hspec >= 0.8 && < 0.10
|
||||||
,wai-test
|
,wai-test >= 0.1.2 && < 0.2
|
||||||
,wai
|
,wai
|
||||||
,yesod-core
|
,yesod-core
|
||||||
,bytestring
|
,bytestring
|
||||||
@ -105,6 +129,7 @@ test-suite runtests
|
|||||||
, random
|
, random
|
||||||
,HUnit
|
,HUnit
|
||||||
,QuickCheck >= 2 && < 3
|
,QuickCheck >= 2 && < 3
|
||||||
|
, enumerator
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
|
|
||||||
source-repository head
|
source-repository head
|
||||||
|
|||||||
25
yesod-default/LICENSE
Normal file
25
yesod-default/LICENSE
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
The following license covers this documentation, and the source code, except
|
||||||
|
where otherwise indicated.
|
||||||
|
|
||||||
|
Copyright 2010, Michael Snoyman. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR
|
||||||
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||||
|
EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||||
|
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||||
|
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
226
yesod-default/Yesod/Default/Config.hs
Normal file
226
yesod-default/Yesod/Default/Config.hs
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
{-# LANGUAGE DeriveDataTypeable #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
module Yesod.Default.Config
|
||||||
|
( DefaultEnv (..)
|
||||||
|
, fromArgs
|
||||||
|
, fromArgsExtra
|
||||||
|
, loadDevelopmentConfig
|
||||||
|
|
||||||
|
-- reexport
|
||||||
|
, AppConfig (..)
|
||||||
|
, ConfigSettings (..)
|
||||||
|
, configSettings
|
||||||
|
, loadConfig
|
||||||
|
, withYamlEnvironment
|
||||||
|
) where
|
||||||
|
|
||||||
|
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.Maybe (fromMaybe)
|
||||||
|
|
||||||
|
-- | A yesod-provided @'AppEnv'@, allows for Development, Testing, and
|
||||||
|
-- Production environments
|
||||||
|
data DefaultEnv = Development
|
||||||
|
| Testing
|
||||||
|
| Staging
|
||||||
|
| Production deriving (Read, Show, Enum, Bounded)
|
||||||
|
|
||||||
|
-- | Setup commandline arguments for environment and port
|
||||||
|
data ArgConfig = ArgConfig
|
||||||
|
{ environment :: String
|
||||||
|
, port :: Int
|
||||||
|
} deriving (Show, Data, Typeable)
|
||||||
|
|
||||||
|
-- | A default @'ArgConfig'@ if using the provided @'DefaultEnv'@ type.
|
||||||
|
defaultArgConfig :: ArgConfig
|
||||||
|
defaultArgConfig =
|
||||||
|
ArgConfig
|
||||||
|
{ environment = def
|
||||||
|
&= argPos 0
|
||||||
|
&= typ "ENVIRONMENT"
|
||||||
|
, port = def
|
||||||
|
&= help "the port to listen on"
|
||||||
|
&= typ "PORT"
|
||||||
|
}
|
||||||
|
|
||||||
|
-- | Load an @'AppConfig'@ using the @'DefaultEnv'@ environments from
|
||||||
|
-- commandline arguments.
|
||||||
|
fromArgs :: IO (AppConfig DefaultEnv ())
|
||||||
|
fromArgs = fromArgsExtra (const $ const $ return ())
|
||||||
|
|
||||||
|
-- | Same as 'fromArgs', but allows you to specify how to parse the 'appExtra'
|
||||||
|
-- record.
|
||||||
|
fromArgsExtra :: (DefaultEnv -> TextObject -> IO extra)
|
||||||
|
-> IO (AppConfig DefaultEnv extra)
|
||||||
|
fromArgsExtra = fromArgsWith defaultArgConfig
|
||||||
|
|
||||||
|
fromArgsWith :: (Read env, Show env)
|
||||||
|
=> ArgConfig
|
||||||
|
-> (env -> TextObject -> IO extra)
|
||||||
|
-> IO (AppConfig env extra)
|
||||||
|
fromArgsWith argConfig getExtra = do
|
||||||
|
args <- cmdArgs argConfig
|
||||||
|
|
||||||
|
env <-
|
||||||
|
case reads $ capitalize $ environment args of
|
||||||
|
(e, _):_ -> return e
|
||||||
|
[] -> error $ "Invalid environment: " ++ environment args
|
||||||
|
|
||||||
|
let cs = (configSettings env)
|
||||||
|
{ csLoadExtra = getExtra
|
||||||
|
}
|
||||||
|
config <- loadConfig cs
|
||||||
|
|
||||||
|
return $ if port args /= 0
|
||||||
|
then config { appPort = port args }
|
||||||
|
else config
|
||||||
|
|
||||||
|
where
|
||||||
|
capitalize [] = []
|
||||||
|
capitalize (x:xs) = toUpper x : map toLower xs
|
||||||
|
|
||||||
|
-- | Load your development config (when using @'DefaultEnv'@)
|
||||||
|
loadDevelopmentConfig :: IO (AppConfig DefaultEnv ())
|
||||||
|
loadDevelopmentConfig = loadConfig $ configSettings Development
|
||||||
|
|
||||||
|
-- | Dynamic per-environment configuration which can be loaded at
|
||||||
|
-- run-time negating the need to recompile between environments.
|
||||||
|
data AppConfig environment extra = AppConfig
|
||||||
|
{ appEnv :: environment
|
||||||
|
, appPort :: Int
|
||||||
|
, appRoot :: Text
|
||||||
|
, appExtra :: extra
|
||||||
|
} deriving (Show)
|
||||||
|
|
||||||
|
data ConfigSettings environment extra = ConfigSettings
|
||||||
|
{
|
||||||
|
-- | An arbitrary value, used below, to indicate the current running
|
||||||
|
-- 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
|
||||||
|
-- | 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
|
||||||
|
}
|
||||||
|
|
||||||
|
-- | Default config settings.
|
||||||
|
configSettings :: Show env => env -> ConfigSettings env ()
|
||||||
|
configSettings env0 = ConfigSettings
|
||||||
|
{ csEnv = env0
|
||||||
|
, csLoadExtra = \_ _ -> return ()
|
||||||
|
, csFile = \_ -> return "config/settings.yml"
|
||||||
|
, csGetObject = \env obj -> do
|
||||||
|
envs <- fromMapping obj
|
||||||
|
let senv = show env
|
||||||
|
tenv = T.pack senv
|
||||||
|
maybe
|
||||||
|
(error $ "Could not find environment: " ++ senv)
|
||||||
|
return
|
||||||
|
(lookup tenv envs)
|
||||||
|
}
|
||||||
|
|
||||||
|
-- | Load an @'AppConfig'@.
|
||||||
|
--
|
||||||
|
-- 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 :: ConfigSettings environment extra
|
||||||
|
-> IO (AppConfig environment extra)
|
||||||
|
loadConfig (ConfigSettings env loadExtra getFile getObject) = do
|
||||||
|
fp <- getFile env
|
||||||
|
topObj <- join $ decodeFile fp
|
||||||
|
obj <- getObject env topObj
|
||||||
|
|
||||||
|
m <- maybe (fail "Expected map") return $ fromMapping obj
|
||||||
|
let mssl = lookupScalar "ssl" m
|
||||||
|
let mhost = lookupScalar "host" m
|
||||||
|
let mport = lookupScalar "port" m
|
||||||
|
let mapproot = lookupScalar "approot" m
|
||||||
|
|
||||||
|
extra <- loadExtra env obj
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
, appExtra = extra
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
-- | 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
|
||||||
|
|
||||||
|
-- | 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
|
||||||
14
yesod-default/Yesod/Default/Handlers.hs
Normal file
14
yesod-default/Yesod/Default/Handlers.hs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
module Yesod.Default.Handlers
|
||||||
|
( getFaviconR
|
||||||
|
, getRobotsR
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Yesod.Handler (GHandler, sendFile)
|
||||||
|
import Yesod.Content (RepPlain(..))
|
||||||
|
|
||||||
|
getFaviconR :: GHandler s m ()
|
||||||
|
getFaviconR = sendFile "image/x-icon" "config/favicon.ico"
|
||||||
|
|
||||||
|
getRobotsR :: GHandler s m RepPlain
|
||||||
|
getRobotsR = sendFile "text/plain" "config/robots.txt"
|
||||||
115
yesod-default/Yesod/Default/Main.hs
Normal file
115
yesod-default/Yesod/Default/Main.hs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
{-# LANGUAGE DeriveDataTypeable #-}
|
||||||
|
module Yesod.Default.Main
|
||||||
|
( defaultMain
|
||||||
|
, defaultRunner
|
||||||
|
, defaultDevelApp
|
||||||
|
, defaultDevelAppWith
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Yesod.Core hiding (AppConfig (..))
|
||||||
|
import Yesod.Default.Config
|
||||||
|
import Yesod.Logger (Logger, makeLogger, logString, logLazyText, flushLogger)
|
||||||
|
import Network.Wai (Application)
|
||||||
|
import Network.Wai.Handler.Warp (run)
|
||||||
|
import Network.Wai.Middleware.Debug (debugHandle)
|
||||||
|
import System.Directory (doesDirectoryExist, removeDirectoryRecursive)
|
||||||
|
import Network.Wai.Middleware.Gzip (gzip', GzipFiles (GzipCacheFolder), gzipFiles, def)
|
||||||
|
import Network.Wai.Middleware.Autohead (autohead)
|
||||||
|
import Network.Wai.Middleware.Jsonp (jsonp)
|
||||||
|
import Control.Monad (when)
|
||||||
|
|
||||||
|
#ifndef WINDOWS
|
||||||
|
import qualified System.Posix.Signals as Signal
|
||||||
|
import Control.Concurrent (forkIO, killThread)
|
||||||
|
import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
-- | Run your app, taking environment and port settings from the
|
||||||
|
-- commandline.
|
||||||
|
--
|
||||||
|
-- Use @'fromArgs'@ when using the provided @'DefaultEnv'@ type, or
|
||||||
|
-- @'fromArgsWith'@ when using a custom type
|
||||||
|
--
|
||||||
|
-- > main :: IO ()
|
||||||
|
-- > main = defaultMain fromArgs withMySite
|
||||||
|
--
|
||||||
|
-- or
|
||||||
|
--
|
||||||
|
-- > main :: IO ()
|
||||||
|
-- > main = defaultMain (fromArgsWith customArgConfig) withMySite
|
||||||
|
--
|
||||||
|
defaultMain :: (Show env, Read env)
|
||||||
|
=> IO (AppConfig env extra)
|
||||||
|
-> (AppConfig env extra -> Logger -> (Application -> IO ()) -> IO ())
|
||||||
|
-> IO ()
|
||||||
|
defaultMain load withSite = do
|
||||||
|
config <- load
|
||||||
|
logger <- makeLogger
|
||||||
|
withSite config logger $ run (appPort config)
|
||||||
|
|
||||||
|
-- | Run your application continously, listening for SIGINT and exiting
|
||||||
|
-- when recieved
|
||||||
|
--
|
||||||
|
-- > withYourSite :: AppConfig DefaultEnv -> Logger -> (Application -> IO a) -> IO ()
|
||||||
|
-- > withYourSite conf logger f = do
|
||||||
|
-- > Settings.withConnectionPool conf $ \p -> do
|
||||||
|
-- > runConnectionPool (runMigration yourMigration) p
|
||||||
|
-- > defaultRunner f $ YourSite conf logger p
|
||||||
|
--
|
||||||
|
-- TODO: ifdef WINDOWS
|
||||||
|
--
|
||||||
|
defaultRunner :: (YesodDispatch y y, Yesod y)
|
||||||
|
=> (Application -> IO a)
|
||||||
|
-> y -- ^ your foundation type
|
||||||
|
-> IO ()
|
||||||
|
defaultRunner f h = do
|
||||||
|
-- clear the .static-cache so we don't have stale content
|
||||||
|
exists <- doesDirectoryExist staticCache
|
||||||
|
when exists $ removeDirectoryRecursive staticCache
|
||||||
|
#ifdef WINDOWS
|
||||||
|
toWaiAppPlain h >>= f . middlewares >> return ()
|
||||||
|
#else
|
||||||
|
tid <- forkIO $ toWaiAppPlain h >>= f . middlewares >> return ()
|
||||||
|
flag <- newEmptyMVar
|
||||||
|
_ <- Signal.installHandler Signal.sigINT (Signal.CatchOnce $ do
|
||||||
|
putStrLn "Caught an interrupt"
|
||||||
|
killThread tid
|
||||||
|
putMVar flag ()) Nothing
|
||||||
|
takeMVar flag
|
||||||
|
#endif
|
||||||
|
where
|
||||||
|
middlewares = gzip' gset . jsonp . autohead
|
||||||
|
gset = def { gzipFiles = GzipCacheFolder staticCache }
|
||||||
|
staticCache = ".static-cache"
|
||||||
|
|
||||||
|
-- | Run your development app using the provided @'DefaultEnv'@ type
|
||||||
|
--
|
||||||
|
-- > withDevelAppPort :: Dynamic
|
||||||
|
-- > withDevelAppPort = toDyn $ defaultDevelApp withMySite
|
||||||
|
--
|
||||||
|
defaultDevelApp :: (AppConfig DefaultEnv () -> Logger -> (Application -> IO ()) -> IO ())
|
||||||
|
-> ((Int, Application) -> IO ())
|
||||||
|
-> IO ()
|
||||||
|
defaultDevelApp = defaultDevelAppWith loadDevelopmentConfig
|
||||||
|
|
||||||
|
-- | Run your development app using a custom environment type and loader
|
||||||
|
-- function
|
||||||
|
--
|
||||||
|
-- > withDevelAppPort :: Dynamic
|
||||||
|
-- > withDevelAppPort = toDyn $ (defaultDevelAppWith customLoadAppConfig) withMySite
|
||||||
|
--
|
||||||
|
defaultDevelAppWith :: (Show env, Read env)
|
||||||
|
=> IO (AppConfig env extra) -- ^ A means to load your development @'AppConfig'@
|
||||||
|
-> (AppConfig env extra -> Logger -> (Application -> IO ()) -> IO ()) -- ^ Your @withMySite@ function
|
||||||
|
-> ((Int, Application) -> IO ()) -> IO ()
|
||||||
|
defaultDevelAppWith load withSite f = do
|
||||||
|
conf <- load
|
||||||
|
logger <- makeLogger
|
||||||
|
let p = appPort conf
|
||||||
|
logString logger $ "Devel application launched, listening on port " ++ show p
|
||||||
|
withSite conf logger $ \app -> f (p, debugHandle (logHandle logger) app)
|
||||||
|
flushLogger logger
|
||||||
|
|
||||||
|
where
|
||||||
|
logHandle logger msg = logLazyText logger msg >> flushLogger logger
|
||||||
79
yesod-default/Yesod/Default/Util.hs
Normal file
79
yesod-default/Yesod/Default/Util.hs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
-- | Various utilities used in the scaffolded site.
|
||||||
|
module Yesod.Default.Util
|
||||||
|
( addStaticContentExternal
|
||||||
|
, globFile
|
||||||
|
, widgetFileNoReload
|
||||||
|
, widgetFileReload
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Control.Monad.IO.Class (liftIO)
|
||||||
|
import qualified Data.ByteString.Lazy as L
|
||||||
|
import Data.Text (Text, pack, unpack)
|
||||||
|
import Yesod.Core -- purposely using complete import so that Haddock will see addStaticContent
|
||||||
|
import Control.Monad (unless)
|
||||||
|
import System.Directory (doesFileExist, createDirectoryIfMissing)
|
||||||
|
import Language.Haskell.TH.Syntax
|
||||||
|
import Text.Lucius (luciusFile, luciusFileReload)
|
||||||
|
import Text.Julius (juliusFile, juliusFileReload)
|
||||||
|
import Text.Cassius (cassiusFile, cassiusFileReload)
|
||||||
|
import Data.Monoid (mempty)
|
||||||
|
|
||||||
|
-- | An implementation of 'addStaticContent' which stores the contents in an
|
||||||
|
-- external file. Files are created in the given static folder with names based
|
||||||
|
-- on a hash of their content. This allows expiration dates to be set far in
|
||||||
|
-- the future without worry of users receiving stale content.
|
||||||
|
addStaticContentExternal
|
||||||
|
:: (L.ByteString -> Either a L.ByteString) -- ^ javascript minifier
|
||||||
|
-> (L.ByteString -> String) -- ^ hash function to determine file name
|
||||||
|
-> FilePath -- ^ location of static directory. files will be placed within a "tmp" subfolder
|
||||||
|
-> ([Text] -> Route master) -- ^ route constructor, taking a list of pieces
|
||||||
|
-> Text -- ^ filename extension
|
||||||
|
-> Text -- ^ mime type
|
||||||
|
-> L.ByteString -- ^ file contents
|
||||||
|
-> GHandler sub master (Maybe (Either Text (Route master, [(Text, Text)])))
|
||||||
|
addStaticContentExternal minify hash staticDir toRoute ext' _ content = do
|
||||||
|
liftIO $ createDirectoryIfMissing True statictmp
|
||||||
|
exists <- liftIO $ doesFileExist fn'
|
||||||
|
unless exists $ liftIO $ L.writeFile fn' content'
|
||||||
|
return $ Just $ Right (toRoute ["tmp", pack fn], [])
|
||||||
|
where
|
||||||
|
fn, statictmp, fn' :: FilePath
|
||||||
|
-- by basing the hash off of the un-minified content, we avoid a costly
|
||||||
|
-- minification if the file already exists
|
||||||
|
fn = hash content ++ '.' : unpack ext'
|
||||||
|
statictmp = staticDir ++ "/tmp/"
|
||||||
|
fn' = statictmp ++ fn
|
||||||
|
|
||||||
|
content' :: L.ByteString
|
||||||
|
content'
|
||||||
|
| ext' == "js" = either (const content) id $ minify content
|
||||||
|
| otherwise = content
|
||||||
|
|
||||||
|
-- | expects a file extension for each type, e.g: hamlet lucius julius
|
||||||
|
globFile :: String -> String -> FilePath
|
||||||
|
globFile kind x = "templates/" ++ x ++ "." ++ kind
|
||||||
|
|
||||||
|
widgetFileNoReload :: FilePath -> Q Exp
|
||||||
|
widgetFileNoReload x = do
|
||||||
|
let h = whenExists x "hamlet" whamletFile
|
||||||
|
let c = whenExists x "cassius" cassiusFile
|
||||||
|
let j = whenExists x "julius" juliusFile
|
||||||
|
let l = whenExists x "lucius" luciusFile
|
||||||
|
[|$h >> addCassius $c >> addJulius $j >> addLucius $l|]
|
||||||
|
|
||||||
|
widgetFileReload :: FilePath -> Q Exp
|
||||||
|
widgetFileReload x = do
|
||||||
|
let h = whenExists x "hamlet" whamletFile
|
||||||
|
let c = whenExists x "cassius" cassiusFileReload
|
||||||
|
let j = whenExists x "julius" juliusFileReload
|
||||||
|
let l = whenExists x "lucius" luciusFileReload
|
||||||
|
[|$h >> addCassius $c >> addJulius $j >> addLucius $l|]
|
||||||
|
|
||||||
|
whenExists :: String -> String -> (FilePath -> Q Exp) -> Q Exp
|
||||||
|
whenExists x glob f = do
|
||||||
|
let fn = globFile glob x
|
||||||
|
e <- qRunIO $ doesFileExist fn
|
||||||
|
if e then f fn else [|mempty|]
|
||||||
48
yesod-default/yesod-default.cabal
Normal file
48
yesod-default/yesod-default.cabal
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
name: yesod-default
|
||||||
|
version: 0.5.0
|
||||||
|
license: BSD3
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Patrick Brisbin
|
||||||
|
maintainer: Patrick Brisbin <pbrisbin@gmail.com>
|
||||||
|
synopsis: Default config and main functions for your yesod application
|
||||||
|
category: Web, Yesod
|
||||||
|
stability: Stable
|
||||||
|
cabal-version: >= 1.6
|
||||||
|
build-type: Simple
|
||||||
|
homepage: http://www.yesodweb.com/
|
||||||
|
description: Convenient wrappers for your the configuration and
|
||||||
|
execution of your yesod application
|
||||||
|
|
||||||
|
library
|
||||||
|
if os(windows)
|
||||||
|
cpp-options: -DWINDOWS
|
||||||
|
|
||||||
|
build-depends: base >= 4 && < 5
|
||||||
|
, yesod-core >= 0.9.4 && < 0.10
|
||||||
|
, cmdargs >= 0.8
|
||||||
|
, warp >= 0.4 && < 0.5
|
||||||
|
, wai >= 0.4 && < 0.5
|
||||||
|
, wai-extra >= 0.4.4 && < 0.5
|
||||||
|
, bytestring >= 0.9.1.4
|
||||||
|
, transformers >= 0.2.2 && < 0.3
|
||||||
|
, text >= 0.9
|
||||||
|
, directory >= 1.0
|
||||||
|
, 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
|
||||||
|
|
||||||
|
if !os(windows)
|
||||||
|
build-depends: unix
|
||||||
|
|
||||||
|
exposed-modules: Yesod.Default.Config
|
||||||
|
, Yesod.Default.Main
|
||||||
|
, Yesod.Default.Util
|
||||||
|
, Yesod.Default.Handlers
|
||||||
|
|
||||||
|
ghc-options: -Wall
|
||||||
|
|
||||||
|
source-repository head
|
||||||
|
type: git
|
||||||
|
location: git://github.com/yesodweb/yesod.git
|
||||||
@ -5,15 +5,14 @@
|
|||||||
> {-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings #-}
|
> {-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings #-}
|
||||||
> import Yesod
|
> import Yesod
|
||||||
> import Yesod.Static
|
> import Yesod.Static
|
||||||
> import Data.Monoid (mempty)
|
> import Data.Text (Text, unpack)
|
||||||
> import Text.Blaze (string)
|
|
||||||
|
|
||||||
Like the blog example, we'll define some data first.
|
Like the blog example, we'll define some data first.
|
||||||
|
|
||||||
> data Page = Page
|
> data Page = Page
|
||||||
> { pageName :: String
|
> { pageName :: Text
|
||||||
> , pageSlug :: String
|
> , pageSlug :: Text
|
||||||
> , pageContent :: String
|
> , pageContent :: Text
|
||||||
> }
|
> }
|
||||||
|
|
||||||
> loadPages :: IO [Page]
|
> loadPages :: IO [Page]
|
||||||
@ -36,7 +35,7 @@ Now the routes; we'll have a homepage, a pattern for the pages, and use a static
|
|||||||
|
|
||||||
> mkYesod "Ajax" [parseRoutes|
|
> mkYesod "Ajax" [parseRoutes|
|
||||||
> / HomeR GET
|
> / HomeR GET
|
||||||
> /page/#String PageR GET
|
> /page/#Text PageR GET
|
||||||
> /static StaticR Static ajaxStatic
|
> /static StaticR Static ajaxStatic
|
||||||
> |]
|
> |]
|
||||||
|
|
||||||
@ -49,7 +48,7 @@ Now the routes; we'll have a homepage, a pattern for the pages, and use a static
|
|||||||
> defaultLayout widget = do
|
> defaultLayout widget = do
|
||||||
> Ajax pages _ <- getYesod
|
> Ajax pages _ <- getYesod
|
||||||
> content <- widgetToPageContent widget
|
> content <- widgetToPageContent widget
|
||||||
> hamletToRepHtml [$hamlet|
|
> hamletToRepHtml [hamlet|
|
||||||
> \<!DOCTYPE html>
|
> \<!DOCTYPE html>
|
||||||
>
|
>
|
||||||
> <html>
|
> <html>
|
||||||
@ -80,23 +79,23 @@ Now the routes; we'll have a homepage, a pattern for the pages, and use a static
|
|||||||
|
|
||||||
And now the cool part: a handler that returns either HTML or JSON data, depending on the request headers.
|
And now the cool part: a handler that returns either HTML or JSON data, depending on the request headers.
|
||||||
|
|
||||||
> getPageR :: String -> Handler RepHtmlJson
|
> getPageR :: Text -> Handler RepHtmlJson
|
||||||
> getPageR slug = do
|
> getPageR slug = do
|
||||||
> Ajax pages _ <- getYesod
|
> Ajax pages _ <- getYesod
|
||||||
> case filter (\e -> pageSlug e == slug) pages of
|
> case filter (\e -> pageSlug e == slug) pages of
|
||||||
> [] -> notFound
|
> [] -> notFound
|
||||||
> page:_ -> defaultLayoutJson (do
|
> page:_ -> defaultLayoutJson (do
|
||||||
> setTitle $ string $ pageName page
|
> setTitle $ toHtml $ pageName page
|
||||||
> addHamlet $ html page
|
> addHamlet $ html page
|
||||||
> ) (json page)
|
> ) (json page)
|
||||||
> where
|
> where
|
||||||
> html page = [$hamlet|
|
> html page = [hamlet|
|
||||||
> <h1>#{pageName page}
|
> <h1>#{pageName page}
|
||||||
> <article>#{pageContent page}
|
> <article>#{pageContent page}
|
||||||
> |]
|
> |]
|
||||||
> json page = jsonMap
|
> json page = jsonMap
|
||||||
> [ ("name", jsonScalar $ pageName page)
|
> [ ("name", jsonScalar $ unpack $ pageName page)
|
||||||
> , ("content", jsonScalar $ pageContent page)
|
> , ("content", jsonScalar $ unpack $ pageContent page)
|
||||||
> ]
|
> ]
|
||||||
|
|
||||||
<p>We first try and find the appropriate Page, returning a 404 if it's not there. We then use the applyLayoutJson function, which is really the heart of this example. It allows you an easy way to create responses that will be either HTML or JSON, and which use the default layout in the HTML responses. It takes four arguments: 1) the title of the HTML page, 2) some value, 3) a function from that value to a Hamlet value, and 4) a function from that value to a Json value.</p>
|
<p>We first try and find the appropriate Page, returning a 404 if it's not there. We then use the applyLayoutJson function, which is really the heart of this example. It allows you an easy way to create responses that will be either HTML or JSON, and which use the default layout in the HTML responses. It takes four arguments: 1) the title of the HTML page, 2) some value, 3) a function from that value to a Hamlet value, and 4) a function from that value to a Json value.</p>
|
||||||
@ -110,3 +109,8 @@ And now the cool part: a handler that returns either HTML or JSON data, dependin
|
|||||||
> pages <- loadPages
|
> pages <- loadPages
|
||||||
> s <- static "static/yesod/ajax"
|
> s <- static "static/yesod/ajax"
|
||||||
> warpDebug 3000 $ Ajax pages s
|
> warpDebug 3000 $ Ajax pages s
|
||||||
|
|
||||||
|
And just to avoid some warnings...
|
||||||
|
|
||||||
|
> _ignored :: Widget
|
||||||
|
> _ignored = undefined ajaxPages
|
||||||
|
|||||||
@ -113,3 +113,8 @@ All that's left now is the main function. Yesod is built on top of WAI, so you c
|
|||||||
> main = do
|
> main = do
|
||||||
> entries <- loadEntries
|
> entries <- loadEntries
|
||||||
> warpDebug 3000 $ Blog entries
|
> warpDebug 3000 $ Blog entries
|
||||||
|
|
||||||
|
And this is just to avoid some warnings...
|
||||||
|
|
||||||
|
> _ignored :: Widget
|
||||||
|
> _ignored = undefined blogEntries
|
||||||
|
|||||||
@ -6,11 +6,9 @@
|
|||||||
module Main where
|
module Main where
|
||||||
|
|
||||||
import Yesod
|
import Yesod
|
||||||
import Yesod.Helpers.Static
|
import Yesod.Static
|
||||||
|
|
||||||
import Control.Concurrent.STM
|
import Control.Concurrent.STM
|
||||||
import Control.Concurrent.STM.TChan
|
|
||||||
import Control.Concurrent.STM.TVar
|
|
||||||
|
|
||||||
import Control.Arrow ((***))
|
import Control.Arrow ((***))
|
||||||
import Data.Text (Text, unpack)
|
import Data.Text (Text, unpack)
|
||||||
@ -18,8 +16,6 @@ import Data.Text (Text, unpack)
|
|||||||
-- speaker and content
|
-- speaker and content
|
||||||
data Message = Message Text Text
|
data Message = Message Text Text
|
||||||
|
|
||||||
type Handler yesod = GHandler yesod yesod
|
|
||||||
|
|
||||||
-- all those TChans are dupes, so writing to any one writes to them all, but reading is separate
|
-- all those TChans are dupes, so writing to any one writes to them all, but reading is separate
|
||||||
data Chat = Chat
|
data Chat = Chat
|
||||||
{ chatClients :: TVar [(Int, TChan Message)]
|
{ chatClients :: TVar [(Int, TChan Message)]
|
||||||
@ -29,7 +25,7 @@ data Chat = Chat
|
|||||||
|
|
||||||
staticFiles "static"
|
staticFiles "static"
|
||||||
|
|
||||||
mkYesod "Chat" [$parseRoutes|
|
mkYesod "Chat" [parseRoutes|
|
||||||
/ HomeR GET
|
/ HomeR GET
|
||||||
/check CheckR GET
|
/check CheckR GET
|
||||||
/post PostR GET
|
/post PostR GET
|
||||||
@ -40,21 +36,20 @@ instance Yesod Chat where
|
|||||||
approot _ = ""
|
approot _ = ""
|
||||||
defaultLayout widget = do
|
defaultLayout widget = do
|
||||||
content <- widgetToPageContent widget
|
content <- widgetToPageContent widget
|
||||||
hamletToRepHtml [$hamlet|\
|
hamletToRepHtml [hamlet|
|
||||||
\<!DOCTYPE html>
|
!!!
|
||||||
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>#{pageTitle content}
|
<title>#{pageTitle content}
|
||||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">
|
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">
|
||||||
<script src="@{StaticR chat_js}">
|
<script src="@{StaticR chat_js}">
|
||||||
\^{pageHead content}
|
^{pageHead content}
|
||||||
<body>
|
<body>
|
||||||
\^{pageBody content}
|
^{pageBody content}
|
||||||
\
|
|
||||||
|]
|
|]
|
||||||
|
|
||||||
getHomeR :: Handler Chat RepHtml
|
getHomeR :: Handler RepHtml
|
||||||
getHomeR = do
|
getHomeR = do
|
||||||
Chat clients next _ <- getYesod
|
Chat clients next _ <- getYesod
|
||||||
client <- liftIO . atomically $ do
|
client <- liftIO . atomically $ do
|
||||||
@ -68,8 +63,8 @@ getHomeR = do
|
|||||||
return c
|
return c
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
setTitle "Chat Page"
|
setTitle "Chat Page"
|
||||||
addWidget [$hamlet|\
|
toWidget [hamlet|
|
||||||
\<!DOCTYPE html>
|
!!!
|
||||||
|
|
||||||
<h1>Chat Example
|
<h1>Chat Example
|
||||||
<form>
|
<form>
|
||||||
@ -81,7 +76,7 @@ getHomeR = do
|
|||||||
<script>var clientNumber = #{show client}
|
<script>var clientNumber = #{show client}
|
||||||
|]
|
|]
|
||||||
|
|
||||||
getCheckR :: Handler Chat RepJson
|
getCheckR :: Handler RepJson
|
||||||
getCheckR = do
|
getCheckR = do
|
||||||
liftIO $ putStrLn "Check"
|
liftIO $ putStrLn "Check"
|
||||||
Chat clients _ _ <- getYesod
|
Chat clients _ _ <- getYesod
|
||||||
@ -99,9 +94,10 @@ getCheckR = do
|
|||||||
let Message s c = first
|
let Message s c = first
|
||||||
jsonToRepJson $ zipJson ["sender", "content"] [s,c]
|
jsonToRepJson $ zipJson ["sender", "content"] [s,c]
|
||||||
|
|
||||||
|
zipJson :: [Text] -> [Text] -> Json
|
||||||
zipJson x y = jsonMap $ map (unpack *** jsonScalar . unpack) $ zip x y
|
zipJson x y = jsonMap $ map (unpack *** jsonScalar . unpack) $ zip x y
|
||||||
|
|
||||||
getPostR :: Handler Chat RepJson
|
getPostR :: Handler RepJson
|
||||||
getPostR = do
|
getPostR = do
|
||||||
liftIO $ putStrLn "Post"
|
liftIO $ putStrLn "Post"
|
||||||
Chat clients _ _ <- getYesod
|
Chat clients _ _ <- getYesod
|
||||||
@ -122,4 +118,5 @@ main :: IO ()
|
|||||||
main = do
|
main = do
|
||||||
clients <- newTVarIO []
|
clients <- newTVarIO []
|
||||||
next <- newTVarIO 0
|
next <- newTVarIO 0
|
||||||
warpDebug 3000 $ Chat clients next $ static "static"
|
s <- static "static"
|
||||||
|
warpDebug 3000 $ Chat clients next s
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
> {-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings #-}
|
> {-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings #-}
|
||||||
|
|
||||||
> import Yesod
|
> import Yesod
|
||||||
> import Data.Monoid (mempty)
|
|
||||||
> import qualified Data.ByteString.Char8 as S8
|
> import qualified Data.ByteString.Char8 as S8
|
||||||
> import qualified Data.Text as T
|
> import qualified Data.Text as T
|
||||||
> import Text.Blaze (string)
|
|
||||||
|
|
||||||
> data Echo = Echo
|
> data Echo = Echo
|
||||||
|
|
||||||
@ -14,18 +12,26 @@
|
|||||||
|
|
||||||
> instance Yesod Echo where approot _ = ""
|
> instance Yesod Echo where approot _ = ""
|
||||||
|
|
||||||
|
> getHomepage :: Handler RepHtml
|
||||||
> getHomepage = defaultLayout $ do
|
> getHomepage = defaultLayout $ do
|
||||||
> setTitle $ string "Upload a file"
|
> setTitle "Upload a file"
|
||||||
> addHamlet [$hamlet|
|
> addHamlet [hamlet|
|
||||||
> %form!method=post!action=.!enctype=multipart/form-data
|
> <form method=post action=. enctype=multipart/form-data>
|
||||||
> File name:
|
> File name:
|
||||||
> %input!type=file!name=file
|
> <input type=file name=file
|
||||||
> %input!type=submit
|
> <input type=submit
|
||||||
> |]
|
> |]
|
||||||
|
|
||||||
|
> postHomepage :: Handler [(ContentType, Content)]
|
||||||
> postHomepage = do
|
> postHomepage = do
|
||||||
> (_, files) <- runRequestBody
|
> (_, files) <- runRequestBody
|
||||||
> fi <- maybe notFound return $ lookup "file" files
|
> fi <- maybe notFound return $ lookup "file" files
|
||||||
> return [(S8.pack $ T.unpack $ fileContentType fi, toContent $ fileContent fi)]
|
> return [(S8.pack $ T.unpack $ fileContentType fi, toContent $ fileContent fi)]
|
||||||
|
|
||||||
|
> main :: IO ()
|
||||||
> main = warpDebug 3000 Echo
|
> main = warpDebug 3000 Echo
|
||||||
|
|
||||||
|
To avoid warnings
|
||||||
|
|
||||||
|
> _ignored :: Widget
|
||||||
|
> _ignored = undefined
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
|
|
||||||
> {-# LANGUAGE TypeFamilies, QuasiQuotes, OverloadedStrings, MultiParamTypeClasses, TemplateHaskell #-}
|
> {-# LANGUAGE TypeFamilies, QuasiQuotes, OverloadedStrings, MultiParamTypeClasses, TemplateHaskell #-}
|
||||||
> import Yesod
|
> import Yesod hiding (Form)
|
||||||
> import Control.Applicative
|
> import Control.Applicative
|
||||||
> import Data.Text (Text)
|
> import Data.Text (Text)
|
||||||
|
|
||||||
@ -10,6 +10,8 @@
|
|||||||
> mkYesod "FormExample" [parseRoutes|
|
> mkYesod "FormExample" [parseRoutes|
|
||||||
> / RootR GET
|
> / RootR GET
|
||||||
> |]
|
> |]
|
||||||
|
> type Form a = Html -> MForm FormExample FormExample (FormResult a, Widget)
|
||||||
|
> type Formlet a = Maybe a -> Form a
|
||||||
> instance Yesod FormExample where approot _ = ""
|
> instance Yesod FormExample where approot _ = ""
|
||||||
> instance RenderMessage FormExample FormMessage where
|
> instance RenderMessage FormExample FormMessage where
|
||||||
> renderMessage _ _ = defaultFormMessage
|
> renderMessage _ _ = defaultFormMessage
|
||||||
@ -18,6 +20,7 @@ Next, we'll declare a Person datatype with a name and age. After that, we'll cre
|
|||||||
|
|
||||||
> data Person = Person { name :: Text, age :: Int }
|
> data Person = Person { name :: Text, age :: Int }
|
||||||
> deriving Show
|
> deriving Show
|
||||||
|
> personFormlet :: Formlet Person
|
||||||
> personFormlet p = renderTable $ Person
|
> personFormlet p = renderTable $ Person
|
||||||
> <$> areq textField "Name" (fmap name p)
|
> <$> areq textField "Name" (fmap name p)
|
||||||
> <*> areq intField "Age" (fmap age p)
|
> <*> areq intField "Age" (fmap age p)
|
||||||
@ -38,14 +41,15 @@ We use an applicative approach and stay mostly declarative. The "fmap name p" bi
|
|||||||
|
|
||||||
<p>extractBody returns the HTML of a widget and "passes" all of the other declarations (the CSS, Javascript, etc) up to the parent widget. The rest of this is just standard Hamlet code and our main function.</p>
|
<p>extractBody returns the HTML of a widget and "passes" all of the other declarations (the CSS, Javascript, etc) up to the parent widget. The rest of this is just standard Hamlet code and our main function.</p>
|
||||||
|
|
||||||
> addHamlet [$hamlet|
|
> addHamlet [hamlet|
|
||||||
> <p>Last result: #{show res}
|
> <p>Last result: #{show res}
|
||||||
> <form enctype="#{enctype}">
|
> <form enctype="#{enctype}">
|
||||||
> <table>
|
> <table>
|
||||||
> \^{form}
|
> ^{form}
|
||||||
> <tr>
|
> <tr>
|
||||||
> <td colspan="2">
|
> <td colspan="2">
|
||||||
> <input type="submit">
|
> <input type="submit">
|
||||||
> |]
|
> |]
|
||||||
>
|
>
|
||||||
|
> main :: IO ()
|
||||||
> main = warpDebug 3000 FormExample
|
> main = warpDebug 3000 FormExample
|
||||||
|
|||||||
@ -1,52 +0,0 @@
|
|||||||
This example shows how generalized hamlet templates allow the creation of
|
|
||||||
different types of values. The key component here is the HamletValue typeclass.
|
|
||||||
Yesod has instances for:
|
|
||||||
|
|
||||||
* Html
|
|
||||||
|
|
||||||
* HtmlUrl (= (url -> [(String, String)] -> String) -> Html)
|
|
||||||
|
|
||||||
* GWidget s m ()
|
|
||||||
|
|
||||||
This example uses all three. You are of course free in your own code to make
|
|
||||||
your own instances.
|
|
||||||
|
|
||||||
> {-# LANGUAGE QuasiQuotes, TypeFamilies, MultiParamTypeClasses, OverloadedStrings, TemplateHaskell #-}
|
|
||||||
> import Yesod
|
|
||||||
> import Text.Hamlet (shamlet)
|
|
||||||
> data NewHamlet = NewHamlet
|
|
||||||
> mkYesod "NewHamlet" [$parseRoutes|/ RootR GET|]
|
|
||||||
> instance Yesod NewHamlet where approot _ = ""
|
|
||||||
>
|
|
||||||
> myHtml :: Html
|
|
||||||
> myHtml = [shamlet|<p>Just don't use any URLs in here!|]
|
|
||||||
>
|
|
||||||
> myInnerWidget :: Widget
|
|
||||||
> myInnerWidget = do
|
|
||||||
> addHamlet [$hamlet|
|
|
||||||
> <div #inner>Inner widget
|
|
||||||
> #{myHtml}
|
|
||||||
> |]
|
|
||||||
> addCassius [$cassius|
|
|
||||||
>#inner
|
|
||||||
> color: red|]
|
|
||||||
>
|
|
||||||
> myPlainTemplate :: HtmlUrl NewHamletRoute
|
|
||||||
> myPlainTemplate = [hamlet|
|
|
||||||
> <p
|
|
||||||
> <a href=@{RootR}>Link to home
|
|
||||||
> |]
|
|
||||||
>
|
|
||||||
> myWidget :: Widget
|
|
||||||
> myWidget = [whamlet|
|
|
||||||
> <h1>Embed another widget
|
|
||||||
> \^{myInnerWidget}
|
|
||||||
> <h1>Embed a Hamlet
|
|
||||||
> \^{addHamlet myPlainTemplate}
|
|
||||||
> |]
|
|
||||||
>
|
|
||||||
> getRootR :: GHandler NewHamlet NewHamlet RepHtml
|
|
||||||
> getRootR = defaultLayout myWidget
|
|
||||||
>
|
|
||||||
> main :: IO ()
|
|
||||||
> main = warpDebug 3000 NewHamlet
|
|
||||||
@ -3,14 +3,14 @@
|
|||||||
> {-# LANGUAGE TypeFamilies #-}
|
> {-# LANGUAGE TypeFamilies #-}
|
||||||
> {-# LANGUAGE MultiParamTypeClasses #-}
|
> {-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
> {-# LANGUAGE OverloadedStrings #-}
|
> {-# LANGUAGE OverloadedStrings #-}
|
||||||
|
> {-# LANGUAGE CPP #-}
|
||||||
|
|
||||||
> import Yesod
|
> import Yesod
|
||||||
> import Data.Monoid (mempty)
|
|
||||||
> import Data.Text (Text)
|
> import Data.Text (Text)
|
||||||
|
|
||||||
> data I18N = I18N
|
> data I18N = I18N
|
||||||
|
|
||||||
> mkYesod "I18N" [$parseRoutes|
|
> mkYesod "I18N" [parseRoutes|
|
||||||
> / HomepageR GET
|
> / HomepageR GET
|
||||||
> /set/#Text SetLangR GET
|
> /set/#Text SetLangR GET
|
||||||
> |]
|
> |]
|
||||||
@ -24,12 +24,12 @@
|
|||||||
> let hello = chooseHello ls
|
> let hello = chooseHello ls
|
||||||
> let choices =
|
> let choices =
|
||||||
> [ ("en", "English") :: (Text, Text)
|
> [ ("en", "English") :: (Text, Text)
|
||||||
> , ("es", "Spanish")
|
> , ("es", "Español")
|
||||||
> , ("he", "Hebrew")
|
> , ("he", "עִבְרִית")
|
||||||
> ]
|
> ]
|
||||||
> defaultLayout $ do
|
> defaultLayout $ do
|
||||||
> setTitle "I18N Homepage"
|
> setTitle "I18N Homepage"
|
||||||
> addHamlet [$hamlet|
|
> addHamlet [hamlet|
|
||||||
> <h1>#{hello}
|
> <h1>#{hello}
|
||||||
> <p>In other languages:
|
> <p>In other languages:
|
||||||
> <ul>
|
> <ul>
|
||||||
@ -40,8 +40,8 @@
|
|||||||
|
|
||||||
> chooseHello :: [Text] -> Text
|
> chooseHello :: [Text] -> Text
|
||||||
> chooseHello [] = "Hello"
|
> chooseHello [] = "Hello"
|
||||||
> chooseHello ("he":_) = "Shalom"
|
> chooseHello ("he":_) = "שלום"
|
||||||
> chooseHello ("es":_) = "Hola"
|
> chooseHello ("es":_) = "¡Hola!"
|
||||||
> chooseHello (_:rest) = chooseHello rest
|
> chooseHello (_:rest) = chooseHello rest
|
||||||
|
|
||||||
> getSetLangR :: Text -> Handler ()
|
> getSetLangR :: Text -> Handler ()
|
||||||
@ -51,3 +51,6 @@
|
|||||||
|
|
||||||
> main :: IO ()
|
> main :: IO ()
|
||||||
> main = warpDebug 3000 I18N
|
> main = warpDebug 3000 I18N
|
||||||
|
|
||||||
|
> _ignored :: Widget
|
||||||
|
> _ignored = undefined
|
||||||
|
|||||||
@ -10,14 +10,14 @@
|
|||||||
|
|
||||||
> data PY = PY
|
> data PY = PY
|
||||||
|
|
||||||
> mkYesod "PY" [$parseRoutes|
|
> mkYesod "PY" [parseRoutes|
|
||||||
> / Homepage GET POST
|
> / Homepage GET POST
|
||||||
> |]
|
> |]
|
||||||
|
|
||||||
> instance Yesod PY where approot _ = ""
|
> instance Yesod PY where approot _ = ""
|
||||||
|
|
||||||
> template :: Maybe (HtmlUrl url) -> HtmlUrl url
|
> template :: Maybe (HtmlUrl url) -> HtmlUrl url
|
||||||
> template myaml = [$hamlet|
|
> template myaml = [hamlet|
|
||||||
> !!!
|
> !!!
|
||||||
>
|
>
|
||||||
> <html>
|
> <html>
|
||||||
@ -46,13 +46,13 @@
|
|||||||
> hamletToRepHtml $ template $ Just $ objToHamlet so
|
> hamletToRepHtml $ template $ Just $ objToHamlet so
|
||||||
|
|
||||||
> objToHamlet :: StringObject -> HtmlUrl url
|
> objToHamlet :: StringObject -> HtmlUrl url
|
||||||
> objToHamlet (Scalar s) = [$hamlet|#{s}|]
|
> objToHamlet (Scalar s) = [hamlet|#{s}|]
|
||||||
> objToHamlet (Sequence list) = [$hamlet|
|
> objToHamlet (Sequence list) = [hamlet|
|
||||||
> <ul
|
> <ul
|
||||||
> $forall o <- list
|
> $forall o <- list
|
||||||
> <li>^{objToHamlet o}
|
> <li>^{objToHamlet o}
|
||||||
> |]
|
> |]
|
||||||
> objToHamlet (Mapping pairs) = [$hamlet|
|
> objToHamlet (Mapping pairs) = [hamlet|
|
||||||
> <dl
|
> <dl
|
||||||
> $forall pair <- pairs
|
> $forall pair <- pairs
|
||||||
> <dt>#{fst pair}
|
> <dt>#{fst pair}
|
||||||
@ -61,3 +61,6 @@
|
|||||||
|
|
||||||
> main :: IO ()
|
> main :: IO ()
|
||||||
> main = warpDebug 3000 PY
|
> main = warpDebug 3000 PY
|
||||||
|
|
||||||
|
> _ignored :: Widget
|
||||||
|
> _ignored = undefined
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
> getRoot :: Handler RepHtml
|
> getRoot :: Handler RepHtml
|
||||||
> getRoot = do
|
> getRoot = do
|
||||||
> sess <- getSession
|
> sess <- getSession
|
||||||
> hamletToRepHtml [$hamlet|
|
> hamletToRepHtml [hamlet|
|
||||||
> <form method=post
|
> <form method=post
|
||||||
> <input type=text name=key
|
> <input type=text name=key
|
||||||
> <input type=text name=val
|
> <input type=text name=val
|
||||||
@ -32,4 +32,8 @@
|
|||||||
> liftIO $ print (key, val)
|
> liftIO $ print (key, val)
|
||||||
> redirect RedirectTemporary Root
|
> redirect RedirectTemporary Root
|
||||||
>
|
>
|
||||||
|
> main :: IO ()
|
||||||
> main = warpDebug 3000 Session
|
> main = warpDebug 3000 Session
|
||||||
|
|
||||||
|
> _ignored :: Widget
|
||||||
|
> _ignored = undefined
|
||||||
|
|||||||
@ -2,8 +2,10 @@
|
|||||||
{-# LANGUAGE QuasiQuotes, OverloadedStrings #-}
|
{-# LANGUAGE QuasiQuotes, OverloadedStrings #-}
|
||||||
|
|
||||||
import Text.Hamlet
|
import Text.Hamlet
|
||||||
import qualified Data.ByteString.Lazy as L
|
|
||||||
import Data.Text (Text, cons)
|
import Data.Text (Text, cons)
|
||||||
|
import qualified Data.Text.Lazy.IO as L
|
||||||
|
import Text.Blaze.Renderer.Text (renderHtml)
|
||||||
|
|
||||||
|
|
||||||
data Person = Person
|
data Person = Person
|
||||||
{ name :: String
|
{ name :: String
|
||||||
@ -16,23 +18,23 @@ data PersonUrls = Homepage | PersonPage Text
|
|||||||
|
|
||||||
renderUrls :: PersonUrls -> [(Text, Text)] -> Text
|
renderUrls :: PersonUrls -> [(Text, Text)] -> Text
|
||||||
renderUrls Homepage _ = "/"
|
renderUrls Homepage _ = "/"
|
||||||
renderUrls (PersonPage name) _ = '/' `cons` name
|
renderUrls (PersonPage name') _ = '/' `cons` name'
|
||||||
|
|
||||||
footer :: Hamlet url
|
footer :: HtmlUrl url
|
||||||
footer = [$hamlet|\
|
footer = [hamlet|
|
||||||
<div id="footer">Thank you, come again
|
<div id="footer">Thank you, come again
|
||||||
|]
|
|]
|
||||||
|
|
||||||
template :: Person -> Hamlet PersonUrls
|
template :: Person -> HtmlUrl PersonUrls
|
||||||
template person = [$hamlet|
|
template person = [hamlet|
|
||||||
!!!
|
!!!
|
||||||
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Hamlet Demo
|
<title>Hamlet Demo
|
||||||
<body>
|
<body>
|
||||||
<h1>Information on #{string (name person)}
|
<h1>Information on #{name person}
|
||||||
<p>#{string (name person)} is #{string (age person)} years old.
|
<p>#{name person} is #{age person} years old.
|
||||||
<h2>
|
<h2>
|
||||||
$if isMarried person
|
$if isMarried person
|
||||||
\Married
|
\Married
|
||||||
@ -40,7 +42,7 @@ template person = [$hamlet|
|
|||||||
\Not married
|
\Not married
|
||||||
<ul>
|
<ul>
|
||||||
$forall child <- children person
|
$forall child <- children person
|
||||||
<li>#{string child}
|
<li>#{child}
|
||||||
<p>
|
<p>
|
||||||
<a href="@{page person}">See the page.
|
<a href="@{page person}">See the page.
|
||||||
\^{footer}
|
\^{footer}
|
||||||
@ -55,7 +57,7 @@ main = do
|
|||||||
, isMarried = True
|
, isMarried = True
|
||||||
, children = ["Adam", "Ben", "Chris"]
|
, children = ["Adam", "Ben", "Chris"]
|
||||||
}
|
}
|
||||||
L.putStrLn $ renderHamlet renderUrls $ template person
|
L.putStrLn $ renderHtml $ (template person) renderUrls
|
||||||
\end{code}
|
\end{code}
|
||||||
|
|
||||||
Outputs (new lines added for readability):
|
Outputs (new lines added for readability):
|
||||||
|
|||||||
@ -37,3 +37,6 @@ Just (Person {personName = "Michael", personAge = 25})
|
|||||||
Just (Person {personName = "Michael", personAge = 26})
|
Just (Person {personName = "Michael", personAge = 26})
|
||||||
[(PersonId 1,Person {personName = "Michael", personAge = 26})]
|
[(PersonId 1,Person {personName = "Michael", personAge = 26})]
|
||||||
[]</pre></code>
|
[]</pre></code>
|
||||||
|
|
||||||
|
> _ignored :: PersonId
|
||||||
|
> _ignored = undefined personName personAge
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
Name: yesod-examples
|
Name: yesod-examples
|
||||||
Version: 0.8.0.3
|
Version: 0.9.0
|
||||||
Synopsis: Example programs using the Yesod Web Framework.
|
Synopsis: Example programs using the Yesod Web Framework.
|
||||||
Description: These are the same examples and tutorials found on the documentation site.
|
Description: These are the same examples and tutorials found on the documentation site.
|
||||||
Homepage: http://www.yesodweb.com/
|
Homepage: http://www.yesodweb.com/
|
||||||
@ -15,6 +15,8 @@ extra-source-files: static/yesod/ajax/script.js,
|
|||||||
static/yesod/ajax/style.css,
|
static/yesod/ajax/style.css,
|
||||||
static/chat.js
|
static/chat.js
|
||||||
|
|
||||||
|
flag ghc7
|
||||||
|
|
||||||
Executable yesod-blog
|
Executable yesod-blog
|
||||||
Main-is: src/blog.lhs
|
Main-is: src/blog.lhs
|
||||||
Build-depends: base >= 4 && < 5,
|
Build-depends: base >= 4 && < 5,
|
||||||
@ -23,23 +25,25 @@ Executable yesod-blog
|
|||||||
Executable yesod-ajax
|
Executable yesod-ajax
|
||||||
Main-is: src/ajax.lhs
|
Main-is: src/ajax.lhs
|
||||||
Build-depends: yesod-static,
|
Build-depends: yesod-static,
|
||||||
blaze-html,
|
blaze-html >= 0.4.1.3 && < 0.5,
|
||||||
yesod >= 0.9
|
yesod >= 0.9
|
||||||
|
|
||||||
Executable yesod-file-echo
|
Executable yesod-file-echo
|
||||||
Main-is: src/file-echo.lhs
|
Main-is: src/file-echo.lhs
|
||||||
Build-depends: text,
|
Build-depends: text >= 0.9 && < 0.12,
|
||||||
yesod >= 0.9
|
yesod >= 0.9
|
||||||
|
|
||||||
Executable yesod-pretty-yaml
|
Executable yesod-pretty-yaml
|
||||||
Main-is: src/pretty-yaml.lhs
|
Main-is: src/pretty-yaml.lhs
|
||||||
Build-depends: data-object-yaml >= 0.3.0 && < 0.4,
|
Build-depends: data-object-yaml >= 0.3.0 && < 0.4,
|
||||||
data-object >= 0.3.1 && < 0.4,
|
data-object >= 0.3.1 && < 0.4,
|
||||||
bytestring >= 0.9 && < 0.10,
|
bytestring >= 0.9.1.4 && < 0.10,
|
||||||
yesod >= 0.9
|
yesod >= 0.9
|
||||||
|
|
||||||
Executable yesod-i18n
|
Executable yesod-i18n
|
||||||
Main-is: src/i18n.lhs
|
Main-is: src/i18n.lhs
|
||||||
|
if flag(ghc7)
|
||||||
|
cpp-options: -DGHC7
|
||||||
|
|
||||||
Executable yesod-session
|
Executable yesod-session
|
||||||
Main-is: src/session.lhs
|
Main-is: src/session.lhs
|
||||||
@ -48,21 +52,19 @@ Executable yesod-session
|
|||||||
-- Main-is: src/widgets.lhs
|
-- Main-is: src/widgets.lhs
|
||||||
-- Build-depends: yesod-form
|
-- Build-depends: yesod-form
|
||||||
|
|
||||||
Executable yesod-generalized-hamlet
|
|
||||||
Main-is: src/generalized-hamlet.lhs
|
|
||||||
|
|
||||||
Executable yesod-form
|
Executable yesod-form
|
||||||
Main-is: src/form.lhs
|
Main-is: src/form.lhs
|
||||||
|
|
||||||
Executable yesod-persistent-synopsis
|
Executable yesod-persistent-synopsis
|
||||||
Main-is: synopsis/persistent.lhs
|
Main-is: synopsis/persistent.lhs
|
||||||
Build-depends: transformers >= 0.2.1 && < 0.3,
|
Build-depends: transformers >= 0.2.2 && < 0.3,
|
||||||
persistent-sqlite >= 0.6,
|
persistent-sqlite >= 0.6 && < 0.7,
|
||||||
persistent-template
|
persistent-template >= 0.6 && < 0.7
|
||||||
|
extra-libraries: sqlite3
|
||||||
|
|
||||||
Executable yesod-hamlet-synopsis
|
Executable yesod-hamlet-synopsis
|
||||||
Main-is: synopsis/hamlet.lhs
|
Main-is: synopsis/hamlet.lhs
|
||||||
Build-depends: hamlet
|
Build-depends: hamlet, yesod-core
|
||||||
|
|
||||||
Executable yesod-chat
|
Executable yesod-chat
|
||||||
Main-is: src/chat.hs
|
Main-is: src/chat.hs
|
||||||
|
|||||||
@ -35,6 +35,8 @@ module Yesod.Form.Fields
|
|||||||
, selectField'
|
, selectField'
|
||||||
, radioField'
|
, radioField'
|
||||||
, Option (..)
|
, Option (..)
|
||||||
|
, OptionList (..)
|
||||||
|
, mkOptionList
|
||||||
, optionsPersist
|
, optionsPersist
|
||||||
, optionsPairs
|
, optionsPairs
|
||||||
, optionsEnum
|
, optionsEnum
|
||||||
@ -66,7 +68,6 @@ import qualified Data.ByteString as S
|
|||||||
import qualified Data.ByteString.Lazy as L
|
import qualified Data.ByteString.Lazy as L
|
||||||
import Data.Text (Text, unpack, pack)
|
import Data.Text (Text, unpack, pack)
|
||||||
import qualified Data.Text.Read
|
import qualified Data.Text.Read
|
||||||
import Control.Monad.IO.Class (liftIO)
|
|
||||||
import Control.Monad.Trans.Class (lift)
|
import Control.Monad.Trans.Class (lift)
|
||||||
|
|
||||||
import Control.Applicative ((<$>))
|
import Control.Applicative ((<$>))
|
||||||
@ -76,6 +77,7 @@ import Yesod.Request (FileInfo)
|
|||||||
|
|
||||||
import Yesod.Core (toSinglePiece, GGHandler, SinglePiece)
|
import Yesod.Core (toSinglePiece, GGHandler, SinglePiece)
|
||||||
import Yesod.Persist (selectList, runDB, Filter, SelectOpt, YesodPersistBackend, Key, YesodPersist, PersistEntity, PersistBackend)
|
import Yesod.Persist (selectList, runDB, Filter, SelectOpt, YesodPersistBackend, Key, YesodPersist, PersistEntity, PersistBackend)
|
||||||
|
import Control.Arrow ((&&&))
|
||||||
|
|
||||||
#if __GLASGOW_HASKELL__ >= 700
|
#if __GLASGOW_HASKELL__ >= 700
|
||||||
#define WHAMLET whamlet
|
#define WHAMLET whamlet
|
||||||
@ -303,7 +305,7 @@ urlField = Field
|
|||||||
selectField :: (Eq a, RenderMessage master FormMessage) => [(Text, a)] -> Field sub master a
|
selectField :: (Eq a, RenderMessage master FormMessage) => [(Text, a)] -> Field sub master a
|
||||||
selectField = selectField' . optionsPairs
|
selectField = selectField' . optionsPairs
|
||||||
|
|
||||||
selectField' :: (Eq a, RenderMessage master FormMessage) => GGHandler sub master IO [Option a] -> Field sub master a
|
selectField' :: (Eq a, RenderMessage master FormMessage) => GGHandler sub master IO (OptionList a) -> Field sub master a
|
||||||
selectField' = selectFieldHelper
|
selectField' = selectFieldHelper
|
||||||
(\theId name inside -> [WHAMLET|<select ##{theId} name=#{name}>^{inside}|]) -- outside
|
(\theId name inside -> [WHAMLET|<select ##{theId} name=#{name}>^{inside}|]) -- outside
|
||||||
(\_theId _name isSel -> [WHAMLET|<option value=none :isSel:selected>_{MsgSelectNone}|]) -- onOpt
|
(\_theId _name isSel -> [WHAMLET|<option value=none :isSel:selected>_{MsgSelectNone}|]) -- onOpt
|
||||||
@ -317,7 +319,7 @@ multiSelectField = multiSelectFieldHelper
|
|||||||
radioField :: (Eq a, RenderMessage master FormMessage) => [(Text, a)] -> Field sub master a
|
radioField :: (Eq a, RenderMessage master FormMessage) => [(Text, a)] -> Field sub master a
|
||||||
radioField = radioField' . optionsPairs
|
radioField = radioField' . optionsPairs
|
||||||
|
|
||||||
radioField' :: (Eq a, RenderMessage master FormMessage) => GGHandler sub master IO [Option a] -> Field sub master a
|
radioField' :: (Eq a, RenderMessage master FormMessage) => GGHandler sub master IO (OptionList a) -> Field sub master a
|
||||||
radioField' = selectFieldHelper
|
radioField' = selectFieldHelper
|
||||||
(\theId _name inside -> [WHAMLET|<div ##{theId}>^{inside}|])
|
(\theId _name inside -> [WHAMLET|<div ##{theId}>^{inside}|])
|
||||||
(\theId name isSel -> [WHAMLET|
|
(\theId name isSel -> [WHAMLET|
|
||||||
@ -380,27 +382,38 @@ multiSelectFieldHelper outside inside opts = Field
|
|||||||
selectParser xs | not $ null (["", "none"] `intersect` xs) = Right Nothing
|
selectParser xs | not $ null (["", "none"] `intersect` xs) = Right Nothing
|
||||||
| otherwise = (Right . Just . map snd . catMaybes . map (\y -> lookup y pairs) . nub . map fst . rights . map Data.Text.Read.decimal) xs
|
| otherwise = (Right . Just . map snd . catMaybes . map (\y -> lookup y pairs) . nub . map fst . rights . map Data.Text.Read.decimal) xs
|
||||||
|
|
||||||
|
data OptionList a = OptionList
|
||||||
|
{ olOptions :: [Option a]
|
||||||
|
, olReadExternal :: Text -> Maybe a
|
||||||
|
}
|
||||||
|
|
||||||
|
mkOptionList :: [Option a] -> OptionList a
|
||||||
|
mkOptionList os = OptionList
|
||||||
|
{ olOptions = os
|
||||||
|
, olReadExternal = flip Map.lookup $ Map.fromList $ map (optionExternalValue &&& optionInternalValue) os
|
||||||
|
}
|
||||||
|
|
||||||
data Option a = Option
|
data Option a = Option
|
||||||
{ optionDisplay :: Text
|
{ optionDisplay :: Text
|
||||||
, optionInternalValue :: a
|
, optionInternalValue :: a
|
||||||
, optionExternalValue :: Text
|
, optionExternalValue :: Text
|
||||||
}
|
}
|
||||||
|
|
||||||
optionsPairs :: [(Text, a)] -> GGHandler sub master IO [Option a]
|
optionsPairs :: [(Text, a)] -> GGHandler sub master IO (OptionList a)
|
||||||
optionsPairs = return . zipWith (\external (display, internal) -> Option
|
optionsPairs = return . mkOptionList . zipWith (\external (display, internal) -> Option
|
||||||
{ optionDisplay = display
|
{ optionDisplay = display
|
||||||
, optionInternalValue = internal
|
, optionInternalValue = internal
|
||||||
, optionExternalValue = pack $ show external
|
, optionExternalValue = pack $ show external
|
||||||
}) [1 :: Int ..]
|
}) [1 :: Int ..]
|
||||||
|
|
||||||
optionsEnum :: (Show a, Enum a, Bounded a) => GGHandler sub master IO [Option a]
|
optionsEnum :: (Show a, Enum a, Bounded a) => GGHandler sub master IO (OptionList a)
|
||||||
optionsEnum = optionsPairs $ map (\x -> (pack $ show x, x)) [minBound..maxBound]
|
optionsEnum = optionsPairs $ map (\x -> (pack $ show x, x)) [minBound..maxBound]
|
||||||
|
|
||||||
optionsPersist :: ( YesodPersist master, PersistEntity a, PersistBackend (YesodPersistBackend master) (GGHandler sub master IO)
|
optionsPersist :: ( YesodPersist master, PersistEntity a, PersistBackend (YesodPersistBackend master) (GGHandler sub master IO)
|
||||||
, SinglePiece (Key (YesodPersistBackend master) a)
|
, SinglePiece (Key (YesodPersistBackend master) a)
|
||||||
)
|
)
|
||||||
=> [Filter a] -> [SelectOpt a] -> (a -> Text) -> GGHandler sub master IO [Option (Key (YesodPersistBackend master) a, a)]
|
=> [Filter a] -> [SelectOpt a] -> (a -> Text) -> GGHandler sub master IO (OptionList (Key (YesodPersistBackend master) a, a))
|
||||||
optionsPersist filts ords toDisplay = do
|
optionsPersist filts ords toDisplay = fmap mkOptionList $ do
|
||||||
pairs <- runDB $ selectList filts ords
|
pairs <- runDB $ selectList filts ords
|
||||||
return $ map (\(key, value) -> Option
|
return $ map (\(key, value) -> Option
|
||||||
{ optionDisplay = toDisplay value
|
{ optionDisplay = toDisplay value
|
||||||
@ -413,13 +426,13 @@ selectFieldHelper
|
|||||||
=> (Text -> Text -> GWidget sub master () -> GWidget sub master ())
|
=> (Text -> Text -> GWidget sub master () -> GWidget sub master ())
|
||||||
-> (Text -> Text -> Bool -> GWidget sub master ())
|
-> (Text -> Text -> Bool -> GWidget sub master ())
|
||||||
-> (Text -> Text -> Text -> Bool -> Text -> GWidget sub master ())
|
-> (Text -> Text -> Text -> Bool -> Text -> GWidget sub master ())
|
||||||
-> GGHandler sub master IO [Option a] -> Field sub master a
|
-> GGHandler sub master IO (OptionList a) -> Field sub master a
|
||||||
selectFieldHelper outside onOpt inside opts' = Field
|
selectFieldHelper outside onOpt inside opts' = Field
|
||||||
{ fieldParse = \x -> do
|
{ fieldParse = \x -> do
|
||||||
opts <- opts'
|
opts <- opts'
|
||||||
return $ selectParser opts x
|
return $ selectParser opts x
|
||||||
, fieldView = \theId name val isReq -> do
|
, fieldView = \theId name val isReq -> do
|
||||||
opts <- lift $ liftIOHandler opts'
|
opts <- fmap olOptions $ lift $ liftIOHandler opts'
|
||||||
outside theId name $ do
|
outside theId name $ do
|
||||||
unless isReq $ onOpt theId name $ not $ render opts val `elem` map optionExternalValue opts
|
unless isReq $ onOpt theId name $ not $ render opts val `elem` map optionExternalValue opts
|
||||||
flip mapM_ opts $ \opt -> inside
|
flip mapM_ opts $ \opt -> inside
|
||||||
@ -436,9 +449,9 @@ selectFieldHelper outside onOpt inside opts' = Field
|
|||||||
selectParser opts (s:_) = case s of
|
selectParser opts (s:_) = case s of
|
||||||
"" -> Right Nothing
|
"" -> Right Nothing
|
||||||
"none" -> Right Nothing
|
"none" -> Right Nothing
|
||||||
x -> case listToMaybe $ filter ((== x) . optionExternalValue) opts of
|
x -> case olReadExternal opts x of
|
||||||
Nothing -> Left $ SomeMessage $ MsgInvalidEntry x
|
Nothing -> Left $ SomeMessage $ MsgInvalidEntry x
|
||||||
Just y -> Right $ Just $ optionInternalValue y
|
Just y -> Right $ Just y
|
||||||
|
|
||||||
fileAFormReq :: (RenderMessage master msg, RenderMessage master FormMessage) => FieldSettings msg -> AForm sub master FileInfo
|
fileAFormReq :: (RenderMessage master msg, RenderMessage master FormMessage) => FieldSettings msg -> AForm sub master FileInfo
|
||||||
fileAFormReq fs = AForm $ \(master, langs) menvs ints -> do
|
fileAFormReq fs = AForm $ \(master, langs) menvs ints -> do
|
||||||
@ -472,7 +485,6 @@ fileAFormReq fs = AForm $ \(master, langs) menvs ints -> do
|
|||||||
|
|
||||||
fileAFormOpt :: (RenderMessage master msg, RenderMessage master FormMessage) => FieldSettings msg -> AForm sub master (Maybe FileInfo)
|
fileAFormOpt :: (RenderMessage master msg, RenderMessage master FormMessage) => FieldSettings msg -> AForm sub master (Maybe FileInfo)
|
||||||
fileAFormOpt fs = AForm $ \(master, langs) menvs ints -> do
|
fileAFormOpt fs = AForm $ \(master, langs) menvs ints -> do
|
||||||
liftIO $ print menvs
|
|
||||||
let (name, ints') =
|
let (name, ints') =
|
||||||
case fsName fs of
|
case fsName fs of
|
||||||
Just x -> (x, ints)
|
Just x -> (x, ints)
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
module Yesod.Form.Functions
|
module Yesod.Form.Functions
|
||||||
( -- * Running in Form monad
|
( -- * Running in MForm monad
|
||||||
newFormIdent
|
newFormIdent
|
||||||
, askParams
|
, askParams
|
||||||
, askFiles
|
, askFiles
|
||||||
@ -31,6 +31,7 @@ module Yesod.Form.Functions
|
|||||||
, check
|
, check
|
||||||
, checkBool
|
, checkBool
|
||||||
, checkM
|
, checkM
|
||||||
|
, customErrorMessage
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Yesod.Form.Types
|
import Yesod.Form.Types
|
||||||
@ -61,7 +62,7 @@ import qualified Data.ByteString.Lazy as L
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
-- | Get a unique identifier.
|
-- | Get a unique identifier.
|
||||||
newFormIdent :: Form sub master Text
|
newFormIdent :: MForm sub master Text
|
||||||
newFormIdent = do
|
newFormIdent = do
|
||||||
i <- get
|
i <- get
|
||||||
let i' = incrInts i
|
let i' = incrInts i
|
||||||
@ -71,12 +72,12 @@ newFormIdent = do
|
|||||||
incrInts (IntSingle i) = IntSingle $ i + 1
|
incrInts (IntSingle i) = IntSingle $ i + 1
|
||||||
incrInts (IntCons i is) = (i + 1) `IntCons` is
|
incrInts (IntCons i is) = (i + 1) `IntCons` is
|
||||||
|
|
||||||
formToAForm :: Form sub master (FormResult a, FieldView sub master) -> AForm sub master a
|
formToAForm :: MForm sub master (FormResult a, FieldView sub master) -> AForm sub master a
|
||||||
formToAForm form = AForm $ \(master, langs) env ints -> do
|
formToAForm form = AForm $ \(master, langs) env ints -> do
|
||||||
((a, xml), ints', enc) <- runRWST form (env, master, langs) ints
|
((a, xml), ints', enc) <- runRWST form (env, master, langs) ints
|
||||||
return (a, (:) xml, ints', enc)
|
return (a, (:) xml, ints', enc)
|
||||||
|
|
||||||
aFormToForm :: AForm sub master a -> Form sub master (FormResult a, [FieldView sub master] -> [FieldView sub master])
|
aFormToForm :: AForm sub master a -> MForm sub master (FormResult a, [FieldView sub master] -> [FieldView sub master])
|
||||||
aFormToForm (AForm aform) = do
|
aFormToForm (AForm aform) = do
|
||||||
ints <- get
|
ints <- get
|
||||||
(env, master, langs) <- ask
|
(env, master, langs) <- ask
|
||||||
@ -85,24 +86,24 @@ aFormToForm (AForm aform) = do
|
|||||||
tell enc
|
tell enc
|
||||||
return (a, xml)
|
return (a, xml)
|
||||||
|
|
||||||
askParams :: Form sub master (Maybe Env)
|
askParams :: MForm sub master (Maybe Env)
|
||||||
askParams = do
|
askParams = do
|
||||||
(x, _, _) <- ask
|
(x, _, _) <- ask
|
||||||
return $ liftM fst x
|
return $ liftM fst x
|
||||||
|
|
||||||
askFiles :: Form sub master (Maybe FileEnv)
|
askFiles :: MForm sub master (Maybe FileEnv)
|
||||||
askFiles = do
|
askFiles = do
|
||||||
(x, _, _) <- ask
|
(x, _, _) <- ask
|
||||||
return $ liftM snd x
|
return $ liftM snd x
|
||||||
|
|
||||||
mreq :: (RenderMessage master msg, RenderMessage master FormMessage)
|
mreq :: (RenderMessage master msg, RenderMessage master FormMessage)
|
||||||
=> Field sub master a -> FieldSettings msg -> Maybe a
|
=> Field sub master a -> FieldSettings msg -> Maybe a
|
||||||
-> Form sub master (FormResult a, FieldView sub master)
|
-> MForm sub master (FormResult a, FieldView sub master)
|
||||||
mreq field fs mdef = mhelper field fs mdef (\m l -> FormFailure [renderMessage m l MsgValueRequired]) FormSuccess True
|
mreq field fs mdef = mhelper field fs mdef (\m l -> FormFailure [renderMessage m l MsgValueRequired]) FormSuccess True
|
||||||
|
|
||||||
mopt :: RenderMessage master msg
|
mopt :: RenderMessage master msg
|
||||||
=> Field sub master a -> FieldSettings msg -> Maybe (Maybe a)
|
=> Field sub master a -> FieldSettings msg -> Maybe (Maybe a)
|
||||||
-> Form sub master (FormResult (Maybe a), FieldView sub master)
|
-> MForm sub master (FormResult (Maybe a), FieldView sub master)
|
||||||
mopt field fs mdef = mhelper field fs (join mdef) (const $ const $ FormSuccess Nothing) (FormSuccess . Just) False
|
mopt field fs mdef = mhelper field fs (join mdef) (const $ const $ FormSuccess Nothing) (FormSuccess . Just) False
|
||||||
|
|
||||||
mhelper :: RenderMessage master msg
|
mhelper :: RenderMessage master msg
|
||||||
@ -112,7 +113,7 @@ mhelper :: RenderMessage master msg
|
|||||||
-> (master -> [Text] -> FormResult b) -- ^ on missing
|
-> (master -> [Text] -> FormResult b) -- ^ on missing
|
||||||
-> (a -> FormResult b) -- ^ on success
|
-> (a -> FormResult b) -- ^ on success
|
||||||
-> Bool -- ^ is it required?
|
-> Bool -- ^ is it required?
|
||||||
-> Form sub master (FormResult b, FieldView sub master)
|
-> MForm sub master (FormResult b, FieldView sub master)
|
||||||
|
|
||||||
mhelper Field {..} FieldSettings {..} mdef onMissing onFound isReq = do
|
mhelper Field {..} FieldSettings {..} mdef onMissing onFound isReq = do
|
||||||
mp <- askParams
|
mp <- askParams
|
||||||
@ -156,7 +157,7 @@ aopt :: RenderMessage master msg
|
|||||||
-> AForm sub master (Maybe a)
|
-> AForm sub master (Maybe a)
|
||||||
aopt a b = formToAForm . mopt a b
|
aopt a b = formToAForm . mopt a b
|
||||||
|
|
||||||
runFormGeneric :: MonadIO m => Form 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) -> GGHandler sub master m (a, Enctype)
|
||||||
runFormGeneric form master langs env = liftIOHandler $ evalRWST form (env, master, langs) (IntSingle 1)
|
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
|
-- | This function is used to both initially render a form and to later extract
|
||||||
@ -169,14 +170,14 @@ runFormGeneric form master langs env = liftIOHandler $ evalRWST form (env, maste
|
|||||||
-- the form submit to a POST page. In such a case, both the GET and POST
|
-- the form submit to a POST page. In such a case, both the GET and POST
|
||||||
-- handlers should use 'runFormPost'.
|
-- handlers should use 'runFormPost'.
|
||||||
runFormPost :: RenderMessage master FormMessage
|
runFormPost :: RenderMessage master FormMessage
|
||||||
=> (Html -> Form sub master (FormResult a, xml))
|
=> (Html -> MForm sub master (FormResult a, xml))
|
||||||
-> GHandler sub master ((FormResult a, xml), Enctype)
|
-> GHandler sub master ((FormResult a, xml), Enctype)
|
||||||
runFormPost form = do
|
runFormPost form = do
|
||||||
env <- postEnv
|
env <- postEnv
|
||||||
postHelper form env
|
postHelper form env
|
||||||
|
|
||||||
postHelper :: RenderMessage master FormMessage
|
postHelper :: RenderMessage master FormMessage
|
||||||
=> (Html -> Form sub master (FormResult a, xml))
|
=> (Html -> MForm sub master (FormResult a, xml))
|
||||||
-> Maybe (Env, FileEnv)
|
-> Maybe (Env, FileEnv)
|
||||||
-> GHandler sub master ((FormResult a, xml), Enctype)
|
-> GHandler sub master ((FormResult a, xml), Enctype)
|
||||||
postHelper form env = do
|
postHelper form env = do
|
||||||
@ -203,7 +204,7 @@ postHelper form env = do
|
|||||||
-- general usage, you can stick with @runFormPost@.
|
-- general usage, you can stick with @runFormPost@.
|
||||||
generateFormPost
|
generateFormPost
|
||||||
:: RenderMessage master FormMessage
|
:: RenderMessage master FormMessage
|
||||||
=> (Html -> Form sub master (FormResult a, xml))
|
=> (Html -> MForm sub master (FormResult a, xml))
|
||||||
-> GHandler sub master ((FormResult a, xml), Enctype)
|
-> GHandler sub master ((FormResult a, xml), Enctype)
|
||||||
generateFormPost form = postHelper form Nothing
|
generateFormPost form = postHelper form Nothing
|
||||||
|
|
||||||
@ -219,14 +220,14 @@ postEnv = do
|
|||||||
where
|
where
|
||||||
notEmpty = not . L.null . fileContent
|
notEmpty = not . L.null . fileContent
|
||||||
|
|
||||||
runFormPostNoNonce :: (Html -> Form sub master (FormResult a, xml)) -> GHandler sub master ((FormResult a, xml), Enctype)
|
runFormPostNoNonce :: (Html -> MForm sub master (FormResult a, xml)) -> GHandler sub master ((FormResult a, xml), Enctype)
|
||||||
runFormPostNoNonce form = do
|
runFormPostNoNonce form = do
|
||||||
langs <- languages
|
langs <- languages
|
||||||
m <- getYesod
|
m <- getYesod
|
||||||
env <- postEnv
|
env <- postEnv
|
||||||
runFormGeneric (form mempty) m langs env
|
runFormGeneric (form mempty) m langs env
|
||||||
|
|
||||||
runFormGet :: (Html -> Form sub master a) -> GHandler sub master (a, Enctype)
|
runFormGet :: (Html -> MForm sub master a) -> GHandler sub master (a, Enctype)
|
||||||
runFormGet form = do
|
runFormGet form = do
|
||||||
gets <- liftM reqGetParams getRequest
|
gets <- liftM reqGetParams getRequest
|
||||||
let env =
|
let env =
|
||||||
@ -235,13 +236,13 @@ runFormGet form = do
|
|||||||
Just _ -> Just (Map.unionsWith (++) $ map (\(x, y) -> Map.singleton x [y]) gets, Map.empty)
|
Just _ -> Just (Map.unionsWith (++) $ map (\(x, y) -> Map.singleton x [y]) gets, Map.empty)
|
||||||
getHelper form env
|
getHelper form env
|
||||||
|
|
||||||
generateFormGet :: (Html -> Form sub master a) -> GHandler sub master (a, Enctype)
|
generateFormGet :: (Html -> MForm sub master a) -> GHandler sub master (a, Enctype)
|
||||||
generateFormGet form = getHelper form Nothing
|
generateFormGet form = getHelper form Nothing
|
||||||
|
|
||||||
getKey :: Text
|
getKey :: Text
|
||||||
getKey = "_hasdata"
|
getKey = "_hasdata"
|
||||||
|
|
||||||
getHelper :: (Html -> Form sub master a) -> Maybe (Env, FileEnv) -> GHandler sub master (a, Enctype)
|
getHelper :: (Html -> MForm sub master a) -> Maybe (Env, FileEnv) -> GHandler sub master (a, Enctype)
|
||||||
getHelper form env = do
|
getHelper form env = do
|
||||||
let fragment = [HTML|<input type=hidden name=#{getKey}>|]
|
let fragment = [HTML|<input type=hidden name=#{getKey}>|]
|
||||||
langs <- languages
|
langs <- languages
|
||||||
@ -251,7 +252,7 @@ getHelper form env = do
|
|||||||
type FormRender sub master a =
|
type FormRender sub master a =
|
||||||
AForm sub master a
|
AForm sub master a
|
||||||
-> Html
|
-> Html
|
||||||
-> Form sub master (FormResult a, GWidget sub master ())
|
-> MForm sub master (FormResult a, GWidget sub master ())
|
||||||
|
|
||||||
renderTable, renderDivs :: FormRender sub master a
|
renderTable, renderDivs :: FormRender sub master a
|
||||||
renderTable aform fragment = do
|
renderTable aform fragment = do
|
||||||
@ -309,3 +310,8 @@ checkM f field = field
|
|||||||
Right Nothing -> return $ Right Nothing
|
Right Nothing -> return $ Right Nothing
|
||||||
Right (Just a) -> fmap (either (Left . SomeMessage) (Right . Just)) $ f a
|
Right (Just a) -> fmap (either (Left . SomeMessage) (Right . Just)) $ f a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- | Allows you to overwrite the error message on parse error.
|
||||||
|
customErrorMessage :: SomeMessage master -> Field sub master a -> Field sub master a
|
||||||
|
customErrorMessage msg field = field { fieldParse = \ts -> fmap (either
|
||||||
|
(const $ Left msg) Right) $ fieldParse field ts }
|
||||||
|
|||||||
@ -35,7 +35,7 @@ import Data.Maybe (listToMaybe)
|
|||||||
#define WHAMLET $whamlet
|
#define WHAMLET $whamlet
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
down :: Int -> Form sub master ()
|
down :: Int -> MForm sub master ()
|
||||||
down 0 = return ()
|
down 0 = return ()
|
||||||
down i | i < 0 = error "called down with a negative number"
|
down i | i < 0 = error "called down with a negative number"
|
||||||
down i = do
|
down i = do
|
||||||
@ -43,7 +43,7 @@ down i = do
|
|||||||
put $ IntCons 0 is
|
put $ IntCons 0 is
|
||||||
down $ i - 1
|
down $ i - 1
|
||||||
|
|
||||||
up :: Int -> Form sub master ()
|
up :: Int -> MForm sub master ()
|
||||||
up 0 = return ()
|
up 0 = return ()
|
||||||
up i | i < 0 = error "called down with a negative number"
|
up i | i < 0 = error "called down with a negative number"
|
||||||
up i = do
|
up i = do
|
||||||
@ -98,7 +98,7 @@ inputList label fixXml single mdef = formToAForm $ do
|
|||||||
|
|
||||||
withDelete :: (xml ~ GWidget sub master (), RenderMessage master FormMessage)
|
withDelete :: (xml ~ GWidget sub master (), RenderMessage master FormMessage)
|
||||||
=> AForm sub master a
|
=> AForm sub master a
|
||||||
-> Form sub master (Either xml (FormResult a, [FieldView sub master]))
|
-> MForm sub master (Either xml (FormResult a, [FieldView sub master]))
|
||||||
withDelete af = do
|
withDelete af = do
|
||||||
down 1
|
down 1
|
||||||
deleteName <- newFormIdent
|
deleteName <- newFormIdent
|
||||||
|
|||||||
@ -11,6 +11,7 @@ module Yesod.Form.Types
|
|||||||
, Ints (..)
|
, Ints (..)
|
||||||
-- * Form
|
-- * Form
|
||||||
, Form
|
, Form
|
||||||
|
, MForm
|
||||||
, AForm (..)
|
, AForm (..)
|
||||||
-- * Build forms
|
-- * Build forms
|
||||||
, Field (..)
|
, Field (..)
|
||||||
@ -75,6 +76,8 @@ type FileEnv = Map.Map Text FileInfo
|
|||||||
|
|
||||||
type Lang = Text
|
type Lang = Text
|
||||||
type Form sub master a = RWST (Maybe (Env, FileEnv), master, [Lang]) Enctype Ints (GGHandler sub master IO) a
|
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
|
||||||
|
|
||||||
newtype AForm sub master a = AForm
|
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 -> GGHandler sub master IO (FormResult a, [FieldView sub master] -> [FieldView sub master], Ints, Enctype)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-form
|
name: yesod-form
|
||||||
version: 0.3.3
|
version: 0.3.4
|
||||||
license: BSD3
|
license: BSD3
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Michael Snoyman <michael@snoyman.com>
|
author: Michael Snoyman <michael@snoyman.com>
|
||||||
@ -13,27 +13,27 @@ homepage: http://www.yesodweb.com/
|
|||||||
description: Form handling support for Yesod Web Framework
|
description: Form handling support for Yesod Web Framework
|
||||||
|
|
||||||
library
|
library
|
||||||
build-depends: base >= 4 && < 5
|
build-depends: base >= 4 && < 5
|
||||||
, yesod-core >= 0.9 && < 0.10
|
, yesod-core >= 0.9 && < 0.10
|
||||||
, yesod-persistent >= 0.2 && < 0.3
|
, yesod-persistent >= 0.2 && < 0.3
|
||||||
, time >= 1.1.4 && < 1.3
|
, time >= 1.1.4
|
||||||
, hamlet >= 0.10 && < 0.11
|
, hamlet >= 0.10 && < 0.11
|
||||||
, shakespeare-css >= 0.10 && < 0.11
|
, shakespeare-css >= 0.10 && < 0.11
|
||||||
, shakespeare-js >= 0.10 && < 0.11
|
, shakespeare-js >= 0.10 && < 0.11
|
||||||
, persistent >= 0.6 && < 0.7
|
, persistent >= 0.6 && < 0.7
|
||||||
, yesod-persistent >= 0.2 && < 0.3
|
, yesod-persistent >= 0.2 && < 0.3
|
||||||
, template-haskell
|
, template-haskell
|
||||||
, transformers >= 0.2.2 && < 0.3
|
, transformers >= 0.2.2 && < 0.3
|
||||||
, data-default >= 0.3 && < 0.4
|
, data-default >= 0.3 && < 0.4
|
||||||
, xss-sanitize >= 0.3.0.1 && < 0.4
|
, xss-sanitize >= 0.3.0.1 && < 0.4
|
||||||
, blaze-builder >= 0.2.1 && < 0.4
|
, blaze-builder >= 0.2.1.4 && < 0.4
|
||||||
, network >= 2.2 && < 2.4
|
, network >= 2.2 && < 2.4
|
||||||
, email-validate >= 0.2.6 && < 0.3
|
, email-validate >= 0.2.6 && < 0.3
|
||||||
, blaze-html >= 0.4 && < 0.5
|
, blaze-html >= 0.4.1.3 && < 0.5
|
||||||
, bytestring >= 0.9 && < 0.10
|
, bytestring >= 0.9.1.4 && < 0.10
|
||||||
, text >= 0.7 && < 1.0
|
, text >= 0.9 && < 0.12
|
||||||
, wai >= 0.4 && < 0.5
|
, wai >= 0.4 && < 0.5
|
||||||
, containers >= 0.2 && < 0.5
|
, containers >= 0.2 && < 0.5
|
||||||
exposed-modules: Yesod.Form
|
exposed-modules: Yesod.Form
|
||||||
Yesod.Form.Class
|
Yesod.Form.Class
|
||||||
Yesod.Form.Types
|
Yesod.Form.Types
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{-# LANGUAGE TypeSynonymInstances #-}
|
{-# LANGUAGE TypeSynonymInstances #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
module Yesod.Json
|
module Yesod.Json
|
||||||
( -- * Convert from a JSON value
|
( -- * Convert from a JSON value
|
||||||
@ -23,7 +24,11 @@ import qualified Data.Aeson.Encode as JE
|
|||||||
import Data.Aeson.Encode (fromValue)
|
import Data.Aeson.Encode (fromValue)
|
||||||
import Data.Text (pack)
|
import Data.Text (pack)
|
||||||
import Control.Arrow (first)
|
import Control.Arrow (first)
|
||||||
|
#if MIN_VERSION_aeson(0, 4, 0)
|
||||||
|
import Data.HashMap.Strict (fromList)
|
||||||
|
#else
|
||||||
import Data.Map (fromList)
|
import Data.Map (fromList)
|
||||||
|
#endif
|
||||||
import qualified Data.Vector as V
|
import qualified Data.Vector as V
|
||||||
import Text.Julius (ToJavascript (..))
|
import Text.Julius (ToJavascript (..))
|
||||||
import Data.Text.Lazy.Builder (fromLazyText)
|
import Data.Text.Lazy.Builder (fromLazyText)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-json
|
name: yesod-json
|
||||||
version: 0.2.1
|
version: 0.2.2.1
|
||||||
license: BSD3
|
license: BSD3
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Michael Snoyman <michael@snoyman.com>
|
author: Michael Snoyman <michael@snoyman.com>
|
||||||
@ -13,13 +13,14 @@ homepage: http://www.yesodweb.com/
|
|||||||
description: Generate content for Yesod using the aeson package.
|
description: Generate content for Yesod using the aeson package.
|
||||||
|
|
||||||
library
|
library
|
||||||
build-depends: base >= 4 && < 5
|
build-depends: base >= 4 && < 5
|
||||||
, yesod-core >= 0.9 && < 0.10
|
, yesod-core >= 0.9 && < 0.10
|
||||||
, aeson-native >= 0.3.2.11 && < 0.4
|
, aeson >= 0.3
|
||||||
, text >= 0.8 && < 0.12
|
, text >= 0.8 && < 0.12
|
||||||
, shakespeare-js >= 0.10 && < 0.11
|
, shakespeare-js >= 0.10 && < 0.11
|
||||||
, vector
|
, vector >= 0.9
|
||||||
, containers
|
, containers >= 0.2 && < 0.5
|
||||||
|
, unordered-containers
|
||||||
exposed-modules: Yesod.Json
|
exposed-modules: Yesod.Json
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
|
|
||||||
|
|||||||
234
yesod-mega.cabal
Normal file
234
yesod-mega.cabal
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
name: yesod-mega
|
||||||
|
version: 0.9.3
|
||||||
|
license: BSD3
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Michael Snoyman <michael@snoyman.com>
|
||||||
|
maintainer: Michael Snoyman <michael@snoyman.com>
|
||||||
|
synopsis: Creation of type-safe, RESTful web applications.
|
||||||
|
description:
|
||||||
|
Builds all yesod* repo code at once
|
||||||
|
|
||||||
|
category: Web, Yesod
|
||||||
|
stability: Stable
|
||||||
|
cabal-version: >= 1.8
|
||||||
|
build-type: Simple
|
||||||
|
homepage: http://www.yesodweb.com/
|
||||||
|
|
||||||
|
flag ghc7
|
||||||
|
|
||||||
|
flag threaded
|
||||||
|
default: True
|
||||||
|
description: Build with support for multithreaded execution
|
||||||
|
|
||||||
|
flag test
|
||||||
|
description: Build for use with running tests
|
||||||
|
default: False
|
||||||
|
|
||||||
|
library
|
||||||
|
hs-source-dirs: yesod, yesod-auth, yesod-core, yesod-default, yesod-examples, yesod-form, yesod-json, yesod-newsfeed, yesod-persistent, yesod-sitemap, yesod-static
|
||||||
|
|
||||||
|
exposed-modules:
|
||||||
|
-- yesod
|
||||||
|
Yesod
|
||||||
|
-- yesod-static
|
||||||
|
Yesod.Static
|
||||||
|
-- yesod-persistent
|
||||||
|
Yesod.Persist
|
||||||
|
-- yesod-json
|
||||||
|
Yesod.Json
|
||||||
|
-- yesod-sitemap
|
||||||
|
Yesod.Sitemap
|
||||||
|
|
||||||
|
-- yesod-core
|
||||||
|
Yesod.Content
|
||||||
|
Yesod.Core
|
||||||
|
Yesod.Dispatch
|
||||||
|
Yesod.Handler
|
||||||
|
Yesod.Logger
|
||||||
|
Yesod.Request
|
||||||
|
Yesod.Widget
|
||||||
|
Yesod.Message
|
||||||
|
Yesod.Config
|
||||||
|
Yesod.Internal.TestApi
|
||||||
|
|
||||||
|
-- yesod-form
|
||||||
|
Yesod.Form
|
||||||
|
Yesod.Form.Class
|
||||||
|
Yesod.Form.Types
|
||||||
|
Yesod.Form.Functions
|
||||||
|
Yesod.Form.Input
|
||||||
|
Yesod.Form.Fields
|
||||||
|
Yesod.Form.Jquery
|
||||||
|
Yesod.Form.Nic
|
||||||
|
Yesod.Form.MassInput
|
||||||
|
Yesod.Form.I18n.English
|
||||||
|
Yesod.Form.I18n.Swedish
|
||||||
|
|
||||||
|
-- yesod-auth
|
||||||
|
Yesod.Auth
|
||||||
|
Yesod.Auth.BrowserId
|
||||||
|
Yesod.Auth.Dummy
|
||||||
|
Yesod.Auth.Email
|
||||||
|
Yesod.Auth.Facebook
|
||||||
|
Yesod.Auth.OpenId
|
||||||
|
Yesod.Auth.OAuth
|
||||||
|
Yesod.Auth.Rpxnow
|
||||||
|
Yesod.Auth.HashDB
|
||||||
|
Yesod.Auth.Message
|
||||||
|
Yesod.Auth.Kerberos
|
||||||
|
|
||||||
|
-- yesod-default
|
||||||
|
Yesod.Default.Config
|
||||||
|
Yesod.Default.Main
|
||||||
|
Yesod.Default.Util
|
||||||
|
Yesod.Default.Handlers
|
||||||
|
|
||||||
|
-- yesod-newsfeed
|
||||||
|
Yesod.AtomFeed
|
||||||
|
Yesod.RssFeed
|
||||||
|
Yesod.Feed
|
||||||
|
|
||||||
|
other-modules:
|
||||||
|
-- yesod-newsfeed
|
||||||
|
Yesod.FeedTypes
|
||||||
|
-- yesod-core
|
||||||
|
Yesod.Internal
|
||||||
|
Yesod.Internal.Core
|
||||||
|
Yesod.Internal.Session
|
||||||
|
Yesod.Internal.Request
|
||||||
|
Yesod.Internal.Dispatch
|
||||||
|
Yesod.Internal.RouteParsing
|
||||||
|
|
||||||
|
-- yesod
|
||||||
|
Scaffolding.CodeGen
|
||||||
|
Scaffolding.Scaffolder
|
||||||
|
Devel
|
||||||
|
Build
|
||||||
|
|
||||||
|
|
||||||
|
cpp-options: -DMEGA
|
||||||
|
|
||||||
|
if flag(ghc7)
|
||||||
|
build-depends: base >= 4.3 && < 5
|
||||||
|
cpp-options: -DGHC7
|
||||||
|
else
|
||||||
|
build-depends: base >= 4 && < 4.3
|
||||||
|
|
||||||
|
include-dirs: yesod-auth/include
|
||||||
|
|
||||||
|
if !os(windows)
|
||||||
|
build-depends: unix
|
||||||
|
|
||||||
|
if flag(test)
|
||||||
|
cpp-options: -DTEST
|
||||||
|
|
||||||
|
build-depends:
|
||||||
|
-- yesod
|
||||||
|
Cabal >= 1.8 && < 1.13
|
||||||
|
, shakespeare-text >= 0.10 && < 0.11
|
||||||
|
, filepath >= 1.1 && < 1.3
|
||||||
|
, process
|
||||||
|
, attoparsec >= 0.10
|
||||||
|
-- yesod-sitemap
|
||||||
|
-- empty
|
||||||
|
|
||||||
|
-- yesod-newsfeed
|
||||||
|
, wai-extra >= 0.4.4 && < 0.5
|
||||||
|
|
||||||
|
-- yesod-default
|
||||||
|
, cmdargs >= 0.8 && < 0.9
|
||||||
|
|
||||||
|
-- yesod-auth
|
||||||
|
, authenticate >= 0.10.3 && < 0.11
|
||||||
|
, control-monad-attempt >= 0.3.0 && < 0.4
|
||||||
|
, mime-mail >= 0.3 && < 0.4
|
||||||
|
, SHA >= 1.4.1.3 && < 1.6
|
||||||
|
, http-enumerator >= 0.6 && < 0.8
|
||||||
|
, pwstore-fast >= 2.2 && < 3
|
||||||
|
, old-time >= 1.0
|
||||||
|
, base64-bytestring >= 0.1.0.1 && < 0.2
|
||||||
|
, pureMD5 >= 2.1.0.3 && < 2.2
|
||||||
|
, cereal >= 0.3 && < 0.4
|
||||||
|
, wai-app-static >= 0.3.2.1 && < 0.4
|
||||||
|
, file-embed >= 0.0.4.1 && < 0.5
|
||||||
|
, unix-compat >= 0.2 && < 0.3
|
||||||
|
, enumerator >= 0.4.14 && < 0.5
|
||||||
|
, 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 && < 0.4
|
||||||
|
, network >= 2.2 && < 2.4
|
||||||
|
, email-validate >= 0.2.6 && < 0.3
|
||||||
|
, persistent >= 0.6 && < 0.7
|
||||||
|
, persistent-template >= 0.6 && < 0.7
|
||||||
|
, failure >= 0.1 && < 0.2
|
||||||
|
, warp >= 0.4 && < 0.5
|
||||||
|
, wai >= 0.4 && < 0.5
|
||||||
|
, wai-extra >= 0.4.1 && < 0.5
|
||||||
|
, time >= 1.1.4
|
||||||
|
, bytestring >= 0.9.1.4 && < 0.12
|
||||||
|
, text >= 0.9 && < 0.12
|
||||||
|
, template-haskell
|
||||||
|
, path-pieces >= 0.0 && < 0.1
|
||||||
|
, hamlet >= 0.10 && < 0.11
|
||||||
|
, shakespeare >= 0.10 && < 0.11
|
||||||
|
, shakespeare-js >= 0.10 && < 0.11
|
||||||
|
, shakespeare-css >= 0.10 && < 0.11
|
||||||
|
, blaze-builder >= 0.2.1 && < 0.4
|
||||||
|
, clientsession >= 0.7.3.1 && < 0.8
|
||||||
|
, random >= 1.0.0.2 && < 1.1
|
||||||
|
, old-locale >= 1.0.0.2 && < 1.1
|
||||||
|
, failure >= 0.1 && < 0.2
|
||||||
|
, containers >= 0.2 && < 0.5
|
||||||
|
, monad-control >= 0.2 && < 0.3
|
||||||
|
, cookie >= 0.3 && < 0.4
|
||||||
|
, blaze-html >= 0.4.1.3 && < 0.5
|
||||||
|
, http-types >= 0.6.5 && < 0.7
|
||||||
|
, case-insensitive >= 0.2 && < 0.4
|
||||||
|
, parsec >= 2.0 && < 3.2
|
||||||
|
, directory >= 1.0 && < 1.2
|
||||||
|
, data-object >= 0.3 && < 0.4
|
||||||
|
, data-object-yaml >= 0.3 && < 0.4
|
||||||
|
, strict-concurrency >= 0.2.4 && < 0.2.5
|
||||||
|
, vector >= 0.9 && < 0.10
|
||||||
|
, aeson >= 0.3
|
||||||
|
|
||||||
|
ghc-options: -Wall
|
||||||
|
|
||||||
|
test-suite tests
|
||||||
|
type: exitcode-stdio-1.0
|
||||||
|
main-is: main.hs
|
||||||
|
hs-source-dirs:
|
||||||
|
test
|
||||||
|
yesod-core/test
|
||||||
|
yesod-static/test
|
||||||
|
|
||||||
|
if flag(ghc7)
|
||||||
|
type: exitcode-stdio-1.0
|
||||||
|
build-depends: base >= 4.3 && < 5
|
||||||
|
cpp-options: -DGHC7
|
||||||
|
main-is: test.hs
|
||||||
|
else
|
||||||
|
type: exitcode-stdio-1.0
|
||||||
|
build-depends: base >= 4 && < 4.3
|
||||||
|
main-is: test.hs
|
||||||
|
cpp-options: -DTEST
|
||||||
|
build-depends: yesod-mega
|
||||||
|
,hspec >= 0.8 && < 0.10
|
||||||
|
,wai-test >= 0.1.2 && < 0.2
|
||||||
|
,wai
|
||||||
|
,bytestring
|
||||||
|
,hamlet
|
||||||
|
,shakespeare-css
|
||||||
|
,shakespeare-js
|
||||||
|
,text
|
||||||
|
,http-types
|
||||||
|
, random
|
||||||
|
,HUnit
|
||||||
|
,QuickCheck >= 2 && < 3
|
||||||
|
, enumerator
|
||||||
|
ghc-options: -Wall
|
||||||
|
|
||||||
|
source-repository head
|
||||||
|
type: git
|
||||||
|
location: git://github.com/yesodweb/yesod.git
|
||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-newsfeed
|
name: yesod-newsfeed
|
||||||
version: 0.3.1
|
version: 0.3.2
|
||||||
license: BSD3
|
license: BSD3
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Michael Snoyman, Patrick Brisbin
|
author: Michael Snoyman, Patrick Brisbin
|
||||||
@ -13,12 +13,12 @@ homepage: http://www.yesodweb.com/
|
|||||||
description: Helper functions and data types for producing News feeds.
|
description: Helper functions and data types for producing News feeds.
|
||||||
|
|
||||||
library
|
library
|
||||||
build-depends: base >= 4 && < 5
|
build-depends: base >= 4 && < 5
|
||||||
, yesod-core >= 0.9 && < 0.10
|
, yesod-core >= 0.9 && < 0.10
|
||||||
, time >= 1.1.4 && < 1.3
|
, time >= 1.1.4
|
||||||
, hamlet >= 0.10 && < 0.11
|
, hamlet >= 0.10 && < 0.11
|
||||||
, bytestring >= 0.9 && < 0.10
|
, bytestring >= 0.9.1.4 && < 0.10
|
||||||
, text >= 0.9 && < 1.0
|
, text >= 0.9 && < 0.12
|
||||||
exposed-modules: Yesod.AtomFeed
|
exposed-modules: Yesod.AtomFeed
|
||||||
, Yesod.RssFeed
|
, Yesod.RssFeed
|
||||||
, Yesod.Feed
|
, Yesod.Feed
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-persistent
|
name: yesod-persistent
|
||||||
version: 0.2.1
|
version: 0.2.2
|
||||||
license: BSD3
|
license: BSD3
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Michael Snoyman <michael@snoyman.com>
|
author: Michael Snoyman <michael@snoyman.com>
|
||||||
@ -18,7 +18,7 @@ library
|
|||||||
, persistent >= 0.6 && < 0.7
|
, persistent >= 0.6 && < 0.7
|
||||||
, persistent-template >= 0.6 && < 0.7
|
, persistent-template >= 0.6 && < 0.7
|
||||||
, failure >= 0.1 && < 0.2
|
, failure >= 0.1 && < 0.2
|
||||||
, transformers >= 0.2 && < 0.3
|
, transformers >= 0.2.2 && < 0.3
|
||||||
exposed-modules: Yesod.Persist
|
exposed-modules: Yesod.Persist
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-sitemap
|
name: yesod-sitemap
|
||||||
version: 0.2.1
|
version: 0.2.2
|
||||||
license: BSD3
|
license: BSD3
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Michael Snoyman <michael@snoyman.com>
|
author: Michael Snoyman <michael@snoyman.com>
|
||||||
@ -15,7 +15,7 @@ description: Generate XML sitemaps.
|
|||||||
library
|
library
|
||||||
build-depends: base >= 4 && < 5
|
build-depends: base >= 4 && < 5
|
||||||
, yesod-core >= 0.9 && < 0.10
|
, yesod-core >= 0.9 && < 0.10
|
||||||
, time >= 1.1.4 && < 1.3
|
, time >= 1.1.4
|
||||||
, hamlet >= 0.10 && < 0.11
|
, hamlet >= 0.10 && < 0.11
|
||||||
exposed-modules: Yesod.Sitemap
|
exposed-modules: Yesod.Sitemap
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
|
|||||||
@ -29,9 +29,13 @@ module Yesod.Static
|
|||||||
, embed
|
, embed
|
||||||
-- * Template Haskell helpers
|
-- * Template Haskell helpers
|
||||||
, staticFiles
|
, staticFiles
|
||||||
|
, staticFilesList
|
||||||
, publicFiles
|
, publicFiles
|
||||||
-- * Hashing
|
-- * Hashing
|
||||||
, base64md5
|
, base64md5
|
||||||
|
#ifdef TEST
|
||||||
|
, getFileListPieces
|
||||||
|
#endif
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Prelude hiding (FilePath)
|
import Prelude hiding (FilePath)
|
||||||
@ -64,6 +68,9 @@ import qualified Data.ByteString as S
|
|||||||
import Network.HTTP.Types (status301)
|
import Network.HTTP.Types (status301)
|
||||||
import System.PosixCompat.Files (getFileStatus, modificationTime)
|
import System.PosixCompat.Files (getFileStatus, modificationTime)
|
||||||
import System.Posix.Types (EpochTime)
|
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 Network.Wai.Application.Static
|
import Network.Wai.Application.Static
|
||||||
( StaticSettings (..)
|
( StaticSettings (..)
|
||||||
@ -155,6 +162,25 @@ getFileListPieces = flip go id
|
|||||||
staticFiles :: Prelude.FilePath -> Q [Dec]
|
staticFiles :: Prelude.FilePath -> Q [Dec]
|
||||||
staticFiles dir = mkStaticFiles dir
|
staticFiles dir = mkStaticFiles dir
|
||||||
|
|
||||||
|
-- | Same as 'staticFiles', but takes an explicit list of files to create
|
||||||
|
-- identifiers for. The files are given relative to the static folder. For
|
||||||
|
-- example, to get the files \"static/js/jquery.js\" and
|
||||||
|
-- \"static/css/normalize.css\", you would use:
|
||||||
|
--
|
||||||
|
-- > staticFilesList "static" ["js/jquery.js"], ["css/normalize.css"]]
|
||||||
|
--
|
||||||
|
-- This can be useful when you have a very large number of static files, but
|
||||||
|
-- only need to refer to a few of them from Haskell.
|
||||||
|
staticFilesList :: Prelude.FilePath -> [Prelude.FilePath] -> Q [Dec]
|
||||||
|
staticFilesList dir fs =
|
||||||
|
mkStaticFilesList dir (map split fs) "StaticRoute" True
|
||||||
|
where
|
||||||
|
split :: Prelude.FilePath -> [String]
|
||||||
|
split [] = []
|
||||||
|
split x =
|
||||||
|
let (a, b) = break (== '/') x
|
||||||
|
in a : split (drop 1 b)
|
||||||
|
|
||||||
-- | like staticFiles, but doesn't append an etag to the query string
|
-- | like staticFiles, but doesn't append an etag to the query string
|
||||||
-- This will compile faster, but doesn't achieve as great of caching.
|
-- This will compile faster, but doesn't achieve as great of caching.
|
||||||
-- The browser can avoid downloading the file, but it always needs to send a request with the etag value or the last-modified value to the server to see if its copy is up to dat
|
-- The browser can avoid downloading the file, but it always needs to send a request with the etag value or the last-modified value to the server to see if its copy is up to dat
|
||||||
@ -212,6 +238,15 @@ mkStaticFiles' :: Prelude.FilePath -- ^ static directory
|
|||||||
-> Q [Dec]
|
-> Q [Dec]
|
||||||
mkStaticFiles' fp routeConName makeHash = do
|
mkStaticFiles' fp routeConName makeHash = do
|
||||||
fs <- qRunIO $ getFileListPieces fp
|
fs <- qRunIO $ getFileListPieces fp
|
||||||
|
mkStaticFilesList fp fs routeConName makeHash
|
||||||
|
|
||||||
|
mkStaticFilesList
|
||||||
|
:: Prelude.FilePath -- ^ static directory
|
||||||
|
-> [[String]] -- ^ list of files to create identifiers for
|
||||||
|
-> String -- ^ route constructor "StaticRoute"
|
||||||
|
-> Bool -- ^ append checksum query parameter
|
||||||
|
-> Q [Dec]
|
||||||
|
mkStaticFilesList fp fs routeConName makeHash = do
|
||||||
concat `fmap` mapM mkRoute fs
|
concat `fmap` mapM mkRoute fs
|
||||||
where
|
where
|
||||||
replace' c
|
replace' c
|
||||||
@ -233,7 +268,6 @@ mkStaticFiles' fp routeConName makeHash = do
|
|||||||
pack' <- [|pack|]
|
pack' <- [|pack|]
|
||||||
qs <- if makeHash
|
qs <- if makeHash
|
||||||
then do hash <- qRunIO $ base64md5File $ pathFromRawPieces fp f
|
then do hash <- qRunIO $ base64md5File $ pathFromRawPieces fp f
|
||||||
-- FIXME hash <- qRunIO . calcHash $ fp ++ '/' : intercalate "/" f
|
|
||||||
[|[(pack $(lift hash), mempty)]|]
|
[|[(pack $(lift hash), mempty)]|]
|
||||||
else return $ ListE []
|
else return $ ListE []
|
||||||
return
|
return
|
||||||
@ -243,22 +277,35 @@ mkStaticFiles' fp routeConName makeHash = do
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
-- don't use L.readFile here, since it doesn't close handles quickly enough if
|
||||||
|
-- there are lots of files in the static folder, it will cause exhausted file
|
||||||
|
-- descriptors
|
||||||
base64md5File :: Prelude.FilePath -> IO String
|
base64md5File :: Prelude.FilePath -> IO String
|
||||||
base64md5File file = do
|
base64md5File file = do
|
||||||
contents <- L.readFile file
|
bss <- E.run_ $ EB.enumFile file E.$$ EL.consume
|
||||||
return $ base64md5 contents
|
return $ base64md5 $ L.fromChunks bss
|
||||||
|
-- FIXME I'd like something streaming instead
|
||||||
|
{-
|
||||||
|
fmap (base64 . finalize) $ E.run_ $
|
||||||
|
EB.enumFile file E.$$ EL.fold go (md5InitialContext, "")
|
||||||
|
where
|
||||||
|
go (context, prev) next = (md5Update context prev, next)
|
||||||
|
finalize (context, end) = md5Finalize context end
|
||||||
|
-}
|
||||||
|
|
||||||
-- | md5-hashes the given lazy bytestring and returns the hash as
|
-- | md5-hashes the given lazy bytestring and returns the hash as
|
||||||
-- base64url-encoded string.
|
-- base64url-encoded string.
|
||||||
--
|
--
|
||||||
-- This function returns the first 8 characters of the hash.
|
-- This function returns the first 8 characters of the hash.
|
||||||
base64md5 :: L.ByteString -> String
|
base64md5 :: L.ByteString -> String
|
||||||
base64md5 = map tr
|
base64md5 = base64 . md5
|
||||||
. take 8
|
|
||||||
. S8.unpack
|
base64 :: MD5Digest -> String
|
||||||
. Data.ByteString.Base64.encode
|
base64 = map tr
|
||||||
. Data.Serialize.encode
|
. take 8
|
||||||
. md5
|
. S8.unpack
|
||||||
|
. Data.ByteString.Base64.encode
|
||||||
|
. Data.Serialize.encode
|
||||||
where
|
where
|
||||||
tr '+' = '-'
|
tr '+' = '-'
|
||||||
tr '/' = '_'
|
tr '/' = '_'
|
||||||
|
|||||||
16
yesod-static/test/YesodStaticTest.hs
Normal file
16
yesod-static/test/YesodStaticTest.hs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module YesodStaticTest (specs) where
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import Test.HUnit ( (@?=) )
|
||||||
|
import Test.Hspec.HUnit ( )
|
||||||
|
|
||||||
|
import Yesod.Static (getFileListPieces)
|
||||||
|
|
||||||
|
specs :: [Specs]
|
||||||
|
specs = [
|
||||||
|
describe "get file list" [
|
||||||
|
it "pieces" $ do
|
||||||
|
x <- getFileListPieces "test/fs"
|
||||||
|
x @?= [["foo"], ["bar", "baz"]]
|
||||||
|
]
|
||||||
|
]
|
||||||
25
yesod-static/test/unicode/LICENSE
Normal file
25
yesod-static/test/unicode/LICENSE
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
The following license covers this documentation, and the source code, except
|
||||||
|
where otherwise indicated.
|
||||||
|
|
||||||
|
Copyright 2010, Michael Snoyman. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR
|
||||||
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||||
|
EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||||
|
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||||
|
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
7
yesod-static/test/unicode/Setup.lhs
Executable file
7
yesod-static/test/unicode/Setup.lhs
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env runhaskell
|
||||||
|
|
||||||
|
> module Main where
|
||||||
|
> import Distribution.Simple
|
||||||
|
|
||||||
|
> main :: IO ()
|
||||||
|
> main = defaultMain
|
||||||
|
Before Width: | Height: | Size: 891 B After Width: | Height: | Size: 891 B |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 683 B After Width: | Height: | Size: 683 B |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user