merge master, switch to optparse-applicative
This commit is contained in:
commit
75b8dc4457
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,5 +6,4 @@ dist
|
|||||||
client_session_key.aes
|
client_session_key.aes
|
||||||
cabal-dev/
|
cabal-dev/
|
||||||
yesod/foobar/
|
yesod/foobar/
|
||||||
yesod-platform/yesod-platform.cabal
|
|
||||||
.virthualenv
|
.virthualenv
|
||||||
|
|||||||
7
.travis.yml
Normal file
7
.travis.yml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
language: haskell
|
||||||
|
|
||||||
|
install:
|
||||||
|
- cabal install mega-sdist hspec cabal-meta cabal-src
|
||||||
|
- cabal-meta install
|
||||||
|
|
||||||
|
script: mega-sdist --test
|
||||||
39
LICENSE
39
LICENSE
@ -1,25 +1,20 @@
|
|||||||
The following license covers this documentation, and the source code, except
|
Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
|
||||||
where otherwise indicated.
|
|
||||||
|
|
||||||
Copyright 2010, Michael Snoyman. All rights reserved.
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
The above copyright notice and this permission notice shall be
|
||||||
modification, are permitted provided that the following conditions are met:
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
list of conditions and the following disclaimer.
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
this list of conditions and the following disclaimer in the documentation
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
and/or other materials provided with the distribution.
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
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.
|
|
||||||
|
|||||||
121
README.md
121
README.md
@ -1,3 +1,5 @@
|
|||||||
|
# Yesod
|
||||||
|
|
||||||
An advanced web framework using the Haskell programming language. Featuring:
|
An advanced web framework using the Haskell programming language. Featuring:
|
||||||
|
|
||||||
* safety & security guaranteed at compile time
|
* safety & security guaranteed at compile time
|
||||||
@ -9,99 +11,118 @@ An advanced web framework using the Haskell programming language. Featuring:
|
|||||||
* this is built in to the Haskell programming language (like Erlang)
|
* this is built in to the Haskell programming language (like Erlang)
|
||||||
* handles a greater concurrent load than any other web application server
|
* handles a greater concurrent load than any other web application server
|
||||||
|
|
||||||
## Learn more: http://yesodweb.com/
|
# Learn more: http://yesodweb.com/
|
||||||
|
|
||||||
## Installation: http://www.yesodweb.com/page/five-minutes
|
## Install the latests stable Yesod: http://www.yesodweb.com/page/quickstart
|
||||||
|
|
||||||
cabal update && cabal install yesod
|
cabal update && cabal install yesod
|
||||||
|
|
||||||
## Create a new project after installing
|
### Create a new project after installing
|
||||||
|
|
||||||
yesod init
|
yesod init
|
||||||
|
|
||||||
|
Your application is a cabal package and you use `cabal` to install its dependencies.
|
||||||
|
|
||||||
|
# Installing & isolation
|
||||||
|
|
||||||
|
Install conflicts are unfortunately common in Haskell development.
|
||||||
|
If you are not using any sandbox tools, you may discover that some of the other haskell installs on your system are broken.
|
||||||
|
You can prevent this by using sandbox tools: `cabal-dev` or `virthualenv`, now being renamed to `hsenv`.
|
||||||
|
|
||||||
|
Isolating an entire project with a virtual machine is also a great idea, you just need some tools to help that process.
|
||||||
|
[Vagrant](http://vagrantup.com) is a great tool for that and there is a [Haskell Platform installer](https://bitbucket.org/puffnfresh/vagrant-haskell-heroku) for it.
|
||||||
|
|
||||||
## Using cabal-dev
|
## Using cabal-dev
|
||||||
|
|
||||||
cabal-dev creates a sandboxed environment for an individual cabal package.
|
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 which will use the sandbox.
|
||||||
Instead of using the `cabal` command, use the `cabal-dev` command.
|
|
||||||
|
|
||||||
Use `yesod-devel --dev` when developing your application.
|
Use `yesod-devel --dev` when developing your application.
|
||||||
|
|
||||||
## Installing the latest development version from github
|
|
||||||
|
|
||||||
Yesod is broken up into 4 separate code repositories each built upon many smaller packages.
|
|
||||||
|
|
||||||
Install conflicts are unfortunately common in Haskell development.
|
## Installing the latest development version from github for use with your application
|
||||||
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.
|
cabal update
|
||||||
|
cabal install cabal-meta cabal-src
|
||||||
|
|
||||||
### virthualenv
|
In your application folder, create a `sources.txt` file with the following contents:
|
||||||
|
|
||||||
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.
|
https://github.com/yesodweb/yesod
|
||||||
|
https://github.com/yesodweb/shakespeare
|
||||||
|
https://github.com/yesodweb/persistent
|
||||||
|
https://github.com/yesodweb/wai
|
||||||
|
|
||||||
virthualenv will not work on Windows - Windows users should use only cabal-dev.
|
`./` means build your app. The yesod repos will be cloned and placed in a `vendor` repo.
|
||||||
|
Now run: `cabal-meta install`. If you use `cabal-dev`, run `cabal-meta --dev install`
|
||||||
|
|
||||||
|
This should work almost all of the time. You can read more on [cabal-meta](https://github.com/yesodweb/cabal-meta)
|
||||||
|
If you aren't building from an application, remove the `./` and create a new directory for your sources.txt first.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## virthualenv
|
||||||
|
|
||||||
|
We recommend using [virthualenv](http://hackage.haskell.org/package/virthualenv)/[hsenv](https://github.com/Paczesiowa/hsenv) when hacking on Yesod from Linux. This is optional, but prevents your custom build of Yesod from interfering with your currently installed cabal packages.
|
||||||
|
|
||||||
|
virthualenv will not work on Windows and maybe not Mac. Use cabal-dev instead
|
||||||
|
|
||||||
* virthualenv creates an isolated environment like cabal-dev
|
* virthualenv creates an isolated environment like cabal-dev
|
||||||
* virthualenv works at the shell level, so every shell must activate the virthualenv
|
* virthualenv works at the shell level, so every shell must activate the virthualenv
|
||||||
* cabal-dev by default isolates a single cabal package, but virthualenv isolates multiple packages together.
|
* cabal-dev by default isolates a single cabal package, but virthualenv isolates multiple packages together.
|
||||||
* cabal-dev can isolate multiple packages together by using the -s sandbox argument
|
* cabal-dev can isolate multiple packages together by using the -s sandbox argument
|
||||||
|
|
||||||
To just install Yesod from github, we only need cabal-dev. For hacking we prefer virthualenv: it is more convenient to just use normal cabal commands rather than `cabal-dev -s`.
|
|
||||||
|
|
||||||
|
## cabal-src
|
||||||
|
|
||||||
### cabal-src
|
The cabal-src tool helps resolve dependency conflicts when installing local packages.
|
||||||
|
This capability is already built in if you are using cabal-dev or cabal-meta. Otherwise install cabal-src with:
|
||||||
Michael Snoyman just released the cabal-src tool, which helps resolve dependency conflicts when installing local packages. This capability is already built in if you are using cabal-dev. Otherwise install it with:
|
|
||||||
|
|
||||||
cabal install cabal-src
|
cabal install cabal-src
|
||||||
|
|
||||||
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.
|
Whenever you would use `cabal install` to install a local package, use `cabal-src-install` instead.
|
||||||
|
Our installer script now uses cabal-src-install when it is available.
|
||||||
|
|
||||||
|
|
||||||
### Building Yesod
|
## Cloning the repos
|
||||||
|
|
||||||
|
The above instructions for building the latest should work well.
|
||||||
|
But you can clone the repos without the help of cabal-meta:
|
||||||
|
|
||||||
~~~ { .bash }
|
~~~ { .bash }
|
||||||
# update your package database if you haven't recently
|
for repo in shakespeare persistent wai yesod; do
|
||||||
cabal update
|
|
||||||
# install required libraries
|
|
||||||
cabal install Cabal cabal-install
|
|
||||||
|
|
||||||
# use cabal-dev
|
|
||||||
cabal install cabal-dev
|
|
||||||
|
|
||||||
# or use virthualenv
|
|
||||||
cabal install cabal-src virthualenv
|
|
||||||
cd yesodweb # the folder where you put the yesod, persistent, hamlet & wai repos
|
|
||||||
virthualenv --name=yesod
|
|
||||||
. .virthualenv/bin/activate
|
|
||||||
|
|
||||||
# clone and install all repos
|
|
||||||
# see below about first using virthualenv/cabal-dev before running ./scripts/install
|
|
||||||
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
|
||||||
./scripts/install
|
|
||||||
)
|
)
|
||||||
done
|
done
|
||||||
|
~~~~
|
||||||
|
|
||||||
|
## Building your changes to Yesod
|
||||||
|
|
||||||
|
Yesod is composed of 4 "mega-repos", each with multiple cabal packages. `./script/install` will run tests against each package and install each package.
|
||||||
|
|
||||||
|
### install package in all repos
|
||||||
|
|
||||||
|
~~~ { .bash }
|
||||||
|
for repo in shakespeare persistent wai yesod; do
|
||||||
|
pushd $repo
|
||||||
|
./scripts/install
|
||||||
|
popd
|
||||||
|
done
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
### Clean build (sometimes necessary)
|
||||||
#### installing repo packages
|
|
||||||
|
|
||||||
~~~ { .bash }
|
~~~ { .bash }
|
||||||
# install and test all packages in a repo
|
|
||||||
./scripts/install
|
|
||||||
|
|
||||||
# If things seem weird, you may need to do a clean.
|
|
||||||
./scripts/install --clean
|
./scripts/install --clean
|
||||||
|
~~~
|
||||||
|
|
||||||
|
### Building individual packages
|
||||||
|
|
||||||
|
~~~ { .bash }
|
||||||
# move to the individual package you are working on
|
# move to the individual package you are working on
|
||||||
cd shakespeare-text
|
cd shakespeare-text
|
||||||
|
|
||||||
@ -110,11 +131,3 @@ cabal configure -ftest --enable-tests
|
|||||||
cabal build
|
cabal build
|
||||||
cabal test
|
cabal test
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
|
||||||
### Use your development version of Yesod in your application
|
|
||||||
|
|
||||||
Note that we have recommended to you to install Yesod into a sandboxed virthualenv environment.
|
|
||||||
This is great for development, but when you want to use these development versions in your application that means they are not available through your user/global cabal database for your application.
|
|
||||||
You should just continue to use your yesod virthualenv shell for your application.
|
|
||||||
You can also use the same`cabal-dev shared sandbox.
|
|
||||||
|
|||||||
2
scripts
2
scripts
@ -1 +1 @@
|
|||||||
Subproject commit eba05a0b5fe121883969f8fa9b7f7669592430a4
|
Subproject commit e1128e3eacb21cb4a59c00f30abf536c8ba66893
|
||||||
@ -10,4 +10,3 @@
|
|||||||
./yesod-default
|
./yesod-default
|
||||||
./yesod-test
|
./yesod-test
|
||||||
./yesod
|
./yesod
|
||||||
./yesod-test
|
|
||||||
|
|||||||
@ -1,25 +1,20 @@
|
|||||||
The following license covers this documentation, and the source code, except
|
Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
|
||||||
where otherwise indicated.
|
|
||||||
|
|
||||||
Copyright 2010, Michael Snoyman. All rights reserved.
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
The above copyright notice and this permission notice shall be
|
||||||
modification, are permitted provided that the following conditions are met:
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
list of conditions and the following disclaimer.
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
this list of conditions and the following disclaimer in the documentation
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
and/or other materials provided with the distribution.
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
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.
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-auth-oauth
|
name: yesod-auth-oauth
|
||||||
version: 1.0.0
|
version: 1.1.0.0
|
||||||
license: BSD3
|
license: BSD3
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Hiromi Ishii
|
author: Hiromi Ishii
|
||||||
@ -10,8 +10,7 @@ stability: Stable
|
|||||||
cabal-version: >= 1.6.0
|
cabal-version: >= 1.6.0
|
||||||
build-type: Simple
|
build-type: Simple
|
||||||
homepage: http://www.yesodweb.com/
|
homepage: http://www.yesodweb.com/
|
||||||
extra-source-files: include/qq.h
|
description: Oauth Authentication for Yesod.
|
||||||
description: Authentication for Yesod.
|
|
||||||
|
|
||||||
flag ghc7
|
flag ghc7
|
||||||
|
|
||||||
@ -21,13 +20,13 @@ library
|
|||||||
cpp-options: -DGHC7
|
cpp-options: -DGHC7
|
||||||
else
|
else
|
||||||
build-depends: base >= 4 && < 4.3
|
build-depends: base >= 4 && < 4.3
|
||||||
build-depends: authenticate-oauth >= 1.3 && < 1.4
|
build-depends: authenticate-oauth >= 1.4 && < 1.5
|
||||||
, bytestring >= 0.9.1.4 && < 0.10
|
, bytestring >= 0.9.1.4 && < 0.10
|
||||||
, yesod-core >= 1.0 && < 1.1
|
, yesod-core >= 1.1 && < 1.2
|
||||||
, yesod-auth >= 1.0 && < 1.1
|
, yesod-auth >= 1.1 && < 1.2
|
||||||
, text >= 0.7 && < 0.12
|
, text >= 0.7 && < 0.12
|
||||||
, yesod-form >= 1.0 && < 1.1
|
, yesod-form >= 1.1 && < 1.2
|
||||||
, transformers >= 0.2.2 && < 0.3
|
, transformers >= 0.2.2 && < 0.4
|
||||||
exposed-modules: Yesod.Auth.OAuth
|
exposed-modules: Yesod.Auth.OAuth
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
|
|
||||||
|
|||||||
@ -1,25 +1,20 @@
|
|||||||
The following license covers this documentation, and the source code, except
|
Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
|
||||||
where otherwise indicated.
|
|
||||||
|
|
||||||
Copyright 2010, Michael Snoyman. All rights reserved.
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
The above copyright notice and this permission notice shall be
|
||||||
modification, are permitted provided that the following conditions are met:
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
list of conditions and the following disclaimer.
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
this list of conditions and the following disclaimer in the documentation
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
and/or other materials provided with the distribution.
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
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.
|
|
||||||
|
|||||||
@ -149,7 +149,10 @@ setCreds doRedirects creds = do
|
|||||||
Nothing ->
|
Nothing ->
|
||||||
when doRedirects $ do
|
when doRedirects $ do
|
||||||
case authRoute y of
|
case authRoute y of
|
||||||
Nothing -> do rh <- defaultLayout $ toWidget [shamlet| <h1>Invalid login |]
|
Nothing -> do rh <- defaultLayout $ toWidget [shamlet|
|
||||||
|
$newline never
|
||||||
|
<h1>Invalid login
|
||||||
|
|]
|
||||||
sendResponse rh
|
sendResponse rh
|
||||||
Just ar -> do setMessageI Msg.InvalidLogin
|
Just ar -> do setMessageI Msg.InvalidLogin
|
||||||
redirect ar
|
redirect ar
|
||||||
@ -168,6 +171,7 @@ getCheckR = do
|
|||||||
where
|
where
|
||||||
html' creds =
|
html' creds =
|
||||||
[shamlet|
|
[shamlet|
|
||||||
|
$newline never
|
||||||
<h1>Authentication Status
|
<h1>Authentication Status
|
||||||
$maybe _ <- creds
|
$maybe _ <- creds
|
||||||
<p>Logged in.
|
<p>Logged in.
|
||||||
|
|||||||
@ -62,6 +62,7 @@ helper maudience = AuthPlugin
|
|||||||
, apLogin = \toMaster -> do
|
, apLogin = \toMaster -> do
|
||||||
addScriptRemote browserIdJs
|
addScriptRemote browserIdJs
|
||||||
toWidget [hamlet|
|
toWidget [hamlet|
|
||||||
|
$newline never
|
||||||
<p>
|
<p>
|
||||||
<a href="javascript:navigator.id.getVerifiedEmail(function(a){if(a)document.location='@{toMaster complete}/'+a});">
|
<a href="javascript:navigator.id.getVerifiedEmail(function(a){if(a)document.location='@{toMaster complete}/'+a});">
|
||||||
<img src="https://browserid.org/i/sign_in_green.png">
|
<img src="https://browserid.org/i/sign_in_green.png">
|
||||||
|
|||||||
@ -24,6 +24,7 @@ authDummy =
|
|||||||
url = PluginR "dummy" []
|
url = PluginR "dummy" []
|
||||||
login authToMaster =
|
login authToMaster =
|
||||||
toWidget [hamlet|
|
toWidget [hamlet|
|
||||||
|
$newline never
|
||||||
<form method="post" action="@{authToMaster url}">
|
<form method="post" action="@{authToMaster url}">
|
||||||
Your new identifier is: #
|
Your new identifier is: #
|
||||||
<input type="text" name="ident">
|
<input type="text" name="ident">
|
||||||
|
|||||||
@ -79,6 +79,7 @@ authEmail :: YesodAuthEmail m => AuthPlugin m
|
|||||||
authEmail =
|
authEmail =
|
||||||
AuthPlugin "email" dispatch $ \tm ->
|
AuthPlugin "email" dispatch $ \tm ->
|
||||||
[whamlet|
|
[whamlet|
|
||||||
|
$newline never
|
||||||
<form method="post" action="@{tm loginR}">
|
<form method="post" action="@{tm loginR}">
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
@ -112,6 +113,7 @@ getRegisterR = do
|
|||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
setTitleI Msg.RegisterLong
|
setTitleI Msg.RegisterLong
|
||||||
[whamlet|
|
[whamlet|
|
||||||
|
$newline never
|
||||||
<p>_{Msg.EnterEmail}
|
<p>_{Msg.EnterEmail}
|
||||||
<form method="post" action="@{toMaster registerR}">
|
<form method="post" action="@{toMaster registerR}">
|
||||||
<label for="email">_{Msg.Email}
|
<label for="email">_{Msg.Email}
|
||||||
@ -141,7 +143,10 @@ postRegisterR = do
|
|||||||
sendVerifyEmail email verKey verUrl
|
sendVerifyEmail email verKey verUrl
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
setTitleI Msg.ConfirmationEmailSentTitle
|
setTitleI Msg.ConfirmationEmailSentTitle
|
||||||
[whamlet| <p>_{Msg.ConfirmationEmailSent email} |]
|
[whamlet|
|
||||||
|
$newline never
|
||||||
|
<p>_{Msg.ConfirmationEmailSent email}
|
||||||
|
|]
|
||||||
|
|
||||||
getVerifyR :: YesodAuthEmail m
|
getVerifyR :: YesodAuthEmail m
|
||||||
=> AuthEmailId m -> Text -> GHandler Auth m RepHtml
|
=> AuthEmailId m -> Text -> GHandler Auth m RepHtml
|
||||||
@ -161,7 +166,10 @@ getVerifyR lid key = do
|
|||||||
_ -> return ()
|
_ -> return ()
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
setTitleI Msg.InvalidKey
|
setTitleI Msg.InvalidKey
|
||||||
[whamlet| <p>_{Msg.InvalidKey} |]
|
[whamlet|
|
||||||
|
$newline never
|
||||||
|
<p>_{Msg.InvalidKey}
|
||||||
|
|]
|
||||||
|
|
||||||
postLoginR :: YesodAuthEmail master => GHandler Auth master ()
|
postLoginR :: YesodAuthEmail master => GHandler Auth master ()
|
||||||
postLoginR = do
|
postLoginR = do
|
||||||
@ -200,6 +208,7 @@ getPasswordR = do
|
|||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
setTitleI Msg.SetPassTitle
|
setTitleI Msg.SetPassTitle
|
||||||
[whamlet|
|
[whamlet|
|
||||||
|
$newline never
|
||||||
<h3>_{Msg.SetPass}
|
<h3>_{Msg.SetPass}
|
||||||
<form method="post" action="@{toMaster setpassR}">
|
<form method="post" action="@{toMaster setpassR}">
|
||||||
<table>
|
<table>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{-# LANGUAGE QuasiQuotes #-}
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
-- | Use an email address as an identifier via Google's OpenID login system.
|
-- | 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
|
-- This backend will not use the OpenID identifier at all. It only uses OpenID
|
||||||
@ -20,25 +21,35 @@ import qualified Web.Authenticate.OpenId as OpenId
|
|||||||
import Yesod.Handler
|
import Yesod.Handler
|
||||||
import Yesod.Widget (whamlet)
|
import Yesod.Widget (whamlet)
|
||||||
import Yesod.Request
|
import Yesod.Request
|
||||||
|
#if MIN_VERSION_blaze_html(0, 5, 0)
|
||||||
|
import Text.Blaze.Html (toHtml)
|
||||||
|
#else
|
||||||
import Text.Blaze (toHtml)
|
import Text.Blaze (toHtml)
|
||||||
|
#endif
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Yesod.Auth.Message as Msg
|
import qualified Yesod.Auth.Message as Msg
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Control.Exception.Lifted (try, SomeException)
|
import Control.Exception.Lifted (try, SomeException)
|
||||||
|
|
||||||
|
pid :: Text
|
||||||
|
pid = "googleemail"
|
||||||
|
|
||||||
forwardUrl :: AuthRoute
|
forwardUrl :: AuthRoute
|
||||||
forwardUrl = PluginR "googleemail" ["forward"]
|
forwardUrl = PluginR pid ["forward"]
|
||||||
|
|
||||||
googleIdent :: Text
|
googleIdent :: Text
|
||||||
googleIdent = "https://www.google.com/accounts/o8/id"
|
googleIdent = "https://www.google.com/accounts/o8/id"
|
||||||
|
|
||||||
authGoogleEmail :: YesodAuth m => AuthPlugin m
|
authGoogleEmail :: YesodAuth m => AuthPlugin m
|
||||||
authGoogleEmail =
|
authGoogleEmail =
|
||||||
AuthPlugin "googleemail" dispatch login
|
AuthPlugin pid dispatch login
|
||||||
where
|
where
|
||||||
complete = PluginR "googleemail" ["complete"]
|
complete = PluginR pid ["complete"]
|
||||||
login tm =
|
login tm =
|
||||||
[whamlet|<a href=@{tm forwardUrl}>_{Msg.LoginGoogle}|]
|
[whamlet|
|
||||||
|
$newline never
|
||||||
|
<a href=@{tm forwardUrl}>_{Msg.LoginGoogle}
|
||||||
|
|]
|
||||||
dispatch "GET" ["forward"] = do
|
dispatch "GET" ["forward"] = do
|
||||||
render <- getUrlRender
|
render <- getUrlRender
|
||||||
toMaster <- getRouteToMaster
|
toMaster <- getRouteToMaster
|
||||||
@ -72,15 +83,16 @@ authGoogleEmail =
|
|||||||
completeHelper :: YesodAuth m => [(Text, Text)] -> GHandler Auth m ()
|
completeHelper :: YesodAuth m => [(Text, Text)] -> GHandler Auth m ()
|
||||||
completeHelper gets' = do
|
completeHelper gets' = do
|
||||||
master <- getYesod
|
master <- getYesod
|
||||||
eres <- lift $ try $ OpenId.authenticate gets' (authHttpManager master)
|
eres <- lift $ try $ OpenId.authenticateClaimed gets' (authHttpManager master)
|
||||||
toMaster <- getRouteToMaster
|
toMaster <- getRouteToMaster
|
||||||
let onFailure err = do
|
let onFailure err = do
|
||||||
setMessage $ toHtml $ show (err :: SomeException)
|
setMessage $ toHtml $ show (err :: SomeException)
|
||||||
redirect $ toMaster LoginR
|
redirect $ toMaster LoginR
|
||||||
let onSuccess (OpenId.Identifier ident, _) = do
|
let onSuccess oir = do
|
||||||
|
let OpenId.Identifier ident = OpenId.oirOpLocal oir
|
||||||
memail <- lookupGetParam "openid.ext1.value.email"
|
memail <- lookupGetParam "openid.ext1.value.email"
|
||||||
case (memail, "https://www.google.com/accounts/o8/id" `T.isPrefixOf` ident) of
|
case (memail, "https://www.google.com/accounts/o8/id" `T.isPrefixOf` ident) of
|
||||||
(Just email, True) -> setCreds True $ Creds "openid" email []
|
(Just email, True) -> setCreds True $ Creds pid email []
|
||||||
(_, False) -> do
|
(_, False) -> do
|
||||||
setMessage "Only Google login is supported"
|
setMessage "Only Google login is supported"
|
||||||
redirect $ toMaster LoginR
|
redirect $ toMaster LoginR
|
||||||
|
|||||||
@ -76,7 +76,7 @@ import Yesod.Handler
|
|||||||
import Yesod.Form
|
import Yesod.Form
|
||||||
import Yesod.Auth
|
import Yesod.Auth
|
||||||
import Yesod.Widget (toWidget)
|
import Yesod.Widget (toWidget)
|
||||||
import Text.Hamlet (hamlet, shamlet)
|
import Text.Hamlet (hamlet)
|
||||||
|
|
||||||
import Control.Applicative ((<$>), (<*>))
|
import Control.Applicative ((<$>), (<*>))
|
||||||
import Control.Monad (replicateM,liftM)
|
import Control.Monad (replicateM,liftM)
|
||||||
@ -176,7 +176,7 @@ postLoginR uniq = do
|
|||||||
(validateUser <$> (uniq =<< mu) <*> mp)
|
(validateUser <$> (uniq =<< mu) <*> mp)
|
||||||
if isValid
|
if isValid
|
||||||
then setCreds True $ Creds "hashdb" (fromMaybe "" mu) []
|
then setCreds True $ Creds "hashdb" (fromMaybe "" mu) []
|
||||||
else do setMessage [shamlet| Invalid username/password |]
|
else do setMessage "Invalid username/password"
|
||||||
toMaster <- getRouteToMaster
|
toMaster <- getRouteToMaster
|
||||||
redirect $ toMaster LoginR
|
redirect $ toMaster LoginR
|
||||||
|
|
||||||
@ -207,7 +207,7 @@ getAuthIdHashDB authR uniq creds = do
|
|||||||
-- user exists
|
-- user exists
|
||||||
Just (Entity uid _) -> return $ Just uid
|
Just (Entity uid _) -> return $ Just uid
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
setMessage [shamlet| User not found |]
|
setMessage "User not found"
|
||||||
redirect $ authR LoginR
|
redirect $ authR LoginR
|
||||||
|
|
||||||
-- | Prompt for username and password, validate that against a database
|
-- | Prompt for username and password, validate that against a database
|
||||||
@ -221,6 +221,7 @@ authHashDB :: ( YesodAuth m, YesodPersist m
|
|||||||
, PersistUnique b (GHandler Auth m))
|
, PersistUnique b (GHandler Auth m))
|
||||||
=> (Text -> Maybe (Unique user b)) -> AuthPlugin m
|
=> (Text -> Maybe (Unique user b)) -> AuthPlugin m
|
||||||
authHashDB uniq = AuthPlugin "hashdb" dispatch $ \tm -> toWidget [hamlet|
|
authHashDB uniq = AuthPlugin "hashdb" dispatch $ \tm -> toWidget [hamlet|
|
||||||
|
$newline never
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<h1>Login
|
<h1>Login
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,10 @@ module Yesod.Auth.Message
|
|||||||
, portugueseMessage
|
, portugueseMessage
|
||||||
, swedishMessage
|
, swedishMessage
|
||||||
, germanMessage
|
, germanMessage
|
||||||
|
, frenchMessage
|
||||||
|
, norwegianBokmålMessage
|
||||||
|
, japaneseMessage
|
||||||
|
, finnishMessage
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Monoid (mappend)
|
import Data.Monoid (mappend)
|
||||||
@ -183,3 +187,143 @@ germanMessage NowLoggedIn = "Login erfolgreich"
|
|||||||
germanMessage LoginTitle = "Login"
|
germanMessage LoginTitle = "Login"
|
||||||
germanMessage PleaseProvideUsername = "Bitte Nutzername angeben"
|
germanMessage PleaseProvideUsername = "Bitte Nutzername angeben"
|
||||||
germanMessage PleaseProvidePassword = "Bitte Passwort angeben"
|
germanMessage PleaseProvidePassword = "Bitte Passwort angeben"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
frenchMessage :: AuthMessage -> Text
|
||||||
|
frenchMessage NoOpenID = "Aucun fournisseur OpenID n'a été trouvé"
|
||||||
|
frenchMessage LoginOpenID = "Se connecter avec OpenID"
|
||||||
|
frenchMessage LoginGoogle = "Se connecter avec Google"
|
||||||
|
frenchMessage LoginYahoo = "Se connecter avec Yahoo"
|
||||||
|
frenchMessage Email = "Adresse électronique"
|
||||||
|
frenchMessage Password = "Mot de passe"
|
||||||
|
frenchMessage Register = "S'inscrire"
|
||||||
|
frenchMessage RegisterLong = "Créer un compte"
|
||||||
|
frenchMessage EnterEmail = "Entrez ci-dessous votre adresse électronique, et un message de confirmation vous sera envoyé"
|
||||||
|
frenchMessage ConfirmationEmailSentTitle = "Message de confirmation"
|
||||||
|
frenchMessage (ConfirmationEmailSent email) =
|
||||||
|
"Un message de confirmation a été envoyé à " `mappend`
|
||||||
|
email `mappend`
|
||||||
|
"."
|
||||||
|
frenchMessage AddressVerified = "Votre adresse électronique a été validée, merci de choisir un nouveau mot de passe."
|
||||||
|
frenchMessage InvalidKeyTitle = "Clef de validation incorrecte"
|
||||||
|
frenchMessage InvalidKey = "Désolé, mais cette clef de validation est incorrecte"
|
||||||
|
frenchMessage InvalidEmailPass = "Le couple mot de passe/adresse électronique n'est pas correct"
|
||||||
|
frenchMessage BadSetPass = "Vous devez être connecté pour choisir un mot de passe"
|
||||||
|
frenchMessage SetPassTitle = "Changer de mot de passe"
|
||||||
|
frenchMessage SetPass = "Choisir un nouveau mot de passe"
|
||||||
|
frenchMessage NewPass = "Nouveau mot de passe"
|
||||||
|
frenchMessage ConfirmPass = "Confirmation du mot de passe"
|
||||||
|
frenchMessage PassMismatch = "Le deux mots de passe sont différents, veuillez les corriger"
|
||||||
|
frenchMessage PassUpdated = "Le mot de passe a bien été changé"
|
||||||
|
frenchMessage Facebook = "Se connecter avec Facebook"
|
||||||
|
frenchMessage LoginViaEmail = "Se connecter à l'aide d'une adresse électronique"
|
||||||
|
frenchMessage InvalidLogin = "Nom d'utilisateur incorrect"
|
||||||
|
frenchMessage NowLoggedIn = "Vous êtes maintenant connecté"
|
||||||
|
frenchMessage LoginTitle = "Se connecter"
|
||||||
|
frenchMessage PleaseProvideUsername = "Merci de renseigner votre nom d'utilisateur"
|
||||||
|
frenchMessage PleaseProvidePassword = "Merci de spécifier un mot de passe"
|
||||||
|
|
||||||
|
norwegianBokmålMessage :: AuthMessage -> Text
|
||||||
|
norwegianBokmålMessage NoOpenID = "Ingen OpenID-identifiserer funnet"
|
||||||
|
norwegianBokmålMessage LoginOpenID = "Logg inn med OpenID"
|
||||||
|
norwegianBokmålMessage LoginGoogle = "Logg inn med Google"
|
||||||
|
norwegianBokmålMessage LoginYahoo = "Logg inn med Yahoo"
|
||||||
|
norwegianBokmålMessage Email = "E-post"
|
||||||
|
norwegianBokmålMessage Password = "Passord"
|
||||||
|
norwegianBokmålMessage Register = "Registrer"
|
||||||
|
norwegianBokmålMessage RegisterLong = "Registrer en ny konto"
|
||||||
|
norwegianBokmålMessage EnterEmail = "Skriv inn e-postadressen din nedenfor og en e-postkonfirmasjon vil bli sendt."
|
||||||
|
norwegianBokmålMessage ConfirmationEmailSentTitle = "E-postkonfirmasjon sendt."
|
||||||
|
norwegianBokmålMessage (ConfirmationEmailSent email) =
|
||||||
|
"En e-postkonfirmasjon har blitt sendt til " `mappend`
|
||||||
|
email `mappend`
|
||||||
|
"."
|
||||||
|
norwegianBokmålMessage AddressVerified = "Adresse verifisert, vennligst sett et nytt passord."
|
||||||
|
norwegianBokmålMessage InvalidKeyTitle = "Ugyldig verifiseringsnøkkel"
|
||||||
|
norwegianBokmålMessage InvalidKey = "Beklager, men det var en ugyldig verifiseringsnøkkel."
|
||||||
|
norwegianBokmålMessage InvalidEmailPass = "Ugyldig e-post/passord-kombinasjon"
|
||||||
|
norwegianBokmålMessage BadSetPass = "Du må være logget inn for å sette et passord."
|
||||||
|
norwegianBokmålMessage SetPassTitle = "Sett passord"
|
||||||
|
norwegianBokmålMessage SetPass = "Sett et nytt passord"
|
||||||
|
norwegianBokmålMessage NewPass = "Nytt passord"
|
||||||
|
norwegianBokmålMessage ConfirmPass = "Bekreft"
|
||||||
|
norwegianBokmålMessage PassMismatch = "Passordene stemte ikke overens, vennligst prøv igjen"
|
||||||
|
norwegianBokmålMessage PassUpdated = "Passord oppdatert"
|
||||||
|
norwegianBokmålMessage Facebook = "Logg inn med Facebook"
|
||||||
|
norwegianBokmålMessage LoginViaEmail = "Logg inn med e-post"
|
||||||
|
norwegianBokmålMessage InvalidLogin = "Ugyldig innlogging"
|
||||||
|
norwegianBokmålMessage NowLoggedIn = "Du er nå logget inn"
|
||||||
|
norwegianBokmålMessage LoginTitle = "Logg inn"
|
||||||
|
norwegianBokmålMessage PleaseProvideUsername = "Vennligst fyll inn ditt brukernavn"
|
||||||
|
norwegianBokmålMessage PleaseProvidePassword = "Vennligst fyll inn ditt passord"
|
||||||
|
|
||||||
|
japaneseMessage :: AuthMessage -> Text
|
||||||
|
japaneseMessage NoOpenID = "OpenID識別子がありません"
|
||||||
|
japaneseMessage LoginOpenID = "OpenIDでログイン"
|
||||||
|
japaneseMessage LoginGoogle = "Googleでログイン"
|
||||||
|
japaneseMessage LoginYahoo = "Yahooでログイン"
|
||||||
|
japaneseMessage Email = "Eメール"
|
||||||
|
japaneseMessage Password = "パスワード"
|
||||||
|
japaneseMessage Register = "登録"
|
||||||
|
japaneseMessage RegisterLong = "新規アカウント登録"
|
||||||
|
japaneseMessage EnterEmail = "メールアドレスを入力してください。確認メールが送られます"
|
||||||
|
japaneseMessage ConfirmationEmailSentTitle = "確認メールを送信しました"
|
||||||
|
japaneseMessage (ConfirmationEmailSent email) =
|
||||||
|
"確認メールを " `mappend`
|
||||||
|
email `mappend`
|
||||||
|
" に送信しました"
|
||||||
|
japaneseMessage AddressVerified = "アドレスは認証されました。新しいパスワードを設定してください"
|
||||||
|
japaneseMessage InvalidKeyTitle = "認証キーが無効です"
|
||||||
|
japaneseMessage InvalidKey = "申し訳ありません。無効な認証キーです"
|
||||||
|
japaneseMessage InvalidEmailPass = "メールアドレスまたはパスワードが無効です"
|
||||||
|
japaneseMessage BadSetPass = "パスワードを設定するためには、ログインしてください"
|
||||||
|
japaneseMessage SetPassTitle = "パスワードの設定"
|
||||||
|
japaneseMessage SetPass = "新しいパスワードを設定する"
|
||||||
|
japaneseMessage NewPass = "新しいパスワード"
|
||||||
|
japaneseMessage ConfirmPass = "確認"
|
||||||
|
japaneseMessage PassMismatch = "パスワードが合いません。もう一度試してください"
|
||||||
|
japaneseMessage PassUpdated = "パスワードは更新されました"
|
||||||
|
japaneseMessage Facebook = "Facebookでログイン"
|
||||||
|
japaneseMessage LoginViaEmail = "Eメールでログイン"
|
||||||
|
japaneseMessage InvalidLogin = "無効なログインです"
|
||||||
|
japaneseMessage NowLoggedIn = "ログインしました"
|
||||||
|
japaneseMessage LoginTitle = "ログイン"
|
||||||
|
japaneseMessage PleaseProvideUsername = "ユーザ名を入力してください"
|
||||||
|
japaneseMessage PleaseProvidePassword = "パスワードを入力してください"
|
||||||
|
|
||||||
|
finnishMessage :: AuthMessage -> Text
|
||||||
|
finnishMessage NoOpenID = "OpenID-tunnistetta ei löydy"
|
||||||
|
finnishMessage LoginOpenID = "Kirjaudu OpenID-tilillä"
|
||||||
|
finnishMessage LoginGoogle = "Kirjaudu Google-tilillä"
|
||||||
|
finnishMessage LoginYahoo = "Kirjaudu Yahoo-tilillä"
|
||||||
|
finnishMessage Email = "Sähköposti"
|
||||||
|
finnishMessage Password = "Salasana"
|
||||||
|
finnishMessage Register = "Luo uusi"
|
||||||
|
finnishMessage RegisterLong = "Luo uusi tili"
|
||||||
|
finnishMessage EnterEmail = "Kirjoita alle sähköpostiosoitteesi, johon vahvistussähköposti lähetetään."
|
||||||
|
finnishMessage ConfirmationEmailSentTitle = "Vahvistussähköposti lähetetty."
|
||||||
|
finnishMessage (ConfirmationEmailSent email) =
|
||||||
|
"Vahvistussähköposti on lähetty osoitteeseen " `mappend`
|
||||||
|
email `mappend`
|
||||||
|
"."
|
||||||
|
finnishMessage AddressVerified = "Sähköpostiosoite vahvistettu. Anna uusi salasana"
|
||||||
|
finnishMessage InvalidKeyTitle = "Virheellinen varmistusavain"
|
||||||
|
finnishMessage InvalidKey = "Valitettavasti varmistusavain on virheellinen."
|
||||||
|
finnishMessage InvalidEmailPass = "Virheellinen sähköposti tai salasana."
|
||||||
|
finnishMessage BadSetPass = "Kirjaudu ensin sisään asettaaksesi salasanan"
|
||||||
|
finnishMessage SetPassTitle = "Salasanan asettaminen"
|
||||||
|
finnishMessage SetPass = "Aseta uusi salasana"
|
||||||
|
finnishMessage NewPass = "Uusi salasana"
|
||||||
|
finnishMessage ConfirmPass = "Vahvista"
|
||||||
|
finnishMessage PassMismatch = "Salasanat eivät täsmää"
|
||||||
|
finnishMessage PassUpdated = "Salasana vaihdettu"
|
||||||
|
finnishMessage Facebook = "Kirjaudu Facebook-tilillä"
|
||||||
|
finnishMessage LoginViaEmail = "Kirjaudu sähköpostitilillä"
|
||||||
|
finnishMessage InvalidLogin = "Kirjautuminen epäonnistui"
|
||||||
|
finnishMessage NowLoggedIn = "Olet nyt kirjautunut sisään"
|
||||||
|
finnishMessage LoginTitle = "Kirjautuminen"
|
||||||
|
finnishMessage PleaseProvideUsername = "Käyttäjänimi puuttuu"
|
||||||
|
finnishMessage PleaseProvidePassword = "Salasana puuttuu"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
{-# LANGUAGE QuasiQuotes #-}
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
module Yesod.Auth.OpenId
|
module Yesod.Auth.OpenId
|
||||||
( authOpenId
|
( authOpenId
|
||||||
, authOpenIdExtended
|
|
||||||
, forwardUrl
|
, forwardUrl
|
||||||
|
, claimedKey
|
||||||
|
, opLocalKey
|
||||||
|
, credsIdentClaimed
|
||||||
|
, IdentifierType (..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Yesod.Auth
|
import Yesod.Auth
|
||||||
@ -14,30 +18,42 @@ import Yesod.Handler
|
|||||||
import Yesod.Widget (toWidget, whamlet)
|
import Yesod.Widget (toWidget, whamlet)
|
||||||
import Yesod.Request
|
import Yesod.Request
|
||||||
import Text.Cassius (cassius)
|
import Text.Cassius (cassius)
|
||||||
|
#if MIN_VERSION_blaze_html(0, 5, 0)
|
||||||
|
import Text.Blaze.Html (toHtml)
|
||||||
|
#else
|
||||||
import Text.Blaze (toHtml)
|
import Text.Blaze (toHtml)
|
||||||
import Data.Text (Text)
|
#endif
|
||||||
|
import Data.Text (Text, isPrefixOf)
|
||||||
import qualified Yesod.Auth.Message as Msg
|
import qualified Yesod.Auth.Message as Msg
|
||||||
import Control.Exception.Lifted (SomeException, try)
|
import Control.Exception.Lifted (SomeException, try)
|
||||||
|
import Data.Maybe (fromMaybe)
|
||||||
|
|
||||||
forwardUrl :: AuthRoute
|
forwardUrl :: AuthRoute
|
||||||
forwardUrl = PluginR "openid" ["forward"]
|
forwardUrl = PluginR "openid" ["forward"]
|
||||||
|
|
||||||
authOpenId :: YesodAuth m => AuthPlugin m
|
data IdentifierType = Claimed | OPLocal
|
||||||
authOpenId = authOpenIdExtended []
|
|
||||||
|
|
||||||
authOpenIdExtended :: YesodAuth m => [(Text, Text)] -> AuthPlugin m
|
authOpenId :: YesodAuth m
|
||||||
authOpenIdExtended extensionFields =
|
=> IdentifierType
|
||||||
|
-> [(Text, Text)] -- ^ extension fields
|
||||||
|
-> AuthPlugin m
|
||||||
|
authOpenId idType extensionFields =
|
||||||
AuthPlugin "openid" dispatch login
|
AuthPlugin "openid" dispatch login
|
||||||
where
|
where
|
||||||
complete = PluginR "openid" ["complete"]
|
complete = PluginR "openid" ["complete"]
|
||||||
name = "openid_identifier"
|
name = "openid_identifier"
|
||||||
login tm = do
|
login tm = do
|
||||||
ident <- lift newIdent
|
ident <- lift newIdent
|
||||||
toWidget [cassius|##{ident}
|
-- FIXME this is a hack to get GHC 7.6's type checker to allow the
|
||||||
|
-- code, but it shouldn't be necessary
|
||||||
|
let y :: a -> [(Text, Text)] -> Text
|
||||||
|
y = undefined
|
||||||
|
toWidget (\x -> [cassius|##{ident}
|
||||||
background: #fff url(http://www.myopenid.com/static/openid-icon-small.gif) no-repeat scroll 0pt 50%;
|
background: #fff url(http://www.myopenid.com/static/openid-icon-small.gif) no-repeat scroll 0pt 50%;
|
||||||
padding-left: 18px;
|
padding-left: 18px;
|
||||||
|]
|
|] $ x `asTypeOf` y)
|
||||||
[whamlet|
|
[whamlet|
|
||||||
|
$newline never
|
||||||
<form method="get" action="@{tm forwardUrl}">
|
<form method="get" action="@{tm forwardUrl}">
|
||||||
<input type="hidden" name="openid_identifier" value="https://www.google.com/accounts/o8/id">
|
<input type="hidden" name="openid_identifier" value="https://www.google.com/accounts/o8/id">
|
||||||
<button .openid-google>_{Msg.LoginGoogle}
|
<button .openid-google>_{Msg.LoginGoogle}
|
||||||
@ -70,21 +86,64 @@ authOpenIdExtended extensionFields =
|
|||||||
dispatch "GET" ["complete", ""] = dispatch "GET" ["complete"] -- compatibility issues
|
dispatch "GET" ["complete", ""] = dispatch "GET" ["complete"] -- compatibility issues
|
||||||
dispatch "GET" ["complete"] = do
|
dispatch "GET" ["complete"] = do
|
||||||
rr <- getRequest
|
rr <- getRequest
|
||||||
completeHelper $ reqGetParams rr
|
completeHelper idType $ reqGetParams rr
|
||||||
dispatch "POST" ["complete", ""] = dispatch "POST" ["complete"] -- compatibility issues
|
dispatch "POST" ["complete", ""] = dispatch "POST" ["complete"] -- compatibility issues
|
||||||
dispatch "POST" ["complete"] = do
|
dispatch "POST" ["complete"] = do
|
||||||
(posts, _) <- runRequestBody
|
(posts, _) <- runRequestBody
|
||||||
completeHelper posts
|
completeHelper idType posts
|
||||||
dispatch _ _ = notFound
|
dispatch _ _ = notFound
|
||||||
|
|
||||||
completeHelper :: YesodAuth m => [(Text, Text)] -> GHandler Auth m ()
|
completeHelper :: YesodAuth m => IdentifierType -> [(Text, Text)] -> GHandler Auth m ()
|
||||||
completeHelper gets' = do
|
completeHelper idType gets' = do
|
||||||
master <- getYesod
|
master <- getYesod
|
||||||
eres <- lift $ try $ OpenId.authenticate gets' (authHttpManager master)
|
eres <- lift $ try $ OpenId.authenticateClaimed gets' (authHttpManager master)
|
||||||
toMaster <- getRouteToMaster
|
toMaster <- getRouteToMaster
|
||||||
let onFailure err = do
|
let onFailure err = do
|
||||||
setMessage $ toHtml $ show (err :: SomeException)
|
setMessage $ toHtml $ show (err :: SomeException)
|
||||||
redirect $ toMaster LoginR
|
redirect $ toMaster LoginR
|
||||||
let onSuccess (OpenId.Identifier ident, _) =
|
let onSuccess oir = do
|
||||||
setCreds True $ Creds "openid" ident gets'
|
let claimed =
|
||||||
|
case OpenId.oirClaimed oir of
|
||||||
|
Nothing -> id
|
||||||
|
Just (OpenId.Identifier i') -> ((claimedKey, i'):)
|
||||||
|
oplocal =
|
||||||
|
case OpenId.oirOpLocal oir of
|
||||||
|
OpenId.Identifier i' -> ((opLocalKey, i'):)
|
||||||
|
gets'' = oplocal $ claimed $ filter (\(k, _) -> not $ "__" `isPrefixOf` k) gets'
|
||||||
|
i = OpenId.identifier $
|
||||||
|
case idType of
|
||||||
|
OPLocal -> OpenId.oirOpLocal oir
|
||||||
|
Claimed -> fromMaybe (OpenId.oirOpLocal oir) $ OpenId.oirClaimed oir
|
||||||
|
setCreds True $ Creds "openid" i gets''
|
||||||
either onFailure onSuccess eres
|
either onFailure onSuccess eres
|
||||||
|
|
||||||
|
-- | The main identifier provided by the OpenID authentication plugin is the
|
||||||
|
-- \"OP-local identifier\". There is also sometimes a \"claimed\" identifier
|
||||||
|
-- available.
|
||||||
|
--
|
||||||
|
-- In the 'credsExtra' field of the 'Creds' datatype, you can lookup this key
|
||||||
|
-- to find the claimed identifier, if available.
|
||||||
|
--
|
||||||
|
-- > let finalID = fromMaybe (credsIdent creds)
|
||||||
|
-- > $ lookup claimedKey (credsExtra creds)
|
||||||
|
--
|
||||||
|
-- Since 1.0.2
|
||||||
|
claimedKey :: Text
|
||||||
|
claimedKey = "__CLAIMED"
|
||||||
|
|
||||||
|
opLocalKey :: Text
|
||||||
|
opLocalKey = "__OPLOCAL"
|
||||||
|
|
||||||
|
-- | A helper function which will get the claimed identifier, if available, falling back to the OP local identifier.
|
||||||
|
--
|
||||||
|
-- See 'claimedKey'.
|
||||||
|
--
|
||||||
|
-- Since 1.0.2
|
||||||
|
credsIdentClaimed :: Creds m -> Text
|
||||||
|
|
||||||
|
-- Prevent other backends from overloading the __CLAIMED value, which could
|
||||||
|
-- possibly open us to security holes.
|
||||||
|
credsIdentClaimed c | credsPlugin c /= "openid" = credsIdent c
|
||||||
|
|
||||||
|
credsIdentClaimed c = fromMaybe (credsIdent c)
|
||||||
|
$ lookup claimedKey (credsExtra c)
|
||||||
|
|||||||
@ -25,6 +25,7 @@ authRpxnow app apiKey =
|
|||||||
login tm = do
|
login tm = do
|
||||||
let url = {- FIXME urlEncode $ -} tm $ PluginR "rpxnow" []
|
let url = {- FIXME urlEncode $ -} tm $ PluginR "rpxnow" []
|
||||||
toWidget [hamlet|
|
toWidget [hamlet|
|
||||||
|
$newline never
|
||||||
<iframe src="http://#{app}.rpxnow.com/openid/embed?token_url=@{url}" scrolling="no" frameBorder="no" allowtransparency="true" style="width:400px;height:240px">
|
<iframe src="http://#{app}.rpxnow.com/openid/embed?token_url=@{url}" scrolling="no" frameBorder="no" allowtransparency="true" style="width:400px;height:240px">
|
||||||
|]
|
|]
|
||||||
dispatch _ [] = do
|
dispatch _ [] = do
|
||||||
|
|||||||
@ -38,13 +38,13 @@ $nothing
|
|||||||
|]
|
|]
|
||||||
|
|
||||||
instance Yesod BID where
|
instance Yesod BID where
|
||||||
approot _ = "http://localhost:3000"
|
approot = ApprootStatic "http://localhost:3000"
|
||||||
|
|
||||||
instance YesodAuth BID where
|
instance YesodAuth BID where
|
||||||
type AuthId BID = Text
|
type AuthId BID = Text
|
||||||
loginDest _ = AfterLoginR
|
loginDest _ = AfterLoginR
|
||||||
logoutDest _ = AuthR LoginR
|
logoutDest _ = AuthR LoginR
|
||||||
getAuthId = return . Just . credsIdent
|
getAuthId = return . Just . credsIdentClaimed
|
||||||
authPlugins _ = [authOpenId]
|
authPlugins _ = [authOpenId]
|
||||||
authHttpManager = httpManager
|
authHttpManager = httpManager
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-auth
|
name: yesod-auth
|
||||||
version: 1.0.0
|
version: 1.1.1.2
|
||||||
license: MIT
|
license: MIT
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Michael Snoyman, Patrick Brisbin
|
author: Michael Snoyman, Patrick Brisbin
|
||||||
@ -12,35 +12,34 @@ build-type: Simple
|
|||||||
homepage: http://www.yesodweb.com/
|
homepage: http://www.yesodweb.com/
|
||||||
description: Authentication for Yesod.
|
description: Authentication for Yesod.
|
||||||
|
|
||||||
flag ghc7
|
|
||||||
|
|
||||||
library
|
library
|
||||||
build-depends: base >= 4 && < 5
|
build-depends: base >= 4 && < 5
|
||||||
, authenticate >= 1.2 && < 1.3
|
, authenticate >= 1.3 && < 1.4
|
||||||
, bytestring >= 0.9.1.4 && < 0.10
|
, bytestring >= 0.9.1.4
|
||||||
, yesod-core >= 1.0 && < 1.1
|
, yesod-core >= 1.1 && < 1.2
|
||||||
, wai >= 1.2 && < 1.3
|
, wai >= 1.3 && < 1.4
|
||||||
, template-haskell
|
, template-haskell
|
||||||
, pureMD5 >= 2.0 && < 2.2
|
, pureMD5 >= 2.0 && < 2.2
|
||||||
, random >= 1.0.0.2 && < 1.1
|
, random >= 1.0.0.2 && < 1.1
|
||||||
, text >= 0.7 && < 0.12
|
, text >= 0.7 && < 0.12
|
||||||
, mime-mail >= 0.3 && < 0.5
|
, mime-mail >= 0.3 && < 0.5
|
||||||
, blaze-html >= 0.4.1.3 && < 0.5
|
, yesod-persistent >= 1.1 && < 1.2
|
||||||
, yesod-persistent >= 1.0 && < 1.1
|
, hamlet >= 1.1 && < 1.2
|
||||||
, hamlet >= 1.0 && < 1.1
|
|
||||||
, shakespeare-css >= 1.0 && < 1.1
|
, shakespeare-css >= 1.0 && < 1.1
|
||||||
, yesod-json >= 1.0 && < 1.1
|
, yesod-json >= 1.1 && < 1.2
|
||||||
, containers
|
, containers
|
||||||
, unordered-containers
|
, unordered-containers
|
||||||
, yesod-form >= 1.0 && < 1.1
|
, yesod-form >= 1.1 && < 1.2
|
||||||
, transformers >= 0.2.2 && < 0.4
|
, transformers >= 0.2.2 && < 0.4
|
||||||
, persistent >= 0.9 && < 0.10
|
, persistent >= 1.0 && < 1.1
|
||||||
, persistent-template >= 0.9 && < 0.10
|
, persistent-template >= 1.0 && < 1.1
|
||||||
, SHA >= 1.4.1.3 && < 1.6
|
, SHA >= 1.4.1.3 && < 1.6
|
||||||
, http-conduit >= 1.4 && < 1.5
|
, http-conduit >= 1.5 && < 1.7
|
||||||
, aeson >= 0.5
|
, aeson >= 0.5
|
||||||
, pwstore-fast >= 2.2 && < 3
|
, pwstore-fast >= 2.2 && < 3
|
||||||
, lifted-base >= 0.1 && < 0.2
|
, lifted-base >= 0.1
|
||||||
|
, blaze-html >= 0.5 && < 0.6
|
||||||
|
, blaze-markup >= 0.5.1 && < 0.6
|
||||||
|
|
||||||
exposed-modules: Yesod.Auth
|
exposed-modules: Yesod.Auth
|
||||||
Yesod.Auth.BrowserId
|
Yesod.Auth.BrowserId
|
||||||
|
|||||||
@ -1,25 +1,20 @@
|
|||||||
The following license covers this documentation, and the source code, except
|
Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
|
||||||
where otherwise indicated.
|
|
||||||
|
|
||||||
Copyright 2010, Michael Snoyman. All rights reserved.
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
The above copyright notice and this permission notice shall be
|
||||||
modification, are permitted provided that the following conditions are met:
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
list of conditions and the following disclaimer.
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
this list of conditions and the following disclaimer in the documentation
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
and/or other materials provided with the distribution.
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
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.
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
{-# LANGUAGE TypeSynonymInstances #-}
|
{-# LANGUAGE TypeSynonymInstances #-}
|
||||||
{-# LANGUAGE Rank2Types #-}
|
{-# LANGUAGE Rank2Types #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
module Yesod.Content
|
module Yesod.Content
|
||||||
( -- * Content
|
( -- * Content
|
||||||
Content (..)
|
Content (..)
|
||||||
@ -27,6 +28,8 @@ module Yesod.Content
|
|||||||
, typeOctet
|
, typeOctet
|
||||||
-- * Utilities
|
-- * Utilities
|
||||||
, simpleContentType
|
, simpleContentType
|
||||||
|
-- * Evaluation strategy
|
||||||
|
, DontFullyEvaluate (..)
|
||||||
-- * Representations
|
-- * Representations
|
||||||
, ChooseRep
|
, ChooseRep
|
||||||
, HasReps (..)
|
, HasReps (..)
|
||||||
@ -59,14 +62,15 @@ import Blaze.ByteString.Builder (Builder, fromByteString, fromLazyByteString)
|
|||||||
import Data.Monoid (mempty)
|
import Data.Monoid (mempty)
|
||||||
|
|
||||||
import Text.Hamlet (Html)
|
import Text.Hamlet (Html)
|
||||||
import Text.Blaze.Renderer.Utf8 (renderHtmlBuilder)
|
import Text.Blaze.Html.Renderer.Utf8 (renderHtmlBuilder)
|
||||||
import Data.String (IsString (fromString))
|
import Data.String (IsString (fromString))
|
||||||
import Network.Wai (FilePart)
|
import Network.Wai (FilePart)
|
||||||
import Data.Conduit (Source, ResourceT, Flush)
|
import Data.Conduit (Source, ResourceT, Flush)
|
||||||
|
|
||||||
data Content = ContentBuilder Builder (Maybe Int) -- ^ The content and optional content length.
|
data Content = ContentBuilder !Builder !(Maybe Int) -- ^ The content and optional content length.
|
||||||
| ContentSource (Source (ResourceT IO) (Flush Builder))
|
| ContentSource !(Source (ResourceT IO) (Flush Builder))
|
||||||
| ContentFile FilePath (Maybe FilePart)
|
| ContentFile !FilePath !(Maybe FilePart)
|
||||||
|
| ContentDontEvaluate !Content
|
||||||
|
|
||||||
-- | Zero-length enumerator.
|
-- | Zero-length enumerator.
|
||||||
emptyContent :: Content
|
emptyContent :: Content
|
||||||
@ -234,3 +238,15 @@ formatRFC1123 = T.pack . formatTime defaultTimeLocale "%a, %d %b %Y %X %Z"
|
|||||||
-- | Format as per RFC 822.
|
-- | Format as per RFC 822.
|
||||||
formatRFC822 :: UTCTime -> T.Text
|
formatRFC822 :: UTCTime -> T.Text
|
||||||
formatRFC822 = T.pack . formatTime defaultTimeLocale "%a, %d %b %Y %H:%M:%S %z"
|
formatRFC822 = T.pack . formatTime defaultTimeLocale "%a, %d %b %Y %H:%M:%S %z"
|
||||||
|
|
||||||
|
-- | Prevents a response body from being fully evaluated before sending the
|
||||||
|
-- request.
|
||||||
|
--
|
||||||
|
-- Since 1.1.0
|
||||||
|
newtype DontFullyEvaluate a = DontFullyEvaluate a
|
||||||
|
|
||||||
|
instance HasReps a => HasReps (DontFullyEvaluate a) where
|
||||||
|
chooseRep (DontFullyEvaluate a) = fmap (fmap (fmap ContentDontEvaluate)) $ chooseRep a
|
||||||
|
|
||||||
|
instance ToContent a => ToContent (DontFullyEvaluate a) where
|
||||||
|
toContent (DontFullyEvaluate a) = ContentDontEvaluate $ toContent a
|
||||||
|
|||||||
@ -10,6 +10,7 @@ module Yesod.Core
|
|||||||
, breadcrumbs
|
, breadcrumbs
|
||||||
-- * Types
|
-- * Types
|
||||||
, Approot (..)
|
, Approot (..)
|
||||||
|
, FileUpload (..)
|
||||||
-- * Utitlities
|
-- * Utitlities
|
||||||
, maybeAuthorized
|
, maybeAuthorized
|
||||||
, widgetToPageContent
|
, widgetToPageContent
|
||||||
@ -20,13 +21,16 @@ module Yesod.Core
|
|||||||
, unauthorizedI
|
, unauthorizedI
|
||||||
-- * Logging
|
-- * Logging
|
||||||
, LogLevel (..)
|
, LogLevel (..)
|
||||||
, formatLogMessage
|
|
||||||
, fileLocationToString
|
|
||||||
, logDebug
|
, logDebug
|
||||||
, logInfo
|
, logInfo
|
||||||
, logWarn
|
, logWarn
|
||||||
, logError
|
, logError
|
||||||
, logOther
|
, logOther
|
||||||
|
, logDebugS
|
||||||
|
, logInfoS
|
||||||
|
, logWarnS
|
||||||
|
, logErrorS
|
||||||
|
, logOtherS
|
||||||
-- * Sessions
|
-- * Sessions
|
||||||
, SessionBackend (..)
|
, SessionBackend (..)
|
||||||
, defaultClientSessionBackend
|
, defaultClientSessionBackend
|
||||||
@ -41,6 +45,7 @@ module Yesod.Core
|
|||||||
-- * Misc
|
-- * Misc
|
||||||
, yesodVersion
|
, yesodVersion
|
||||||
, yesodRender
|
, yesodRender
|
||||||
|
, runFakeHandler
|
||||||
-- * Re-exports
|
-- * Re-exports
|
||||||
, module Yesod.Content
|
, module Yesod.Content
|
||||||
, module Yesod.Dispatch
|
, module Yesod.Dispatch
|
||||||
@ -59,38 +64,7 @@ import Yesod.Request
|
|||||||
import Yesod.Widget
|
import Yesod.Widget
|
||||||
import Yesod.Message
|
import Yesod.Message
|
||||||
|
|
||||||
import Language.Haskell.TH.Syntax
|
import Control.Monad.Logger
|
||||||
import qualified Language.Haskell.TH.Syntax as TH
|
|
||||||
import Data.Text (Text)
|
|
||||||
|
|
||||||
logTH :: LogLevel -> Q Exp
|
|
||||||
logTH level =
|
|
||||||
[|messageLoggerHandler $(qLocation >>= liftLoc) $(TH.lift level)|]
|
|
||||||
where
|
|
||||||
liftLoc :: Loc -> Q Exp
|
|
||||||
liftLoc (Loc a b c d e) = [|Loc $(TH.lift a) $(TH.lift b) $(TH.lift c) $(TH.lift d) $(TH.lift e)|]
|
|
||||||
|
|
||||||
-- | Generates a function that takes a 'Text' and logs a 'LevelDebug' message. Usage:
|
|
||||||
--
|
|
||||||
-- > $(logDebug) "This is a debug log message"
|
|
||||||
logDebug :: Q Exp
|
|
||||||
logDebug = logTH LevelDebug
|
|
||||||
|
|
||||||
-- | See 'logDebug'
|
|
||||||
logInfo :: Q Exp
|
|
||||||
logInfo = logTH LevelInfo
|
|
||||||
-- | See 'logDebug'
|
|
||||||
logWarn :: Q Exp
|
|
||||||
logWarn = logTH LevelWarn
|
|
||||||
-- | See 'logDebug'
|
|
||||||
logError :: Q Exp
|
|
||||||
logError = logTH LevelError
|
|
||||||
|
|
||||||
-- | Generates a function that takes a 'Text' and logs a 'LevelOther' message. Usage:
|
|
||||||
--
|
|
||||||
-- > $(logOther "My new level") "This is a log message"
|
|
||||||
logOther :: Text -> Q Exp
|
|
||||||
logOther = logTH . LevelOther
|
|
||||||
|
|
||||||
-- | Return an 'Unauthorized' value, with the given i18n message.
|
-- | Return an 'Unauthorized' value, with the given i18n message.
|
||||||
unauthorizedI :: RenderMessage master msg => msg -> GHandler sub master AuthResult
|
unauthorizedI :: RenderMessage master msg => msg -> GHandler sub master AuthResult
|
||||||
|
|||||||
@ -28,7 +28,7 @@ module Yesod.Dispatch
|
|||||||
, WaiSubsite (..)
|
, WaiSubsite (..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Functor ((<$>))
|
import Control.Applicative ((<$>), (<*>))
|
||||||
import Prelude hiding (exp)
|
import Prelude hiding (exp)
|
||||||
import Yesod.Internal.Core
|
import Yesod.Internal.Core
|
||||||
import Yesod.Handler hiding (lift)
|
import Yesod.Handler hiding (lift)
|
||||||
@ -53,6 +53,7 @@ import Network.HTTP.Types (status301)
|
|||||||
import Yesod.Routes.TH
|
import Yesod.Routes.TH
|
||||||
import Yesod.Content (chooseRep)
|
import Yesod.Content (chooseRep)
|
||||||
import Yesod.Routes.Parse
|
import Yesod.Routes.Parse
|
||||||
|
import System.Log.FastLogger (Logger)
|
||||||
|
|
||||||
type Texts = [Text]
|
type Texts = [Text]
|
||||||
|
|
||||||
@ -60,7 +61,7 @@ type Texts = [Text]
|
|||||||
-- is used for creating sites, /not/ subsites. See 'mkYesodSub' for the latter.
|
-- is used for creating sites, /not/ subsites. See 'mkYesodSub' for the latter.
|
||||||
-- Use 'parseRoutes' to create the 'Resource's.
|
-- Use 'parseRoutes' to create the 'Resource's.
|
||||||
mkYesod :: String -- ^ name of the argument datatype
|
mkYesod :: String -- ^ name of the argument datatype
|
||||||
-> [Resource String]
|
-> [ResourceTree String]
|
||||||
-> Q [Dec]
|
-> Q [Dec]
|
||||||
mkYesod name = fmap (uncurry (++)) . mkYesodGeneral name [] [] False
|
mkYesod name = fmap (uncurry (++)) . mkYesodGeneral name [] [] False
|
||||||
|
|
||||||
@ -71,7 +72,7 @@ mkYesod name = fmap (uncurry (++)) . mkYesodGeneral name [] [] False
|
|||||||
-- be embedded in other sites.
|
-- be embedded in other sites.
|
||||||
mkYesodSub :: String -- ^ name of the argument datatype
|
mkYesodSub :: String -- ^ name of the argument datatype
|
||||||
-> Cxt
|
-> Cxt
|
||||||
-> [Resource String]
|
-> [ResourceTree String]
|
||||||
-> Q [Dec]
|
-> Q [Dec]
|
||||||
mkYesodSub name clazzes =
|
mkYesodSub name clazzes =
|
||||||
fmap (uncurry (++)) . mkYesodGeneral name' rest clazzes True
|
fmap (uncurry (++)) . mkYesodGeneral name' rest clazzes True
|
||||||
@ -82,28 +83,28 @@ mkYesodSub name clazzes =
|
|||||||
-- your handlers elsewhere. For example, this is the only way to break up a
|
-- your handlers elsewhere. For example, this is the only way to break up a
|
||||||
-- monolithic file into smaller parts. Use this function, paired with
|
-- monolithic file into smaller parts. Use this function, paired with
|
||||||
-- 'mkYesodDispatch', to do just that.
|
-- 'mkYesodDispatch', to do just that.
|
||||||
mkYesodData :: String -> [Resource String] -> Q [Dec]
|
mkYesodData :: String -> [ResourceTree String] -> Q [Dec]
|
||||||
mkYesodData name res = mkYesodDataGeneral name [] False res
|
mkYesodData name res = mkYesodDataGeneral name [] False res
|
||||||
|
|
||||||
mkYesodSubData :: String -> Cxt -> [Resource String] -> Q [Dec]
|
mkYesodSubData :: String -> Cxt -> [ResourceTree String] -> Q [Dec]
|
||||||
mkYesodSubData name clazzes res = mkYesodDataGeneral name clazzes True res
|
mkYesodSubData name clazzes res = mkYesodDataGeneral name clazzes True res
|
||||||
|
|
||||||
mkYesodDataGeneral :: String -> Cxt -> Bool -> [Resource String] -> Q [Dec]
|
mkYesodDataGeneral :: String -> Cxt -> Bool -> [ResourceTree String] -> Q [Dec]
|
||||||
mkYesodDataGeneral name clazzes isSub res = do
|
mkYesodDataGeneral name clazzes isSub res = do
|
||||||
let (name':rest) = words name
|
let (name':rest) = words name
|
||||||
(x, _) <- mkYesodGeneral name' rest clazzes isSub res
|
(x, _) <- mkYesodGeneral name' rest clazzes isSub res
|
||||||
let rname = mkName $ "resources" ++ name
|
let rname = mkName $ "resources" ++ name
|
||||||
eres <- lift res
|
eres <- lift res
|
||||||
let y = [ SigD rname $ ListT `AppT` (ConT ''Resource `AppT` ConT ''String)
|
let y = [ SigD rname $ ListT `AppT` (ConT ''ResourceTree `AppT` ConT ''String)
|
||||||
, FunD rname [Clause [] (NormalB eres) []]
|
, FunD rname [Clause [] (NormalB eres) []]
|
||||||
]
|
]
|
||||||
return $ x ++ y
|
return $ x ++ y
|
||||||
|
|
||||||
-- | See 'mkYesodData'.
|
-- | See 'mkYesodData'.
|
||||||
mkYesodDispatch :: String -> [Resource String] -> Q [Dec]
|
mkYesodDispatch :: String -> [ResourceTree String] -> Q [Dec]
|
||||||
mkYesodDispatch name = fmap snd . mkYesodGeneral name [] [] False
|
mkYesodDispatch name = fmap snd . mkYesodGeneral name [] [] False
|
||||||
|
|
||||||
mkYesodSubDispatch :: String -> Cxt -> [Resource String] -> Q [Dec]
|
mkYesodSubDispatch :: String -> Cxt -> [ResourceTree String] -> Q [Dec]
|
||||||
mkYesodSubDispatch name clazzes = fmap snd . mkYesodGeneral name' rest clazzes True
|
mkYesodSubDispatch name clazzes = fmap snd . mkYesodGeneral name' rest clazzes True
|
||||||
where (name':rest) = words name
|
where (name':rest) = words name
|
||||||
|
|
||||||
@ -111,7 +112,7 @@ mkYesodGeneral :: String -- ^ foundation type
|
|||||||
-> [String]
|
-> [String]
|
||||||
-> Cxt -- ^ classes
|
-> Cxt -- ^ classes
|
||||||
-> Bool -- ^ is subsite?
|
-> Bool -- ^ is subsite?
|
||||||
-> [Resource String]
|
-> [ResourceTree String]
|
||||||
-> Q ([Dec], [Dec])
|
-> Q ([Dec], [Dec])
|
||||||
mkYesodGeneral name args clazzes isSub resS = do
|
mkYesodGeneral name args clazzes isSub resS = do
|
||||||
let args' = map mkName args
|
let args' = map mkName args
|
||||||
@ -119,7 +120,13 @@ mkYesodGeneral name args clazzes isSub resS = do
|
|||||||
let res = map (fmap parseType) resS
|
let res = map (fmap parseType) resS
|
||||||
renderRouteDec <- mkRenderRouteInstance arg res
|
renderRouteDec <- mkRenderRouteInstance arg res
|
||||||
|
|
||||||
disp <- mkDispatchClause [|yesodRunner|] [|yesodDispatch|] [|fmap chooseRep|] res
|
let logger = mkName "logger"
|
||||||
|
Clause pat body decs <- mkDispatchClause
|
||||||
|
[|yesodRunner $(return $ VarE logger)|]
|
||||||
|
[|yesodDispatch $(return $ VarE logger)|]
|
||||||
|
[|fmap chooseRep|]
|
||||||
|
res
|
||||||
|
let disp = Clause (VarP logger : pat) body decs
|
||||||
let master = mkName "master"
|
let master = mkName "master"
|
||||||
let ctx = if isSub
|
let ctx = if isSub
|
||||||
then ClassP (mkName "Yesod") [VarT master] : clazzes
|
then ClassP (mkName "Yesod") [VarT master] : clazzes
|
||||||
@ -130,7 +137,7 @@ mkYesodGeneral name args clazzes isSub resS = do
|
|||||||
let yesodDispatch' =
|
let yesodDispatch' =
|
||||||
InstanceD ctx ytyp [FunD (mkName "yesodDispatch") [disp]]
|
InstanceD ctx ytyp [FunD (mkName "yesodDispatch") [disp]]
|
||||||
|
|
||||||
return (renderRouteDec : masterTypSyns, [yesodDispatch'])
|
return (renderRouteDec ++ masterTypSyns, [yesodDispatch'])
|
||||||
where
|
where
|
||||||
name' = mkName name
|
name' = mkName name
|
||||||
masterTypSyns
|
masterTypSyns
|
||||||
@ -160,23 +167,24 @@ toWaiApp y = gzip (gzipSettings y) . autohead <$> toWaiAppPlain y
|
|||||||
toWaiAppPlain :: ( Yesod master
|
toWaiAppPlain :: ( Yesod master
|
||||||
, YesodDispatch master master
|
, YesodDispatch master master
|
||||||
) => master -> IO W.Application
|
) => master -> IO W.Application
|
||||||
toWaiAppPlain a = toWaiApp' a <$> makeSessionBackend a
|
toWaiAppPlain a = toWaiApp' a <$> getLogger a <*> makeSessionBackend a
|
||||||
|
|
||||||
|
|
||||||
toWaiApp' :: ( Yesod master
|
toWaiApp' :: ( Yesod master
|
||||||
, YesodDispatch master master
|
, YesodDispatch master master
|
||||||
)
|
)
|
||||||
=> master
|
=> master
|
||||||
|
-> Logger
|
||||||
-> Maybe (SessionBackend master)
|
-> Maybe (SessionBackend master)
|
||||||
-> W.Application
|
-> W.Application
|
||||||
toWaiApp' y sb env =
|
toWaiApp' y logger sb env =
|
||||||
case cleanPath y $ W.pathInfo env of
|
case cleanPath y $ W.pathInfo env of
|
||||||
Left pieces -> sendRedirect y pieces env
|
Left pieces -> sendRedirect y pieces env
|
||||||
Right pieces ->
|
Right pieces ->
|
||||||
yesodDispatch y y id app404 handler405 method pieces sb env
|
yesodDispatch logger y y id app404 handler405 method pieces sb env
|
||||||
where
|
where
|
||||||
app404 = yesodRunner notFound y y Nothing id
|
app404 = yesodRunner logger notFound y y Nothing id
|
||||||
handler405 route = yesodRunner badMethod y y (Just route) id
|
handler405 route = yesodRunner logger badMethod y y (Just route) id
|
||||||
method = decodeUtf8With lenientDecode $ W.requestMethod env
|
method = decodeUtf8With lenientDecode $ W.requestMethod env
|
||||||
|
|
||||||
sendRedirect :: Yesod master => master -> [Text] -> W.Application
|
sendRedirect :: Yesod master => master -> [Text] -> W.Application
|
||||||
@ -202,4 +210,4 @@ instance RenderRoute WaiSubsite where
|
|||||||
renderRoute (WaiSubsiteRoute ps qs) = (ps, qs)
|
renderRoute (WaiSubsiteRoute ps qs) = (ps, qs)
|
||||||
|
|
||||||
instance YesodDispatch WaiSubsite master where
|
instance YesodDispatch WaiSubsite master where
|
||||||
yesodDispatch _master (WaiSubsite app) _tomaster _404 _405 _method _pieces _session = app
|
yesodDispatch _logger _master (WaiSubsite app) _tomaster _404 _405 _method _pieces _session = app
|
||||||
|
|||||||
@ -76,6 +76,7 @@ module Yesod.Handler
|
|||||||
, setSession
|
, setSession
|
||||||
, setSessionBS
|
, setSessionBS
|
||||||
, deleteSession
|
, deleteSession
|
||||||
|
, clearSession
|
||||||
-- ** Ultimate destination
|
-- ** Ultimate destination
|
||||||
, setUltDest
|
, setUltDest
|
||||||
, setUltDestCurrent
|
, setUltDestCurrent
|
||||||
@ -94,6 +95,7 @@ module Yesod.Handler
|
|||||||
, newIdent
|
, newIdent
|
||||||
-- * Lifting
|
-- * Lifting
|
||||||
, MonadLift (..)
|
, MonadLift (..)
|
||||||
|
, handlerToIO
|
||||||
-- * i18n
|
-- * i18n
|
||||||
, getMessageRender
|
, getMessageRender
|
||||||
-- * Per-request caching
|
-- * Per-request caching
|
||||||
@ -137,7 +139,7 @@ import qualified Network.Wai as W
|
|||||||
import qualified Network.HTTP.Types as H
|
import qualified Network.HTTP.Types as H
|
||||||
|
|
||||||
import Text.Hamlet
|
import Text.Hamlet
|
||||||
import qualified Text.Blaze.Renderer.Text
|
import qualified Text.Blaze.Html.Renderer.Text as RenderText
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Data.Text.Encoding (encodeUtf8, decodeUtf8With)
|
import Data.Text.Encoding (encodeUtf8, decodeUtf8With)
|
||||||
import Data.Text.Encoding.Error (lenientDecode)
|
import Data.Text.Encoding.Error (lenientDecode)
|
||||||
@ -145,22 +147,28 @@ import qualified Data.Text.Lazy as TL
|
|||||||
|
|
||||||
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 qualified Data.ByteString.Lazy as L
|
||||||
import Network.Wai.Parse (parseHttpAccept)
|
import Network.Wai.Parse (parseHttpAccept)
|
||||||
|
|
||||||
import Yesod.Content
|
import Yesod.Content
|
||||||
import Data.Maybe (fromMaybe)
|
import Data.Maybe (fromMaybe, mapMaybe)
|
||||||
import Web.Cookie (SetCookie (..), renderSetCookie)
|
import Web.Cookie (SetCookie (..), renderSetCookie)
|
||||||
import Control.Arrow ((***))
|
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
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
import Data.CaseInsensitive (CI)
|
import Data.CaseInsensitive (CI)
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
import Blaze.ByteString.Builder (toByteString)
|
import Blaze.ByteString.Builder (toByteString, toLazyByteString, fromLazyByteString)
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Yesod.Message (RenderMessage (..))
|
import Yesod.Message (RenderMessage (..))
|
||||||
|
|
||||||
import Text.Blaze (toHtml, preEscapedText)
|
import Text.Blaze.Html (toHtml, preEscapedToMarkup)
|
||||||
|
#define preEscapedText preEscapedToMarkup
|
||||||
|
|
||||||
|
import System.Log.FastLogger
|
||||||
|
import Control.Monad.Logger
|
||||||
|
|
||||||
import qualified Yesod.Internal.Cache as Cache
|
import qualified Yesod.Internal.Cache as Cache
|
||||||
import Yesod.Internal.Cache (mkCacheKey, CacheKey)
|
import Yesod.Internal.Cache (mkCacheKey, CacheKey)
|
||||||
@ -171,6 +179,8 @@ import Control.Monad.Trans.Control
|
|||||||
import Control.Monad.Trans.Resource
|
import Control.Monad.Trans.Resource
|
||||||
import Control.Monad.Base
|
import Control.Monad.Base
|
||||||
import Yesod.Routes.Class
|
import Yesod.Routes.Class
|
||||||
|
import Data.Word (Word64)
|
||||||
|
import Language.Haskell.TH.Syntax (Loc)
|
||||||
|
|
||||||
class YesodSubRoute s y where
|
class YesodSubRoute s y where
|
||||||
fromSubRoute :: s -> y -> Route s -> Route y
|
fromSubRoute :: s -> y -> Route s -> Route y
|
||||||
@ -183,6 +193,8 @@ data HandlerData sub master = HandlerData
|
|||||||
, 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
|
, handlerState :: I.IORef GHState
|
||||||
|
, handlerUpload :: Word64 -> FileUpload
|
||||||
|
, handlerLog :: Loc -> LogSource -> LogLevel -> LogStr -> IO ()
|
||||||
}
|
}
|
||||||
|
|
||||||
handlerSubData :: (Route sub -> Route master)
|
handlerSubData :: (Route sub -> Route master)
|
||||||
@ -312,22 +324,43 @@ hcError = liftIO . throwIO . HCError
|
|||||||
|
|
||||||
runRequestBody :: GHandler s m RequestBodyContents
|
runRequestBody :: GHandler s m RequestBodyContents
|
||||||
runRequestBody = do
|
runRequestBody = do
|
||||||
|
hd <- ask
|
||||||
|
let getUpload = handlerUpload hd
|
||||||
|
len = reqBodySize $ handlerRequest hd
|
||||||
|
upload = getUpload len
|
||||||
x <- 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 upload rr
|
||||||
put x { ghsRBC = Just rbc }
|
put x { ghsRBC = Just rbc }
|
||||||
return rbc
|
return rbc
|
||||||
|
|
||||||
rbHelper :: W.Request -> ResourceT IO RequestBodyContents
|
rbHelper :: FileUpload -> W.Request -> ResourceT IO RequestBodyContents
|
||||||
rbHelper req =
|
rbHelper upload =
|
||||||
(map fix1 *** map fix2) <$> (NWP.parseRequestBody NWP.lbsBackEnd req)
|
case upload of
|
||||||
|
FileUploadMemory s -> rbHelper' s mkFileInfoLBS
|
||||||
|
FileUploadDisk s -> rbHelper' s mkFileInfoFile
|
||||||
|
FileUploadSource s -> rbHelper' s mkFileInfoSource
|
||||||
|
|
||||||
|
rbHelper' :: NWP.BackEnd x
|
||||||
|
-> (Text -> Text -> x -> FileInfo)
|
||||||
|
-> W.Request
|
||||||
|
-> ResourceT IO ([(Text, Text)], [(Text, FileInfo)])
|
||||||
|
rbHelper' backend mkFI req =
|
||||||
|
(map fix1 *** mapMaybe fix2) <$> (NWP.parseRequestBody backend req)
|
||||||
where
|
where
|
||||||
fix1 = go *** go
|
fix1 = go *** go
|
||||||
fix2 (x, NWP.FileInfo a b c) =
|
fix2 (x, NWP.FileInfo a' b c)
|
||||||
(go x, FileInfo (go a) (go b) c)
|
| S.null a = Nothing
|
||||||
|
| otherwise = Just (go x, mkFI (go a) (go b) c)
|
||||||
|
where
|
||||||
|
a
|
||||||
|
| S.length a' < 2 = a'
|
||||||
|
| S8.head a' == '"' && S8.last a' == '"' = S.tail $ S.init a'
|
||||||
|
| S8.head a' == '\'' && S8.last a' == '\'' = S.tail $ S.init a'
|
||||||
|
| otherwise = a'
|
||||||
go = decodeUtf8With lenientDecode
|
go = decodeUtf8With lenientDecode
|
||||||
|
|
||||||
-- | Get the sub application argument.
|
-- | Get the sub application argument.
|
||||||
@ -359,6 +392,75 @@ getCurrentRoute = handlerRoute `liftM` ask
|
|||||||
getRouteToMaster :: GHandler sub master (Route sub -> Route master)
|
getRouteToMaster :: GHandler sub master (Route sub -> Route master)
|
||||||
getRouteToMaster = handlerToMaster `liftM` ask
|
getRouteToMaster = handlerToMaster `liftM` ask
|
||||||
|
|
||||||
|
|
||||||
|
-- | Returns a function that runs 'GHandler' actions inside @IO@.
|
||||||
|
--
|
||||||
|
-- Sometimes you want to run an inner 'GHandler' action outside
|
||||||
|
-- the control flow of an HTTP request (on the outer 'GHandler'
|
||||||
|
-- action). For example, you may want to spawn a new thread:
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
-- getFooR :: Handler RepHtml
|
||||||
|
-- getFooR = do
|
||||||
|
-- runInnerHandler <- handlerToIO
|
||||||
|
-- liftIO $ forkIO $ runInnerHandler $ do
|
||||||
|
-- /Code here runs inside GHandler but on a new thread./
|
||||||
|
-- /This is the inner GHandler./
|
||||||
|
-- ...
|
||||||
|
-- /Code here runs inside the request's control flow./
|
||||||
|
-- /This is the outer GHandler./
|
||||||
|
-- ...
|
||||||
|
-- @
|
||||||
|
--
|
||||||
|
-- Another use case for this function is creating a stream of
|
||||||
|
-- server-sent events using 'GHandler' actions (see
|
||||||
|
-- @yesod-eventsource@).
|
||||||
|
--
|
||||||
|
-- Most of the environment from the outer 'GHandler' is preserved
|
||||||
|
-- on the inner 'GHandler', however:
|
||||||
|
--
|
||||||
|
-- * The request body is cleared (otherwise it would be very
|
||||||
|
-- difficult to prevent huge memory leaks).
|
||||||
|
--
|
||||||
|
-- * The cache is cleared (see 'CacheKey').
|
||||||
|
--
|
||||||
|
-- Changes to the response made inside the inner 'GHandler' are
|
||||||
|
-- ignored (e.g., session variables, cookies, response headers).
|
||||||
|
-- This allows the inner 'GHandler' to outlive the outer
|
||||||
|
-- 'GHandler' (e.g., on the @forkIO@ example above, a response
|
||||||
|
-- may be sent to the client without killing the new thread).
|
||||||
|
handlerToIO :: MonadIO m => GHandler sub master (GHandler sub master a -> m a)
|
||||||
|
handlerToIO =
|
||||||
|
GHandler $ \oldHandlerData -> do
|
||||||
|
-- Let go of the request body, cache and response headers.
|
||||||
|
let oldReq = handlerRequest oldHandlerData
|
||||||
|
oldWaiReq = reqWaiRequest oldReq
|
||||||
|
newWaiReq = oldWaiReq { W.requestBody = mempty }
|
||||||
|
newReq = oldReq { reqWaiRequest = newWaiReq
|
||||||
|
, reqBodySize = 0 }
|
||||||
|
clearedOldHandlerData =
|
||||||
|
oldHandlerData { handlerRequest = err "handlerRequest never here"
|
||||||
|
, handlerState = err "handlerState never here" }
|
||||||
|
where
|
||||||
|
err :: String -> a
|
||||||
|
err = error . ("handlerToIO: clearedOldHandlerData/" ++)
|
||||||
|
newState <- liftIO $ do
|
||||||
|
oldState <- I.readIORef (handlerState oldHandlerData)
|
||||||
|
return $ oldState { ghsRBC = Nothing
|
||||||
|
, ghsIdent = 1
|
||||||
|
, ghsCache = mempty
|
||||||
|
, ghsHeaders = mempty }
|
||||||
|
|
||||||
|
-- Return GHandler running function.
|
||||||
|
return $ \(GHandler f) -> liftIO $ do
|
||||||
|
-- The state IORef needs to be created here, otherwise it
|
||||||
|
-- will be shared by different invocations of this function.
|
||||||
|
newStateIORef <- I.newIORef newState
|
||||||
|
runResourceT $ f clearedOldHandlerData
|
||||||
|
{ handlerRequest = newReq
|
||||||
|
, handlerState = newStateIORef }
|
||||||
|
|
||||||
|
|
||||||
-- | 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.
|
||||||
runHandler :: HasReps c
|
runHandler :: HasReps c
|
||||||
@ -368,8 +470,10 @@ runHandler :: HasReps c
|
|||||||
-> (Route sub -> Route master)
|
-> (Route sub -> Route master)
|
||||||
-> master
|
-> master
|
||||||
-> sub
|
-> sub
|
||||||
|
-> (Word64 -> FileUpload)
|
||||||
|
-> (Loc -> LogSource -> LogLevel -> LogStr -> IO ())
|
||||||
-> YesodApp
|
-> YesodApp
|
||||||
runHandler handler mrender sroute tomr master sub =
|
runHandler handler mrender sroute tomr master sub upload log' =
|
||||||
YesodApp $ \eh rr cts initSession -> do
|
YesodApp $ \eh rr cts initSession -> do
|
||||||
let toErrorHandler e =
|
let toErrorHandler e =
|
||||||
case fromException e of
|
case fromException e of
|
||||||
@ -390,6 +494,8 @@ runHandler handler mrender sroute tomr master sub =
|
|||||||
, handlerRender = mrender
|
, handlerRender = mrender
|
||||||
, handlerToMaster = tomr
|
, handlerToMaster = tomr
|
||||||
, handlerState = istate
|
, handlerState = istate
|
||||||
|
, handlerUpload = upload
|
||||||
|
, handlerLog = log'
|
||||||
}
|
}
|
||||||
contents' <- catch (fmap Right $ unGHandler handler hd)
|
contents' <- catch (fmap Right $ unGHandler handler hd)
|
||||||
(\e -> return $ Left $ maybe (HCError $ toErrorHandler e) id
|
(\e -> return $ Left $ maybe (HCError $ toErrorHandler e) id
|
||||||
@ -410,7 +516,10 @@ runHandler handler mrender sroute tomr master sub =
|
|||||||
case contents of
|
case contents of
|
||||||
HCContent status a -> do
|
HCContent status a -> do
|
||||||
(ct, c) <- liftIO $ a cts
|
(ct, c) <- liftIO $ a cts
|
||||||
return $ YARPlain status (appEndo headers []) ct c finalSession
|
ec' <- liftIO $ evaluateContent c
|
||||||
|
case ec' of
|
||||||
|
Left e -> handleError e
|
||||||
|
Right c' -> return $ YARPlain status (appEndo headers []) ct c' finalSession
|
||||||
HCError e -> handleError e
|
HCError e -> handleError e
|
||||||
HCRedirect status loc -> do
|
HCRedirect status loc -> do
|
||||||
let hs = Header "Location" (encodeUtf8 loc) : appEndo headers []
|
let hs = Header "Location" (encodeUtf8 loc) : appEndo headers []
|
||||||
@ -430,6 +539,15 @@ runHandler handler mrender sroute tomr master sub =
|
|||||||
finalSession
|
finalSession
|
||||||
HCWai r -> return $ YARWai r
|
HCWai r -> return $ YARWai r
|
||||||
|
|
||||||
|
evaluateContent :: Content -> IO (Either ErrorResponse Content)
|
||||||
|
evaluateContent (ContentBuilder b mlen) = Control.Exception.handle f $ do
|
||||||
|
let lbs = toLazyByteString b
|
||||||
|
L.length lbs `seq` return (Right $ ContentBuilder (fromLazyByteString lbs) mlen)
|
||||||
|
where
|
||||||
|
f :: SomeException -> IO (Either ErrorResponse Content)
|
||||||
|
f = return . Left . InternalError . T.pack . show
|
||||||
|
evaluateContent c = return (Right c)
|
||||||
|
|
||||||
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
|
||||||
@ -527,7 +645,7 @@ msgKey = "_MSG"
|
|||||||
--
|
--
|
||||||
-- See 'getMessage'.
|
-- See 'getMessage'.
|
||||||
setMessage :: Html -> GHandler sub master ()
|
setMessage :: Html -> GHandler sub master ()
|
||||||
setMessage = setSession msgKey . T.concat . TL.toChunks . Text.Blaze.Renderer.Text.renderHtml
|
setMessage = setSession msgKey . T.concat . TL.toChunks . RenderText.renderHtml
|
||||||
|
|
||||||
-- | Sets a message in the user's session.
|
-- | Sets a message in the user's session.
|
||||||
--
|
--
|
||||||
@ -639,7 +757,7 @@ getExpires m = do
|
|||||||
--
|
--
|
||||||
-- Note: although the value used for key and path is 'Text', you should only
|
-- Note: although the value used for key and path is 'Text', you should only
|
||||||
-- use ASCII values to be HTTP compliant.
|
-- use ASCII values to be HTTP compliant.
|
||||||
deleteCookie :: Text -- ^ key
|
deleteCookie :: Text -- ^ key
|
||||||
-> Text -- ^ path
|
-> Text -- ^ path
|
||||||
-> GHandler sub master ()
|
-> GHandler sub master ()
|
||||||
deleteCookie a = addHeader . DeleteCookie (encodeUtf8 a) . encodeUtf8
|
deleteCookie a = addHeader . DeleteCookie (encodeUtf8 a) . encodeUtf8
|
||||||
@ -700,6 +818,12 @@ setSessionBS k = modify . modSession . Map.insert k
|
|||||||
deleteSession :: Text -> GHandler sub master ()
|
deleteSession :: Text -> GHandler sub master ()
|
||||||
deleteSession = modify . modSession . Map.delete
|
deleteSession = modify . modSession . Map.delete
|
||||||
|
|
||||||
|
-- | Clear all session variables.
|
||||||
|
--
|
||||||
|
-- Since: 1.0.1
|
||||||
|
clearSession :: GHandler sub master ()
|
||||||
|
clearSession = modify $ \x -> x { ghsSession = Map.empty }
|
||||||
|
|
||||||
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 }
|
||||||
|
|
||||||
@ -756,6 +880,8 @@ getSession = liftM ghsSession get
|
|||||||
handlerToYAR :: (HasReps a, HasReps b)
|
handlerToYAR :: (HasReps a, HasReps b)
|
||||||
=> master -- ^ master site foundation
|
=> master -- ^ master site foundation
|
||||||
-> sub -- ^ sub site foundation
|
-> sub -- ^ sub site foundation
|
||||||
|
-> (Word64 -> FileUpload)
|
||||||
|
-> (Loc -> LogSource -> LogLevel -> LogStr -> IO ())
|
||||||
-> (Route sub -> Route master)
|
-> (Route sub -> Route master)
|
||||||
-> (Route master -> [(Text, Text)] -> Text) -- route renderer
|
-> (Route master -> [(Text, Text)] -> Text) -- route renderer
|
||||||
-> (ErrorResponse -> GHandler sub master a)
|
-> (ErrorResponse -> GHandler sub master a)
|
||||||
@ -764,28 +890,31 @@ handlerToYAR :: (HasReps a, HasReps b)
|
|||||||
-> SessionMap
|
-> SessionMap
|
||||||
-> GHandler sub master b
|
-> GHandler sub master b
|
||||||
-> ResourceT IO YesodAppResult
|
-> ResourceT IO YesodAppResult
|
||||||
handlerToYAR y s toMasterRoute render errorHandler rr murl sessionMap h =
|
handlerToYAR y s upload log' toMasterRoute render errorHandler rr murl sessionMap h =
|
||||||
unYesodApp ya eh' rr types sessionMap
|
unYesodApp ya eh' rr types sessionMap
|
||||||
where
|
where
|
||||||
ya = runHandler h render murl toMasterRoute y s
|
ya = runHandler h render murl toMasterRoute y s upload log'
|
||||||
eh' er = runHandler (errorHandler' er) render murl toMasterRoute y s
|
eh' er = runHandler (errorHandler' er) render murl toMasterRoute y s upload log'
|
||||||
types = httpAccept $ reqWaiRequest rr
|
types = httpAccept $ reqWaiRequest rr
|
||||||
errorHandler' = localNoCurrent . errorHandler
|
errorHandler' = localNoCurrent . errorHandler
|
||||||
|
|
||||||
yarToResponse :: YesodAppResult -> [(CI H.Ascii, H.Ascii)] -> W.Response
|
yarToResponse :: YesodAppResult -> [(CI ByteString, ByteString)] -> W.Response
|
||||||
yarToResponse (YARWai a) _ = a
|
yarToResponse (YARWai a) _ = a
|
||||||
yarToResponse (YARPlain s hs _ c _) extraHeaders =
|
yarToResponse (YARPlain s hs _ c _) extraHeaders =
|
||||||
case c of
|
go c
|
||||||
ContentBuilder b mlen ->
|
|
||||||
let hs' = maybe finalHeaders finalHeaders' mlen
|
|
||||||
in W.ResponseBuilder s hs' b
|
|
||||||
ContentFile fp p -> W.ResponseFile s finalHeaders fp p
|
|
||||||
ContentSource body -> W.ResponseSource s finalHeaders body
|
|
||||||
where
|
where
|
||||||
finalHeaders = extraHeaders ++ map headerToPair hs
|
finalHeaders = extraHeaders ++ map headerToPair hs
|
||||||
finalHeaders' len = ("Content-Length", S8.pack $ show len)
|
finalHeaders' len = ("Content-Length", S8.pack $ show len)
|
||||||
: finalHeaders
|
: finalHeaders
|
||||||
|
|
||||||
|
go (ContentBuilder b mlen) =
|
||||||
|
W.ResponseBuilder s hs' b
|
||||||
|
where
|
||||||
|
hs' = maybe finalHeaders finalHeaders' mlen
|
||||||
|
go (ContentFile fp p) = W.ResponseFile s finalHeaders fp p
|
||||||
|
go (ContentSource body) = W.ResponseSource s finalHeaders body
|
||||||
|
go (ContentDontEvaluate c') = go c'
|
||||||
|
|
||||||
httpAccept :: W.Request -> [ContentType]
|
httpAccept :: W.Request -> [ContentType]
|
||||||
httpAccept = parseHttpAccept
|
httpAccept = parseHttpAccept
|
||||||
. fromMaybe mempty
|
. fromMaybe mempty
|
||||||
@ -794,7 +923,7 @@ httpAccept = parseHttpAccept
|
|||||||
|
|
||||||
-- | Convert Header to a key/value pair.
|
-- | Convert Header to a key/value pair.
|
||||||
headerToPair :: Header
|
headerToPair :: Header
|
||||||
-> (CI H.Ascii, H.Ascii)
|
-> (CI ByteString, ByteString)
|
||||||
headerToPair (AddCookie sc) =
|
headerToPair (AddCookie sc) =
|
||||||
("Set-Cookie", toByteString $ renderSetCookie $ sc)
|
("Set-Cookie", toByteString $ renderSetCookie $ sc)
|
||||||
headerToPair (DeleteCookie key path) =
|
headerToPair (DeleteCookie key path) =
|
||||||
@ -826,6 +955,7 @@ redirectToPost :: RedirectUrl master url => url -> GHandler sub master a
|
|||||||
redirectToPost url = do
|
redirectToPost url = do
|
||||||
urlText <- toTextUrl url
|
urlText <- toTextUrl url
|
||||||
hamletToRepHtml [hamlet|
|
hamletToRepHtml [hamlet|
|
||||||
|
$newline never
|
||||||
$doctype 5
|
$doctype 5
|
||||||
|
|
||||||
<html>
|
<html>
|
||||||
@ -916,7 +1046,17 @@ instance MonadUnsafeIO (GHandler sub master) where
|
|||||||
instance MonadThrow (GHandler sub master) where
|
instance MonadThrow (GHandler sub master) where
|
||||||
monadThrow = liftIO . throwIO
|
monadThrow = liftIO . throwIO
|
||||||
instance MonadResource (GHandler sub master) where
|
instance MonadResource (GHandler sub master) where
|
||||||
|
#if MIN_VERSION_resourcet(0,4,0)
|
||||||
|
liftResourceT = lift . liftResourceT
|
||||||
|
#else
|
||||||
allocate a = lift . allocate a
|
allocate a = lift . allocate a
|
||||||
register = lift . register
|
register = lift . register
|
||||||
release = lift . release
|
release = lift . release
|
||||||
resourceMask = lift . resourceMask
|
resourceMask = lift . resourceMask
|
||||||
|
#endif
|
||||||
|
|
||||||
|
instance MonadLogger (GHandler sub master) where
|
||||||
|
monadLoggerLog a c d = monadLoggerLogSource a "" c d
|
||||||
|
monadLoggerLogSource a b c d = do
|
||||||
|
hd <- ask
|
||||||
|
liftIO $ handlerLog hd a b c (toLogStr d)
|
||||||
|
|||||||
@ -4,6 +4,8 @@
|
|||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE DeriveDataTypeable #-}
|
{-# LANGUAGE DeriveDataTypeable #-}
|
||||||
-- | Normal users should never need access to these.
|
-- | Normal users should never need access to these.
|
||||||
|
--
|
||||||
|
-- Note that no guarantees of API stability are provided on this module. Use at your own risk.
|
||||||
module Yesod.Internal
|
module Yesod.Internal
|
||||||
( -- * Error responses
|
( -- * Error responses
|
||||||
ErrorResponse (..)
|
ErrorResponse (..)
|
||||||
@ -24,11 +26,11 @@ module Yesod.Internal
|
|||||||
, runUniqueList
|
, runUniqueList
|
||||||
, toUnique
|
, toUnique
|
||||||
-- * Names
|
-- * Names
|
||||||
, sessionName
|
|
||||||
, tokenKey
|
, tokenKey
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Text.Hamlet (HtmlUrl, hamlet, Html)
|
import Text.Hamlet (HtmlUrl, Html)
|
||||||
|
import Text.Blaze.Html (toHtml)
|
||||||
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)
|
||||||
@ -42,8 +44,8 @@ import qualified Network.HTTP.Types as H
|
|||||||
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)
|
import Data.Text.Lazy.Builder (Builder)
|
||||||
import Network.HTTP.Types (Ascii)
|
|
||||||
import Web.Cookie (SetCookie (..))
|
import Web.Cookie (SetCookie (..))
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
|
||||||
-- | Responses to indicate some form of an error occurred. These are different
|
-- | Responses to indicate some form of an error occurred. These are different
|
||||||
-- from 'SpecialResponse' in that they allow for custom error pages.
|
-- from 'SpecialResponse' in that they allow for custom error pages.
|
||||||
@ -60,8 +62,8 @@ instance Exception ErrorResponse
|
|||||||
-- | Headers to be added to a 'Result'.
|
-- | Headers to be added to a 'Result'.
|
||||||
data Header =
|
data Header =
|
||||||
AddCookie SetCookie
|
AddCookie SetCookie
|
||||||
| DeleteCookie Ascii Ascii
|
| DeleteCookie ByteString ByteString
|
||||||
| Header Ascii Ascii
|
| Header ByteString ByteString
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
langKey :: IsString a => a
|
langKey :: IsString a => a
|
||||||
@ -70,10 +72,8 @@ langKey = "_LANG"
|
|||||||
data Location url = Local url | Remote Text
|
data Location url = Local url | Remote Text
|
||||||
deriving (Show, Eq)
|
deriving (Show, Eq)
|
||||||
locationToHtmlUrl :: Location url -> HtmlUrl url
|
locationToHtmlUrl :: Location url -> HtmlUrl url
|
||||||
locationToHtmlUrl (Local url) = [hamlet|\@{url}
|
locationToHtmlUrl (Local url) render = toHtml $ render url []
|
||||||
|]
|
locationToHtmlUrl (Remote s) _ = toHtml s
|
||||||
locationToHtmlUrl (Remote s) = [hamlet|\#{s}
|
|
||||||
|]
|
|
||||||
|
|
||||||
newtype UniqueList x = UniqueList ([x] -> [x])
|
newtype UniqueList x = UniqueList ([x] -> [x])
|
||||||
instance Monoid (UniqueList x) where
|
instance Monoid (UniqueList x) where
|
||||||
@ -98,19 +98,17 @@ newtype Body url = Body (HtmlUrl url)
|
|||||||
tokenKey :: IsString a => a
|
tokenKey :: IsString a => a
|
||||||
tokenKey = "_TOKEN"
|
tokenKey = "_TOKEN"
|
||||||
|
|
||||||
sessionName :: IsString a => a
|
|
||||||
sessionName = "_SESSION"
|
|
||||||
|
|
||||||
type CssBuilderUrl a = (a -> [(Text, Text)] -> Text) -> Builder
|
type CssBuilderUrl a = (a -> [(Text, Text)] -> Text) -> Builder
|
||||||
|
|
||||||
data GWData a = GWData
|
data GWData a = GWData
|
||||||
!(Body a)
|
{ gwdBody :: !(Body a)
|
||||||
!(Last Title)
|
, gwdTitle :: !(Last Title)
|
||||||
!(UniqueList (Script a))
|
, gwdScripts :: !(UniqueList (Script a))
|
||||||
!(UniqueList (Stylesheet a))
|
, gwdStylesheets :: !(UniqueList (Stylesheet a))
|
||||||
!(Map.Map (Maybe Text) (CssBuilderUrl a)) -- media type
|
, gwdCss :: !(Map.Map (Maybe Text) (CssBuilderUrl a)) -- media type
|
||||||
!(Maybe (JavascriptUrl a))
|
, gwdJavascript :: !(Maybe (JavascriptUrl a))
|
||||||
!(Head a)
|
, gwdHead :: !(Head a)
|
||||||
|
}
|
||||||
instance Monoid (GWData a) where
|
instance Monoid (GWData a) where
|
||||||
mempty = GWData mempty mempty mempty mempty mempty mempty mempty
|
mempty = GWData mempty mempty mempty mempty mempty mempty mempty
|
||||||
mappend (GWData a1 a2 a3 a4 a5 a6 a7)
|
mappend (GWData a1 a2 a3 a4 a5 a6 a7)
|
||||||
|
|||||||
@ -20,11 +20,6 @@ module Yesod.Internal.Core
|
|||||||
, defaultErrorHandler
|
, defaultErrorHandler
|
||||||
-- * Data types
|
-- * Data types
|
||||||
, AuthResult (..)
|
, AuthResult (..)
|
||||||
-- * Logging
|
|
||||||
, LogLevel (..)
|
|
||||||
, formatLogMessage
|
|
||||||
, fileLocationToString
|
|
||||||
, messageLoggerHandler
|
|
||||||
-- * Sessions
|
-- * Sessions
|
||||||
, SessionBackend (..)
|
, SessionBackend (..)
|
||||||
, defaultClientSessionBackend
|
, defaultClientSessionBackend
|
||||||
@ -40,6 +35,8 @@ module Yesod.Internal.Core
|
|||||||
, yesodRender
|
, yesodRender
|
||||||
, resolveApproot
|
, resolveApproot
|
||||||
, Approot (..)
|
, Approot (..)
|
||||||
|
, FileUpload (..)
|
||||||
|
, runFakeHandler
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Yesod.Content
|
import Yesod.Content
|
||||||
@ -47,6 +44,7 @@ import Yesod.Handler hiding (lift, getExpires)
|
|||||||
|
|
||||||
import Yesod.Routes.Class
|
import Yesod.Routes.Class
|
||||||
|
|
||||||
|
import Data.Word (Word64)
|
||||||
import Control.Arrow ((***))
|
import Control.Arrow ((***))
|
||||||
import Control.Monad (forM)
|
import Control.Monad (forM)
|
||||||
import Yesod.Widget
|
import Yesod.Widget
|
||||||
@ -58,6 +56,7 @@ import Yesod.Internal.Request
|
|||||||
import qualified Web.ClientSession as CS
|
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 qualified Data.IORef as I
|
||||||
import Data.Monoid
|
import Data.Monoid
|
||||||
import Text.Hamlet
|
import Text.Hamlet
|
||||||
import Text.Julius
|
import Text.Julius
|
||||||
@ -67,6 +66,7 @@ import Data.Text.Lazy.Builder (toLazyText)
|
|||||||
import Data.Text.Lazy.Encoding (encodeUtf8)
|
import Data.Text.Lazy.Encoding (encodeUtf8)
|
||||||
import Data.Maybe (fromMaybe, isJust)
|
import Data.Maybe (fromMaybe, isJust)
|
||||||
import Control.Monad.IO.Class (MonadIO (liftIO))
|
import Control.Monad.IO.Class (MonadIO (liftIO))
|
||||||
|
import Control.Monad.Trans.Resource (runResourceT)
|
||||||
import Web.Cookie (parseCookies)
|
import Web.Cookie (parseCookies)
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
import Data.Time
|
import Data.Time
|
||||||
@ -80,17 +80,19 @@ import Blaze.ByteString.Builder.Char.Utf8 (fromText)
|
|||||||
import Data.List (foldl')
|
import Data.List (foldl')
|
||||||
import qualified Network.HTTP.Types as H
|
import qualified Network.HTTP.Types as H
|
||||||
import Web.Cookie (SetCookie (..))
|
import Web.Cookie (SetCookie (..))
|
||||||
import qualified Data.Text.Lazy as TL
|
import Language.Haskell.TH.Syntax (Loc (..))
|
||||||
import qualified Data.Text.Lazy.IO
|
import Text.Blaze (preEscapedToMarkup)
|
||||||
import qualified Data.Text.Lazy.Builder as TB
|
|
||||||
import Language.Haskell.TH.Syntax (Loc (..), Lift (..))
|
|
||||||
import Text.Blaze (preEscapedLazyText)
|
|
||||||
import Data.Aeson (Value (Array, String))
|
import Data.Aeson (Value (Array, String))
|
||||||
import Data.Aeson.Encode (encode)
|
import Data.Aeson.Encode (encode)
|
||||||
import qualified Data.Vector as Vector
|
import qualified Data.Vector as Vector
|
||||||
import Network.Wai.Middleware.Gzip (GzipSettings, def)
|
import Network.Wai.Middleware.Gzip (GzipSettings, def)
|
||||||
|
import Network.Wai.Parse (tempFileBackEnd, lbsBackEnd)
|
||||||
import qualified Paths_yesod_core
|
import qualified Paths_yesod_core
|
||||||
import Data.Version (showVersion)
|
import Data.Version (showVersion)
|
||||||
|
import System.Log.FastLogger (Logger, mkLogger, loggerDate, LogStr (..), loggerPutStr)
|
||||||
|
import Control.Monad.Logger (LogLevel (LevelInfo, LevelOther), LogSource)
|
||||||
|
import System.Log.FastLogger.Date (ZonedDate)
|
||||||
|
import System.IO (stdout)
|
||||||
|
|
||||||
yesodVersion :: String
|
yesodVersion :: String
|
||||||
yesodVersion = showVersion Paths_yesod_core.version
|
yesodVersion = showVersion Paths_yesod_core.version
|
||||||
@ -100,7 +102,8 @@ yesodVersion = showVersion Paths_yesod_core.version
|
|||||||
class YesodDispatch sub master where
|
class YesodDispatch sub master where
|
||||||
yesodDispatch
|
yesodDispatch
|
||||||
:: Yesod master
|
:: Yesod master
|
||||||
=> master
|
=> Logger
|
||||||
|
-> master
|
||||||
-> sub
|
-> sub
|
||||||
-> (Route sub -> Route master)
|
-> (Route sub -> Route master)
|
||||||
-> (Maybe (SessionBackend master) -> W.Application) -- ^ 404 handler
|
-> (Maybe (SessionBackend master) -> W.Application) -- ^ 404 handler
|
||||||
@ -111,7 +114,8 @@ class YesodDispatch sub master where
|
|||||||
-> W.Application
|
-> W.Application
|
||||||
|
|
||||||
yesodRunner :: Yesod master
|
yesodRunner :: Yesod master
|
||||||
=> GHandler sub master ChooseRep
|
=> Logger
|
||||||
|
-> GHandler sub master ChooseRep
|
||||||
-> master
|
-> master
|
||||||
-> sub
|
-> sub
|
||||||
-> Maybe (Route sub)
|
-> Maybe (Route sub)
|
||||||
@ -161,6 +165,7 @@ class RenderRoute a => Yesod a where
|
|||||||
p <- widgetToPageContent w
|
p <- widgetToPageContent w
|
||||||
mmsg <- getMessage
|
mmsg <- getMessage
|
||||||
hamletToRepHtml [hamlet|
|
hamletToRepHtml [hamlet|
|
||||||
|
$newline never
|
||||||
$doctype 5
|
$doctype 5
|
||||||
|
|
||||||
<html>
|
<html>
|
||||||
@ -222,10 +227,13 @@ $doctype 5
|
|||||||
cleanPath :: a -> [Text] -> Either [Text] [Text]
|
cleanPath :: a -> [Text] -> Either [Text] [Text]
|
||||||
cleanPath _ s =
|
cleanPath _ s =
|
||||||
if corrected == s
|
if corrected == s
|
||||||
then Right s
|
then Right $ map dropDash s
|
||||||
else Left corrected
|
else Left corrected
|
||||||
where
|
where
|
||||||
corrected = filter (not . T.null) s
|
corrected = filter (not . T.null) s
|
||||||
|
dropDash t
|
||||||
|
| T.all (== '-') t = T.drop 1 t
|
||||||
|
| otherwise = t
|
||||||
|
|
||||||
-- | Builds an absolute URL by concatenating the application root with the
|
-- | Builds an absolute URL by concatenating the application root with the
|
||||||
-- pieces of a path and a query string, if any.
|
-- pieces of a path and a query string, if any.
|
||||||
@ -235,12 +243,16 @@ $doctype 5
|
|||||||
-> [T.Text] -- ^ path pieces
|
-> [T.Text] -- ^ path pieces
|
||||||
-> [(T.Text, T.Text)] -- ^ query string
|
-> [(T.Text, T.Text)] -- ^ query string
|
||||||
-> Builder
|
-> Builder
|
||||||
joinPath _ ar pieces' qs' = fromText ar `mappend` encodePath pieces qs
|
joinPath _ ar pieces' qs' =
|
||||||
|
fromText ar `mappend` encodePath pieces qs
|
||||||
where
|
where
|
||||||
pieces = if null pieces' then [""] else pieces'
|
pieces = if null pieces' then [""] else map addDash pieces'
|
||||||
qs = map (TE.encodeUtf8 *** go) qs'
|
qs = map (TE.encodeUtf8 *** go) qs'
|
||||||
go "" = Nothing
|
go "" = Nothing
|
||||||
go x = Just $ TE.encodeUtf8 x
|
go x = Just $ TE.encodeUtf8 x
|
||||||
|
addDash t
|
||||||
|
| T.all (== '-') t = T.cons '-' t
|
||||||
|
| otherwise = t
|
||||||
|
|
||||||
-- | This function is used to store some static content to be served as an
|
-- | This function is used to store some static content to be served as an
|
||||||
-- external file. The most common case of this is stashing CSS and
|
-- external file. The most common case of this is stashing CSS and
|
||||||
@ -281,21 +293,41 @@ $doctype 5
|
|||||||
cookieDomain _ = Nothing
|
cookieDomain _ = Nothing
|
||||||
|
|
||||||
-- | Maximum allowed length of the request body, in bytes.
|
-- | Maximum allowed length of the request body, in bytes.
|
||||||
maximumContentLength :: a -> Maybe (Route a) -> Int
|
--
|
||||||
|
-- Default: 2 megabytes.
|
||||||
|
maximumContentLength :: a -> Maybe (Route a) -> Word64
|
||||||
maximumContentLength _ _ = 2 * 1024 * 1024 -- 2 megabytes
|
maximumContentLength _ _ = 2 * 1024 * 1024 -- 2 megabytes
|
||||||
|
|
||||||
-- | Send a message to the log. By default, prints to stdout.
|
-- | Returns a @Logger@ to use for log messages.
|
||||||
|
--
|
||||||
|
-- Default: Sends to stdout and automatically flushes on each write.
|
||||||
|
getLogger :: a -> IO Logger
|
||||||
|
getLogger _ = mkLogger True stdout
|
||||||
|
|
||||||
|
-- | Send a message to the @Logger@ provided by @getLogger@.
|
||||||
|
--
|
||||||
|
-- Note: This method is no longer used. Instead, you should override
|
||||||
|
-- 'messageLoggerSource'.
|
||||||
messageLogger :: a
|
messageLogger :: a
|
||||||
|
-> Logger
|
||||||
-> Loc -- ^ position in source code
|
-> Loc -- ^ position in source code
|
||||||
-> LogLevel
|
-> LogLevel
|
||||||
-> Text -- ^ message
|
-> LogStr -- ^ message
|
||||||
-> IO ()
|
-> IO ()
|
||||||
messageLogger a loc level msg =
|
messageLogger a logger loc = messageLoggerSource a logger loc ""
|
||||||
if level < logLevel a
|
|
||||||
then return ()
|
-- | Send a message to the @Logger@ provided by @getLogger@.
|
||||||
else
|
messageLoggerSource :: a
|
||||||
formatLogMessage loc level msg >>=
|
-> Logger
|
||||||
Data.Text.Lazy.IO.putStrLn
|
-> Loc -- ^ position in source code
|
||||||
|
-> LogSource
|
||||||
|
-> LogLevel
|
||||||
|
-> LogStr -- ^ message
|
||||||
|
-> IO ()
|
||||||
|
messageLoggerSource a logger loc source level msg =
|
||||||
|
if shouldLog a source level
|
||||||
|
then formatLogMessage (loggerDate logger) loc source level msg >>= loggerPutStr logger
|
||||||
|
else return ()
|
||||||
|
|
||||||
-- | The logging level in place for this application. Any messages below
|
-- | The logging level in place for this application. Any messages below
|
||||||
-- this level will simply be ignored.
|
-- this level will simply be ignored.
|
||||||
@ -323,38 +355,50 @@ $doctype 5
|
|||||||
key <- CS.getKey CS.defaultKeyFile
|
key <- CS.getKey CS.defaultKeyFile
|
||||||
return $ Just $ clientSessionBackend key 120
|
return $ Just $ clientSessionBackend key 120
|
||||||
|
|
||||||
|
-- | How to store uploaded files.
|
||||||
|
--
|
||||||
|
-- Default: Whe nthe request body is greater than 50kb, store in a temp
|
||||||
|
-- file. Otherwise, store in memory.
|
||||||
|
fileUpload :: a
|
||||||
|
-> Word64 -- ^ request body size
|
||||||
|
-> FileUpload
|
||||||
|
fileUpload _ size
|
||||||
|
| size > 50000 = FileUploadDisk tempFileBackEnd
|
||||||
|
| otherwise = FileUploadMemory lbsBackEnd
|
||||||
|
|
||||||
messageLoggerHandler :: Yesod m
|
-- | Should we log the given log source/level combination.
|
||||||
=> Loc -> LogLevel -> Text -> GHandler s m ()
|
--
|
||||||
messageLoggerHandler loc level msg = do
|
-- Default: Logs everything at or above 'logLevel'
|
||||||
y <- getYesod
|
shouldLog :: a -> LogSource -> LogLevel -> Bool
|
||||||
liftIO $ messageLogger y loc level msg
|
shouldLog a _ level = level >= logLevel a
|
||||||
|
|
||||||
data LogLevel = LevelDebug | LevelInfo | LevelWarn | LevelError | LevelOther Text
|
{-# DEPRECATED messageLogger "Please use messageLoggerSource (since yesod-core 1.1.2)" #-}
|
||||||
deriving (Eq, Show, Read, Ord)
|
|
||||||
|
|
||||||
instance Lift LogLevel where
|
formatLogMessage :: IO ZonedDate
|
||||||
lift LevelDebug = [|LevelDebug|]
|
-> Loc
|
||||||
lift LevelInfo = [|LevelInfo|]
|
-> LogSource
|
||||||
lift LevelWarn = [|LevelWarn|]
|
|
||||||
lift LevelError = [|LevelError|]
|
|
||||||
lift (LevelOther x) = [|LevelOther $ T.pack $(lift $ T.unpack x)|]
|
|
||||||
|
|
||||||
formatLogMessage :: Loc
|
|
||||||
-> LogLevel
|
-> LogLevel
|
||||||
-> Text -- ^ message
|
-> LogStr -- ^ message
|
||||||
-> IO TL.Text
|
-> IO [LogStr]
|
||||||
formatLogMessage loc level msg = do
|
formatLogMessage getdate loc src level msg = do
|
||||||
now <- getCurrentTime
|
now <- getdate
|
||||||
return $ TB.toLazyText $
|
return
|
||||||
TB.fromText (T.pack $ show now)
|
[ LB now
|
||||||
`mappend` TB.fromText " ["
|
, LB " ["
|
||||||
`mappend` TB.fromText (T.pack $ drop 5 $ show level)
|
, LS $
|
||||||
`mappend` TB.fromText "] "
|
case level of
|
||||||
`mappend` TB.fromText msg
|
LevelOther t -> T.unpack t
|
||||||
`mappend` TB.fromText " @("
|
_ -> drop 5 $ show level
|
||||||
`mappend` TB.fromText (T.pack $ fileLocationToString loc)
|
, LS $
|
||||||
`mappend` TB.fromText ") "
|
if T.null src
|
||||||
|
then ""
|
||||||
|
else "#" ++ T.unpack src
|
||||||
|
, LB "] "
|
||||||
|
, msg
|
||||||
|
, LB " @("
|
||||||
|
, LS $ fileLocationToString loc
|
||||||
|
, LB ")\n"
|
||||||
|
]
|
||||||
|
|
||||||
-- taken from file-location package
|
-- taken from file-location package
|
||||||
-- turn the TH Loc loaction information into a human readable string
|
-- turn the TH Loc loaction information into a human readable string
|
||||||
@ -367,31 +411,26 @@ fileLocationToString loc = (loc_package loc) ++ ':' : (loc_module loc) ++
|
|||||||
char = show . snd . loc_start
|
char = show . snd . loc_start
|
||||||
|
|
||||||
defaultYesodRunner :: Yesod master
|
defaultYesodRunner :: Yesod master
|
||||||
=> GHandler sub master ChooseRep
|
=> Logger
|
||||||
|
-> GHandler sub master ChooseRep
|
||||||
-> master
|
-> master
|
||||||
-> sub
|
-> sub
|
||||||
-> Maybe (Route sub)
|
-> Maybe (Route sub)
|
||||||
-> (Route sub -> Route master)
|
-> (Route sub -> Route master)
|
||||||
-> Maybe (SessionBackend master)
|
-> Maybe (SessionBackend master)
|
||||||
-> W.Application
|
-> W.Application
|
||||||
defaultYesodRunner _ master _ murl toMaster _ req
|
defaultYesodRunner logger handler master sub murl toMasterRoute msb req
|
||||||
| maximumContentLength master (fmap toMaster murl) < len =
|
| maximumContentLength master (fmap toMasterRoute murl) < len =
|
||||||
return $ W.responseLBS
|
return $ W.responseLBS
|
||||||
(H.Status 413 "Too Large")
|
(H.Status 413 "Too Large")
|
||||||
[("Content-Type", "text/plain")]
|
[("Content-Type", "text/plain")]
|
||||||
"Request body too large to be processed."
|
"Request body too large to be processed."
|
||||||
where
|
| otherwise = do
|
||||||
len = fromMaybe 0 $ lookup "content-length" (W.requestHeaders req) >>= readMay
|
|
||||||
readMay s =
|
|
||||||
case reads $ S8.unpack s of
|
|
||||||
[] -> Nothing
|
|
||||||
(x, _):_ -> Just x
|
|
||||||
defaultYesodRunner handler master sub murl toMasterRoute msb req = do
|
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
let dontSaveSession _ _ = return []
|
let dontSaveSession _ _ = return []
|
||||||
(session, saveSession) <- liftIO $
|
(session, saveSession) <- liftIO $
|
||||||
maybe (return ([], dontSaveSession)) (\sb -> sbLoadSession sb master req now) msb
|
maybe (return ([], dontSaveSession)) (\sb -> sbLoadSession sb master req now) msb
|
||||||
rr <- liftIO $ parseWaiRequest req session (isJust msb)
|
rr <- liftIO $ parseWaiRequest req session (isJust msb) len
|
||||||
let h = {-# SCC "h" #-} do
|
let h = {-# SCC "h" #-} do
|
||||||
case murl of
|
case murl of
|
||||||
Nothing -> handler
|
Nothing -> handler
|
||||||
@ -411,7 +450,8 @@ defaultYesodRunner handler master sub murl toMasterRoute msb req = do
|
|||||||
handler
|
handler
|
||||||
let sessionMap = Map.fromList . filter ((/=) tokenKey . fst) $ session
|
let sessionMap = Map.fromList . filter ((/=) tokenKey . fst) $ session
|
||||||
let ra = resolveApproot master req
|
let ra = resolveApproot master req
|
||||||
yar <- handlerToYAR master sub toMasterRoute
|
let log' = messageLoggerSource master logger
|
||||||
|
yar <- handlerToYAR master sub (fileUpload master) log' toMasterRoute
|
||||||
(yesodRender master ra) errorHandler rr murl sessionMap h
|
(yesodRender master ra) errorHandler rr murl sessionMap h
|
||||||
extraHeaders <- case yar of
|
extraHeaders <- case yar of
|
||||||
(YARPlain _ _ ct _ newSess) -> do
|
(YARPlain _ _ ct _ newSess) -> do
|
||||||
@ -423,6 +463,12 @@ defaultYesodRunner handler master sub murl toMasterRoute msb req = do
|
|||||||
return $ ("Content-Type", ct) : map headerToPair sessionHeaders
|
return $ ("Content-Type", ct) : map headerToPair sessionHeaders
|
||||||
_ -> return []
|
_ -> return []
|
||||||
return $ yarToResponse yar extraHeaders
|
return $ yarToResponse yar extraHeaders
|
||||||
|
where
|
||||||
|
len = fromMaybe 0 $ lookup "content-length" (W.requestHeaders req) >>= readMay
|
||||||
|
readMay s =
|
||||||
|
case reads $ S8.unpack s of
|
||||||
|
[] -> Nothing
|
||||||
|
(x, _):_ -> Just x
|
||||||
|
|
||||||
data AuthResult = Authorized | AuthenticationRequired | Unauthorized Text
|
data AuthResult = Authorized | AuthenticationRequired | Unauthorized Text
|
||||||
deriving (Eq, Show, Read)
|
deriving (Eq, Show, Read)
|
||||||
@ -469,18 +515,21 @@ defaultErrorHandler NotFound = do
|
|||||||
let path' = TE.decodeUtf8With TEE.lenientDecode $ W.rawPathInfo r
|
let path' = TE.decodeUtf8With TEE.lenientDecode $ W.rawPathInfo r
|
||||||
applyLayout' "Not Found"
|
applyLayout' "Not Found"
|
||||||
[hamlet|
|
[hamlet|
|
||||||
|
$newline never
|
||||||
<h1>Not Found
|
<h1>Not Found
|
||||||
<p>#{path'}
|
<p>#{path'}
|
||||||
|]
|
|]
|
||||||
defaultErrorHandler (PermissionDenied msg) =
|
defaultErrorHandler (PermissionDenied msg) =
|
||||||
applyLayout' "Permission Denied"
|
applyLayout' "Permission Denied"
|
||||||
[hamlet|
|
[hamlet|
|
||||||
|
$newline never
|
||||||
<h1>Permission denied
|
<h1>Permission denied
|
||||||
<p>#{msg}
|
<p>#{msg}
|
||||||
|]
|
|]
|
||||||
defaultErrorHandler (InvalidArgs ia) =
|
defaultErrorHandler (InvalidArgs ia) =
|
||||||
applyLayout' "Invalid Arguments"
|
applyLayout' "Invalid Arguments"
|
||||||
[hamlet|
|
[hamlet|
|
||||||
|
$newline never
|
||||||
<h1>Invalid Arguments
|
<h1>Invalid Arguments
|
||||||
<ul>
|
<ul>
|
||||||
$forall msg <- ia
|
$forall msg <- ia
|
||||||
@ -489,12 +538,14 @@ defaultErrorHandler (InvalidArgs ia) =
|
|||||||
defaultErrorHandler (InternalError e) =
|
defaultErrorHandler (InternalError e) =
|
||||||
applyLayout' "Internal Server Error"
|
applyLayout' "Internal Server Error"
|
||||||
[hamlet|
|
[hamlet|
|
||||||
|
$newline never
|
||||||
<h1>Internal Server Error
|
<h1>Internal Server Error
|
||||||
<p>#{e}
|
<p>#{e}
|
||||||
|]
|
|]
|
||||||
defaultErrorHandler (BadMethod m) =
|
defaultErrorHandler (BadMethod m) =
|
||||||
applyLayout' "Bad Method"
|
applyLayout' "Bad Method"
|
||||||
[hamlet|
|
[hamlet|
|
||||||
|
$newline never
|
||||||
<h1>Method Not Supported
|
<h1>Method Not Supported
|
||||||
<p>Method "#{S8.unpack m}" not supported
|
<p>Method "#{S8.unpack m}" not supported
|
||||||
|]
|
|]
|
||||||
@ -512,7 +563,7 @@ maybeAuthorized r isWrite = do
|
|||||||
return $ if x == Authorized then Just r else Nothing
|
return $ if x == Authorized then Just r else Nothing
|
||||||
|
|
||||||
jsToHtml :: Javascript -> Html
|
jsToHtml :: Javascript -> Html
|
||||||
jsToHtml (Javascript b) = preEscapedLazyText $ toLazyText b
|
jsToHtml (Javascript b) = preEscapedToMarkup $ toLazyText b
|
||||||
|
|
||||||
jelper :: JavascriptUrl url -> HtmlUrl url
|
jelper :: JavascriptUrl url -> HtmlUrl url
|
||||||
jelper = fmap jsToHtml
|
jelper = fmap jsToHtml
|
||||||
@ -540,7 +591,7 @@ widgetToPageContent w = do
|
|||||||
$ encodeUtf8 rendered
|
$ encodeUtf8 rendered
|
||||||
return (mmedia,
|
return (mmedia,
|
||||||
case x of
|
case x of
|
||||||
Nothing -> Left $ preEscapedLazyText rendered
|
Nothing -> Left $ preEscapedToMarkup rendered
|
||||||
Just y -> Right $ either id (uncurry render) y)
|
Just y -> Right $ either id (uncurry render) y)
|
||||||
jsLoc <-
|
jsLoc <-
|
||||||
case jscript of
|
case jscript of
|
||||||
@ -554,6 +605,7 @@ widgetToPageContent w = do
|
|||||||
-- the asynchronous loader means your page doesn't have to wait for all the js to load
|
-- the asynchronous loader means your page doesn't have to wait for all the js to load
|
||||||
let (mcomplete, asyncScripts) = asyncHelper render scripts jscript jsLoc
|
let (mcomplete, asyncScripts) = asyncHelper render scripts jscript jsLoc
|
||||||
regularScriptLoad = [hamlet|
|
regularScriptLoad = [hamlet|
|
||||||
|
$newline never
|
||||||
$forall s <- scripts
|
$forall s <- scripts
|
||||||
^{mkScriptTag s}
|
^{mkScriptTag s}
|
||||||
$maybe j <- jscript
|
$maybe j <- jscript
|
||||||
@ -564,6 +616,7 @@ $maybe j <- jscript
|
|||||||
|]
|
|]
|
||||||
|
|
||||||
headAll = [hamlet|
|
headAll = [hamlet|
|
||||||
|
$newline never
|
||||||
\^{head'}
|
\^{head'}
|
||||||
$forall s <- stylesheets
|
$forall s <- stylesheets
|
||||||
^{mkLinkTag s}
|
^{mkLinkTag s}
|
||||||
@ -586,6 +639,7 @@ $case jsLoader master
|
|||||||
^{regularScriptLoad}
|
^{regularScriptLoad}
|
||||||
|]
|
|]
|
||||||
let bodyScript = [hamlet|
|
let bodyScript = [hamlet|
|
||||||
|
$newline never
|
||||||
^{body}
|
^{body}
|
||||||
^{regularScriptLoad}
|
^{regularScriptLoad}
|
||||||
|]
|
|]
|
||||||
@ -632,6 +686,7 @@ jsonArray = unsafeLazyByteString . encode . Array . Vector.fromList . map String
|
|||||||
loadJsYepnope :: Yesod master => Either Text (Route master) -> [Text] -> Maybe (HtmlUrl (Route master)) -> (HtmlUrl (Route master))
|
loadJsYepnope :: Yesod master => Either Text (Route master) -> [Text] -> Maybe (HtmlUrl (Route master)) -> (HtmlUrl (Route master))
|
||||||
loadJsYepnope eyn scripts mcomplete =
|
loadJsYepnope eyn scripts mcomplete =
|
||||||
[hamlet|
|
[hamlet|
|
||||||
|
$newline never
|
||||||
$maybe yn <- left eyn
|
$maybe yn <- left eyn
|
||||||
<script src=#{yn}>
|
<script src=#{yn}>
|
||||||
$maybe yn <- right eyn
|
$maybe yn <- right eyn
|
||||||
@ -699,17 +754,18 @@ clientSessionBackend :: Yesod master
|
|||||||
-> Int -- ^ Inactive session valitity in minutes
|
-> Int -- ^ Inactive session valitity in minutes
|
||||||
-> SessionBackend master
|
-> SessionBackend master
|
||||||
clientSessionBackend key timeout = SessionBackend
|
clientSessionBackend key timeout = SessionBackend
|
||||||
{ sbLoadSession = loadClientSession key timeout
|
{ sbLoadSession = loadClientSession key timeout "_SESSION"
|
||||||
}
|
}
|
||||||
|
|
||||||
loadClientSession :: Yesod master
|
loadClientSession :: Yesod master
|
||||||
=> CS.Key
|
=> CS.Key
|
||||||
-> Int
|
-> Int -- ^ timeout
|
||||||
|
-> S8.ByteString -- ^ session name
|
||||||
-> master
|
-> master
|
||||||
-> W.Request
|
-> W.Request
|
||||||
-> UTCTime
|
-> UTCTime
|
||||||
-> IO (BackendSession, SaveSession)
|
-> IO (BackendSession, SaveSession)
|
||||||
loadClientSession key timeout master req now = return (sess, save)
|
loadClientSession key timeout sessionName master req now = return (sess, save)
|
||||||
where
|
where
|
||||||
sess = fromMaybe [] $ do
|
sess = fromMaybe [] $ do
|
||||||
raw <- lookup "Cookie" $ W.requestHeaders req
|
raw <- lookup "Cookie" $ W.requestHeaders req
|
||||||
@ -717,7 +773,7 @@ loadClientSession key timeout master req now = return (sess, save)
|
|||||||
let host = "" -- fixme, properly lock sessions to client address
|
let host = "" -- fixme, properly lock sessions to client address
|
||||||
decodeClientSession key now host val
|
decodeClientSession key now host val
|
||||||
save sess' now' = do
|
save sess' now' = do
|
||||||
-- fixme should we be caching this?
|
-- We should never cache the IV! Be careful!
|
||||||
iv <- liftIO CS.randomIV
|
iv <- liftIO CS.randomIV
|
||||||
return [AddCookie def
|
return [AddCookie def
|
||||||
{ setCookieName = sessionName
|
{ setCookieName = sessionName
|
||||||
@ -732,3 +788,82 @@ loadClientSession key timeout master req now = return (sess, save)
|
|||||||
expires = fromIntegral (timeout * 60) `addUTCTime` now'
|
expires = fromIntegral (timeout * 60) `addUTCTime` now'
|
||||||
sessionVal iv = encodeClientSession key iv expires host sess'
|
sessionVal iv = encodeClientSession key iv expires host sess'
|
||||||
|
|
||||||
|
|
||||||
|
-- | Run a 'GHandler' completely outside of Yesod. This
|
||||||
|
-- function comes with many caveats and you shouldn't use it
|
||||||
|
-- unless you fully understand what it's doing and how it works.
|
||||||
|
--
|
||||||
|
-- As of now, there's only one reason to use this function at
|
||||||
|
-- all: in order to run unit tests of functions inside 'GHandler'
|
||||||
|
-- but that aren't easily testable with a full HTTP request.
|
||||||
|
-- Even so, it's better to use @wai-test@ or @yesod-test@ instead
|
||||||
|
-- of using this function.
|
||||||
|
--
|
||||||
|
-- This function will create a fake HTTP request (both @wai@'s
|
||||||
|
-- 'W.Request' and @yesod@'s 'Request') and feed it to the
|
||||||
|
-- @GHandler@. The only useful information the @GHandler@ may
|
||||||
|
-- get from the request is the session map, which you must supply
|
||||||
|
-- as argument to @runFakeHandler@. All other fields contain
|
||||||
|
-- fake information, which means that they can be accessed but
|
||||||
|
-- won't have any useful information. The response of the
|
||||||
|
-- @GHandler@ is completely ignored, including changes to the
|
||||||
|
-- session, cookies or headers. We only return you the
|
||||||
|
-- @GHandler@'s return value.
|
||||||
|
runFakeHandler :: (Yesod master, MonadIO m) =>
|
||||||
|
SessionMap
|
||||||
|
-> (master -> Logger)
|
||||||
|
-> master
|
||||||
|
-> GHandler master master a
|
||||||
|
-> m (Either ErrorResponse a)
|
||||||
|
runFakeHandler fakeSessionMap logger master handler = liftIO $ do
|
||||||
|
ret <- I.newIORef (Left $ InternalError "runFakeHandler: no result")
|
||||||
|
let handler' = do liftIO . I.writeIORef ret . Right =<< handler
|
||||||
|
return ()
|
||||||
|
let YesodApp yapp =
|
||||||
|
runHandler
|
||||||
|
handler'
|
||||||
|
(yesodRender master "")
|
||||||
|
Nothing
|
||||||
|
id
|
||||||
|
master
|
||||||
|
master
|
||||||
|
(fileUpload master)
|
||||||
|
(messageLoggerSource master $ logger master)
|
||||||
|
errHandler err =
|
||||||
|
YesodApp $ \_ _ _ session -> do
|
||||||
|
liftIO $ I.writeIORef ret (Left err)
|
||||||
|
return $ YARPlain
|
||||||
|
H.status500
|
||||||
|
[]
|
||||||
|
typePlain
|
||||||
|
(toContent ("runFakeHandler: errHandler" :: S8.ByteString))
|
||||||
|
session
|
||||||
|
fakeWaiRequest =
|
||||||
|
W.Request
|
||||||
|
{ W.requestMethod = "POST"
|
||||||
|
, W.httpVersion = H.http11
|
||||||
|
, W.rawPathInfo = "/runFakeHandler/pathInfo"
|
||||||
|
, W.rawQueryString = ""
|
||||||
|
, W.serverName = "runFakeHandler-serverName"
|
||||||
|
, W.serverPort = 80
|
||||||
|
, W.requestHeaders = []
|
||||||
|
, W.isSecure = False
|
||||||
|
, W.remoteHost = error "runFakeHandler-remoteHost"
|
||||||
|
, W.pathInfo = ["runFakeHandler", "pathInfo"]
|
||||||
|
, W.queryString = []
|
||||||
|
, W.requestBody = mempty
|
||||||
|
, W.vault = mempty
|
||||||
|
}
|
||||||
|
fakeRequest =
|
||||||
|
Request
|
||||||
|
{ reqGetParams = []
|
||||||
|
, reqCookies = []
|
||||||
|
, reqWaiRequest = fakeWaiRequest
|
||||||
|
, reqLangs = []
|
||||||
|
, reqToken = Just "NaN" -- not a nonce =)
|
||||||
|
, reqBodySize = 0
|
||||||
|
}
|
||||||
|
fakeContentType = []
|
||||||
|
_ <- runResourceT $ yapp errHandler fakeRequest fakeContentType fakeSessionMap
|
||||||
|
I.readIORef ret
|
||||||
|
{-# WARNING runFakeHandler "Usually you should *not* use runFakeHandler unless you really understand how it works and why you need it." #-}
|
||||||
|
|||||||
@ -4,7 +4,15 @@ module Yesod.Internal.Request
|
|||||||
( parseWaiRequest
|
( parseWaiRequest
|
||||||
, Request (..)
|
, Request (..)
|
||||||
, RequestBodyContents
|
, RequestBodyContents
|
||||||
, FileInfo (..)
|
, FileInfo
|
||||||
|
, fileName
|
||||||
|
, fileContentType
|
||||||
|
, fileSource
|
||||||
|
, fileMove
|
||||||
|
, mkFileInfoLBS
|
||||||
|
, mkFileInfoFile
|
||||||
|
, mkFileInfoSource
|
||||||
|
, FileUpload (..)
|
||||||
-- The below are exported for testing.
|
-- The below are exported for testing.
|
||||||
, randomString
|
, randomString
|
||||||
, parseWaiRequest'
|
, parseWaiRequest'
|
||||||
@ -28,6 +36,10 @@ import qualified Data.Set as Set
|
|||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Data.Text.Encoding (decodeUtf8With)
|
import Data.Text.Encoding (decodeUtf8With)
|
||||||
import Data.Text.Encoding.Error (lenientDecode)
|
import Data.Text.Encoding.Error (lenientDecode)
|
||||||
|
import Data.Conduit
|
||||||
|
import Data.Conduit.List (sourceList)
|
||||||
|
import Data.Conduit.Binary (sourceFile, sinkFile)
|
||||||
|
import Data.Word (Word64)
|
||||||
|
|
||||||
-- | The parsed request information.
|
-- | The parsed request information.
|
||||||
data Request = Request
|
data Request = Request
|
||||||
@ -38,23 +50,27 @@ data Request = Request
|
|||||||
, reqLangs :: [Text]
|
, reqLangs :: [Text]
|
||||||
-- | A random, session-specific token used to prevent CSRF attacks.
|
-- | A random, session-specific token used to prevent CSRF attacks.
|
||||||
, reqToken :: Maybe Text
|
, reqToken :: Maybe Text
|
||||||
|
-- | Size of the request body.
|
||||||
|
, reqBodySize :: Word64
|
||||||
}
|
}
|
||||||
|
|
||||||
parseWaiRequest :: W.Request
|
parseWaiRequest :: W.Request
|
||||||
-> [(Text, ByteString)] -- ^ session
|
-> [(Text, ByteString)] -- ^ session
|
||||||
-> Bool
|
-> Bool
|
||||||
|
-> Word64
|
||||||
-> IO Request
|
-> IO Request
|
||||||
parseWaiRequest env session' useToken =
|
parseWaiRequest env session' useToken bodySize =
|
||||||
parseWaiRequest' env session' useToken <$> newStdGen
|
parseWaiRequest' env session' useToken bodySize <$> newStdGen
|
||||||
|
|
||||||
parseWaiRequest' :: RandomGen g
|
parseWaiRequest' :: RandomGen g
|
||||||
=> W.Request
|
=> W.Request
|
||||||
-> [(Text, ByteString)] -- ^ session
|
-> [(Text, ByteString)] -- ^ session
|
||||||
-> Bool
|
-> Bool
|
||||||
|
-> Word64
|
||||||
-> g
|
-> g
|
||||||
-> Request
|
-> Request
|
||||||
parseWaiRequest' env session' useToken gen =
|
parseWaiRequest' env session' useToken bodySize gen =
|
||||||
Request gets'' cookies' env langs'' token
|
Request gets'' cookies' env langs'' token bodySize
|
||||||
where
|
where
|
||||||
gets' = queryToQueryText $ W.queryString env
|
gets' = queryToQueryText $ W.queryString env
|
||||||
gets'' = map (second $ fromMaybe "") gets'
|
gets'' = map (second $ fromMaybe "") gets'
|
||||||
@ -116,6 +132,19 @@ type RequestBodyContents =
|
|||||||
data FileInfo = FileInfo
|
data FileInfo = FileInfo
|
||||||
{ fileName :: Text
|
{ fileName :: Text
|
||||||
, fileContentType :: Text
|
, fileContentType :: Text
|
||||||
, fileContent :: L.ByteString
|
, fileSource :: Source (ResourceT IO) ByteString
|
||||||
|
, fileMove :: FilePath -> IO ()
|
||||||
}
|
}
|
||||||
deriving (Eq, Show)
|
|
||||||
|
mkFileInfoLBS :: Text -> Text -> L.ByteString -> FileInfo
|
||||||
|
mkFileInfoLBS name ct lbs = FileInfo name ct (sourceList $ L.toChunks lbs) (\fp -> L.writeFile fp lbs)
|
||||||
|
|
||||||
|
mkFileInfoFile :: Text -> Text -> FilePath -> FileInfo
|
||||||
|
mkFileInfoFile name ct fp = FileInfo name ct (sourceFile fp) (\dst -> runResourceT $ sourceFile fp $$ sinkFile dst)
|
||||||
|
|
||||||
|
mkFileInfoSource :: Text -> Text -> Source (ResourceT IO) ByteString -> FileInfo
|
||||||
|
mkFileInfoSource name ct src = FileInfo name ct src (\dst -> runResourceT $ src $$ sinkFile dst)
|
||||||
|
|
||||||
|
data FileUpload = FileUploadMemory (NWP.BackEnd L.ByteString)
|
||||||
|
| FileUploadDisk (NWP.BackEnd FilePath)
|
||||||
|
| FileUploadSource (NWP.BackEnd (Source (ResourceT IO) ByteString))
|
||||||
|
|||||||
@ -21,9 +21,9 @@ import qualified Network.Wai as W
|
|||||||
|
|
||||||
type BackendSession = [(Text, S8.ByteString)]
|
type BackendSession = [(Text, S8.ByteString)]
|
||||||
|
|
||||||
type SaveSession = BackendSession -> -- ^ The session contents after running the handler
|
type SaveSession = BackendSession -- ^ The session contents after running the handler
|
||||||
UTCTime -> -- ^ current time
|
-> UTCTime -- ^ current time
|
||||||
IO [Header]
|
-> IO [Header]
|
||||||
|
|
||||||
newtype SessionBackend master = SessionBackend
|
newtype SessionBackend master = SessionBackend
|
||||||
{ sbLoadSession :: master
|
{ sbLoadSession :: master
|
||||||
|
|||||||
@ -1,138 +0,0 @@
|
|||||||
{-# LANGUAGE BangPatterns #-}
|
|
||||||
module Yesod.Logger
|
|
||||||
( Logger
|
|
||||||
, handle
|
|
||||||
, developmentLogger, productionLogger
|
|
||||||
, defaultDevelopmentLogger, defaultProductionLogger
|
|
||||||
, toProduction
|
|
||||||
, flushLogger
|
|
||||||
, logText
|
|
||||||
, logLazyText
|
|
||||||
, logString
|
|
||||||
, logBS
|
|
||||||
, logMsg
|
|
||||||
, formatLogText
|
|
||||||
, timed
|
|
||||||
-- * Deprecated
|
|
||||||
, makeLoggerWithHandle
|
|
||||||
, makeDefaultLogger
|
|
||||||
) where
|
|
||||||
|
|
||||||
import System.IO (Handle, stdout, hFlush)
|
|
||||||
import Data.ByteString (ByteString)
|
|
||||||
import Data.ByteString.Char8 (pack)
|
|
||||||
import Data.ByteString.Lazy (toChunks)
|
|
||||||
import qualified Data.Text.Lazy as TL
|
|
||||||
import Data.Text (Text)
|
|
||||||
import Data.Text.Encoding (encodeUtf8)
|
|
||||||
import qualified Data.Text.Lazy.Encoding as TLE
|
|
||||||
import System.Log.FastLogger
|
|
||||||
import Network.Wai.Logger.Date (DateRef, dateInit, getDate)
|
|
||||||
|
|
||||||
-- for timed logging
|
|
||||||
import Data.Time (getCurrentTime, diffUTCTime)
|
|
||||||
import Control.Monad.IO.Class (MonadIO, liftIO)
|
|
||||||
import Text.Printf (printf)
|
|
||||||
import Data.Text (unpack)
|
|
||||||
|
|
||||||
-- for formatter
|
|
||||||
import Language.Haskell.TH.Syntax (Loc)
|
|
||||||
import Yesod.Core (LogLevel, fileLocationToString)
|
|
||||||
|
|
||||||
data Logger = Logger {
|
|
||||||
loggerLogFun :: [LogStr] -> IO ()
|
|
||||||
, loggerHandle :: Handle
|
|
||||||
, loggerDateRef :: DateRef
|
|
||||||
}
|
|
||||||
|
|
||||||
handle :: Logger -> Handle
|
|
||||||
handle = loggerHandle
|
|
||||||
|
|
||||||
flushLogger :: Logger -> IO ()
|
|
||||||
flushLogger = hFlush . loggerHandle
|
|
||||||
|
|
||||||
makeDefaultLogger :: IO Logger
|
|
||||||
makeDefaultLogger = defaultDevelopmentLogger
|
|
||||||
{-# DEPRECATED makeDefaultLogger "Use defaultProductionLogger or defaultDevelopmentLogger instead" #-}
|
|
||||||
|
|
||||||
makeLoggerWithHandle, developmentLogger, productionLogger :: Handle -> IO Logger
|
|
||||||
makeLoggerWithHandle = productionLogger
|
|
||||||
{-# DEPRECATED makeLoggerWithHandle "Use productionLogger or developmentLogger instead" #-}
|
|
||||||
|
|
||||||
-- | uses stdout handle
|
|
||||||
defaultProductionLogger, defaultDevelopmentLogger :: IO Logger
|
|
||||||
defaultProductionLogger = productionLogger stdout
|
|
||||||
defaultDevelopmentLogger = developmentLogger stdout
|
|
||||||
|
|
||||||
|
|
||||||
productionLogger h = mkLogger h (handleToLogFun h)
|
|
||||||
-- | a development logger gets automatically flushed
|
|
||||||
developmentLogger h = mkLogger h (\bs -> (handleToLogFun h) bs >> hFlush h)
|
|
||||||
|
|
||||||
mkLogger :: Handle -> ([LogStr] -> IO ()) -> IO Logger
|
|
||||||
mkLogger h logFun = do
|
|
||||||
initHandle h
|
|
||||||
dateInit >>= return . Logger logFun h
|
|
||||||
|
|
||||||
-- convert (a development) logger to production settings
|
|
||||||
toProduction :: Logger -> Logger
|
|
||||||
toProduction (Logger _ h d) = Logger (handleToLogFun h) h d
|
|
||||||
|
|
||||||
handleToLogFun :: Handle -> ([LogStr] -> IO ())
|
|
||||||
handleToLogFun = hPutLogStr
|
|
||||||
|
|
||||||
logMsg :: Logger -> [LogStr] -> IO ()
|
|
||||||
logMsg = hPutLogStr . handle
|
|
||||||
|
|
||||||
logLazyText :: Logger -> TL.Text -> IO ()
|
|
||||||
logLazyText logger msg = loggerLogFun logger $
|
|
||||||
map LB (toChunks $ TLE.encodeUtf8 msg) ++ [newLine]
|
|
||||||
|
|
||||||
logText :: Logger -> Text -> IO ()
|
|
||||||
logText logger = logBS logger . encodeUtf8
|
|
||||||
|
|
||||||
logBS :: Logger -> ByteString -> IO ()
|
|
||||||
logBS logger msg = loggerLogFun logger $ [LB msg, newLine]
|
|
||||||
|
|
||||||
logString :: Logger -> String -> IO ()
|
|
||||||
logString logger msg = loggerLogFun logger $ [LS msg, newLine]
|
|
||||||
|
|
||||||
formatLogText :: Logger -> Loc -> LogLevel -> Text -> IO [LogStr]
|
|
||||||
formatLogText logger loc level msg = formatLogMsg logger loc level (toLB msg)
|
|
||||||
|
|
||||||
toLB :: Text -> LogStr
|
|
||||||
toLB = LB . encodeUtf8
|
|
||||||
|
|
||||||
formatLogMsg :: Logger -> Loc -> LogLevel -> LogStr -> IO [LogStr]
|
|
||||||
formatLogMsg logger loc level msg = do
|
|
||||||
date <- liftIO $ getDate $ loggerDateRef logger
|
|
||||||
return
|
|
||||||
[ LB date
|
|
||||||
, LB $ pack" ["
|
|
||||||
, LS (drop 5 $ show level)
|
|
||||||
, LB $ pack "] "
|
|
||||||
, msg
|
|
||||||
, LB $ pack " @("
|
|
||||||
, LS (fileLocationToString loc)
|
|
||||||
, LB $ pack ") "
|
|
||||||
]
|
|
||||||
|
|
||||||
newLine :: LogStr
|
|
||||||
newLine = LB $ pack "\n"
|
|
||||||
|
|
||||||
-- | Execute a monadic action and log the duration
|
|
||||||
--
|
|
||||||
timed :: MonadIO m
|
|
||||||
=> Logger -- ^ Logger
|
|
||||||
-> Text -- ^ Message
|
|
||||||
-> m a -- ^ Action
|
|
||||||
-> m a -- ^ Timed and logged action
|
|
||||||
timed logger msg action = do
|
|
||||||
start <- liftIO getCurrentTime
|
|
||||||
!result <- action
|
|
||||||
stop <- liftIO getCurrentTime
|
|
||||||
let diff = fromEnum $ diffUTCTime stop start
|
|
||||||
ms = diff `div` 10 ^ (9 :: Int)
|
|
||||||
formatted = printf " [%4dms] %s" ms (unpack msg)
|
|
||||||
liftIO $ logString logger formatted
|
|
||||||
return result
|
|
||||||
@ -16,7 +16,11 @@ module Yesod.Request
|
|||||||
-- * Request datatype
|
-- * Request datatype
|
||||||
RequestBodyContents
|
RequestBodyContents
|
||||||
, Request (..)
|
, Request (..)
|
||||||
, FileInfo (..)
|
, FileInfo
|
||||||
|
, fileName
|
||||||
|
, fileContentType
|
||||||
|
, fileSource
|
||||||
|
, fileMove
|
||||||
-- * Convenience functions
|
-- * Convenience functions
|
||||||
, languages
|
, languages
|
||||||
-- * Lookup parameters
|
-- * Lookup parameters
|
||||||
@ -51,6 +55,9 @@ import Data.Text (Text)
|
|||||||
--
|
--
|
||||||
-- * Accept-Language HTTP header.
|
-- * Accept-Language HTTP header.
|
||||||
--
|
--
|
||||||
|
-- Yesod will seek the first language from the returned list matched with languages supporting by your application. This language will be used to render i18n templates.
|
||||||
|
-- If a matching language is not found the default language will be used.
|
||||||
|
--
|
||||||
-- This is handled by parseWaiRequest (not exposed).
|
-- This is handled by parseWaiRequest (not exposed).
|
||||||
languages :: GHandler s m [Text]
|
languages :: GHandler s m [Text]
|
||||||
languages = reqLangs `liftM` getRequest
|
languages = reqLangs `liftM` getRequest
|
||||||
|
|||||||
@ -43,6 +43,7 @@ module Yesod.Widget
|
|||||||
, addStylesheetRemote
|
, addStylesheetRemote
|
||||||
, addStylesheetRemoteAttrs
|
, addStylesheetRemoteAttrs
|
||||||
, addStylesheetEither
|
, addStylesheetEither
|
||||||
|
, CssBuilder (..)
|
||||||
-- ** Javascript
|
-- ** Javascript
|
||||||
, addJulius
|
, addJulius
|
||||||
, addJuliusBody
|
, addJuliusBody
|
||||||
@ -53,6 +54,7 @@ module Yesod.Widget
|
|||||||
, addScriptEither
|
, addScriptEither
|
||||||
-- * Internal
|
-- * Internal
|
||||||
, unGWidget
|
, unGWidget
|
||||||
|
, whamletFileWithSettings
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Monoid
|
import Data.Monoid
|
||||||
@ -79,12 +81,18 @@ import Language.Haskell.TH.Syntax (Q, Exp (InfixE, VarE, LamE, AppE), Pat (VarP)
|
|||||||
import Control.Monad.Trans.Control (MonadBaseControl (..))
|
import Control.Monad.Trans.Control (MonadBaseControl (..))
|
||||||
import Control.Exception (throwIO)
|
import Control.Exception (throwIO)
|
||||||
import qualified Text.Hamlet as NP
|
import qualified Text.Hamlet as NP
|
||||||
import Data.Text.Lazy.Builder (fromLazyText)
|
import Data.Text.Lazy.Builder (fromLazyText, Builder)
|
||||||
import Text.Blaze (toHtml, preEscapedLazyText)
|
import Text.Blaze.Html (toHtml, preEscapedToMarkup)
|
||||||
|
import qualified Data.Text.Lazy as TL
|
||||||
import Control.Monad.Base (MonadBase (liftBase))
|
import Control.Monad.Base (MonadBase (liftBase))
|
||||||
import Control.Arrow (first)
|
import Control.Arrow (first)
|
||||||
import Control.Monad.Trans.Resource
|
import Control.Monad.Trans.Resource
|
||||||
|
|
||||||
|
import Control.Monad.Logger
|
||||||
|
|
||||||
|
preEscapedLazyText :: TL.Text -> Html
|
||||||
|
preEscapedLazyText = preEscapedToMarkup
|
||||||
|
|
||||||
-- | A generic widget, allowing specification of both the subsite and master
|
-- | A generic widget, allowing specification of both the subsite and master
|
||||||
-- site datatypes. While this is simply a @WriterT@, we define a newtype for
|
-- site datatypes. While this is simply a @WriterT@, we define a newtype for
|
||||||
-- better error messages.
|
-- better error messages.
|
||||||
@ -113,10 +121,21 @@ class ToWidget sub master a where
|
|||||||
|
|
||||||
type RY master = Route master -> [(Text, Text)] -> Text
|
type RY master = Route master -> [(Text, Text)] -> Text
|
||||||
|
|
||||||
|
-- | Newtype wrapper allowing injection of arbitrary content into CSS.
|
||||||
|
--
|
||||||
|
-- Usage:
|
||||||
|
--
|
||||||
|
-- > toWidget $ CssBuilder "p { color: red }"
|
||||||
|
--
|
||||||
|
-- Since: 1.1.3
|
||||||
|
newtype CssBuilder = CssBuilder { unCssBuilder :: Builder }
|
||||||
|
|
||||||
instance render ~ RY master => ToWidget sub master (render -> Html) where
|
instance render ~ RY master => ToWidget sub master (render -> Html) where
|
||||||
toWidget x = tell $ GWData (Body x) mempty mempty mempty mempty mempty mempty
|
toWidget x = tell $ GWData (Body x) mempty mempty mempty mempty mempty mempty
|
||||||
instance render ~ RY master => ToWidget sub master (render -> Css) where
|
instance render ~ RY master => ToWidget sub master (render -> Css) where
|
||||||
toWidget x = tell $ GWData mempty mempty mempty mempty (Map.singleton Nothing $ \r -> fromLazyText $ renderCss $ x r) mempty mempty
|
toWidget x = toWidget $ CssBuilder . fromLazyText . renderCss . x
|
||||||
|
instance render ~ RY master => ToWidget sub master (render -> CssBuilder) where
|
||||||
|
toWidget x = tell $ GWData mempty mempty mempty mempty (Map.singleton Nothing $ unCssBuilder . x) mempty mempty
|
||||||
instance render ~ RY master => ToWidget sub master (render -> Javascript) where
|
instance render ~ RY master => ToWidget sub master (render -> Javascript) where
|
||||||
toWidget x = tell $ GWData mempty mempty mempty mempty mempty (Just x) mempty
|
toWidget x = tell $ GWData mempty mempty mempty mempty mempty (Just x) mempty
|
||||||
instance (sub' ~ sub, master' ~ master) => ToWidget sub' master' (GWidget sub master ()) where
|
instance (sub' ~ sub, master' ~ master) => ToWidget sub' master' (GWidget sub master ()) where
|
||||||
@ -141,8 +160,10 @@ instance render ~ RY master => ToWidgetHead sub master (render -> Html) where
|
|||||||
toWidgetHead = tell . GWData mempty mempty mempty mempty mempty mempty . Head
|
toWidgetHead = tell . GWData mempty mempty mempty mempty mempty mempty . Head
|
||||||
instance render ~ RY master => ToWidgetHead sub master (render -> Css) where
|
instance render ~ RY master => ToWidgetHead sub master (render -> Css) where
|
||||||
toWidgetHead = toWidget
|
toWidgetHead = toWidget
|
||||||
instance render ~ RY master => ToWidgetHead sub master (render -> Javascript) where
|
instance render ~ RY master => ToWidgetHead sub master (render -> CssBuilder) where
|
||||||
toWidgetHead = toWidget
|
toWidgetHead = toWidget
|
||||||
|
instance render ~ RY master => ToWidgetHead sub master (render -> Javascript) where
|
||||||
|
toWidgetHead j = toWidgetHead $ \r -> H.script $ preEscapedLazyText $ renderJavascriptUrl r j
|
||||||
instance ToWidgetHead sub master Html where
|
instance ToWidgetHead sub master Html where
|
||||||
toWidgetHead = toWidgetHead . const
|
toWidgetHead = toWidgetHead . const
|
||||||
|
|
||||||
@ -161,6 +182,7 @@ setTitleI msg = do
|
|||||||
{-# DEPRECATED addHamletHead, addHtmlHead "Use toWidgetHead instead" #-}
|
{-# DEPRECATED addHamletHead, addHtmlHead "Use toWidgetHead instead" #-}
|
||||||
{-# DEPRECATED addHamlet, addHtml, addCassius, addLucius, addJulius "Use toWidget instead" #-}
|
{-# DEPRECATED addHamlet, addHtml, addCassius, addLucius, addJulius "Use toWidget instead" #-}
|
||||||
{-# DEPRECATED addJuliusBody "Use toWidgetBody instead" #-}
|
{-# DEPRECATED addJuliusBody "Use toWidgetBody instead" #-}
|
||||||
|
{-# DEPRECATED addWidget "addWidget can be omitted" #-}
|
||||||
|
|
||||||
-- | Add a 'Hamlet' to the head tag.
|
-- | Add a 'Hamlet' to the head tag.
|
||||||
addHamletHead :: HtmlUrl (Route master) -> GWidget sub master ()
|
addHamletHead :: HtmlUrl (Route master) -> GWidget sub master ()
|
||||||
@ -262,6 +284,9 @@ whamlet = NP.hamletWithSettings rules NP.defaultHamletSettings
|
|||||||
whamletFile :: FilePath -> Q Exp
|
whamletFile :: FilePath -> Q Exp
|
||||||
whamletFile = NP.hamletFileWithSettings rules NP.defaultHamletSettings
|
whamletFile = NP.hamletFileWithSettings rules NP.defaultHamletSettings
|
||||||
|
|
||||||
|
whamletFileWithSettings :: NP.HamletSettings -> FilePath -> Q Exp
|
||||||
|
whamletFileWithSettings = NP.hamletFileWithSettings rules
|
||||||
|
|
||||||
rules :: Q NP.HamletRules
|
rules :: Q NP.HamletRules
|
||||||
rules = do
|
rules = do
|
||||||
ah <- [|toWidget|]
|
ah <- [|toWidget|]
|
||||||
@ -330,7 +355,15 @@ instance MonadUnsafeIO (GWidget sub master) where
|
|||||||
instance MonadThrow (GWidget sub master) where
|
instance MonadThrow (GWidget sub master) where
|
||||||
monadThrow = liftIO . throwIO
|
monadThrow = liftIO . throwIO
|
||||||
instance MonadResource (GWidget sub master) where
|
instance MonadResource (GWidget sub master) where
|
||||||
|
#if MIN_VERSION_resourcet(0,4,0)
|
||||||
|
liftResourceT = lift . liftResourceT
|
||||||
|
#else
|
||||||
allocate a = lift . allocate a
|
allocate a = lift . allocate a
|
||||||
register = lift . register
|
register = lift . register
|
||||||
release = lift . release
|
release = lift . release
|
||||||
resourceMask = lift . resourceMask
|
resourceMask = lift . resourceMask
|
||||||
|
#endif
|
||||||
|
|
||||||
|
instance MonadLogger (GWidget sub master) where
|
||||||
|
monadLoggerLog a b = lift . monadLoggerLog a b
|
||||||
|
monadLoggerLogSource a b c = lift . monadLoggerLogSource a b c
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
import Yesod.Core
|
import Yesod.Core
|
||||||
import Control.Monad.IO.Class (liftIO)
|
import Control.Monad.IO.Class (liftIO)
|
||||||
import Network.Wai.Handler.Warp (run)
|
import Network.Wai.Handler.Warp (run)
|
||||||
import Data.Text (unpack)
|
import Data.Text (unpack, pack)
|
||||||
import Text.Julius (julius)
|
import Text.Julius (julius)
|
||||||
|
|
||||||
data Subsite = Subsite String
|
data Subsite = Subsite String
|
||||||
@ -22,13 +22,13 @@ getSubRootR = do
|
|||||||
Subsite s <- getYesodSub
|
Subsite s <- getYesodSub
|
||||||
tm <- getRouteToMaster
|
tm <- getRouteToMaster
|
||||||
render <- getUrlRender
|
render <- getUrlRender
|
||||||
$(logDebug) "I'm in SubRootR"
|
$logDebug "I'm in SubRootR"
|
||||||
return $ RepPlain $ toContent $ "Hello Sub World: " ++ s ++ ". " ++ unpack (render (tm SubRootR))
|
return $ RepPlain $ toContent $ "Hello Sub World: " ++ s ++ ". " ++ unpack (render (tm SubRootR))
|
||||||
|
|
||||||
handleSubMultiR :: Yesod m => Strings -> GHandler Subsite m RepPlain
|
handleSubMultiR :: Yesod m => Strings -> GHandler Subsite m RepPlain
|
||||||
handleSubMultiR x = do
|
handleSubMultiR x = do
|
||||||
Subsite y <- getYesodSub
|
Subsite y <- getYesodSub
|
||||||
$(logInfo) "In SubMultiR"
|
$logInfo "In SubMultiR"
|
||||||
return . RepPlain . toContent . show $ (x, y)
|
return . RepPlain . toContent . show $ (x, y)
|
||||||
|
|
||||||
data HelloWorld = HelloWorld { getSubsite :: String -> Subsite }
|
data HelloWorld = HelloWorld { getSubsite :: String -> Subsite }
|
||||||
@ -38,11 +38,12 @@ mkYesod "HelloWorld" [parseRoutes|
|
|||||||
|]
|
|]
|
||||||
instance Yesod HelloWorld where
|
instance Yesod HelloWorld where
|
||||||
addStaticContent a b c = do
|
addStaticContent a b c = do
|
||||||
liftIO $ print (a, b, c)
|
$logInfo $ pack $ show (a, b, c)
|
||||||
return Nothing
|
return Nothing
|
||||||
|
|
||||||
getRootR = do
|
getRootR = do
|
||||||
$(logOther "HAHAHA") "Here I am"
|
$(logOther "HAHAHA") "Here I am"
|
||||||
|
$logOtherS "source" "level" "message"
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
addScriptRemote "https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"
|
addScriptRemote "https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"
|
||||||
toWidget [julius|$(function(){$("#mypara").css("color", "red")});|]
|
toWidget [julius|$(function(){$("#mypara").css("color", "red")});|]
|
||||||
|
|||||||
@ -2,4 +2,4 @@ import Test.Hspec
|
|||||||
import qualified YesodCoreTest
|
import qualified YesodCoreTest
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = hspecX $ descriptions $ YesodCoreTest.specs
|
main = hspec YesodCoreTest.specs
|
||||||
|
|||||||
@ -15,18 +15,17 @@ import qualified YesodCoreTest.JsLoader as JsLoader
|
|||||||
|
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
|
|
||||||
specs :: [Specs]
|
specs :: Spec
|
||||||
specs =
|
specs = do
|
||||||
[ cleanPathTest
|
cleanPathTest
|
||||||
, exceptionsTest
|
exceptionsTest
|
||||||
, widgetTest
|
widgetTest
|
||||||
, mediaTest
|
mediaTest
|
||||||
, linksTest
|
linksTest
|
||||||
, noOverloadedTest
|
noOverloadedTest
|
||||||
, internalRequestTest
|
internalRequestTest
|
||||||
, errorHandlingTest
|
errorHandlingTest
|
||||||
, cacheTest
|
cacheTest
|
||||||
, WaiSubsite.specs
|
WaiSubsite.specs
|
||||||
, Redirect.specs
|
Redirect.specs
|
||||||
, JsLoader.specs
|
JsLoader.specs
|
||||||
]
|
|
||||||
|
|||||||
@ -35,11 +35,10 @@ getRootR = do
|
|||||||
Nothing <- cacheLookup key
|
Nothing <- cacheLookup key
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
cacheTest :: [Spec]
|
cacheTest :: Spec
|
||||||
cacheTest =
|
cacheTest =
|
||||||
describe "Test.Cache"
|
describe "Test.Cache" $ do
|
||||||
[ it "works" works
|
it "works" works
|
||||||
]
|
|
||||||
|
|
||||||
runner :: Session () -> IO ()
|
runner :: Session () -> IO ()
|
||||||
runner f = toWaiApp C >>= runSession f
|
runner f = toWaiApp C >>= runSession f
|
||||||
|
|||||||
@ -14,6 +14,11 @@ import Network.HTTP.Types (status200, decodePathSegments)
|
|||||||
|
|
||||||
import qualified Data.ByteString.Lazy.Char8 as L8
|
import qualified Data.ByteString.Lazy.Char8 as L8
|
||||||
import qualified Data.Text as TS
|
import qualified Data.Text as TS
|
||||||
|
import qualified Data.Text.Encoding as TE
|
||||||
|
import Control.Arrow ((***))
|
||||||
|
import Network.HTTP.Types (encodePath)
|
||||||
|
import Data.Monoid (mappend)
|
||||||
|
import Blaze.ByteString.Builder.Char.Utf8 (fromText)
|
||||||
|
|
||||||
data Subsite = Subsite
|
data Subsite = Subsite
|
||||||
|
|
||||||
@ -26,7 +31,7 @@ instance RenderRoute Subsite where
|
|||||||
renderRoute (SubsiteRoute x) = (x, [])
|
renderRoute (SubsiteRoute x) = (x, [])
|
||||||
|
|
||||||
instance YesodDispatch Subsite master where
|
instance YesodDispatch Subsite master where
|
||||||
yesodDispatch _ _ _ _ _ _ pieces _ _ = return $ responseLBS
|
yesodDispatch _ _ _ _ _ _ _ pieces _ _ = return $ responseLBS
|
||||||
status200
|
status200
|
||||||
[ ("Content-Type", "SUBSITE")
|
[ ("Content-Type", "SUBSITE")
|
||||||
] $ L8.pack $ show pieces
|
] $ L8.pack $ show pieces
|
||||||
@ -52,6 +57,14 @@ instance Yesod Y where
|
|||||||
where
|
where
|
||||||
corrected = filter (not . TS.null) s
|
corrected = filter (not . TS.null) s
|
||||||
|
|
||||||
|
joinPath Y ar pieces' qs' =
|
||||||
|
fromText ar `mappend` encodePath pieces qs
|
||||||
|
where
|
||||||
|
pieces = if null pieces' then [""] else pieces'
|
||||||
|
qs = map (TE.encodeUtf8 *** go) qs'
|
||||||
|
go "" = Nothing
|
||||||
|
go x = Just $ TE.encodeUtf8 x
|
||||||
|
|
||||||
getFooR :: Handler RepPlain
|
getFooR :: Handler RepPlain
|
||||||
getFooR = return $ RepPlain "foo"
|
getFooR = return $ RepPlain "foo"
|
||||||
|
|
||||||
@ -62,17 +75,16 @@ getBarR, getPlainR :: Handler RepPlain
|
|||||||
getBarR = return $ RepPlain "bar"
|
getBarR = return $ RepPlain "bar"
|
||||||
getPlainR = return $ RepPlain "plain"
|
getPlainR = return $ RepPlain "plain"
|
||||||
|
|
||||||
cleanPathTest :: [Spec]
|
cleanPathTest :: Spec
|
||||||
cleanPathTest =
|
cleanPathTest =
|
||||||
describe "Test.CleanPath"
|
describe "Test.CleanPath" $ do
|
||||||
[ it "remove trailing slash" removeTrailingSlash
|
it "remove trailing slash" removeTrailingSlash
|
||||||
, it "noTrailingSlash" noTrailingSlash
|
it "noTrailingSlash" noTrailingSlash
|
||||||
, it "add trailing slash" addTrailingSlash
|
it "add trailing slash" addTrailingSlash
|
||||||
, it "has trailing slash" hasTrailingSlash
|
it "has trailing slash" hasTrailingSlash
|
||||||
, it "/foo/something" fooSomething
|
it "/foo/something" fooSomething
|
||||||
, it "subsite dispatch" subsiteDispatch
|
it "subsite dispatch" subsiteDispatch
|
||||||
, it "redirect with query string" redQueryString
|
it "redirect with query string" redQueryString
|
||||||
]
|
|
||||||
|
|
||||||
runner :: Session () -> IO ()
|
runner :: Session () -> IO ()
|
||||||
runner f = toWaiApp Y >>= runSession f
|
runner f = toWaiApp Y >>= runSession f
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings #-}
|
{-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
module YesodCoreTest.ErrorHandling
|
module YesodCoreTest.ErrorHandling
|
||||||
( errorHandlingTest
|
( errorHandlingTest
|
||||||
, Widget
|
, Widget
|
||||||
@ -11,6 +12,7 @@ import Network.Wai.Test
|
|||||||
import Text.Hamlet (hamlet)
|
import Text.Hamlet (hamlet)
|
||||||
import qualified Data.ByteString.Lazy as L
|
import qualified Data.ByteString.Lazy as L
|
||||||
import qualified Data.ByteString.Char8 as S8
|
import qualified Data.ByteString.Char8 as S8
|
||||||
|
import Control.Exception (SomeException, try)
|
||||||
|
|
||||||
data App = App
|
data App = App
|
||||||
|
|
||||||
@ -19,12 +21,16 @@ mkYesod "App" [parseRoutes|
|
|||||||
/not_found NotFoundR POST
|
/not_found NotFoundR POST
|
||||||
/first_thing FirstThingR POST
|
/first_thing FirstThingR POST
|
||||||
/after_runRequestBody AfterRunRequestBodyR POST
|
/after_runRequestBody AfterRunRequestBodyR POST
|
||||||
|
/error-in-body ErrorInBodyR GET
|
||||||
|
/error-in-body-noeval ErrorInBodyNoEvalR GET
|
||||||
|]
|
|]
|
||||||
|
|
||||||
instance Yesod App
|
instance Yesod App
|
||||||
|
|
||||||
getHomeR :: Handler RepHtml
|
getHomeR :: Handler RepHtml
|
||||||
getHomeR = defaultLayout $ toWidget [hamlet|
|
getHomeR = do
|
||||||
|
$logDebug "Testing logging"
|
||||||
|
defaultLayout $ toWidget [hamlet|
|
||||||
$doctype 5
|
$doctype 5
|
||||||
|
|
||||||
<html>
|
<html>
|
||||||
@ -49,15 +55,24 @@ postFirstThingR = do
|
|||||||
|
|
||||||
postAfterRunRequestBodyR = do
|
postAfterRunRequestBodyR = do
|
||||||
x <- runRequestBody
|
x <- runRequestBody
|
||||||
_ <- error $ show x
|
_ <- error $ show $ fst x
|
||||||
getHomeR
|
getHomeR
|
||||||
|
|
||||||
errorHandlingTest :: [Spec]
|
getErrorInBodyR :: Handler RepHtml
|
||||||
errorHandlingTest = describe "Test.ErrorHandling"
|
getErrorInBodyR = do
|
||||||
[ it "says not found" caseNotFound
|
let foo = error "error in body 19328" :: String
|
||||||
, it "says 'There was an error' before runRequestBody" caseBefore
|
defaultLayout [whamlet|#{foo}|]
|
||||||
, it "says 'There was an error' after runRequestBody" caseAfter
|
|
||||||
]
|
getErrorInBodyNoEvalR :: Handler (DontFullyEvaluate RepHtml)
|
||||||
|
getErrorInBodyNoEvalR = fmap DontFullyEvaluate getErrorInBodyR
|
||||||
|
|
||||||
|
errorHandlingTest :: Spec
|
||||||
|
errorHandlingTest = describe "Test.ErrorHandling" $ do
|
||||||
|
it "says not found" caseNotFound
|
||||||
|
it "says 'There was an error' before runRequestBody" caseBefore
|
||||||
|
it "says 'There was an error' after runRequestBody" caseAfter
|
||||||
|
it "error in body == 500" caseErrorInBody
|
||||||
|
it "error in body, no eval == 200" caseErrorInBodyNoEval
|
||||||
|
|
||||||
runner :: Session () -> IO ()
|
runner :: Session () -> IO ()
|
||||||
runner f = toWaiApp App >>= runSession f
|
runner f = toWaiApp App >>= runSession f
|
||||||
@ -96,3 +111,18 @@ caseAfter = runner $ do
|
|||||||
}
|
}
|
||||||
assertStatus 500 res
|
assertStatus 500 res
|
||||||
assertBodyContains "bin12345" res
|
assertBodyContains "bin12345" res
|
||||||
|
|
||||||
|
caseErrorInBody :: IO ()
|
||||||
|
caseErrorInBody = runner $ do
|
||||||
|
res <- request defaultRequest { pathInfo = ["error-in-body"] }
|
||||||
|
assertStatus 500 res
|
||||||
|
assertBodyContains "error in body 19328" res
|
||||||
|
|
||||||
|
caseErrorInBodyNoEval :: IO ()
|
||||||
|
caseErrorInBodyNoEval = do
|
||||||
|
eres <- try $ runner $ do
|
||||||
|
_ <- request defaultRequest { pathInfo = ["error-in-body-noeval"] }
|
||||||
|
return ()
|
||||||
|
case eres of
|
||||||
|
Left (_ :: SomeException) -> return ()
|
||||||
|
Right _ -> error "Expected an exception"
|
||||||
|
|||||||
@ -30,11 +30,10 @@ getRedirR = do
|
|||||||
setHeader "foo" "bar"
|
setHeader "foo" "bar"
|
||||||
redirectWith status301 RootR
|
redirectWith status301 RootR
|
||||||
|
|
||||||
exceptionsTest :: [Spec]
|
exceptionsTest :: Spec
|
||||||
exceptionsTest = describe "Test.Exceptions"
|
exceptionsTest = describe "Test.Exceptions" $ do
|
||||||
[ it "500" case500
|
it "500" case500
|
||||||
, it "redirect keeps headers" caseRedirect
|
it "redirect keeps headers" caseRedirect
|
||||||
]
|
|
||||||
|
|
||||||
runner :: Session () -> IO ()
|
runner :: Session () -> IO ()
|
||||||
runner f = toWaiApp Y >>= runSession f
|
runner f = toWaiApp Y >>= runSession f
|
||||||
|
|||||||
@ -10,11 +10,10 @@ import Yesod.Internal.TestApi (randomString, parseWaiRequest')
|
|||||||
import Yesod.Request (Request (..))
|
import Yesod.Request (Request (..))
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
|
|
||||||
randomStringSpecs :: [Spec]
|
randomStringSpecs :: Spec
|
||||||
randomStringSpecs = describe "Yesod.Internal.Request.randomString"
|
randomStringSpecs = describe "Yesod.Internal.Request.randomString" $ do
|
||||||
[ it "looks reasonably random" looksRandom
|
it "looks reasonably random" looksRandom
|
||||||
, it "does not repeat itself" $ noRepeat 10 100
|
it "does not repeat itself" $ noRepeat 10 100
|
||||||
]
|
|
||||||
|
|
||||||
-- NOTE: this testcase may break on other systems/architectures if
|
-- NOTE: this testcase may break on other systems/architectures if
|
||||||
-- mkStdGen is not identical everywhere (is it?).
|
-- mkStdGen is not identical everywhere (is it?).
|
||||||
@ -30,58 +29,56 @@ g :: StdGen
|
|||||||
g = error "test/YesodCoreTest/InternalRequest.g"
|
g = error "test/YesodCoreTest/InternalRequest.g"
|
||||||
|
|
||||||
|
|
||||||
tokenSpecs :: [Spec]
|
tokenSpecs :: Spec
|
||||||
tokenSpecs = describe "Yesod.Internal.Request.parseWaiRequest (reqToken)"
|
tokenSpecs = describe "Yesod.Internal.Request.parseWaiRequest (reqToken)" $ do
|
||||||
[ it "is Nothing if sessions are disabled" noDisabledToken
|
it "is Nothing if sessions are disabled" noDisabledToken
|
||||||
, it "ignores pre-existing token if sessions are disabled" ignoreDisabledToken
|
it "ignores pre-existing token if sessions are disabled" ignoreDisabledToken
|
||||||
, it "uses preexisting token in session" useOldToken
|
it "uses preexisting token in session" useOldToken
|
||||||
, it "generates a new token for sessions without token" generateToken
|
it "generates a new token for sessions without token" generateToken
|
||||||
]
|
|
||||||
|
|
||||||
noDisabledToken :: Bool
|
noDisabledToken :: Bool
|
||||||
noDisabledToken = reqToken r == Nothing where
|
noDisabledToken = reqToken r == Nothing where
|
||||||
r = parseWaiRequest' defaultRequest [] False g
|
r = parseWaiRequest' defaultRequest [] False 0 g
|
||||||
|
|
||||||
ignoreDisabledToken :: Bool
|
ignoreDisabledToken :: Bool
|
||||||
ignoreDisabledToken = reqToken r == Nothing where
|
ignoreDisabledToken = reqToken r == Nothing where
|
||||||
r = parseWaiRequest' defaultRequest [("_TOKEN", "old")] False g
|
r = parseWaiRequest' defaultRequest [("_TOKEN", "old")] False 0 g
|
||||||
|
|
||||||
useOldToken :: Bool
|
useOldToken :: Bool
|
||||||
useOldToken = reqToken r == Just "old" where
|
useOldToken = reqToken r == Just "old" where
|
||||||
r = parseWaiRequest' defaultRequest [("_TOKEN", "old")] True g
|
r = parseWaiRequest' defaultRequest [("_TOKEN", "old")] True 0 g
|
||||||
|
|
||||||
generateToken :: Bool
|
generateToken :: Bool
|
||||||
generateToken = reqToken r /= Nothing where
|
generateToken = reqToken r /= Nothing where
|
||||||
r = parseWaiRequest' defaultRequest [("_TOKEN", "old")] True g
|
r = parseWaiRequest' defaultRequest [("_TOKEN", "old")] True 0 g
|
||||||
|
|
||||||
|
|
||||||
langSpecs :: [Spec]
|
langSpecs :: Spec
|
||||||
langSpecs = describe "Yesod.Internal.Request.parseWaiRequest (reqLangs)"
|
langSpecs = describe "Yesod.Internal.Request.parseWaiRequest (reqLangs)" $ do
|
||||||
[ it "respects Accept-Language" respectAcceptLangs
|
it "respects Accept-Language" respectAcceptLangs
|
||||||
, it "respects sessions" respectSessionLang
|
it "respects sessions" respectSessionLang
|
||||||
, it "respects cookies" respectCookieLang
|
it "respects cookies" respectCookieLang
|
||||||
, it "respects queries" respectQueryLang
|
it "respects queries" respectQueryLang
|
||||||
, it "prioritizes correctly" prioritizeLangs
|
it "prioritizes correctly" prioritizeLangs
|
||||||
]
|
|
||||||
|
|
||||||
respectAcceptLangs :: Bool
|
respectAcceptLangs :: Bool
|
||||||
respectAcceptLangs = reqLangs r == ["en-US", "es", "en"] where
|
respectAcceptLangs = reqLangs r == ["en-US", "es", "en"] where
|
||||||
r = parseWaiRequest' defaultRequest
|
r = parseWaiRequest' defaultRequest
|
||||||
{ requestHeaders = [("Accept-Language", "en-US, es")] } [] False g
|
{ requestHeaders = [("Accept-Language", "en-US, es")] } [] False 0 g
|
||||||
|
|
||||||
respectSessionLang :: Bool
|
respectSessionLang :: Bool
|
||||||
respectSessionLang = reqLangs r == ["en"] where
|
respectSessionLang = reqLangs r == ["en"] where
|
||||||
r = parseWaiRequest' defaultRequest [("_LANG", "en")] False g
|
r = parseWaiRequest' defaultRequest [("_LANG", "en")] False 0 g
|
||||||
|
|
||||||
respectCookieLang :: Bool
|
respectCookieLang :: Bool
|
||||||
respectCookieLang = reqLangs r == ["en"] where
|
respectCookieLang = reqLangs r == ["en"] where
|
||||||
r = parseWaiRequest' defaultRequest
|
r = parseWaiRequest' defaultRequest
|
||||||
{ requestHeaders = [("Cookie", "_LANG=en")]
|
{ requestHeaders = [("Cookie", "_LANG=en")]
|
||||||
} [] False g
|
} [] False 0 g
|
||||||
|
|
||||||
respectQueryLang :: Bool
|
respectQueryLang :: Bool
|
||||||
respectQueryLang = reqLangs r == ["en-US", "en"] where
|
respectQueryLang = reqLangs r == ["en-US", "en"] where
|
||||||
r = parseWaiRequest' defaultRequest { queryString = [("_LANG", Just "en-US")] } [] False g
|
r = parseWaiRequest' defaultRequest { queryString = [("_LANG", Just "en-US")] } [] False 0 g
|
||||||
|
|
||||||
prioritizeLangs :: Bool
|
prioritizeLangs :: Bool
|
||||||
prioritizeLangs = reqLangs r == ["en-QUERY", "en-COOKIE", "en-SESSION", "en", "es"] where
|
prioritizeLangs = reqLangs r == ["en-QUERY", "en-COOKIE", "en-SESSION", "en", "es"] where
|
||||||
@ -90,11 +87,11 @@ prioritizeLangs = reqLangs r == ["en-QUERY", "en-COOKIE", "en-SESSION", "en", "e
|
|||||||
, ("Cookie", "_LANG=en-COOKIE")
|
, ("Cookie", "_LANG=en-COOKIE")
|
||||||
]
|
]
|
||||||
, queryString = [("_LANG", Just "en-QUERY")]
|
, queryString = [("_LANG", Just "en-QUERY")]
|
||||||
} [("_LANG", "en-SESSION")] False g
|
} [("_LANG", "en-SESSION")] False 0 g
|
||||||
|
|
||||||
|
|
||||||
internalRequestTest :: [Spec]
|
internalRequestTest :: Spec
|
||||||
internalRequestTest = descriptions [ randomStringSpecs
|
internalRequestTest = describe "Test.InternalRequestTest" $ do
|
||||||
, tokenSpecs
|
randomStringSpecs
|
||||||
, langSpecs
|
tokenSpecs
|
||||||
]
|
langSpecs
|
||||||
|
|||||||
@ -22,20 +22,19 @@ instance Yesod H where
|
|||||||
getHeadR :: Handler RepHtml
|
getHeadR :: Handler RepHtml
|
||||||
getHeadR = defaultLayout $ addScriptRemote "load.js"
|
getHeadR = defaultLayout $ addScriptRemote "load.js"
|
||||||
|
|
||||||
specs :: [Spec]
|
specs :: Spec
|
||||||
specs = describe "Test.JsLoader" [
|
specs = describe "Test.JsLoader" $ do
|
||||||
it "link from head" $ runner H $ do
|
it "link from head" $ runner H $ do
|
||||||
res <- request defaultRequest
|
res <- request defaultRequest
|
||||||
assertBody "<!DOCTYPE html>\n<html><head><title></title><script src=\"load.js\"></script></head><body></body></html>" res
|
assertBody "<!DOCTYPE html>\n<html><head><title></title><script src=\"load.js\"></script></head><body></body></html>" res
|
||||||
|
|
||||||
, it "link from head async" $ runner HA $ do
|
it "link from head async" $ runner HA $ do
|
||||||
res <- request defaultRequest
|
res <- request defaultRequest
|
||||||
assertBody "<!DOCTYPE html>\n<html><head><title></title><script src=\"yepnope.js\"></script><script>yepnope({load:[\"load.js\"]});</script></head><body></body></html>" res
|
assertBody "<!DOCTYPE html>\n<html><head><title></title><script src=\"yepnope.js\"></script><script>yepnope({load:[\"load.js\"]});</script></head><body></body></html>" res
|
||||||
|
|
||||||
, it "link from bottom" $ runner B $ do
|
it "link from bottom" $ runner B $ do
|
||||||
res <- request defaultRequest
|
res <- request defaultRequest
|
||||||
assertBody "<!DOCTYPE html>\n<html><head><title></title></head><body><script src=\"load.js\"></script></body></html>" res
|
assertBody "<!DOCTYPE html>\n<html><head><title></title></head><body><script src=\"load.js\"></script></body></html>" res
|
||||||
]
|
|
||||||
|
|
||||||
runner :: (YesodDispatch master master, Yesod master) => master -> Session () -> IO ()
|
runner :: (YesodDispatch master master, Yesod master) => master -> Session () -> IO ()
|
||||||
runner app f = toWaiApp app >>= runSession f
|
runner app f = toWaiApp app >>= runSession f
|
||||||
|
|||||||
@ -8,11 +8,17 @@ import Test.Hspec.HUnit ()
|
|||||||
|
|
||||||
import Yesod.Core hiding (Request)
|
import Yesod.Core hiding (Request)
|
||||||
import Text.Hamlet
|
import Text.Hamlet
|
||||||
|
import Network.Wai
|
||||||
import Network.Wai.Test
|
import Network.Wai.Test
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Control.Monad.IO.Class (liftIO)
|
||||||
|
import Blaze.ByteString.Builder (toByteString)
|
||||||
|
|
||||||
data Y = Y
|
data Y = Y
|
||||||
mkYesod "Y" [parseRoutes|
|
mkYesod "Y" [parseRoutes|
|
||||||
/ RootR GET
|
/ RootR GET
|
||||||
|
/single/#Text TextR GET
|
||||||
|
/multi/*Texts TextsR GET
|
||||||
|]
|
|]
|
||||||
|
|
||||||
instance Yesod Y
|
instance Yesod Y
|
||||||
@ -20,10 +26,16 @@ instance Yesod Y
|
|||||||
getRootR :: Handler RepHtml
|
getRootR :: Handler RepHtml
|
||||||
getRootR = defaultLayout $ toWidget [hamlet|<a href=@{RootR}>|]
|
getRootR = defaultLayout $ toWidget [hamlet|<a href=@{RootR}>|]
|
||||||
|
|
||||||
linksTest :: [Spec]
|
getTextR :: Text -> Handler RepHtml
|
||||||
linksTest = describe "Test.Links"
|
getTextR foo = defaultLayout $ toWidget [hamlet|%#{foo}%|]
|
||||||
[ it "linkToHome" case_linkToHome
|
|
||||||
]
|
getTextsR :: [Text] -> Handler RepHtml
|
||||||
|
getTextsR foos = defaultLayout $ toWidget [hamlet|%#{show foos}%|]
|
||||||
|
|
||||||
|
linksTest :: Spec
|
||||||
|
linksTest = describe "Test.Links" $ do
|
||||||
|
it "linkToHome" case_linkToHome
|
||||||
|
it "blank path pieces" case_blanks
|
||||||
|
|
||||||
runner :: Session () -> IO ()
|
runner :: Session () -> IO ()
|
||||||
runner f = toWaiApp Y >>= runSession f
|
runner f = toWaiApp Y >>= runSession f
|
||||||
@ -31,4 +43,26 @@ runner f = toWaiApp Y >>= runSession f
|
|||||||
case_linkToHome :: IO ()
|
case_linkToHome :: IO ()
|
||||||
case_linkToHome = runner $ do
|
case_linkToHome = runner $ do
|
||||||
res <- request defaultRequest
|
res <- request defaultRequest
|
||||||
assertBody "<!DOCTYPE html>\n<html><head><title></title></head><body><a href=\"/\"></a></body></html>" res
|
assertBody "<!DOCTYPE html>\n<html><head><title></title></head><body><a href=\"/\"></a>\n</body></html>" res
|
||||||
|
|
||||||
|
case_blanks :: IO ()
|
||||||
|
case_blanks = runner $ do
|
||||||
|
liftIO $ do
|
||||||
|
let go r =
|
||||||
|
let (ps, qs) = renderRoute r
|
||||||
|
in toByteString $ joinPath Y "" ps qs
|
||||||
|
(go $ TextR "-") `shouldBe` "/single/--"
|
||||||
|
(go $ TextR "") `shouldBe` "/single/-"
|
||||||
|
(go $ TextsR ["", "-", "foo", "", "bar"]) `shouldBe` "/multi/-/--/foo/-/bar"
|
||||||
|
|
||||||
|
res1 <- request defaultRequest
|
||||||
|
{ pathInfo = ["single", "-"]
|
||||||
|
, rawPathInfo = "dummy1"
|
||||||
|
}
|
||||||
|
assertBody "<!DOCTYPE html>\n<html><head><title></title></head><body>%%</body></html>" res1
|
||||||
|
|
||||||
|
res2 <- request defaultRequest
|
||||||
|
{ pathInfo = ["multi", "foo", "-", "bar"]
|
||||||
|
, rawPathInfo = "dummy2"
|
||||||
|
}
|
||||||
|
assertBody "<!DOCTYPE html>\n<html><head><title></title></head><body>%["foo","","bar"]%</body></html>" res2
|
||||||
|
|||||||
@ -49,8 +49,7 @@ caseMediaLink = runner $ do
|
|||||||
assertStatus 200 res
|
assertStatus 200 res
|
||||||
flip assertBody res "<!DOCTYPE html>\n<html><head><title></title><link rel=\"stylesheet\" href=\"all.css\"><link rel=\"stylesheet\" media=\"screen\" href=\"screen.css\"></head><body></body></html>"
|
flip assertBody res "<!DOCTYPE html>\n<html><head><title></title><link rel=\"stylesheet\" href=\"all.css\"><link rel=\"stylesheet\" media=\"screen\" href=\"screen.css\"></head><body></body></html>"
|
||||||
|
|
||||||
mediaTest :: [Spec]
|
mediaTest :: Spec
|
||||||
mediaTest = describe "Test.Media"
|
mediaTest = describe "Test.Media" $ do
|
||||||
[ it "media" caseMedia
|
it "media" caseMedia
|
||||||
, it "media link" caseMediaLink
|
it "media link" caseMediaLink
|
||||||
]
|
|
||||||
|
|||||||
@ -44,7 +44,6 @@ case_sanity = runner $ do
|
|||||||
res <- request defaultRequest
|
res <- request defaultRequest
|
||||||
assertBody mempty res
|
assertBody mempty res
|
||||||
|
|
||||||
noOverloadedTest :: [Spec]
|
noOverloadedTest :: Spec
|
||||||
noOverloadedTest = describe "Test.NoOverloadedStrings"
|
noOverloadedTest = describe "Test.NoOverloadedStrings" $ do
|
||||||
[ it "sanity" case_sanity
|
it "sanity" case_sanity
|
||||||
]
|
|
||||||
|
|||||||
@ -26,34 +26,33 @@ getR303 = redirectWith H.status303 RootR
|
|||||||
getR307 = redirectWith H.status307 RootR
|
getR307 = redirectWith H.status307 RootR
|
||||||
getRRegular = redirect RootR
|
getRRegular = redirect RootR
|
||||||
|
|
||||||
specs :: [Spec]
|
specs :: Spec
|
||||||
specs = describe "Redirect" [
|
specs = describe "Redirect" $ do
|
||||||
it "301 redirect" $ app $ do
|
it "301 redirect" $ app $ do
|
||||||
res <- request defaultRequest { pathInfo = ["r301"] }
|
res <- request defaultRequest { pathInfo = ["r301"] }
|
||||||
assertStatus 301 res
|
assertStatus 301 res
|
||||||
assertBodyContains "" res
|
assertBodyContains "" res
|
||||||
|
|
||||||
, it "303 redirect" $ app $ do
|
it "303 redirect" $ app $ do
|
||||||
res <- request defaultRequest { pathInfo = ["r303"] }
|
res <- request defaultRequest { pathInfo = ["r303"] }
|
||||||
assertStatus 303 res
|
assertStatus 303 res
|
||||||
assertBodyContains "" res
|
assertBodyContains "" res
|
||||||
|
|
||||||
, it "307 redirect" $ app $ do
|
it "307 redirect" $ app $ do
|
||||||
res <- request defaultRequest { pathInfo = ["r307"] }
|
res <- request defaultRequest { pathInfo = ["r307"] }
|
||||||
assertStatus 307 res
|
assertStatus 307 res
|
||||||
assertBodyContains "" res
|
assertBodyContains "" res
|
||||||
|
|
||||||
, it "303 redirect for regular, HTTP 1.1" $ app $ do
|
it "303 redirect for regular, HTTP 1.1" $ app $ do
|
||||||
res <- request defaultRequest {
|
res <- request defaultRequest {
|
||||||
pathInfo = ["rregular"]
|
pathInfo = ["rregular"]
|
||||||
}
|
}
|
||||||
assertStatus 303 res
|
assertStatus 303 res
|
||||||
assertBodyContains "" res
|
assertBodyContains "" res
|
||||||
, it "302 redirect for regular, HTTP 1.0" $ app $ do
|
it "302 redirect for regular, HTTP 1.0" $ app $ do
|
||||||
res <- request defaultRequest {
|
res <- request defaultRequest {
|
||||||
pathInfo = ["rregular"]
|
pathInfo = ["rregular"]
|
||||||
, httpVersion = H.http10
|
, httpVersion = H.http10
|
||||||
}
|
}
|
||||||
assertStatus 302 res
|
assertStatus 302 res
|
||||||
assertBodyContains "" res
|
assertBodyContains "" res
|
||||||
]
|
|
||||||
|
|||||||
@ -25,15 +25,14 @@ app = yesod Y
|
|||||||
getRootR :: Handler ()
|
getRootR :: Handler ()
|
||||||
getRootR = return ()
|
getRootR = return ()
|
||||||
|
|
||||||
specs :: [Spec]
|
specs :: Spec
|
||||||
specs = describe "WaiSubsite" [
|
specs = describe "WaiSubsite" $ do
|
||||||
it "root" $ app $ do
|
it "root" $ app $ do
|
||||||
res <- request defaultRequest { pathInfo = [] }
|
res <- request defaultRequest { pathInfo = [] }
|
||||||
assertStatus 200 res
|
assertStatus 200 res
|
||||||
assertBodyContains "" res
|
assertBodyContains "" res
|
||||||
|
|
||||||
, it "subsite" $ app $ do
|
it "subsite" $ app $ do
|
||||||
res <- request defaultRequest { pathInfo = ["sub", "foo"] }
|
res <- request defaultRequest { pathInfo = ["sub", "foo"] }
|
||||||
assertStatus 200 res
|
assertStatus 200 res
|
||||||
assertBodyContains "WAI" res
|
assertBodyContains "WAI" res
|
||||||
]
|
|
||||||
|
|||||||
@ -26,6 +26,7 @@ mkYesod "Y" [parseRoutes|
|
|||||||
/whamlet WhamletR GET
|
/whamlet WhamletR GET
|
||||||
/towidget TowidgetR GET
|
/towidget TowidgetR GET
|
||||||
/auto AutoR GET
|
/auto AutoR GET
|
||||||
|
/jshead JSHeadR GET
|
||||||
|]
|
|]
|
||||||
|
|
||||||
instance Yesod Y where
|
instance Yesod Y where
|
||||||
@ -55,12 +56,13 @@ getTowidgetR = defaultLayout $ do
|
|||||||
toWidget [lucius|foo{bar:baz}|]
|
toWidget [lucius|foo{bar:baz}|]
|
||||||
toWidgetHead [lucius|foo{bar:baz}|]
|
toWidgetHead [lucius|foo{bar:baz}|]
|
||||||
|
|
||||||
toWidget [hamlet|<foo>|] :: Widget
|
toWidget [hamlet|<foo>|]
|
||||||
toWidgetHead [hamlet|<foo>|]
|
toWidgetHead [hamlet|<foo>|]
|
||||||
toWidgetBody [hamlet|<foo>|]
|
toWidgetBody [hamlet|<foo>|]
|
||||||
|
|
||||||
getWhamletR :: Handler RepHtml
|
getWhamletR :: Handler RepHtml
|
||||||
getWhamletR = defaultLayout [whamlet|
|
getWhamletR = defaultLayout [whamlet|
|
||||||
|
$newline never
|
||||||
<h1>Test
|
<h1>Test
|
||||||
<h2>@{WhamletR}
|
<h2>@{WhamletR}
|
||||||
<h3>_{Goodbye}
|
<h3>_{Goodbye}
|
||||||
@ -68,22 +70,29 @@ getWhamletR = defaultLayout [whamlet|
|
|||||||
^{embed}
|
^{embed}
|
||||||
|]
|
|]
|
||||||
where
|
where
|
||||||
embed = [whamlet|<h4>Embed|]
|
embed = [whamlet|
|
||||||
|
$newline never
|
||||||
|
<h4>Embed
|
||||||
|
|]
|
||||||
|
|
||||||
getAutoR :: Handler RepHtml
|
getAutoR :: Handler RepHtml
|
||||||
getAutoR = defaultLayout [whamlet|
|
getAutoR = defaultLayout [whamlet|
|
||||||
|
$newline never
|
||||||
^{someHtml}
|
^{someHtml}
|
||||||
|]
|
|]
|
||||||
where
|
where
|
||||||
someHtml = [shamlet|somehtml|]
|
someHtml = [shamlet|somehtml|]
|
||||||
|
|
||||||
widgetTest :: [Spec]
|
getJSHeadR :: Handler RepHtml
|
||||||
widgetTest = describe "Test.Widget"
|
getJSHeadR = defaultLayout $ toWidgetHead [julius|alert("hello");|]
|
||||||
[ it "addJuliusBody" case_addJuliusBody
|
|
||||||
, it "whamlet" case_whamlet
|
widgetTest :: Spec
|
||||||
, it "two letter lang codes" case_two_letter_lang
|
widgetTest = describe "Test.Widget" $ do
|
||||||
, it "automatically applies toWidget" case_auto
|
it "addJuliusBody" case_addJuliusBody
|
||||||
]
|
it "whamlet" case_whamlet
|
||||||
|
it "two letter lang codes" case_two_letter_lang
|
||||||
|
it "automatically applies toWidget" case_auto
|
||||||
|
it "toWidgetHead puts JS in head" case_jshead
|
||||||
|
|
||||||
runner :: Session () -> IO ()
|
runner :: Session () -> IO ()
|
||||||
runner f = toWaiApp Y >>= runSession f
|
runner f = toWaiApp Y >>= runSession f
|
||||||
@ -116,3 +125,10 @@ case_auto = runner $ do
|
|||||||
, requestHeaders = [("Accept-Language", "es")]
|
, requestHeaders = [("Accept-Language", "es")]
|
||||||
}
|
}
|
||||||
assertBody "<!DOCTYPE html>\n<html><head><title></title></head><body>somehtml</body></html>" res
|
assertBody "<!DOCTYPE html>\n<html><head><title></title></head><body>somehtml</body></html>" res
|
||||||
|
|
||||||
|
case_jshead :: IO ()
|
||||||
|
case_jshead = runner $ do
|
||||||
|
res <- request defaultRequest
|
||||||
|
{ pathInfo = ["jshead"]
|
||||||
|
}
|
||||||
|
assertBody "<!DOCTYPE html>\n<html><head><title></title><script>alert(\"hello\");</script></head><body></body></html>" res
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-core
|
name: yesod-core
|
||||||
version: 1.0.0
|
version: 1.1.3
|
||||||
license: MIT
|
license: MIT
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Michael Snoyman <michael@snoyman.com>
|
author: Michael Snoyman <michael@snoyman.com>
|
||||||
@ -15,78 +15,80 @@ cabal-version: >= 1.8
|
|||||||
build-type: Simple
|
build-type: Simple
|
||||||
homepage: http://www.yesodweb.com/
|
homepage: http://www.yesodweb.com/
|
||||||
extra-source-files:
|
extra-source-files:
|
||||||
test/en.msg
|
test.hs
|
||||||
test/YesodCoreTest/NoOverloadedStrings.hs
|
test/YesodCoreTest.hs
|
||||||
|
test/YesodCoreTest/Cache.hs
|
||||||
|
test/YesodCoreTest/CleanPath.hs
|
||||||
|
test/YesodCoreTest/ErrorHandling.hs
|
||||||
|
test/YesodCoreTest/Exceptions.hs
|
||||||
|
test/YesodCoreTest/InternalRequest.hs
|
||||||
|
test/YesodCoreTest/JsLoader.hs
|
||||||
|
test/YesodCoreTest/JsLoaderSites/Bottom.hs
|
||||||
|
test/YesodCoreTest/JsLoaderSites/HeadAsync.hs
|
||||||
|
test/YesodCoreTest/Links.hs
|
||||||
test/YesodCoreTest/Media.hs
|
test/YesodCoreTest/Media.hs
|
||||||
test/YesodCoreTest/MediaData.hs
|
test/YesodCoreTest/MediaData.hs
|
||||||
test/YesodCoreTest/Exceptions.hs
|
test/YesodCoreTest/NoOverloadedStrings.hs
|
||||||
|
test/YesodCoreTest/Redirect.hs
|
||||||
|
test/YesodCoreTest/WaiSubsite.hs
|
||||||
test/YesodCoreTest/Widget.hs
|
test/YesodCoreTest/Widget.hs
|
||||||
test/YesodCoreTest/CleanPath.hs
|
test/YesodCoreTest/YesodTest.hs
|
||||||
test/YesodCoreTest/Links.hs
|
test/en.msg
|
||||||
test/YesodCoreTest/InternalRequest.hs
|
test/test.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
|
||||||
default: False
|
default: False
|
||||||
|
|
||||||
flag ghc7
|
|
||||||
|
|
||||||
library
|
library
|
||||||
if flag(ghc7)
|
|
||||||
build-depends: base >= 4.3 && < 5
|
|
||||||
cpp-options: -DGHC7
|
|
||||||
else
|
|
||||||
build-depends: base >= 4 && < 4.3
|
|
||||||
|
|
||||||
-- Work around a bug in cabal. Without this, wai-test doesn't get built and
|
-- Work around a bug in cabal. Without this, wai-test doesn't get built and
|
||||||
-- we have a missing dependency during --enable-tests builds.
|
-- we have a missing dependency during --enable-tests builds.
|
||||||
if flag(test)
|
if flag(test)
|
||||||
build-depends: wai-test
|
build-depends: wai-test
|
||||||
|
|
||||||
build-depends: time >= 1.1.4
|
build-depends: base >= 4.3 && < 5
|
||||||
, yesod-routes >= 1.0 && < 1.1
|
, time >= 1.1.4
|
||||||
, wai >= 1.2 && < 1.3
|
, yesod-routes >= 1.1 && < 1.2
|
||||||
, wai-extra >= 1.2 && < 1.3
|
, wai >= 1.3 && < 1.4
|
||||||
, bytestring >= 0.9.1.4 && < 0.10
|
, wai-extra >= 1.3 && < 1.4
|
||||||
|
, bytestring >= 0.9.1.4
|
||||||
, text >= 0.7 && < 0.12
|
, text >= 0.7 && < 0.12
|
||||||
, template-haskell
|
, template-haskell
|
||||||
, path-pieces >= 0.1 && < 0.2
|
, path-pieces >= 0.1.2 && < 0.2
|
||||||
, hamlet >= 1.0 && < 1.1
|
, hamlet >= 1.1 && < 1.2
|
||||||
, shakespeare >= 1.0 && < 1.1
|
, shakespeare >= 1.0 && < 1.1
|
||||||
, shakespeare-js >= 1.0 && < 1.1
|
, shakespeare-js >= 1.0 && < 1.1
|
||||||
, shakespeare-css >= 1.0 && < 1.1
|
, shakespeare-css >= 1.0 && < 1.1
|
||||||
, shakespeare-i18n >= 1.0 && < 1.1
|
, shakespeare-i18n >= 1.0 && < 1.1
|
||||||
, blaze-builder >= 0.2.1.4 && < 0.4
|
, blaze-builder >= 0.2.1.4 && < 0.4
|
||||||
, transformers >= 0.2.2 && < 0.4
|
, transformers >= 0.2.2 && < 0.4
|
||||||
, clientsession >= 0.7.3.1 && < 0.8
|
, clientsession >= 0.8 && < 0.9
|
||||||
, random >= 1.0.0.2 && < 1.1
|
, random >= 1.0.0.2 && < 1.1
|
||||||
, cereal >= 0.3 && < 0.4
|
, cereal >= 0.3 && < 0.4
|
||||||
, old-locale >= 1.0.0.2 && < 1.1
|
, old-locale >= 1.0.0.2 && < 1.1
|
||||||
, failure >= 0.2 && < 0.3
|
, failure >= 0.2 && < 0.3
|
||||||
, containers >= 0.2 && < 0.5
|
, containers >= 0.2
|
||||||
, monad-control >= 0.3 && < 0.4
|
, monad-control >= 0.3 && < 0.4
|
||||||
, transformers-base >= 0.4
|
, transformers-base >= 0.4
|
||||||
, cookie >= 0.4 && < 0.5
|
, cookie >= 0.4 && < 0.5
|
||||||
, blaze-html >= 0.4.1.3 && < 0.5
|
, http-types >= 0.7 && < 0.8
|
||||||
, http-types >= 0.6.5 && < 0.7
|
|
||||||
, case-insensitive >= 0.2
|
, case-insensitive >= 0.2
|
||||||
, parsec >= 2 && < 3.2
|
, parsec >= 2 && < 3.2
|
||||||
, directory >= 1 && < 1.2
|
, directory >= 1
|
||||||
, vector >= 0.9 && < 0.10
|
, vector >= 0.9 && < 0.11
|
||||||
, aeson >= 0.5
|
, aeson >= 0.5
|
||||||
, fast-logger >= 0.0.2
|
, fast-logger >= 0.2
|
||||||
, wai-logger >= 0.0.1
|
, monad-logger >= 0.2.1 && < 0.3
|
||||||
, conduit >= 0.4 && < 0.5
|
, conduit >= 0.5 && < 0.6
|
||||||
, resourcet >= 0.3 && < 0.4
|
, resourcet >= 0.3 && < 0.5
|
||||||
, lifted-base >= 0.1 && < 0.2
|
, lifted-base >= 0.1
|
||||||
|
, blaze-html >= 0.5 && < 0.6
|
||||||
|
, blaze-markup >= 0.5.1 && < 0.6
|
||||||
|
|
||||||
exposed-modules: Yesod.Content
|
exposed-modules: Yesod.Content
|
||||||
Yesod.Core
|
Yesod.Core
|
||||||
Yesod.Dispatch
|
Yesod.Dispatch
|
||||||
Yesod.Handler
|
Yesod.Handler
|
||||||
Yesod.Logger
|
|
||||||
Yesod.Request
|
Yesod.Request
|
||||||
Yesod.Widget
|
Yesod.Widget
|
||||||
Yesod.Message
|
Yesod.Message
|
||||||
@ -104,17 +106,9 @@ test-suite tests
|
|||||||
main-is: test.hs
|
main-is: test.hs
|
||||||
hs-source-dirs: test
|
hs-source-dirs: 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
|
cpp-options: -DTEST
|
||||||
build-depends: hspec >= 0.8 && < 0.10
|
build-depends: base
|
||||||
|
,hspec >= 1.3 && < 1.4
|
||||||
,wai-test
|
,wai-test
|
||||||
,wai
|
,wai
|
||||||
,yesod-core
|
,yesod-core
|
||||||
@ -125,8 +119,10 @@ test-suite tests
|
|||||||
,text
|
,text
|
||||||
,http-types
|
,http-types
|
||||||
, random
|
, random
|
||||||
|
, blaze-builder
|
||||||
,HUnit
|
,HUnit
|
||||||
,QuickCheck >= 2 && < 3
|
,QuickCheck >= 2 && < 3
|
||||||
|
,transformers
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
|
|
||||||
source-repository head
|
source-repository head
|
||||||
|
|||||||
@ -1,25 +1,20 @@
|
|||||||
The following license covers this documentation, and the source code, except
|
Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
|
||||||
where otherwise indicated.
|
|
||||||
|
|
||||||
Copyright 2010, Michael Snoyman. All rights reserved.
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
The above copyright notice and this permission notice shall be
|
||||||
modification, are permitted provided that the following conditions are met:
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
list of conditions and the following disclaimer.
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
this list of conditions and the following disclaimer in the documentation
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
and/or other materials provided with the distribution.
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
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.
|
|
||||||
|
|||||||
@ -85,9 +85,15 @@ fromArgs getExtra = do
|
|||||||
}
|
}
|
||||||
config <- loadConfig cs
|
config <- loadConfig cs
|
||||||
|
|
||||||
|
env' <- getEnvironment
|
||||||
|
let config' =
|
||||||
|
case lookup "APPROOT" env' of
|
||||||
|
Nothing -> config
|
||||||
|
Just ar -> config { appRoot = T.pack ar }
|
||||||
|
|
||||||
return $ if port args /= 0
|
return $ if port args /= 0
|
||||||
then config { appPort = port args }
|
then config' { appPort = port args }
|
||||||
else config
|
else config'
|
||||||
|
|
||||||
-- | Load your development config (when using @'DefaultEnv'@)
|
-- | Load your development config (when using @'DefaultEnv'@)
|
||||||
loadDevelopmentConfig :: IO (AppConfig DefaultEnv ())
|
loadDevelopmentConfig :: IO (AppConfig DefaultEnv ())
|
||||||
|
|||||||
@ -7,7 +7,6 @@ module Yesod.Default.Main
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import Yesod.Default.Config
|
import Yesod.Default.Config
|
||||||
import Yesod.Logger (Logger, defaultDevelopmentLogger, logString)
|
|
||||||
import Network.Wai (Application)
|
import Network.Wai (Application)
|
||||||
import Network.Wai.Handler.Warp
|
import Network.Wai.Handler.Warp
|
||||||
(runSettings, defaultSettings, settingsPort, settingsHost)
|
(runSettings, defaultSettings, settingsPort, settingsHost)
|
||||||
@ -26,26 +25,18 @@ import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar)
|
|||||||
-- | Run your app, taking environment and port settings from the
|
-- | Run your app, taking environment and port settings from the
|
||||||
-- commandline.
|
-- commandline.
|
||||||
--
|
--
|
||||||
-- Use @'fromArgs'@ when using the provided @'DefaultEnv'@ type, or
|
-- @'fromArgs'@ helps parse a custom configuration
|
||||||
-- @'fromArgsWith'@ when using a custom type
|
|
||||||
--
|
--
|
||||||
-- > main :: IO ()
|
-- > main :: IO ()
|
||||||
-- > main = defaultMain fromArgs withMySite
|
-- > main = defaultMain (fromArgs parseExtra) makeApplication
|
||||||
--
|
|
||||||
-- or
|
|
||||||
--
|
|
||||||
-- > main :: IO ()
|
|
||||||
-- > main = defaultMain (fromArgsWith customArgConfig) withMySite
|
|
||||||
--
|
--
|
||||||
defaultMain :: (Show env, Read env)
|
defaultMain :: (Show env, Read env)
|
||||||
=> IO (AppConfig env extra)
|
=> IO (AppConfig env extra)
|
||||||
-> (AppConfig env extra -> Logger -> IO Application)
|
-> (AppConfig env extra -> IO Application)
|
||||||
-> IO ()
|
-> IO ()
|
||||||
defaultMain load getApp = do
|
defaultMain load getApp = do
|
||||||
config <- load
|
config <- load
|
||||||
logger <- defaultDevelopmentLogger
|
app <- getApp config
|
||||||
app <- getApp config logger
|
|
||||||
print $ appHost config
|
|
||||||
runSettings defaultSettings
|
runSettings defaultSettings
|
||||||
{ settingsPort = appPort config
|
{ settingsPort = appPort config
|
||||||
, settingsHost = appHost config
|
, settingsHost = appHost config
|
||||||
@ -86,12 +77,11 @@ defaultRunner f app = do
|
|||||||
defaultDevelApp
|
defaultDevelApp
|
||||||
:: (Show env, Read env)
|
:: (Show env, Read env)
|
||||||
=> IO (AppConfig env extra) -- ^ A means to load your development @'AppConfig'@
|
=> IO (AppConfig env extra) -- ^ A means to load your development @'AppConfig'@
|
||||||
-> (AppConfig env extra -> Logger -> IO Application) -- ^ Get your @Application@
|
-> (AppConfig env extra -> IO Application) -- ^ Get your @Application@
|
||||||
-> IO (Int, Application)
|
-> IO (Int, Application)
|
||||||
defaultDevelApp load getApp = do
|
defaultDevelApp load getApp = do
|
||||||
conf <- load
|
conf <- load
|
||||||
logger <- defaultDevelopmentLogger
|
|
||||||
let p = appPort conf
|
let p = appPort conf
|
||||||
logString logger $ "Devel application launched: http://localhost:" ++ show p
|
putStrLn $ "Devel application launched: http://localhost:" ++ show p
|
||||||
app <- getApp conf logger
|
app <- getApp conf
|
||||||
return (p, app)
|
return (p, app)
|
||||||
|
|||||||
@ -7,7 +7,11 @@ module Yesod.Default.Util
|
|||||||
, globFile
|
, globFile
|
||||||
, widgetFileNoReload
|
, widgetFileNoReload
|
||||||
, widgetFileReload
|
, widgetFileReload
|
||||||
, widgetFileJsCss
|
, TemplateLanguage (..)
|
||||||
|
, defaultTemplateLanguages
|
||||||
|
, WidgetFileSettings
|
||||||
|
, wfsLanguages
|
||||||
|
, wfsHamletSettings
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Monad.IO.Class (liftIO)
|
import Control.Monad.IO.Class (liftIO)
|
||||||
@ -20,7 +24,9 @@ import Language.Haskell.TH.Syntax
|
|||||||
import Text.Lucius (luciusFile, luciusFileReload)
|
import Text.Lucius (luciusFile, luciusFileReload)
|
||||||
import Text.Julius (juliusFile, juliusFileReload)
|
import Text.Julius (juliusFile, juliusFileReload)
|
||||||
import Text.Cassius (cassiusFile, cassiusFileReload)
|
import Text.Cassius (cassiusFile, cassiusFileReload)
|
||||||
|
import Text.Hamlet (HamletSettings, defaultHamletSettings)
|
||||||
import Data.Maybe (catMaybes)
|
import Data.Maybe (catMaybes)
|
||||||
|
import Data.Default (Default (def))
|
||||||
|
|
||||||
-- | An implementation of 'addStaticContent' which stores the contents in an
|
-- | An implementation of 'addStaticContent' which stores the contents in an
|
||||||
-- external file. Files are created in the given static folder with names based
|
-- external file. Files are created in the given static folder with names based
|
||||||
@ -57,34 +63,40 @@ addStaticContentExternal minify hash staticDir toRoute ext' _ content = do
|
|||||||
globFile :: String -> String -> FilePath
|
globFile :: String -> String -> FilePath
|
||||||
globFile kind x = "templates/" ++ x ++ "." ++ kind
|
globFile kind x = "templates/" ++ x ++ "." ++ kind
|
||||||
|
|
||||||
widgetFileNoReload :: FilePath -> Q Exp
|
data TemplateLanguage = TemplateLanguage
|
||||||
widgetFileNoReload x = combine "widgetFileNoReload" x
|
{ tlRequiresToWidget :: Bool
|
||||||
[ whenExists x False "hamlet" whamletFile
|
, tlExtension :: String
|
||||||
, whenExists x True "cassius" cassiusFile
|
, tlNoReload :: FilePath -> Q Exp
|
||||||
, whenExists x True "julius" juliusFile
|
, tlReload :: FilePath -> Q Exp
|
||||||
, whenExists x True "lucius" luciusFile
|
}
|
||||||
]
|
|
||||||
|
|
||||||
widgetFileReload :: FilePath -> Q Exp
|
defaultTemplateLanguages :: HamletSettings -> [TemplateLanguage]
|
||||||
widgetFileReload x = combine "widgetFileReload" x
|
defaultTemplateLanguages hset =
|
||||||
[ whenExists x False "hamlet" whamletFile
|
[ TemplateLanguage False "hamlet" whamletFile' whamletFile'
|
||||||
, whenExists x True "cassius" cassiusFileReload
|
, TemplateLanguage True "cassius" cassiusFile cassiusFileReload
|
||||||
, whenExists x True "julius" juliusFileReload
|
, TemplateLanguage True "julius" juliusFile juliusFileReload
|
||||||
, whenExists x True "lucius" luciusFileReload
|
, TemplateLanguage True "lucius" luciusFile luciusFileReload
|
||||||
]
|
]
|
||||||
|
where
|
||||||
|
whamletFile' = whamletFileWithSettings hset
|
||||||
|
|
||||||
widgetFileJsCss :: (String, FilePath -> Q Exp) -- ^ Css file extenstion and loading function. example: ("cassius", cassiusFileReload)
|
data WidgetFileSettings = WidgetFileSettings
|
||||||
-> (String, FilePath -> Q Exp) -- ^ Css file extenstion and loading function. example: ("julius", juliusFileReload)
|
{ wfsLanguages :: HamletSettings -> [TemplateLanguage]
|
||||||
-> FilePath -> Q Exp
|
, wfsHamletSettings :: HamletSettings
|
||||||
widgetFileJsCss (jsExt, jsLoad) (csExt, csLoad) x = combine "widgetFileJsCss" x
|
}
|
||||||
[ whenExists x False "hamlet" whamletFile
|
|
||||||
, whenExists x True csExt csLoad
|
|
||||||
, whenExists x True jsExt jsLoad
|
|
||||||
]
|
|
||||||
|
|
||||||
combine :: String -> String -> [Q (Maybe Exp)] -> Q Exp
|
instance Default WidgetFileSettings where
|
||||||
combine func file qmexps = do
|
def = WidgetFileSettings defaultTemplateLanguages defaultHamletSettings
|
||||||
mexps <- sequence qmexps
|
|
||||||
|
widgetFileNoReload :: WidgetFileSettings -> FilePath -> Q Exp
|
||||||
|
widgetFileNoReload wfs x = combine "widgetFileNoReload" x False $ wfsLanguages wfs $ wfsHamletSettings wfs
|
||||||
|
|
||||||
|
widgetFileReload :: WidgetFileSettings -> FilePath -> Q Exp
|
||||||
|
widgetFileReload wfs x = combine "widgetFileReload" x True $ wfsLanguages wfs $ wfsHamletSettings wfs
|
||||||
|
|
||||||
|
combine :: String -> String -> Bool -> [TemplateLanguage] -> Q Exp
|
||||||
|
combine func file isReload tls = do
|
||||||
|
mexps <- qmexps
|
||||||
case catMaybes mexps of
|
case catMaybes mexps of
|
||||||
[] -> error $ concat
|
[] -> error $ concat
|
||||||
[ "Called "
|
[ "Called "
|
||||||
@ -94,6 +106,12 @@ combine func file qmexps = do
|
|||||||
, ", but no template were found."
|
, ", but no template were found."
|
||||||
]
|
]
|
||||||
exps -> return $ DoE $ map NoBindS exps
|
exps -> return $ DoE $ map NoBindS exps
|
||||||
|
where
|
||||||
|
qmexps :: Q [Maybe Exp]
|
||||||
|
qmexps = mapM go tls
|
||||||
|
|
||||||
|
go :: TemplateLanguage -> Q (Maybe Exp)
|
||||||
|
go tl = whenExists file (tlRequiresToWidget tl) (tlExtension tl) ((if isReload then tlReload else tlNoReload) tl)
|
||||||
|
|
||||||
whenExists :: String
|
whenExists :: String
|
||||||
-> Bool -- ^ requires toWidget wrap
|
-> Bool -- ^ requires toWidget wrap
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-default
|
name: yesod-default
|
||||||
version: 1.0.0
|
version: 1.1.0.2
|
||||||
license: MIT
|
license: MIT
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Patrick Brisbin
|
author: Patrick Brisbin
|
||||||
@ -18,10 +18,10 @@ library
|
|||||||
cpp-options: -DWINDOWS
|
cpp-options: -DWINDOWS
|
||||||
|
|
||||||
build-depends: base >= 4 && < 5
|
build-depends: base >= 4 && < 5
|
||||||
, yesod-core >= 1.0 && < 1.1
|
, yesod-core >= 1.1 && < 1.2
|
||||||
, warp >= 1.2 && < 1.3
|
, warp >= 1.3 && < 1.4
|
||||||
, wai >= 1.2 && < 1.3
|
, wai >= 1.3 && < 1.4
|
||||||
, wai-extra >= 1.2 && < 1.3
|
, wai-extra >= 1.3 && < 1.4
|
||||||
, bytestring >= 0.9.1.4
|
, bytestring >= 0.9.1.4
|
||||||
, transformers >= 0.2.2 && < 0.4
|
, transformers >= 0.2.2 && < 0.4
|
||||||
, text >= 0.9
|
, text >= 0.9
|
||||||
@ -29,9 +29,11 @@ library
|
|||||||
, shakespeare-css >= 1.0 && < 1.1
|
, shakespeare-css >= 1.0 && < 1.1
|
||||||
, shakespeare-js >= 1.0 && < 1.1
|
, shakespeare-js >= 1.0 && < 1.1
|
||||||
, template-haskell
|
, template-haskell
|
||||||
, yaml >= 0.7 && < 0.8
|
, yaml >= 0.8 && < 0.9
|
||||||
, network-conduit >= 0.4 && < 0.5
|
, network-conduit >= 0.5 && < 0.7
|
||||||
, unordered-containers
|
, unordered-containers
|
||||||
|
, hamlet >= 1.1 && < 1.2
|
||||||
|
, data-default
|
||||||
|
|
||||||
if !os(windows)
|
if !os(windows)
|
||||||
build-depends: unix
|
build-depends: unix
|
||||||
|
|||||||
20
yesod-eventsource/LICENSE
Normal file
20
yesod-eventsource/LICENSE
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
Copyright (c) 2012 Felipe Lessa
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
7
yesod-eventsource/Setup.lhs
Executable file
7
yesod-eventsource/Setup.lhs
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env runhaskell
|
||||||
|
|
||||||
|
> module Main where
|
||||||
|
> import Distribution.Simple
|
||||||
|
|
||||||
|
> main :: IO ()
|
||||||
|
> main = defaultMain
|
||||||
101
yesod-eventsource/Yesod/EventSource.hs
Normal file
101
yesod-eventsource/Yesod/EventSource.hs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
-- | This module contains everything that you need to support
|
||||||
|
-- server-sent events in Yesod applications.
|
||||||
|
module Yesod.EventSource
|
||||||
|
( RepEventSource
|
||||||
|
, repEventSource
|
||||||
|
, ioToRepEventSource
|
||||||
|
, EventSourcePolyfill(..)
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Blaze.ByteString.Builder (Builder)
|
||||||
|
import Control.Monad (when)
|
||||||
|
import Control.Monad.IO.Class (liftIO)
|
||||||
|
import Data.Functor ((<$>))
|
||||||
|
import Data.Monoid (mappend, mempty)
|
||||||
|
import Yesod.Content
|
||||||
|
import Yesod.Core
|
||||||
|
import qualified Data.Conduit as C
|
||||||
|
import qualified Network.Wai as W
|
||||||
|
import qualified Network.Wai.EventSource as ES
|
||||||
|
import qualified Network.Wai.EventSource.EventStream as ES
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- | Data type representing a response of server-sent events
|
||||||
|
-- (e.g., see 'repEventSource' and 'ioToRepEventSource').
|
||||||
|
newtype RepEventSource =
|
||||||
|
RepEventSource (C.Source (C.ResourceT IO) (C.Flush Builder))
|
||||||
|
|
||||||
|
instance HasReps RepEventSource where
|
||||||
|
chooseRep (RepEventSource src) =
|
||||||
|
const $ return ("text/event-stream", ContentSource src)
|
||||||
|
|
||||||
|
|
||||||
|
-- | (Internal) Find out the request's 'EventSourcePolyfill' and
|
||||||
|
-- set any necessary headers.
|
||||||
|
prepareForEventSource :: GHandler sub master EventSourcePolyfill
|
||||||
|
prepareForEventSource = do
|
||||||
|
reqWith <- lookup "X-Requested-With" . W.requestHeaders <$> waiRequest
|
||||||
|
let polyfill | reqWith == Just "XMLHttpRequest" = Remy'sESPolyfill
|
||||||
|
| otherwise = NoESPolyfill
|
||||||
|
setHeader "Cache-Control" "no-cache" -- extremely important!
|
||||||
|
return polyfill
|
||||||
|
|
||||||
|
|
||||||
|
-- | Returns a Server-Sent Event stream from a 'C.Source' of
|
||||||
|
-- 'ES.ServerEvent'@s@. The HTTP socket is flushed after every
|
||||||
|
-- event. The connection is closed either when the 'C.Source'
|
||||||
|
-- finishes outputting data or a 'ES.CloseEvent' is outputted,
|
||||||
|
-- whichever comes first.
|
||||||
|
repEventSource :: (EventSourcePolyfill -> C.Source (C.ResourceT IO) ES.ServerEvent)
|
||||||
|
-> GHandler sub master RepEventSource
|
||||||
|
repEventSource src = RepEventSource . ES.sourceToSource . src <$> prepareForEventSource
|
||||||
|
|
||||||
|
|
||||||
|
-- | Return a Server-Sent Event stream given an @IO@ action that
|
||||||
|
-- is repeatedly called. A state is threaded for the action so
|
||||||
|
-- that it may avoid using @IORefs@. The @IO@ action may sleep
|
||||||
|
-- or block while waiting for more data. The HTTP socket is
|
||||||
|
-- flushed after every list of simultaneous events. The
|
||||||
|
-- connection is closed as soon as an 'ES.CloseEvent' is
|
||||||
|
-- outputted, after which no other events are sent to the client.
|
||||||
|
ioToRepEventSource :: s
|
||||||
|
-> (EventSourcePolyfill -> s -> IO ([ES.ServerEvent], s))
|
||||||
|
-> GHandler sub master RepEventSource
|
||||||
|
ioToRepEventSource initial act = do
|
||||||
|
polyfill <- prepareForEventSource
|
||||||
|
let -- Get new events to be sent.
|
||||||
|
getEvents s = do
|
||||||
|
(evs, s') <- liftIO (act polyfill s)
|
||||||
|
case evs of
|
||||||
|
[] -> getEvents s'
|
||||||
|
_ -> do
|
||||||
|
let (builder, continue) = joinEvents evs mempty
|
||||||
|
C.yield (C.Chunk builder)
|
||||||
|
C.yield C.Flush
|
||||||
|
when continue (getEvents s')
|
||||||
|
|
||||||
|
-- Join all events in a single Builder. Returns @False@
|
||||||
|
-- when we the connection should be closed.
|
||||||
|
joinEvents (ev:evs) acc =
|
||||||
|
case ES.eventToBuilder ev of
|
||||||
|
Just b -> joinEvents evs (acc `mappend` b)
|
||||||
|
Nothing -> (fst $ joinEvents [] acc, False)
|
||||||
|
joinEvents [] acc = (acc, True)
|
||||||
|
|
||||||
|
return $ RepEventSource $ getEvents initial
|
||||||
|
|
||||||
|
|
||||||
|
-- | Which @EventSource@ polyfill was detected (if any).
|
||||||
|
data EventSourcePolyfill =
|
||||||
|
NoESPolyfill
|
||||||
|
-- ^ We didn't detect any @EventSource@ polyfill that we know.
|
||||||
|
| Remy'sESPolyfill
|
||||||
|
-- ^ See
|
||||||
|
-- <https://github.com/remy/polyfills/blob/master/EventSource.js>.
|
||||||
|
-- In order to support Remy\'s polyfill, your server needs to
|
||||||
|
-- explicitly close the connection from time to
|
||||||
|
-- time--browsers such as IE7 will not show any event until
|
||||||
|
-- the connection is closed.
|
||||||
|
deriving (Eq, Ord, Show, Enum)
|
||||||
42
yesod-eventsource/yesod-eventsource.cabal
Normal file
42
yesod-eventsource/yesod-eventsource.cabal
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
name: yesod-eventsource
|
||||||
|
version: 1.0.0.1
|
||||||
|
license: MIT
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Felipe Lessa <felipe.lessa@gmail.com>
|
||||||
|
maintainer: Felipe Lessa <felipe.lessa@gmail.com>
|
||||||
|
synopsis: Server-sent events support for Yesod apps.
|
||||||
|
category: Web, Yesod
|
||||||
|
stability: Stable
|
||||||
|
cabal-version: >= 1.6
|
||||||
|
build-type: Simple
|
||||||
|
homepage: http://www.yesodweb.com/
|
||||||
|
description:
|
||||||
|
It's easy to send an event from an HTTP client to a server:
|
||||||
|
just send an HTTP request. However, sending events from the
|
||||||
|
server to the client requires more sophisticated approaches.
|
||||||
|
Server-sent events (<http://www.w3.org/TR/eventsource/>) are a
|
||||||
|
standardized way of pushing events from the server to the
|
||||||
|
client.
|
||||||
|
.
|
||||||
|
This package allows your Yesod application to easily send
|
||||||
|
server-sent events. On the client side, you may use the
|
||||||
|
@EventSource@ JavaScript object on browsers that support it
|
||||||
|
(<https://developer.mozilla.org/en-US/docs/Server-sent_events/EventSource>)
|
||||||
|
or a polyfill for browsers that don't (we support Remy's
|
||||||
|
polyfill out-of-the-box, although that requires you to
|
||||||
|
explicitly support it).
|
||||||
|
|
||||||
|
library
|
||||||
|
build-depends: base >= 4 && < 5
|
||||||
|
, yesod-core >= 1.1 && < 1.2
|
||||||
|
, conduit >= 0.5 && < 0.6
|
||||||
|
, wai >= 1.3 && < 1.4
|
||||||
|
, wai-eventsource >= 1.3 && < 1.4
|
||||||
|
, blaze-builder
|
||||||
|
, transformers
|
||||||
|
exposed-modules: Yesod.EventSource
|
||||||
|
ghc-options: -Wall
|
||||||
|
|
||||||
|
source-repository head
|
||||||
|
type: git
|
||||||
|
location: https://github.com/yesodweb/yesod
|
||||||
@ -1,30 +1,20 @@
|
|||||||
Copyright Michael Snoyman 2010
|
Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
|
||||||
|
|
||||||
All rights reserved.
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
The above copyright notice and this permission notice shall be
|
||||||
modification, are permitted provided that the following conditions are met:
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
notice, this list of conditions and the following disclaimer.
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
* Redistributions in binary form must reproduce the above
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
copyright notice, this list of conditions and the following
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
disclaimer in the documentation and/or other materials provided
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
with the distribution.
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
* Neither the name of Michael Snoyman nor the names of other
|
|
||||||
contributors may be used to endorse or promote products derived
|
|
||||||
from this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"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
|
|
||||||
OWNER OR CONTRIBUTORS 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.
|
|
||||||
|
|||||||
@ -1,25 +1,20 @@
|
|||||||
The following license covers this documentation, and the source code, except
|
Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
|
||||||
where otherwise indicated.
|
|
||||||
|
|
||||||
Copyright 2010, Michael Snoyman. All rights reserved.
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
The above copyright notice and this permission notice shall be
|
||||||
modification, are permitted provided that the following conditions are met:
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
list of conditions and the following disclaimer.
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
this list of conditions and the following disclaimer in the documentation
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
and/or other materials provided with the distribution.
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
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.
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
module Yesod.Form.Fields
|
module Yesod.Form.Fields
|
||||||
( -- * i18n
|
( -- * i18n
|
||||||
FormMessage (..)
|
FormMessage (..)
|
||||||
@ -46,11 +47,15 @@ module Yesod.Form.Fields
|
|||||||
|
|
||||||
import Yesod.Form.Types
|
import Yesod.Form.Types
|
||||||
import Yesod.Form.I18n.English
|
import Yesod.Form.I18n.English
|
||||||
|
import Yesod.Form.Functions (parseHelper)
|
||||||
import Yesod.Handler (getMessageRender)
|
import Yesod.Handler (getMessageRender)
|
||||||
import Yesod.Widget (toWidget, whamlet, GWidget)
|
import Yesod.Widget (toWidget, whamlet, GWidget)
|
||||||
import Yesod.Message (RenderMessage (renderMessage), SomeMessage (..))
|
import Yesod.Message (RenderMessage (renderMessage), SomeMessage (..))
|
||||||
import Text.Hamlet
|
import Text.Hamlet
|
||||||
import Text.Blaze (ToHtml (..), preEscapedText, unsafeByteString)
|
import Text.Blaze (ToMarkup (toMarkup), preEscapedToMarkup, unsafeByteString)
|
||||||
|
#define ToHtml ToMarkup
|
||||||
|
#define toHtml toMarkup
|
||||||
|
#define preEscapedText preEscapedToMarkup
|
||||||
import Text.Cassius
|
import Text.Cassius
|
||||||
import Data.Time (Day, TimeOfDay(..))
|
import Data.Time (Day, TimeOfDay(..))
|
||||||
import qualified Text.Email.Validate as Email
|
import qualified Text.Email.Validate as Email
|
||||||
@ -59,14 +64,14 @@ import Database.Persist (PersistField)
|
|||||||
import Database.Persist.Store (Entity (..))
|
import Database.Persist.Store (Entity (..))
|
||||||
import Text.HTML.SanitizeXSS (sanitizeBalance)
|
import Text.HTML.SanitizeXSS (sanitizeBalance)
|
||||||
import Control.Monad (when, unless)
|
import Control.Monad (when, unless)
|
||||||
import Data.Maybe (listToMaybe)
|
import Data.Maybe (listToMaybe, fromMaybe)
|
||||||
|
|
||||||
import qualified Blaze.ByteString.Builder.Html.Utf8 as B
|
import qualified Blaze.ByteString.Builder.Html.Utf8 as B
|
||||||
import Blaze.ByteString.Builder (writeByteString, toLazyByteString)
|
import Blaze.ByteString.Builder (writeByteString, toLazyByteString)
|
||||||
import Blaze.ByteString.Builder.Internal.Write (fromWriteList)
|
import Blaze.ByteString.Builder.Internal.Write (fromWriteList)
|
||||||
import Database.Persist.Store (PersistEntityBackend)
|
import Database.Persist.Store (PersistEntityBackend)
|
||||||
|
|
||||||
import Text.Blaze.Renderer.String (renderHtml)
|
import Text.Blaze.Html.Renderer.String (renderHtml)
|
||||||
import qualified Data.ByteString as S
|
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)
|
||||||
@ -76,29 +81,27 @@ import qualified Data.Map as Map
|
|||||||
import Yesod.Handler (newIdent, lift)
|
import Yesod.Handler (newIdent, lift)
|
||||||
import Yesod.Request (FileInfo)
|
import Yesod.Request (FileInfo)
|
||||||
|
|
||||||
import Yesod.Core (toPathPiece, GHandler, PathPiece)
|
import Yesod.Core (toPathPiece, GHandler, PathPiece, fromPathPiece)
|
||||||
import Yesod.Persist (selectList, runDB, Filter, SelectOpt, YesodPersistBackend, Key, YesodPersist, PersistEntity, PersistQuery)
|
import Yesod.Persist (selectList, runDB, Filter, SelectOpt, YesodPersistBackend, Key, YesodPersist, PersistEntity, PersistQuery)
|
||||||
import Control.Arrow ((&&&))
|
import Control.Arrow ((&&&))
|
||||||
|
|
||||||
import Control.Applicative ((<$>))
|
import Control.Applicative ((<$>), (<|>))
|
||||||
|
|
||||||
|
import Data.Attoparsec.Text (Parser, char, string, digit, skipSpace, endOfInput, parseOnly)
|
||||||
|
|
||||||
defaultFormMessage :: FormMessage -> Text
|
defaultFormMessage :: FormMessage -> Text
|
||||||
defaultFormMessage = englishFormMessage
|
defaultFormMessage = englishFormMessage
|
||||||
|
|
||||||
blank :: (Monad m, RenderMessage master FormMessage)
|
|
||||||
=> (Text -> Either FormMessage a) -> [Text] -> m (Either (SomeMessage master) (Maybe a))
|
|
||||||
blank _ [] = return $ Right Nothing
|
|
||||||
blank _ ("":_) = return $ Right Nothing
|
|
||||||
blank f (x:_) = return $ either (Left . SomeMessage) (Right . Just) $ f x
|
|
||||||
|
|
||||||
intField :: (Integral i, RenderMessage master FormMessage) => Field sub master i
|
intField :: (Integral i, RenderMessage master FormMessage) => Field sub master i
|
||||||
intField = Field
|
intField = Field
|
||||||
{ fieldParse = blank $ \s ->
|
{ fieldParse = parseHelper $ \s ->
|
||||||
case Data.Text.Read.signed Data.Text.Read.decimal s of
|
case Data.Text.Read.signed Data.Text.Read.decimal s of
|
||||||
Right (a, "") -> Right a
|
Right (a, "") -> Right a
|
||||||
_ -> Left $ MsgInvalidInteger s
|
_ -> Left $ MsgInvalidInteger s
|
||||||
|
|
||||||
, fieldView = \theId name attrs val isReq -> toWidget [hamlet|
|
, fieldView = \theId name attrs val isReq -> toWidget [hamlet|
|
||||||
|
$newline never
|
||||||
<input id="#{theId}" name="#{name}" *{attrs} type="number" :isReq:required="" value="#{showVal val}">
|
<input id="#{theId}" name="#{name}" *{attrs} type="number" :isReq:required="" value="#{showVal val}">
|
||||||
|]
|
|]
|
||||||
}
|
}
|
||||||
@ -108,12 +111,13 @@ intField = Field
|
|||||||
|
|
||||||
doubleField :: RenderMessage master FormMessage => Field sub master Double
|
doubleField :: RenderMessage master FormMessage => Field sub master Double
|
||||||
doubleField = Field
|
doubleField = Field
|
||||||
{ fieldParse = blank $ \s ->
|
{ fieldParse = parseHelper $ \s ->
|
||||||
case Data.Text.Read.double s of
|
case Data.Text.Read.double s of
|
||||||
Right (a, "") -> Right a
|
Right (a, "") -> Right a
|
||||||
_ -> Left $ MsgInvalidNumber s
|
_ -> Left $ MsgInvalidNumber s
|
||||||
|
|
||||||
, fieldView = \theId name attrs val isReq -> toWidget [hamlet|
|
, fieldView = \theId name attrs val isReq -> toWidget [hamlet|
|
||||||
|
$newline never
|
||||||
<input id="#{theId}" name="#{name}" *{attrs} type="text" :isReq:required="" value="#{showVal val}">
|
<input id="#{theId}" name="#{name}" *{attrs} type="text" :isReq:required="" value="#{showVal val}">
|
||||||
|]
|
|]
|
||||||
}
|
}
|
||||||
@ -121,8 +125,9 @@ doubleField = Field
|
|||||||
|
|
||||||
dayField :: RenderMessage master FormMessage => Field sub master Day
|
dayField :: RenderMessage master FormMessage => Field sub master Day
|
||||||
dayField = Field
|
dayField = Field
|
||||||
{ fieldParse = blank $ parseDate . unpack
|
{ fieldParse = parseHelper $ parseDate . unpack
|
||||||
, fieldView = \theId name attrs val isReq -> toWidget [hamlet|
|
, fieldView = \theId name attrs val isReq -> toWidget [hamlet|
|
||||||
|
$newline never
|
||||||
<input id="#{theId}" name="#{name}" *{attrs} type="date" :isReq:required="" value="#{showVal val}">
|
<input id="#{theId}" name="#{name}" *{attrs} type="date" :isReq:required="" value="#{showVal val}">
|
||||||
|]
|
|]
|
||||||
}
|
}
|
||||||
@ -130,8 +135,9 @@ dayField = Field
|
|||||||
|
|
||||||
timeField :: RenderMessage master FormMessage => Field sub master TimeOfDay
|
timeField :: RenderMessage master FormMessage => Field sub master TimeOfDay
|
||||||
timeField = Field
|
timeField = Field
|
||||||
{ fieldParse = blank $ parseTime . unpack
|
{ fieldParse = parseHelper parseTime
|
||||||
, fieldView = \theId name attrs val isReq -> toWidget [hamlet|
|
, fieldView = \theId name attrs val isReq -> toWidget [hamlet|
|
||||||
|
$newline never
|
||||||
<input id="#{theId}" name="#{name}" *{attrs} :isReq:required="" value="#{showVal val}">
|
<input id="#{theId}" name="#{name}" *{attrs} :isReq:required="" value="#{showVal val}">
|
||||||
|]
|
|]
|
||||||
}
|
}
|
||||||
@ -144,9 +150,10 @@ timeField = Field
|
|||||||
|
|
||||||
htmlField :: RenderMessage master FormMessage => Field sub master Html
|
htmlField :: RenderMessage master FormMessage => Field sub master Html
|
||||||
htmlField = Field
|
htmlField = Field
|
||||||
{ fieldParse = blank $ Right . preEscapedText . sanitizeBalance
|
{ fieldParse = parseHelper $ Right . preEscapedText . sanitizeBalance
|
||||||
, fieldView = \theId name attrs val _isReq -> toWidget [hamlet|
|
, fieldView = \theId name attrs val _isReq -> toWidget [hamlet|
|
||||||
-- FIXME: There was a class="html" attribute, for what purpose?
|
$newline never
|
||||||
|
$# FIXME: There was a class="html" attribute, for what purpose?
|
||||||
<textarea id="#{theId}" name="#{name}" *{attrs}>#{showVal val}
|
<textarea id="#{theId}" name="#{name}" *{attrs}>#{showVal val}
|
||||||
|]
|
|]
|
||||||
}
|
}
|
||||||
@ -155,7 +162,7 @@ htmlField = Field
|
|||||||
-- | A newtype wrapper around a 'Text' that converts newlines to HTML
|
-- | A newtype wrapper around a 'Text' that converts newlines to HTML
|
||||||
-- br-tags.
|
-- br-tags.
|
||||||
newtype Textarea = Textarea { unTextarea :: Text }
|
newtype Textarea = Textarea { unTextarea :: Text }
|
||||||
deriving (Show, Read, Eq, PersistField)
|
deriving (Show, Read, Eq, PersistField, Ord)
|
||||||
instance ToHtml Textarea where
|
instance ToHtml Textarea where
|
||||||
toHtml =
|
toHtml =
|
||||||
unsafeByteString
|
unsafeByteString
|
||||||
@ -172,33 +179,38 @@ instance ToHtml Textarea where
|
|||||||
|
|
||||||
textareaField :: RenderMessage master FormMessage => Field sub master Textarea
|
textareaField :: RenderMessage master FormMessage => Field sub master Textarea
|
||||||
textareaField = Field
|
textareaField = Field
|
||||||
{ fieldParse = blank $ Right . Textarea
|
{ fieldParse = parseHelper $ Right . Textarea
|
||||||
, fieldView = \theId name attrs val _isReq -> toWidget [hamlet|
|
, fieldView = \theId name attrs val _isReq -> toWidget [hamlet|
|
||||||
|
$newline never
|
||||||
<textarea id="#{theId}" name="#{name}" *{attrs}>#{either id unTextarea val}
|
<textarea id="#{theId}" name="#{name}" *{attrs}>#{either id unTextarea val}
|
||||||
|]
|
|]
|
||||||
}
|
}
|
||||||
|
|
||||||
hiddenField :: RenderMessage master FormMessage => Field sub master Text
|
hiddenField :: (PathPiece p, RenderMessage master FormMessage)
|
||||||
|
=> Field sub master p
|
||||||
hiddenField = Field
|
hiddenField = Field
|
||||||
{ fieldParse = blank $ Right
|
{ fieldParse = parseHelper $ maybe (Left MsgValueRequired) Right . fromPathPiece
|
||||||
, fieldView = \theId name attrs val _isReq -> toWidget [hamlet|
|
, fieldView = \theId name attrs val _isReq -> toWidget [hamlet|
|
||||||
<input type="hidden" id="#{theId}" name="#{name}" *{attrs} value="#{either id id val}">
|
$newline never
|
||||||
|
<input type="hidden" id="#{theId}" name="#{name}" *{attrs} value="#{either id toPathPiece val}">
|
||||||
|]
|
|]
|
||||||
}
|
}
|
||||||
|
|
||||||
textField :: RenderMessage master FormMessage => Field sub master Text
|
textField :: RenderMessage master FormMessage => Field sub master Text
|
||||||
textField = Field
|
textField = Field
|
||||||
{ fieldParse = blank $ Right
|
{ fieldParse = parseHelper $ Right
|
||||||
, fieldView = \theId name attrs val isReq ->
|
, fieldView = \theId name attrs val isReq ->
|
||||||
[whamlet|
|
[whamlet|
|
||||||
|
$newline never
|
||||||
<input id="#{theId}" name="#{name}" *{attrs} type="text" :isReq:required value="#{either id id val}">
|
<input id="#{theId}" name="#{name}" *{attrs} type="text" :isReq:required value="#{either id id val}">
|
||||||
|]
|
|]
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordField :: RenderMessage master FormMessage => Field sub master Text
|
passwordField :: RenderMessage master FormMessage => Field sub master Text
|
||||||
passwordField = Field
|
passwordField = Field
|
||||||
{ fieldParse = blank $ Right
|
{ fieldParse = parseHelper $ Right
|
||||||
, fieldView = \theId name attrs val isReq -> toWidget [hamlet|
|
, fieldView = \theId name attrs val isReq -> toWidget [hamlet|
|
||||||
|
$newline never
|
||||||
<input id="#{theId}" name="#{name}" *{attrs} type="password" :isReq:required="" value="#{either id id val}">
|
<input id="#{theId}" name="#{name}" *{attrs} type="password" :isReq:required="" value="#{either id id val}">
|
||||||
|]
|
|]
|
||||||
}
|
}
|
||||||
@ -217,37 +229,61 @@ parseDate = maybe (Left MsgInvalidDay) Right
|
|||||||
replace :: Eq a => a -> a -> [a] -> [a]
|
replace :: Eq a => a -> a -> [a] -> [a]
|
||||||
replace x y = map (\z -> if z == x then y else z)
|
replace x y = map (\z -> if z == x then y else z)
|
||||||
|
|
||||||
parseTime :: String -> Either FormMessage TimeOfDay
|
parseTime :: Text -> Either FormMessage TimeOfDay
|
||||||
parseTime (h2:':':m1:m2:[]) = parseTimeHelper ('0', h2, m1, m2, '0', '0')
|
parseTime = either (Left . fromMaybe MsgInvalidTimeFormat . readMay . drop 2 . dropWhile (/= ':')) Right . parseOnly timeParser
|
||||||
parseTime (h1:h2:':':m1:m2:[]) = parseTimeHelper (h1, h2, m1, m2, '0', '0')
|
|
||||||
parseTime (h1:h2:':':m1:m2:' ':'A':'M':[]) =
|
|
||||||
parseTimeHelper (h1, h2, m1, m2, '0', '0')
|
|
||||||
parseTime (h1:h2:':':m1:m2:' ':'P':'M':[]) =
|
|
||||||
let [h1', h2'] = show $ (read [h1, h2] :: Int) + 12
|
|
||||||
in parseTimeHelper (h1', h2', m1, m2, '0', '0')
|
|
||||||
parseTime (h1:h2:':':m1:m2:':':s1:s2:[]) =
|
|
||||||
parseTimeHelper (h1, h2, m1, m2, s1, s2)
|
|
||||||
parseTime _ = Left MsgInvalidTimeFormat
|
|
||||||
|
|
||||||
parseTimeHelper :: (Char, Char, Char, Char, Char, Char)
|
timeParser :: Parser TimeOfDay
|
||||||
-> Either FormMessage TimeOfDay
|
timeParser = do
|
||||||
parseTimeHelper (h1, h2, m1, m2, s1, s2)
|
skipSpace
|
||||||
| h < 0 || h > 23 = Left $ MsgInvalidHour $ pack [h1, h2]
|
h <- hour
|
||||||
| m < 0 || m > 59 = Left $ MsgInvalidMinute $ pack [m1, m2]
|
_ <- char ':'
|
||||||
| s < 0 || s > 59 = Left $ MsgInvalidSecond $ pack [s1, s2]
|
m <- minsec MsgInvalidMinute
|
||||||
| otherwise = Right $ TimeOfDay h m s
|
hasSec <- (char ':' >> return True) <|> return False
|
||||||
|
s <- if hasSec then minsec MsgInvalidSecond else return 0
|
||||||
|
skipSpace
|
||||||
|
isPM <-
|
||||||
|
(string "am" >> return (Just False)) <|>
|
||||||
|
(string "AM" >> return (Just False)) <|>
|
||||||
|
(string "pm" >> return (Just True)) <|>
|
||||||
|
(string "PM" >> return (Just True)) <|>
|
||||||
|
return Nothing
|
||||||
|
h' <-
|
||||||
|
case isPM of
|
||||||
|
Nothing -> return h
|
||||||
|
Just x
|
||||||
|
| h <= 0 || h > 12 -> fail $ show $ MsgInvalidHour $ pack $ show h
|
||||||
|
| h == 12 -> return $ if x then 12 else 0
|
||||||
|
| otherwise -> return $ h + (if x then 12 else 0)
|
||||||
|
skipSpace
|
||||||
|
endOfInput
|
||||||
|
return $ TimeOfDay h' m s
|
||||||
where
|
where
|
||||||
h = read [h1, h2] -- FIXME isn't this a really bad idea?
|
hour = do
|
||||||
m = read [m1, m2]
|
x <- digit
|
||||||
s = fromInteger $ read [s1, s2]
|
y <- (return <$> digit) <|> return []
|
||||||
|
let xy = x : y
|
||||||
|
let i = read xy
|
||||||
|
if i < 0 || i >= 24
|
||||||
|
then fail $ show $ MsgInvalidHour $ pack xy
|
||||||
|
else return i
|
||||||
|
minsec :: Num a => (Text -> FormMessage) -> Parser a
|
||||||
|
minsec msg = do
|
||||||
|
x <- digit
|
||||||
|
y <- digit <|> fail (show $ msg $ pack [x])
|
||||||
|
let xy = [x, y]
|
||||||
|
let i = read xy
|
||||||
|
if i < 0 || i >= 60
|
||||||
|
then fail $ show $ msg $ pack xy
|
||||||
|
else return $ fromIntegral (i :: Int)
|
||||||
|
|
||||||
emailField :: RenderMessage master FormMessage => Field sub master Text
|
emailField :: RenderMessage master FormMessage => Field sub master Text
|
||||||
emailField = Field
|
emailField = Field
|
||||||
{ fieldParse = blank $
|
{ fieldParse = parseHelper $
|
||||||
\s -> if Email.isValid (unpack s)
|
\s -> if Email.isValid (unpack s)
|
||||||
then Right s
|
then Right s
|
||||||
else Left $ MsgInvalidEmail s
|
else Left $ MsgInvalidEmail s
|
||||||
, fieldView = \theId name attrs val isReq -> toWidget [hamlet|
|
, fieldView = \theId name attrs val isReq -> toWidget [hamlet|
|
||||||
|
$newline never
|
||||||
<input id="#{theId}" name="#{name}" *{attrs} type="email" :isReq:required="" value="#{either id id val}">
|
<input id="#{theId}" name="#{name}" *{attrs} type="email" :isReq:required="" value="#{either id id val}">
|
||||||
|]
|
|]
|
||||||
}
|
}
|
||||||
@ -255,14 +291,18 @@ emailField = Field
|
|||||||
type AutoFocus = Bool
|
type AutoFocus = Bool
|
||||||
searchField :: RenderMessage master FormMessage => AutoFocus -> Field sub master Text
|
searchField :: RenderMessage master FormMessage => AutoFocus -> Field sub master Text
|
||||||
searchField autoFocus = Field
|
searchField autoFocus = Field
|
||||||
{ fieldParse = blank Right
|
{ fieldParse = parseHelper Right
|
||||||
, fieldView = \theId name attrs val isReq -> do
|
, fieldView = \theId name attrs val isReq -> do
|
||||||
[whamlet|\
|
[whamlet|\
|
||||||
|
$newline never
|
||||||
<input id="#{theId}" name="#{name}" *{attrs} type="search" :isReq:required="" :autoFocus:autofocus="" value="#{either id id val}">
|
<input id="#{theId}" name="#{name}" *{attrs} type="search" :isReq:required="" :autoFocus:autofocus="" value="#{either id id val}">
|
||||||
|]
|
|]
|
||||||
when autoFocus $ do
|
when autoFocus $ do
|
||||||
-- we want this javascript to be placed immediately after the field
|
-- we want this javascript to be placed immediately after the field
|
||||||
[whamlet|<script>if (!('autofocus' in document.createElement('input'))) {document.getElementById('#{theId}').focus();}|]
|
[whamlet|
|
||||||
|
$newline never
|
||||||
|
<script>if (!('autofocus' in document.createElement('input'))) {document.getElementById('#{theId}').focus();}
|
||||||
|
|]
|
||||||
toWidget [cassius|
|
toWidget [cassius|
|
||||||
#{theId}
|
#{theId}
|
||||||
-webkit-appearance: textfield
|
-webkit-appearance: textfield
|
||||||
@ -271,12 +311,13 @@ searchField autoFocus = Field
|
|||||||
|
|
||||||
urlField :: RenderMessage master FormMessage => Field sub master Text
|
urlField :: RenderMessage master FormMessage => Field sub master Text
|
||||||
urlField = Field
|
urlField = Field
|
||||||
{ fieldParse = blank $ \s ->
|
{ fieldParse = parseHelper $ \s ->
|
||||||
case parseURI $ unpack s of
|
case parseURI $ unpack s of
|
||||||
Nothing -> Left $ MsgInvalidUrl s
|
Nothing -> Left $ MsgInvalidUrl s
|
||||||
Just _ -> Right s
|
Just _ -> Right s
|
||||||
, fieldView = \theId name attrs val isReq ->
|
, fieldView = \theId name attrs val isReq ->
|
||||||
[whamlet|
|
[whamlet|
|
||||||
|
$newline never
|
||||||
<input ##{theId} name=#{name} *{attrs} type=url :isReq:required value=#{either id id val}>
|
<input ##{theId} name=#{name} *{attrs} type=url :isReq:required value=#{either id id val}>
|
||||||
|]
|
|]
|
||||||
}
|
}
|
||||||
@ -286,9 +327,18 @@ selectFieldList = selectField . optionsPairs
|
|||||||
|
|
||||||
selectField :: (Eq a, RenderMessage master FormMessage) => GHandler sub master (OptionList a) -> Field sub master a
|
selectField :: (Eq a, RenderMessage master FormMessage) => GHandler sub master (OptionList a) -> Field sub master a
|
||||||
selectField = selectFieldHelper
|
selectField = selectFieldHelper
|
||||||
(\theId name inside -> [whamlet|<select ##{theId} name=#{name}>^{inside}|]) -- outside
|
(\theId name inside -> [whamlet|
|
||||||
(\_theId _name isSel -> [whamlet|<option value=none :isSel:selected>_{MsgSelectNone}|]) -- onOpt
|
$newline never
|
||||||
(\_theId _name attrs value isSel text -> [whamlet|<option value=#{value} :isSel:selected *{attrs}>#{text}|]) -- inside
|
<select ##{theId} name=#{name}>^{inside}
|
||||||
|
|]) -- outside
|
||||||
|
(\_theId _name isSel -> [whamlet|
|
||||||
|
$newline never
|
||||||
|
<option value=none :isSel:selected>_{MsgSelectNone}
|
||||||
|
|]) -- onOpt
|
||||||
|
(\_theId _name attrs value isSel text -> [whamlet|
|
||||||
|
$newline never
|
||||||
|
<option value=#{value} :isSel:selected *{attrs}>#{text}
|
||||||
|
|]) -- inside
|
||||||
|
|
||||||
multiSelectFieldList :: (Eq a, RenderMessage master FormMessage, RenderMessage master msg) => [(msg, a)] -> Field sub master [a]
|
multiSelectFieldList :: (Eq a, RenderMessage master FormMessage, RenderMessage master msg) => [(msg, a)] -> Field sub master [a]
|
||||||
multiSelectFieldList = multiSelectField . optionsPairs
|
multiSelectFieldList = multiSelectField . optionsPairs
|
||||||
@ -310,6 +360,7 @@ multiSelectField ioptlist =
|
|||||||
opts <- fmap olOptions $ lift ioptlist
|
opts <- fmap olOptions $ lift ioptlist
|
||||||
let selOpts = map (id &&& (optselected val)) opts
|
let selOpts = map (id &&& (optselected val)) opts
|
||||||
[whamlet|
|
[whamlet|
|
||||||
|
$newline never
|
||||||
<select ##{theId} name=#{name} :isReq:required multiple *{attrs}>
|
<select ##{theId} name=#{name} :isReq:required multiple *{attrs}>
|
||||||
$forall (opt, optsel) <- selOpts
|
$forall (opt, optsel) <- selOpts
|
||||||
<option value=#{optionExternalValue opt} :optsel:selected>#{optionDisplay opt}
|
<option value=#{optionExternalValue opt} :optsel:selected>#{optionDisplay opt}
|
||||||
@ -323,22 +374,30 @@ radioFieldList = radioField . optionsPairs
|
|||||||
|
|
||||||
radioField :: (Eq a, RenderMessage master FormMessage) => GHandler sub master (OptionList a) -> Field sub master a
|
radioField :: (Eq a, RenderMessage master FormMessage) => GHandler sub master (OptionList a) -> Field sub master a
|
||||||
radioField = selectFieldHelper
|
radioField = selectFieldHelper
|
||||||
(\theId _name inside -> [whamlet|<div ##{theId}>^{inside}|])
|
(\theId _name inside -> [whamlet|
|
||||||
|
$newline never
|
||||||
|
<div ##{theId}>^{inside}
|
||||||
|
|])
|
||||||
(\theId name isSel -> [whamlet|
|
(\theId name isSel -> [whamlet|
|
||||||
<div>
|
$newline never
|
||||||
<input id=#{theId}-none type=radio name=#{name} value=none :isSel:checked>
|
<label .radio for=#{theId}-none>
|
||||||
<label for=#{theId}-none>_{MsgSelectNone}
|
<div>
|
||||||
|
<input id=#{theId}-none type=radio name=#{name} value=none :isSel:checked>
|
||||||
|
_{MsgSelectNone}
|
||||||
|])
|
|])
|
||||||
(\theId name attrs value isSel text -> [whamlet|
|
(\theId name attrs value isSel text -> [whamlet|
|
||||||
<div>
|
$newline never
|
||||||
<input id=#{theId}-#{value} type=radio name=#{name} value=#{value} :isSel:checked *{attrs}>
|
<label .radio for=#{theId}-#{value}>
|
||||||
<label for=#{theId}-#{value}>#{text}
|
<div>
|
||||||
|
<input id=#{theId}-#{value} type=radio name=#{name} value=#{value} :isSel:checked *{attrs}>
|
||||||
|
\#{text}
|
||||||
|])
|
|])
|
||||||
|
|
||||||
boolField :: RenderMessage master FormMessage => Field sub master Bool
|
boolField :: RenderMessage master FormMessage => Field sub master Bool
|
||||||
boolField = Field
|
boolField = Field
|
||||||
{ fieldParse = return . boolParser
|
{ fieldParse = return . boolParser
|
||||||
, fieldView = \theId name attrs val isReq -> [whamlet|
|
, fieldView = \theId name attrs val isReq -> [whamlet|
|
||||||
|
$newline never
|
||||||
$if not isReq
|
$if not isReq
|
||||||
<input id=#{theId}-none *{attrs} type=radio name=#{name} value=none checked>
|
<input id=#{theId}-none *{attrs} type=radio name=#{name} value=none checked>
|
||||||
<label for=#{theId}-none>_{MsgSelectNone}
|
<label for=#{theId}-none>_{MsgSelectNone}
|
||||||
@ -357,6 +416,7 @@ boolField = Field
|
|||||||
"" -> Right Nothing
|
"" -> Right Nothing
|
||||||
"none" -> Right Nothing
|
"none" -> Right Nothing
|
||||||
"yes" -> Right $ Just True
|
"yes" -> Right $ Just True
|
||||||
|
"on" -> Right $ Just True
|
||||||
"no" -> Right $ Just False
|
"no" -> Right $ Just False
|
||||||
t -> Left $ SomeMessage $ MsgInvalidBool t
|
t -> Left $ SomeMessage $ MsgInvalidBool t
|
||||||
showVal = either (\_ -> False)
|
showVal = either (\_ -> False)
|
||||||
@ -372,6 +432,7 @@ checkBoxField :: RenderMessage m FormMessage => Field s m Bool
|
|||||||
checkBoxField = Field
|
checkBoxField = Field
|
||||||
{ fieldParse = return . checkBoxParser
|
{ fieldParse = return . checkBoxParser
|
||||||
, fieldView = \theId name attrs val _ -> [whamlet|
|
, fieldView = \theId name attrs val _ -> [whamlet|
|
||||||
|
$newline never
|
||||||
<input id=#{theId} *{attrs} type=checkbox name=#{name} value=yes :showVal id val:checked>
|
<input id=#{theId} *{attrs} type=checkbox name=#{name} value=yes :showVal id val:checked>
|
||||||
|]
|
|]
|
||||||
}
|
}
|
||||||
@ -380,6 +441,7 @@ checkBoxField = Field
|
|||||||
checkBoxParser [] = Right $ Just False
|
checkBoxParser [] = Right $ Just False
|
||||||
checkBoxParser (x:_) = case x of
|
checkBoxParser (x:_) = case x of
|
||||||
"yes" -> Right $ Just True
|
"yes" -> Right $ Just True
|
||||||
|
"on" -> Right $ Just True
|
||||||
_ -> Right $ Just False
|
_ -> Right $ Just False
|
||||||
|
|
||||||
showVal = either (\_ -> False)
|
showVal = either (\_ -> False)
|
||||||
@ -486,6 +548,7 @@ fileAFormReq fs = AForm $ \(master, langs) menvs ints -> do
|
|||||||
, fvTooltip = fmap (toHtml . renderMessage master langs) $ fsTooltip fs
|
, fvTooltip = fmap (toHtml . renderMessage master langs) $ fsTooltip fs
|
||||||
, fvId = id'
|
, fvId = id'
|
||||||
, fvInput = [whamlet|
|
, fvInput = [whamlet|
|
||||||
|
$newline never
|
||||||
<input type=file name=#{name} ##{id'} *{fsAttrs fs}>
|
<input type=file name=#{name} ##{id'} *{fsAttrs fs}>
|
||||||
|]
|
|]
|
||||||
, fvErrors = errs
|
, fvErrors = errs
|
||||||
@ -514,6 +577,7 @@ fileAFormOpt fs = AForm $ \(master, langs) menvs ints -> do
|
|||||||
, fvTooltip = fmap (toHtml . renderMessage master langs) $ fsTooltip fs
|
, fvTooltip = fmap (toHtml . renderMessage master langs) $ fsTooltip fs
|
||||||
, fvId = id'
|
, fvId = id'
|
||||||
, fvInput = [whamlet|
|
, fvInput = [whamlet|
|
||||||
|
$newline never
|
||||||
<input type=file name=#{name} ##{id'} *{fsAttrs fs}>
|
<input type=file name=#{name} ##{id'} *{fsAttrs fs}>
|
||||||
|]
|
|]
|
||||||
, fvErrors = errs
|
, fvErrors = errs
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
module Yesod.Form.Functions
|
module Yesod.Form.Functions
|
||||||
( -- * Running in MForm monad
|
( -- * Running in MForm monad
|
||||||
newFormIdent
|
newFormIdent
|
||||||
@ -26,15 +27,19 @@ module Yesod.Form.Functions
|
|||||||
, FormRender
|
, FormRender
|
||||||
, renderTable
|
, renderTable
|
||||||
, renderDivs
|
, renderDivs
|
||||||
|
, renderDivsNoLabels
|
||||||
, renderBootstrap
|
, renderBootstrap
|
||||||
-- * Validation
|
-- * Validation
|
||||||
, check
|
, check
|
||||||
, checkBool
|
, checkBool
|
||||||
, checkM
|
, checkM
|
||||||
|
, checkMMap
|
||||||
|
, checkMMod
|
||||||
, customErrorMessage
|
, customErrorMessage
|
||||||
-- * Utilities
|
-- * Utilities
|
||||||
, fieldSettingsLabel
|
, fieldSettingsLabel
|
||||||
, aformM
|
, aformM
|
||||||
|
, parseHelper
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Yesod.Form.Types
|
import Yesod.Form.Types
|
||||||
@ -43,18 +48,21 @@ import Control.Arrow (second)
|
|||||||
import Control.Monad.Trans.RWS (ask, get, put, runRWST, tell, evalRWST)
|
import Control.Monad.Trans.RWS (ask, get, put, runRWST, tell, evalRWST)
|
||||||
import Control.Monad.Trans.Class (lift)
|
import Control.Monad.Trans.Class (lift)
|
||||||
import Control.Monad (liftM, join)
|
import Control.Monad (liftM, join)
|
||||||
import Text.Blaze (Html, toHtml)
|
import Crypto.Classes (constTimeEq)
|
||||||
|
import Text.Blaze (Markup, toMarkup)
|
||||||
|
#define Html Markup
|
||||||
|
#define toHtml toMarkup
|
||||||
import Yesod.Handler (GHandler, getRequest, runRequestBody, newIdent, getYesod)
|
import Yesod.Handler (GHandler, getRequest, runRequestBody, newIdent, getYesod)
|
||||||
import Yesod.Core (RenderMessage, SomeMessage (..))
|
import Yesod.Core (RenderMessage, SomeMessage (..))
|
||||||
import Yesod.Widget (GWidget, whamlet)
|
import Yesod.Widget (GWidget, whamlet)
|
||||||
import Yesod.Request (reqToken, reqWaiRequest, reqGetParams, languages, FileInfo (..))
|
import Yesod.Request (reqToken, reqWaiRequest, reqGetParams, languages)
|
||||||
import Network.Wai (requestMethod)
|
import Network.Wai (requestMethod)
|
||||||
import Text.Hamlet (shamlet)
|
import Text.Hamlet (shamlet)
|
||||||
import Data.Monoid (mempty)
|
import Data.Monoid (mempty)
|
||||||
import Data.Maybe (listToMaybe, fromMaybe)
|
import Data.Maybe (listToMaybe, fromMaybe)
|
||||||
import Yesod.Message (RenderMessage (..))
|
import Yesod.Message (RenderMessage (..))
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
import qualified Data.ByteString.Lazy as L
|
import qualified Data.Text.Encoding as TE
|
||||||
import Control.Applicative ((<$>))
|
import Control.Applicative ((<$>))
|
||||||
import Control.Arrow (first)
|
import Control.Arrow (first)
|
||||||
|
|
||||||
@ -180,16 +188,22 @@ postHelper form env = do
|
|||||||
let token =
|
let token =
|
||||||
case reqToken req of
|
case reqToken req of
|
||||||
Nothing -> mempty
|
Nothing -> mempty
|
||||||
Just n -> [shamlet|<input type=hidden name=#{tokenKey} value=#{n}>|]
|
Just n -> [shamlet|
|
||||||
|
$newline never
|
||||||
|
<input type=hidden name=#{tokenKey} value=#{n}>
|
||||||
|
|]
|
||||||
m <- getYesod
|
m <- getYesod
|
||||||
langs <- languages
|
langs <- languages
|
||||||
((res, xml), enctype) <- runFormGeneric (form token) m langs env
|
((res, xml), enctype) <- runFormGeneric (form token) m langs env
|
||||||
let res' =
|
let res' =
|
||||||
case (res, env) of
|
case (res, env) of
|
||||||
(FormSuccess{}, Just (params, _))
|
(FormSuccess{}, Just (params, _))
|
||||||
| Map.lookup tokenKey params /= fmap return (reqToken req) ->
|
| not (Map.lookup tokenKey params === reqToken req) ->
|
||||||
FormFailure [renderMessage m langs MsgCsrfWarning]
|
FormFailure [renderMessage m langs MsgCsrfWarning]
|
||||||
_ -> res
|
_ -> res
|
||||||
|
where (Just [t1]) === (Just t2) = TE.encodeUtf8 t1 `constTimeEq` TE.encodeUtf8 t2
|
||||||
|
Nothing === Nothing = True -- ^ It's important to use constTimeEq
|
||||||
|
_ === _ = False -- in order to avoid timing attacks.
|
||||||
return ((res', xml), enctype)
|
return ((res', xml), enctype)
|
||||||
|
|
||||||
-- | Similar to 'runFormPost', except it always ignore the currently available
|
-- | Similar to 'runFormPost', except it always ignore the currently available
|
||||||
@ -210,9 +224,7 @@ postEnv = do
|
|||||||
else do
|
else do
|
||||||
(p, f) <- runRequestBody
|
(p, f) <- runRequestBody
|
||||||
let p' = Map.unionsWith (++) $ map (\(x, y) -> Map.singleton x [y]) p
|
let p' = Map.unionsWith (++) $ map (\(x, y) -> Map.singleton x [y]) p
|
||||||
return $ Just (p', Map.fromList $ filter (notEmpty . snd) f)
|
return $ Just (p', Map.fromList f)
|
||||||
where
|
|
||||||
notEmpty = not . L.null . fileContent
|
|
||||||
|
|
||||||
runFormPostNoToken :: (Html -> MForm sub master (FormResult a, xml)) -> GHandler sub master ((FormResult a, xml), Enctype)
|
runFormPostNoToken :: (Html -> MForm sub master (FormResult a, xml)) -> GHandler sub master ((FormResult a, xml), Enctype)
|
||||||
runFormPostNoToken form = do
|
runFormPostNoToken form = do
|
||||||
@ -238,7 +250,10 @@ getKey = "_hasdata"
|
|||||||
|
|
||||||
getHelper :: (Html -> MForm 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 = [shamlet|<input type=hidden name=#{getKey}>|]
|
let fragment = [shamlet|
|
||||||
|
$newline never
|
||||||
|
<input type=hidden name=#{getKey}>
|
||||||
|
|]
|
||||||
langs <- languages
|
langs <- languages
|
||||||
m <- getYesod
|
m <- getYesod
|
||||||
runFormGeneric (form fragment) m langs env
|
runFormGeneric (form fragment) m langs env
|
||||||
@ -248,12 +263,13 @@ type FormRender sub master a =
|
|||||||
-> Html
|
-> Html
|
||||||
-> MForm sub master (FormResult a, GWidget sub master ())
|
-> MForm sub master (FormResult a, GWidget sub master ())
|
||||||
|
|
||||||
renderTable, renderDivs :: FormRender sub master a
|
renderTable, renderDivs, renderDivsNoLabels :: FormRender sub master a
|
||||||
renderTable aform fragment = do
|
renderTable aform fragment = do
|
||||||
(res, views') <- aFormToForm aform
|
(res, views') <- aFormToForm aform
|
||||||
let views = views' []
|
let views = views' []
|
||||||
-- FIXME non-valid HTML
|
-- FIXME non-valid HTML
|
||||||
let widget = [whamlet|
|
let widget = [whamlet|
|
||||||
|
$newline never
|
||||||
\#{fragment}
|
\#{fragment}
|
||||||
$forall view <- views
|
$forall view <- views
|
||||||
<tr :fvRequired view:.required :not $ fvRequired view:.optional>
|
<tr :fvRequired view:.required :not $ fvRequired view:.optional>
|
||||||
@ -267,14 +283,23 @@ $forall view <- views
|
|||||||
|]
|
|]
|
||||||
return (res, widget)
|
return (res, widget)
|
||||||
|
|
||||||
renderDivs aform fragment = do
|
-- | render a field inside a div
|
||||||
|
renderDivs = renderDivsMaybeLabels True
|
||||||
|
|
||||||
|
-- | render a field inside a div, not displaying any label
|
||||||
|
renderDivsNoLabels = renderDivsMaybeLabels False
|
||||||
|
|
||||||
|
renderDivsMaybeLabels :: Bool -> FormRender sub master a
|
||||||
|
renderDivsMaybeLabels withLabels aform fragment = do
|
||||||
(res, views') <- aFormToForm aform
|
(res, views') <- aFormToForm aform
|
||||||
let views = views' []
|
let views = views' []
|
||||||
let widget = [whamlet|
|
let widget = [whamlet|
|
||||||
|
$newline never
|
||||||
\#{fragment}
|
\#{fragment}
|
||||||
$forall view <- views
|
$forall view <- views
|
||||||
<div :fvRequired view:.required :not $ fvRequired view:.optional>
|
<div :fvRequired view:.required :not $ fvRequired view:.optional>
|
||||||
<label for=#{fvId view}>#{fvLabel view}
|
$if withLabels
|
||||||
|
<label for=#{fvId view}>#{fvLabel view}
|
||||||
$maybe tt <- fvTooltip view
|
$maybe tt <- fvTooltip view
|
||||||
<div .tooltip>#{tt}
|
<div .tooltip>#{tt}
|
||||||
^{fvInput view}
|
^{fvInput view}
|
||||||
@ -305,6 +330,7 @@ renderBootstrap aform fragment = do
|
|||||||
has (Just _) = True
|
has (Just _) = True
|
||||||
has Nothing = False
|
has Nothing = False
|
||||||
let widget = [whamlet|
|
let widget = [whamlet|
|
||||||
|
$newline never
|
||||||
\#{fragment}
|
\#{fragment}
|
||||||
$forall view <- views
|
$forall view <- views
|
||||||
<div .control-group .clearfix :fvRequired view:.required :not $ fvRequired view:.optional :has $ fvErrors view:.error>
|
<div .control-group .clearfix :fvRequired view:.required :not $ fvRequired view:.optional :has $ fvErrors view:.error>
|
||||||
@ -331,26 +357,64 @@ checkM :: RenderMessage master msg
|
|||||||
=> (a -> GHandler sub master (Either msg a))
|
=> (a -> GHandler sub master (Either msg a))
|
||||||
-> Field sub master a
|
-> Field sub master a
|
||||||
-> Field sub master a
|
-> Field sub master a
|
||||||
checkM f field = field
|
checkM f = checkMMap f id
|
||||||
|
|
||||||
|
-- | Same as 'checkM', but modifies the datatype.
|
||||||
|
--
|
||||||
|
-- In order to make this work, you must provide a function to convert back from
|
||||||
|
-- the new datatype to the old one (the second argument to this function).
|
||||||
|
--
|
||||||
|
-- Since 1.1.2
|
||||||
|
checkMMap :: RenderMessage master msg
|
||||||
|
=> (a -> GHandler sub master (Either msg b))
|
||||||
|
-> (b -> a)
|
||||||
|
-> Field sub master a
|
||||||
|
-> Field sub master b
|
||||||
|
checkMMap f inv field = field
|
||||||
{ fieldParse = \ts -> do
|
{ fieldParse = \ts -> do
|
||||||
e1 <- fieldParse field ts
|
e1 <- fieldParse field ts
|
||||||
case e1 of
|
case e1 of
|
||||||
Left msg -> return $ Left msg
|
Left msg -> return $ Left msg
|
||||||
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
|
||||||
|
, fieldView = \i n a eres req -> fieldView field i n a (fmap inv eres) req
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- | Deprecated synonym for 'checkMMap'.
|
||||||
|
--
|
||||||
|
-- Since 1.1.1
|
||||||
|
checkMMod :: RenderMessage master msg
|
||||||
|
=> (a -> GHandler sub master (Either msg b))
|
||||||
|
-> (b -> a)
|
||||||
|
-> Field sub master a
|
||||||
|
-> Field sub master b
|
||||||
|
checkMMod = checkMMap
|
||||||
|
{-# DEPRECATED checkMMod "Please use checkMMap instead" #-}
|
||||||
|
|
||||||
-- | Allows you to overwrite the error message on parse error.
|
-- | Allows you to overwrite the error message on parse error.
|
||||||
customErrorMessage :: SomeMessage master -> Field sub master a -> Field sub master a
|
customErrorMessage :: SomeMessage master -> Field sub master a -> Field sub master a
|
||||||
customErrorMessage msg field = field { fieldParse = \ts -> fmap (either
|
customErrorMessage msg field = field { fieldParse = \ts -> fmap (either
|
||||||
(const $ Left msg) Right) $ fieldParse field ts }
|
(const $ Left msg) Right) $ fieldParse field ts }
|
||||||
|
|
||||||
-- | Generate a 'FieldSettings' from the given label.
|
-- | Generate a 'FieldSettings' from the given label.
|
||||||
fieldSettingsLabel :: SomeMessage master -> FieldSettings master
|
fieldSettingsLabel :: RenderMessage master msg => msg -> FieldSettings master
|
||||||
fieldSettingsLabel msg = FieldSettings msg Nothing Nothing Nothing []
|
fieldSettingsLabel msg = FieldSettings (SomeMessage msg) Nothing Nothing Nothing []
|
||||||
|
|
||||||
-- | Generate an 'AForm' that gets its value from the given action.
|
-- | Generate an 'AForm' that gets its value from the given action.
|
||||||
aformM :: GHandler sub master a -> AForm sub master a
|
aformM :: GHandler sub master a -> AForm sub master a
|
||||||
aformM action = AForm $ \_ _ ints -> do
|
aformM action = AForm $ \_ _ ints -> do
|
||||||
value <- action
|
value <- action
|
||||||
return (FormSuccess value, id, ints, mempty)
|
return (FormSuccess value, id, ints, mempty)
|
||||||
|
|
||||||
|
-- | A helper function for creating custom fields.
|
||||||
|
--
|
||||||
|
-- This is intended to help with the common case where a single input value is
|
||||||
|
-- required, such as when parsing a text field.
|
||||||
|
--
|
||||||
|
-- Since 1.1
|
||||||
|
parseHelper :: (Monad m, RenderMessage master FormMessage)
|
||||||
|
=> (Text -> Either FormMessage a)
|
||||||
|
-> [Text] -> m (Either (SomeMessage master) (Maybe a))
|
||||||
|
parseHelper _ [] = return $ Right Nothing
|
||||||
|
parseHelper _ ("":_) = return $ Right Nothing
|
||||||
|
parseHelper f (x:_) = return $ either (Left . SomeMessage) (Right . Just) $ f x
|
||||||
|
|||||||
26
yesod-form/Yesod/Form/I18n/French.hs
Normal file
26
yesod-form/Yesod/Form/I18n/French.hs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
module Yesod.Form.I18n.French (frenchFormMessage) where
|
||||||
|
|
||||||
|
import Yesod.Form.Types (FormMessage (..))
|
||||||
|
import Data.Monoid (mappend)
|
||||||
|
import Data.Text (Text)
|
||||||
|
|
||||||
|
frenchFormMessage :: FormMessage -> Text
|
||||||
|
frenchFormMessage (MsgInvalidInteger t) = "Entier invalide : " `mappend` t
|
||||||
|
frenchFormMessage (MsgInvalidNumber t) = "Nombre invalide : " `mappend` t
|
||||||
|
frenchFormMessage (MsgInvalidEntry t) = "Entrée invalide : " `mappend` t
|
||||||
|
frenchFormMessage MsgInvalidTimeFormat = "Heure invalide (elle doit être au format HH:MM ou HH:MM:SS"
|
||||||
|
frenchFormMessage MsgInvalidDay = "Date invalide (elle doit être au format AAAA-MM-JJ"
|
||||||
|
frenchFormMessage (MsgInvalidUrl t) = "Adresse Internet invalide : " `mappend` t
|
||||||
|
frenchFormMessage (MsgInvalidEmail t) = "Adresse électronique invalide : " `mappend` t
|
||||||
|
frenchFormMessage (MsgInvalidHour t) = "Heure invalide : " `mappend` t
|
||||||
|
frenchFormMessage (MsgInvalidMinute t) = "Minutes invalides : " `mappend` t
|
||||||
|
frenchFormMessage (MsgInvalidSecond t) = "Secondes invalides " `mappend` t
|
||||||
|
frenchFormMessage MsgCsrfWarning = "Afin d'empêcher les attaques CSRF, veuillez ré-envoyer ce formulaire"
|
||||||
|
frenchFormMessage MsgValueRequired = "Ce champ est requis"
|
||||||
|
frenchFormMessage (MsgInputNotFound t) = "Entrée non trouvée : " `mappend` t
|
||||||
|
frenchFormMessage MsgSelectNone = "<Rien>"
|
||||||
|
frenchFormMessage (MsgInvalidBool t) = "Booléen invalide : " `mappend` t
|
||||||
|
frenchFormMessage MsgBoolYes = "Oui"
|
||||||
|
frenchFormMessage MsgBoolNo = "Non"
|
||||||
|
frenchFormMessage MsgDelete = "Détruire ?"
|
||||||
26
yesod-form/Yesod/Form/I18n/Japanese.hs
Normal file
26
yesod-form/Yesod/Form/I18n/Japanese.hs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
module Yesod.Form.I18n.Japanese where
|
||||||
|
|
||||||
|
import Yesod.Form.Types (FormMessage (..))
|
||||||
|
import Data.Monoid (mappend)
|
||||||
|
import Data.Text (Text)
|
||||||
|
|
||||||
|
japaneseFormMessage :: FormMessage -> Text
|
||||||
|
japaneseFormMessage (MsgInvalidInteger t) = "無効な整数です: " `mappend` t
|
||||||
|
japaneseFormMessage (MsgInvalidNumber t) = "無効な数値です: " `mappend` t
|
||||||
|
japaneseFormMessage (MsgInvalidEntry t) = "無効な入力です: " `mappend` t
|
||||||
|
japaneseFormMessage MsgInvalidTimeFormat = "無効な時刻です。HH:MM[:SS]フォーマットで入力してください"
|
||||||
|
japaneseFormMessage MsgInvalidDay = "無効な日付です。YYYY-MM-DDフォーマットで入力してください"
|
||||||
|
japaneseFormMessage (MsgInvalidUrl t) = "無効なURLです: " `mappend` t
|
||||||
|
japaneseFormMessage (MsgInvalidEmail t) = "無効なメールアドレスです: " `mappend` t
|
||||||
|
japaneseFormMessage (MsgInvalidHour t) = "無効な時間です: " `mappend` t
|
||||||
|
japaneseFormMessage (MsgInvalidMinute t) = "無効な分です: " `mappend` t
|
||||||
|
japaneseFormMessage (MsgInvalidSecond t) = "無効な秒です: " `mappend` t
|
||||||
|
japaneseFormMessage MsgCsrfWarning = "CSRF攻撃を防ぐため、フォームの入力を確認してください"
|
||||||
|
japaneseFormMessage MsgValueRequired = "値は必須です"
|
||||||
|
japaneseFormMessage (MsgInputNotFound t) = "入力が見つかりません: " `mappend` t
|
||||||
|
japaneseFormMessage MsgSelectNone = "<なし>"
|
||||||
|
japaneseFormMessage (MsgInvalidBool t) = "無効なbool値です: " `mappend` t
|
||||||
|
japaneseFormMessage MsgBoolYes = "はい"
|
||||||
|
japaneseFormMessage MsgBoolNo = "いいえ"
|
||||||
|
japaneseFormMessage MsgDelete = "削除しますか?"
|
||||||
26
yesod-form/Yesod/Form/I18n/Norwegian.hs
Normal file
26
yesod-form/Yesod/Form/I18n/Norwegian.hs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
module Yesod.Form.I18n.Norwegian where
|
||||||
|
|
||||||
|
import Yesod.Form.Types (FormMessage (..))
|
||||||
|
import Data.Monoid (mappend)
|
||||||
|
import Data.Text (Text)
|
||||||
|
|
||||||
|
norwegianBokmålFormMessage :: FormMessage -> Text
|
||||||
|
norwegianBokmålFormMessage (MsgInvalidInteger t) = "Ugyldig antall: " `mappend` t
|
||||||
|
norwegianBokmålFormMessage (MsgInvalidNumber t) = "Ugyldig nummer: " `mappend` t
|
||||||
|
norwegianBokmålFormMessage (MsgInvalidEntry t) = "Ugyldig oppføring: " `mappend` t
|
||||||
|
norwegianBokmålFormMessage MsgInvalidTimeFormat = "Ugyldig klokkeslett, må være i formatet HH:MM[:SS]"
|
||||||
|
norwegianBokmålFormMessage MsgInvalidDay = "Ugyldig dato, må være i formatet ÅÅÅÅ-MM-DD"
|
||||||
|
norwegianBokmålFormMessage (MsgInvalidUrl t) = "Ugyldig URL: " `mappend` t
|
||||||
|
norwegianBokmålFormMessage (MsgInvalidEmail t) = "Ugyldig e-postadresse: " `mappend` t
|
||||||
|
norwegianBokmålFormMessage (MsgInvalidHour t) = "Ugyldig time: " `mappend` t
|
||||||
|
norwegianBokmålFormMessage (MsgInvalidMinute t) = "Ugyldig minutt: " `mappend` t
|
||||||
|
norwegianBokmålFormMessage (MsgInvalidSecond t) = "Ugyldig sekund: " `mappend` t
|
||||||
|
norwegianBokmålFormMessage MsgValueRequired = "Feltet er obligatorisk"
|
||||||
|
norwegianBokmålFormMessage (MsgInputNotFound t) = "Feltet ble ikke funnet: " `mappend` t
|
||||||
|
norwegianBokmålFormMessage MsgSelectNone = "<Ingenting>"
|
||||||
|
norwegianBokmålFormMessage (MsgInvalidBool t) = "Ugyldig sannhetsverdi: " `mappend` t
|
||||||
|
norwegianBokmålFormMessage MsgBoolYes = "Ja"
|
||||||
|
norwegianBokmålFormMessage MsgBoolNo = "Nei"
|
||||||
|
norwegianBokmålFormMessage MsgDelete = "Slette?"
|
||||||
|
norwegianBokmålFormMessage MsgCsrfWarning = "Som beskyttelse mot «cross-site request forgery»-angrep, vennligst bekreft innsendt skjema."
|
||||||
@ -23,7 +23,7 @@ import Text.Hamlet (shamlet)
|
|||||||
import Text.Julius (julius)
|
import Text.Julius (julius)
|
||||||
import Data.Text (Text, pack, unpack)
|
import Data.Text (Text, pack, unpack)
|
||||||
import Data.Monoid (mconcat)
|
import Data.Monoid (mconcat)
|
||||||
import Yesod.Core (RenderMessage, SomeMessage (..))
|
import Yesod.Core (RenderMessage)
|
||||||
|
|
||||||
-- | Gets the Google hosted jQuery UI 1.8 CSS file with the given theme.
|
-- | Gets the Google hosted jQuery UI 1.8 CSS file with the given theme.
|
||||||
googleHostedJqueryUiCss :: Text -> Text
|
googleHostedJqueryUiCss :: Text -> Text
|
||||||
@ -34,9 +34,15 @@ googleHostedJqueryUiCss theme = mconcat
|
|||||||
]
|
]
|
||||||
|
|
||||||
class YesodJquery a where
|
class YesodJquery a where
|
||||||
-- | The jQuery 1.4 Javascript file.
|
-- | The jQuery Javascript file. Note that in upgrades to this library, the
|
||||||
|
-- version of jQuery referenced, or where it is downloaded from, may be
|
||||||
|
-- changed without warning. If you are relying on a specific version of
|
||||||
|
-- jQuery, you should give an explicit URL instead of relying on the
|
||||||
|
-- default value.
|
||||||
|
--
|
||||||
|
-- Currently, the default value is jQuery 1.7 from Google\'s CDN.
|
||||||
urlJqueryJs :: a -> Either (Route a) Text
|
urlJqueryJs :: a -> Either (Route a) Text
|
||||||
urlJqueryJs _ = Right "http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"
|
urlJqueryJs _ = Right "http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"
|
||||||
|
|
||||||
-- | The jQuery UI 1.8 Javascript file.
|
-- | The jQuery UI 1.8 Javascript file.
|
||||||
urlJqueryUiJs :: a -> Either (Route a) Text
|
urlJqueryUiJs :: a -> Either (Route a) Text
|
||||||
@ -50,20 +56,16 @@ class YesodJquery a where
|
|||||||
urlJqueryUiDateTimePicker :: a -> Either (Route a) Text
|
urlJqueryUiDateTimePicker :: a -> Either (Route a) Text
|
||||||
urlJqueryUiDateTimePicker _ = Right "http://github.com/gregwebs/jquery.ui.datetimepicker/raw/master/jquery.ui.datetimepicker.js"
|
urlJqueryUiDateTimePicker _ = Right "http://github.com/gregwebs/jquery.ui.datetimepicker/raw/master/jquery.ui.datetimepicker.js"
|
||||||
|
|
||||||
blank :: (RenderMessage master FormMessage, Monad m) => (Text -> Either FormMessage a) -> [Text] -> m (Either (SomeMessage master) (Maybe a))
|
|
||||||
blank _ [] = return $ Right Nothing
|
|
||||||
blank _ ("":_) = return $ Right Nothing
|
|
||||||
blank f (x:_) = return $ either (Left . SomeMessage) (Right . Just) $ f x
|
|
||||||
|
|
||||||
jqueryDayField :: (RenderMessage master FormMessage, YesodJquery master) => JqueryDaySettings -> Field sub master Day
|
jqueryDayField :: (RenderMessage master FormMessage, YesodJquery master) => JqueryDaySettings -> Field sub master Day
|
||||||
jqueryDayField jds = Field
|
jqueryDayField jds = Field
|
||||||
{ fieldParse = blank $ maybe
|
{ fieldParse = parseHelper $ maybe
|
||||||
(Left MsgInvalidDay)
|
(Left MsgInvalidDay)
|
||||||
Right
|
Right
|
||||||
. readMay
|
. readMay
|
||||||
. unpack
|
. unpack
|
||||||
, fieldView = \theId name attrs val isReq -> do
|
, fieldView = \theId name attrs val isReq -> do
|
||||||
toWidget [shamlet|
|
toWidget [shamlet|
|
||||||
|
$newline never
|
||||||
<input id="#{theId}" name="#{name}" *{attrs} type="date" :isReq:required="" value="#{showVal val}">
|
<input id="#{theId}" name="#{name}" *{attrs} type="date" :isReq:required="" value="#{showVal val}">
|
||||||
|]
|
|]
|
||||||
addScript' urlJqueryJs
|
addScript' urlJqueryJs
|
||||||
@ -71,9 +73,9 @@ jqueryDayField jds = Field
|
|||||||
addStylesheet' urlJqueryUiCss
|
addStylesheet' urlJqueryUiCss
|
||||||
toWidget [julius|
|
toWidget [julius|
|
||||||
$(function(){
|
$(function(){
|
||||||
var i = $("##{theId}");
|
var i = document.getElementById("#{theId}");
|
||||||
if (i.attr("type") != "date") {
|
if (i.type != "date") {
|
||||||
i.datepicker({
|
$(i).datepicker({
|
||||||
dateFormat:'yy-mm-dd',
|
dateFormat:'yy-mm-dd',
|
||||||
changeMonth:#{jsBool $ jdsChangeMonth jds},
|
changeMonth:#{jsBool $ jdsChangeMonth jds},
|
||||||
changeYear:#{jsBool $ jdsChangeYear jds},
|
changeYear:#{jsBool $ jdsChangeYear jds},
|
||||||
@ -100,9 +102,10 @@ $(function(){
|
|||||||
jqueryAutocompleteField :: (RenderMessage master FormMessage, YesodJquery master)
|
jqueryAutocompleteField :: (RenderMessage master FormMessage, YesodJquery master)
|
||||||
=> Route master -> Field sub master Text
|
=> Route master -> Field sub master Text
|
||||||
jqueryAutocompleteField src = Field
|
jqueryAutocompleteField src = Field
|
||||||
{ fieldParse = blank $ Right
|
{ fieldParse = parseHelper $ Right
|
||||||
, fieldView = \theId name attrs val isReq -> do
|
, fieldView = \theId name attrs val isReq -> do
|
||||||
toWidget [shamlet|
|
toWidget [shamlet|
|
||||||
|
$newline never
|
||||||
<input id="#{theId}" name="#{name}" *{attrs} type="text" :isReq:required="" value="#{either id id val}" .autocomplete>
|
<input id="#{theId}" name="#{name}" *{attrs} type="text" :isReq:required="" value="#{either id id val}" .autocomplete>
|
||||||
|]
|
|]
|
||||||
addScript' urlJqueryJs
|
addScript' urlJqueryJs
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE TypeFamilies #-}
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE CPP#-}
|
||||||
module Yesod.Form.MassInput
|
module Yesod.Form.MassInput
|
||||||
( inputList
|
( inputList
|
||||||
, massDivs
|
, massDivs
|
||||||
@ -14,7 +15,7 @@ import Yesod.Form.Fields (boolField)
|
|||||||
import Yesod.Widget (GWidget, whamlet)
|
import Yesod.Widget (GWidget, whamlet)
|
||||||
import Yesod.Message (RenderMessage)
|
import Yesod.Message (RenderMessage)
|
||||||
import Yesod.Handler (newIdent, GHandler)
|
import Yesod.Handler (newIdent, GHandler)
|
||||||
import Text.Blaze (Html)
|
import Text.Blaze.Html (Html)
|
||||||
import Control.Monad.Trans.Class (lift)
|
import Control.Monad.Trans.Class (lift)
|
||||||
import Control.Monad.Trans.RWS (get, put, ask)
|
import Control.Monad.Trans.RWS (get, put, ask)
|
||||||
import Data.Maybe (fromMaybe)
|
import Data.Maybe (fromMaybe)
|
||||||
@ -75,6 +76,7 @@ inputList label fixXml single mdef = formToAForm $ do
|
|||||||
, fvTooltip = Nothing
|
, fvTooltip = Nothing
|
||||||
, fvId = theId
|
, fvId = theId
|
||||||
, fvInput = [whamlet|
|
, fvInput = [whamlet|
|
||||||
|
$newline never
|
||||||
^{fixXml views}
|
^{fixXml views}
|
||||||
<p>
|
<p>
|
||||||
$forall xml <- xmls
|
$forall xml <- xmls
|
||||||
@ -95,7 +97,10 @@ withDelete af = do
|
|||||||
deleteName <- newFormIdent
|
deleteName <- newFormIdent
|
||||||
(menv, _, _) <- ask
|
(menv, _, _) <- ask
|
||||||
res <- case menv >>= Map.lookup deleteName . fst of
|
res <- case menv >>= Map.lookup deleteName . fst of
|
||||||
Just ("yes":_) -> return $ Left [whamlet|<input type=hidden name=#{deleteName} value=yes>|]
|
Just ("yes":_) -> return $ Left [whamlet|
|
||||||
|
$newline never
|
||||||
|
<input type=hidden name=#{deleteName} value=yes>
|
||||||
|
|]
|
||||||
_ -> do
|
_ -> do
|
||||||
(_, xml2) <- aFormToForm $ areq boolField FieldSettings
|
(_, xml2) <- aFormToForm $ areq boolField FieldSettings
|
||||||
{ fsLabel = SomeMessage MsgDelete
|
{ fsLabel = SomeMessage MsgDelete
|
||||||
@ -122,6 +127,7 @@ massDivs, massTable
|
|||||||
:: [[FieldView sub master]]
|
:: [[FieldView sub master]]
|
||||||
-> GWidget sub master ()
|
-> GWidget sub master ()
|
||||||
massDivs viewss = [whamlet|
|
massDivs viewss = [whamlet|
|
||||||
|
$newline never
|
||||||
$forall views <- viewss
|
$forall views <- viewss
|
||||||
<fieldset>
|
<fieldset>
|
||||||
$forall view <- views
|
$forall view <- views
|
||||||
@ -135,6 +141,7 @@ $forall views <- viewss
|
|||||||
|]
|
|]
|
||||||
|
|
||||||
massTable viewss = [whamlet|
|
massTable viewss = [whamlet|
|
||||||
|
$newline never
|
||||||
$forall views <- viewss
|
$forall views <- viewss
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<table>
|
<table>
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE NoMonomorphismRestriction #-} -- FIXME remove
|
{-# LANGUAGE NoMonomorphismRestriction #-} -- FIXME remove
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
-- | Provide the user with a rich text editor.
|
-- | Provide the user with a rich text editor.
|
||||||
module Yesod.Form.Nic
|
module Yesod.Form.Nic
|
||||||
( YesodNic (..)
|
( YesodNic (..)
|
||||||
@ -16,8 +17,14 @@ import Yesod.Widget
|
|||||||
import Text.HTML.SanitizeXSS (sanitizeBalance)
|
import Text.HTML.SanitizeXSS (sanitizeBalance)
|
||||||
import Text.Hamlet (Html, shamlet)
|
import Text.Hamlet (Html, shamlet)
|
||||||
import Text.Julius (julius)
|
import Text.Julius (julius)
|
||||||
import Text.Blaze.Renderer.String (renderHtml)
|
#if MIN_VERSION_blaze_html(0, 5, 0)
|
||||||
|
import Text.Blaze (preEscapedToMarkup)
|
||||||
|
import Text.Blaze.Html.Renderer.String (renderHtml)
|
||||||
|
#define preEscapedText preEscapedToMarkup
|
||||||
|
#else
|
||||||
import Text.Blaze (preEscapedText)
|
import Text.Blaze (preEscapedText)
|
||||||
|
import Text.Blaze.Renderer.String (renderHtml)
|
||||||
|
#endif
|
||||||
import Data.Text (Text, pack)
|
import Data.Text (Text, pack)
|
||||||
import Data.Maybe (listToMaybe)
|
import Data.Maybe (listToMaybe)
|
||||||
|
|
||||||
@ -31,6 +38,7 @@ nicHtmlField = Field
|
|||||||
{ fieldParse = return . Right . fmap (preEscapedText . sanitizeBalance) . listToMaybe
|
{ fieldParse = return . Right . fmap (preEscapedText . sanitizeBalance) . listToMaybe
|
||||||
, fieldView = \theId name attrs val _isReq -> do
|
, fieldView = \theId name attrs val _isReq -> do
|
||||||
toWidget [shamlet|
|
toWidget [shamlet|
|
||||||
|
$newline never
|
||||||
<textarea id="#{theId}" *{attrs} name="#{name}" .html>#{showVal val}
|
<textarea id="#{theId}" *{attrs} name="#{name}" .html>#{showVal val}
|
||||||
|]
|
|]
|
||||||
addScript' urlNicEdit
|
addScript' urlNicEdit
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE TypeFamilies #-}
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
{-# LANGUAGE ExistentialQuantification #-}
|
{-# LANGUAGE ExistentialQuantification #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
module Yesod.Form.Types
|
module Yesod.Form.Types
|
||||||
( -- * Helpers
|
( -- * Helpers
|
||||||
Enctype (..)
|
Enctype (..)
|
||||||
@ -22,11 +24,14 @@ import Control.Monad.Trans.RWS (RWST)
|
|||||||
import Yesod.Request (FileInfo)
|
import Yesod.Request (FileInfo)
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Data.Monoid (Monoid (..))
|
import Data.Monoid (Monoid (..))
|
||||||
import Text.Blaze (Html, ToHtml (toHtml))
|
import Text.Blaze (Markup, ToMarkup (toMarkup))
|
||||||
|
#define Html Markup
|
||||||
|
#define ToHtml ToMarkup
|
||||||
|
#define toHtml toMarkup
|
||||||
import Control.Applicative ((<$>), Applicative (..))
|
import Control.Applicative ((<$>), Applicative (..))
|
||||||
import Control.Monad (liftM)
|
import Control.Monad (liftM)
|
||||||
import Data.String (IsString (..))
|
import Data.String (IsString (..))
|
||||||
import Yesod.Core (GHandler, GWidget, SomeMessage)
|
import Yesod.Core (GHandler, GWidget, SomeMessage, MonadLift (..))
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
-- | A form can produce three different results: there was no data available,
|
-- | A form can produce three different results: there was no data available,
|
||||||
@ -93,6 +98,10 @@ instance Applicative (AForm sub master) where
|
|||||||
instance Monoid a => Monoid (AForm sub master a) where
|
instance Monoid a => Monoid (AForm sub master a) where
|
||||||
mempty = pure mempty
|
mempty = pure mempty
|
||||||
mappend a b = mappend <$> a <*> b
|
mappend a b = mappend <$> a <*> b
|
||||||
|
instance MonadLift (GHandler sub master) (AForm sub master) where
|
||||||
|
lift f = AForm $ \_ _ ints -> do
|
||||||
|
x <- f
|
||||||
|
return (FormSuccess x, id, ints, mempty)
|
||||||
|
|
||||||
data FieldSettings master = FieldSettings
|
data FieldSettings master = FieldSettings
|
||||||
{ fsLabel :: SomeMessage master
|
{ fsLabel :: SomeMessage master
|
||||||
@ -116,12 +125,11 @@ data FieldView sub master = FieldView
|
|||||||
|
|
||||||
data Field sub master a = Field
|
data Field sub master a = Field
|
||||||
{ fieldParse :: [Text] -> GHandler sub master (Either (SomeMessage master) (Maybe a))
|
{ fieldParse :: [Text] -> GHandler sub master (Either (SomeMessage master) (Maybe a))
|
||||||
-- | ID, name, attrs, (invalid text OR legimiate result), required?
|
, fieldView :: Text -- ^ ID
|
||||||
, fieldView :: Text
|
-> Text -- ^ Name
|
||||||
-> Text
|
-> [(Text, Text)] -- ^ Attributes
|
||||||
-> [(Text, Text)]
|
-> Either Text a -- ^ Either (invalid text) or (legitimate result)
|
||||||
-> Either Text a
|
-> Bool -- ^ Required?
|
||||||
-> Bool
|
|
||||||
-> GWidget sub master ()
|
-> GWidget sub master ()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,3 +151,4 @@ data FormMessage = MsgInvalidInteger Text
|
|||||||
| MsgBoolYes
|
| MsgBoolYes
|
||||||
| MsgBoolNo
|
| MsgBoolNo
|
||||||
| MsgDelete
|
| MsgDelete
|
||||||
|
deriving (Show, Eq, Read)
|
||||||
|
|||||||
41
yesod-form/test/main.hs
Normal file
41
yesod-form/test/main.hs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
import Test.HUnit
|
||||||
|
import Test.Hspec.Monadic
|
||||||
|
import Test.Hspec.HUnit ()
|
||||||
|
import Data.Time (TimeOfDay (TimeOfDay))
|
||||||
|
import Data.Text (pack)
|
||||||
|
|
||||||
|
import Yesod.Form.Fields (parseTime)
|
||||||
|
import Yesod.Form.Types
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = hspec $
|
||||||
|
describe "parseTime" $ mapM_ (\(s, e) -> it s $ parseTime (pack s) @?= e)
|
||||||
|
[ ("01:00:00", Right $ TimeOfDay 1 0 0)
|
||||||
|
, ("1:00", Right $ TimeOfDay 1 0 0)
|
||||||
|
, ("1:00 AM", Right $ TimeOfDay 1 0 0)
|
||||||
|
, ("1:00 am", Right $ TimeOfDay 1 0 0)
|
||||||
|
, ("1:00AM", Right $ TimeOfDay 1 0 0)
|
||||||
|
, ("1:00am", Right $ TimeOfDay 1 0 0)
|
||||||
|
, ("01:00:00am", Right $ TimeOfDay 1 0 0)
|
||||||
|
, ("01:00:00 am", Right $ TimeOfDay 1 0 0)
|
||||||
|
, ("01:00:00AM", Right $ TimeOfDay 1 0 0)
|
||||||
|
, ("01:00:00 AM", Right $ TimeOfDay 1 0 0)
|
||||||
|
, ("1:00:01", Right $ TimeOfDay 1 0 1)
|
||||||
|
, ("1:00:02 AM", Right $ TimeOfDay 1 0 2)
|
||||||
|
, ("1:00:04 am", Right $ TimeOfDay 1 0 4)
|
||||||
|
, ("1:00:05 am", Right $ read "01:00:05")
|
||||||
|
, ("1:00:64 am", Left $ MsgInvalidSecond "64")
|
||||||
|
, ("1:00:4 am", Left $ MsgInvalidSecond "4")
|
||||||
|
, ("0:00", Right $ TimeOfDay 0 0 0)
|
||||||
|
, ("12:00am", Right $ TimeOfDay 0 0 0)
|
||||||
|
, ("12:59:59am", Right $ TimeOfDay 0 59 59)
|
||||||
|
, ("12:59:60am", Left $ MsgInvalidSecond "60")
|
||||||
|
, ("12:60:59am", Left $ MsgInvalidMinute "60")
|
||||||
|
, ("12:00pm", Right $ TimeOfDay 12 0 0)
|
||||||
|
, ("12:59:59pm", Right $ TimeOfDay 12 59 59)
|
||||||
|
, ("12:59:60pm", Left $ MsgInvalidSecond "60")
|
||||||
|
, ("12:60:59pm", Left $ MsgInvalidMinute "60")
|
||||||
|
, ("12:7pm", Left $ MsgInvalidMinute "7")
|
||||||
|
, ("23:47", Right $ TimeOfDay 23 47 0)
|
||||||
|
]
|
||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-form
|
name: yesod-form
|
||||||
version: 1.0.0
|
version: 1.1.3
|
||||||
license: MIT
|
license: MIT
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Michael Snoyman <michael@snoyman.com>
|
author: Michael Snoyman <michael@snoyman.com>
|
||||||
@ -7,32 +7,36 @@ maintainer: Michael Snoyman <michael@snoyman.com>
|
|||||||
synopsis: Form handling support for Yesod Web Framework
|
synopsis: Form handling support for Yesod Web Framework
|
||||||
category: Web, Yesod
|
category: Web, Yesod
|
||||||
stability: Stable
|
stability: Stable
|
||||||
cabal-version: >= 1.6
|
cabal-version: >= 1.8
|
||||||
build-type: Simple
|
build-type: Simple
|
||||||
homepage: http://www.yesodweb.com/
|
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 >= 1.0 && < 1.1
|
, yesod-core >= 1.1 && < 1.2
|
||||||
, yesod-persistent >= 1.0 && < 1.1
|
, yesod-persistent >= 1.1 && < 1.2
|
||||||
, time >= 1.1.4
|
, time >= 1.1.4
|
||||||
, hamlet >= 1.0 && < 1.1
|
, hamlet >= 1.1 && < 1.2
|
||||||
, shakespeare-css >= 1.0 && < 1.1
|
, shakespeare-css >= 1.0 && < 1.1
|
||||||
, shakespeare-js >= 1.0 && < 1.1
|
, shakespeare-js >= 1.0 && < 1.1
|
||||||
, persistent >= 0.9 && < 0.10
|
, persistent >= 1.0 && < 1.1
|
||||||
, template-haskell
|
, template-haskell
|
||||||
, transformers >= 0.2.2 && < 0.4
|
, transformers >= 0.2.2 && < 0.4
|
||||||
, data-default >= 0.3 && < 0.4
|
, data-default
|
||||||
, xss-sanitize >= 0.3.0.1 && < 0.4
|
, xss-sanitize >= 0.3.0.1 && < 0.4
|
||||||
, blaze-builder >= 0.2.1.4 && < 0.4
|
, blaze-builder >= 0.2.1.4 && < 0.4
|
||||||
, network >= 2.2 && < 2.4
|
, network >= 2.2
|
||||||
, email-validate >= 0.2.6 && < 0.3
|
, email-validate >= 0.2.6 && < 0.3
|
||||||
, blaze-html >= 0.4.1.3 && < 0.5
|
, bytestring >= 0.9.1.4
|
||||||
, bytestring >= 0.9.1.4 && < 0.10
|
|
||||||
, text >= 0.9 && < 1.0
|
, text >= 0.9 && < 1.0
|
||||||
, wai >= 1.2 && < 1.3
|
, wai >= 1.3 && < 1.4
|
||||||
, containers >= 0.2 && < 0.5
|
, containers >= 0.2
|
||||||
|
, blaze-html >= 0.5 && < 0.6
|
||||||
|
, blaze-markup >= 0.5.1 && < 0.6
|
||||||
|
, attoparsec >= 0.10 && < 0.11
|
||||||
|
, crypto-api >= 0.8 && < 0.11
|
||||||
|
|
||||||
exposed-modules: Yesod.Form
|
exposed-modules: Yesod.Form
|
||||||
Yesod.Form.Class
|
Yesod.Form.Class
|
||||||
Yesod.Form.Types
|
Yesod.Form.Types
|
||||||
@ -46,9 +50,23 @@ library
|
|||||||
Yesod.Form.I18n.Portuguese
|
Yesod.Form.I18n.Portuguese
|
||||||
Yesod.Form.I18n.Swedish
|
Yesod.Form.I18n.Swedish
|
||||||
Yesod.Form.I18n.German
|
Yesod.Form.I18n.German
|
||||||
|
Yesod.Form.I18n.French
|
||||||
|
Yesod.Form.I18n.Norwegian
|
||||||
|
Yesod.Form.I18n.Japanese
|
||||||
-- FIXME Yesod.Helpers.Crud
|
-- FIXME Yesod.Helpers.Crud
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
|
|
||||||
|
test-suite test
|
||||||
|
type: exitcode-stdio-1.0
|
||||||
|
main-is: main.hs
|
||||||
|
hs-source-dirs: test
|
||||||
|
build-depends: base
|
||||||
|
, yesod-form
|
||||||
|
, time
|
||||||
|
, hspec
|
||||||
|
, HUnit
|
||||||
|
, text
|
||||||
|
|
||||||
source-repository head
|
source-repository head
|
||||||
type: git
|
type: git
|
||||||
location: https://github.com/yesodweb/yesod
|
location: https://github.com/yesodweb/yesod
|
||||||
|
|||||||
@ -1,25 +1,20 @@
|
|||||||
The following license covers this documentation, and the source code, except
|
Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
|
||||||
where otherwise indicated.
|
|
||||||
|
|
||||||
Copyright 2010, Michael Snoyman. All rights reserved.
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
The above copyright notice and this permission notice shall be
|
||||||
modification, are permitted provided that the following conditions are met:
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
list of conditions and the following disclaimer.
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
this list of conditions and the following disclaimer in the documentation
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
and/or other materials provided with the distribution.
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
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.
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ module Yesod.Json
|
|||||||
|
|
||||||
-- * Convenience functions
|
-- * Convenience functions
|
||||||
, jsonOrRedirect
|
, jsonOrRedirect
|
||||||
|
, acceptsJson
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Yesod.Handler (GHandler, waiRequest, lift, invalidArgs, redirect)
|
import Yesod.Handler (GHandler, waiRequest, lift, invalidArgs, redirect)
|
||||||
@ -109,7 +110,8 @@ array = J.Array . V.fromList . map J.toJSON
|
|||||||
-- | jsonOrRedirect simplifies the scenario where a POST handler sends a different
|
-- | jsonOrRedirect simplifies the scenario where a POST handler sends a different
|
||||||
-- response based on Accept headers:
|
-- response based on Accept headers:
|
||||||
--
|
--
|
||||||
-- 1. 200 with JSON data if the client prefers application/json (e.g. AJAX).
|
-- 1. 200 with JSON data if the client prefers
|
||||||
|
-- @application\/json@ (e.g. AJAX, see 'acceptsJSON').
|
||||||
--
|
--
|
||||||
-- 2. 3xx otherwise, following the PRG pattern.
|
-- 2. 3xx otherwise, following the PRG pattern.
|
||||||
jsonOrRedirect :: (Yesod master, J.ToJSON a)
|
jsonOrRedirect :: (Yesod master, J.ToJSON a)
|
||||||
@ -120,9 +122,12 @@ jsonOrRedirect r j = do
|
|||||||
q <- acceptsJson
|
q <- acceptsJson
|
||||||
if q then jsonToRepJson (J.toJSON j)
|
if q then jsonToRepJson (J.toJSON j)
|
||||||
else redirect r
|
else redirect r
|
||||||
where
|
|
||||||
acceptsJson = maybe False ((== "application/json") . B8.takeWhile (/= ';'))
|
-- | Returns @True@ if the client prefers @application\/json@ as
|
||||||
. join
|
-- indicated by the @Accept@ HTTP header.
|
||||||
. fmap (headMay . parseHttpAccept)
|
acceptsJson :: Yesod master => GHandler sub master Bool
|
||||||
. lookup "Accept" . requestHeaders
|
acceptsJson = maybe False ((== "application/json") . B8.takeWhile (/= ';'))
|
||||||
<$> waiRequest
|
. join
|
||||||
|
. fmap (headMay . parseHttpAccept)
|
||||||
|
. lookup "Accept" . requestHeaders
|
||||||
|
<$> waiRequest
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-json
|
name: yesod-json
|
||||||
version: 1.0.0
|
version: 1.1.0
|
||||||
license: MIT
|
license: MIT
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Michael Snoyman <michael@snoyman.com>
|
author: Michael Snoyman <michael@snoyman.com>
|
||||||
@ -14,20 +14,20 @@ description: Generate content for Yesod using the aeson package.
|
|||||||
|
|
||||||
library
|
library
|
||||||
build-depends: base >= 4 && < 5
|
build-depends: base >= 4 && < 5
|
||||||
, yesod-core >= 1.0 && < 1.1
|
, yesod-core >= 1.1 && < 1.2
|
||||||
, yesod-routes >= 1.0 && < 1.1
|
, yesod-routes >= 1.1 && < 1.2
|
||||||
, aeson >= 0.5
|
, aeson >= 0.5
|
||||||
, text >= 0.8 && < 1.0
|
, text >= 0.8 && < 1.0
|
||||||
, shakespeare-js >= 1.0 && < 1.1
|
, shakespeare-js >= 1.0 && < 1.1
|
||||||
, vector >= 0.9
|
, vector >= 0.9
|
||||||
, containers >= 0.2
|
, containers >= 0.2
|
||||||
, blaze-builder
|
, blaze-builder
|
||||||
, attoparsec-conduit >= 0.4 && < 0.5
|
, attoparsec-conduit >= 0.5 && < 0.6
|
||||||
, conduit >= 0.4 && < 0.5
|
, conduit >= 0.5 && < 0.6
|
||||||
, transformers >= 0.2.2 && < 0.4
|
, transformers >= 0.2.2 && < 0.4
|
||||||
, wai >= 1.2 && < 1.3
|
, wai >= 1.3 && < 1.4
|
||||||
, wai-extra >= 1.2 && < 1.3
|
, wai-extra >= 1.3 && < 1.4
|
||||||
, bytestring >= 0.9 && < 0.10
|
, bytestring >= 0.9
|
||||||
, safe >= 0.2 && < 0.4
|
, safe >= 0.2 && < 0.4
|
||||||
exposed-modules: Yesod.Json
|
exposed-modules: Yesod.Json
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
|
|||||||
@ -1,25 +1,20 @@
|
|||||||
The following license covers this documentation, and the source code, except
|
Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
|
||||||
where otherwise indicated.
|
|
||||||
|
|
||||||
Copyright 2010, Michael Snoyman. All rights reserved.
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
The above copyright notice and this permission notice shall be
|
||||||
modification, are permitted provided that the following conditions are met:
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
list of conditions and the following disclaimer.
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
this list of conditions and the following disclaimer in the documentation
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
and/or other materials provided with the distribution.
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
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.
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
{-# LANGUAGE QuasiQuotes #-}
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
---------------------------------------------------------
|
---------------------------------------------------------
|
||||||
--
|
--
|
||||||
-- Module : Yesod.AtomFeed
|
-- Module : Yesod.AtomFeed
|
||||||
@ -30,7 +31,8 @@ import qualified Data.ByteString.Char8 as S8
|
|||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Data.Text.Lazy (toStrict)
|
import Data.Text.Lazy (toStrict)
|
||||||
import Text.XML
|
import Text.XML
|
||||||
import Text.Blaze.Renderer.Text (renderHtml)
|
import Text.Blaze.Html.Renderer.Text (renderHtml)
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
newtype RepAtom = RepAtom Content
|
newtype RepAtom = RepAtom Content
|
||||||
instance HasReps RepAtom where
|
instance HasReps RepAtom where
|
||||||
@ -50,21 +52,22 @@ template Feed {..} render =
|
|||||||
addNS' n = n
|
addNS' n = n
|
||||||
namespace = "http://www.w3.org/2005/Atom"
|
namespace = "http://www.w3.org/2005/Atom"
|
||||||
|
|
||||||
root = Element "feed" [] $ map NodeElement
|
root = Element "feed" Map.empty $ map NodeElement
|
||||||
$ Element "title" [] [NodeContent feedTitle]
|
$ Element "title" Map.empty [NodeContent feedTitle]
|
||||||
: Element "link" [("rel", "self"), ("href", render feedLinkSelf)] []
|
: Element "link" (Map.fromList [("rel", "self"), ("href", render feedLinkSelf)]) []
|
||||||
: Element "link" [("href", render feedLinkHome)] []
|
: Element "link" (Map.singleton "href" $ render feedLinkHome) []
|
||||||
: Element "updated" [] [NodeContent $ formatW3 feedUpdated]
|
: Element "updated" Map.empty [NodeContent $ formatW3 feedUpdated]
|
||||||
: Element "id" [] [NodeContent $ render feedLinkHome]
|
: Element "id" Map.empty [NodeContent $ render feedLinkHome]
|
||||||
|
: Element "author" Map.empty [NodeContent feedAuthor]
|
||||||
: map (flip entryTemplate render) feedEntries
|
: map (flip entryTemplate render) feedEntries
|
||||||
|
|
||||||
entryTemplate :: FeedEntry url -> (url -> Text) -> Element
|
entryTemplate :: FeedEntry url -> (url -> Text) -> Element
|
||||||
entryTemplate FeedEntry {..} render = Element "entry" [] $ map NodeElement
|
entryTemplate FeedEntry {..} render = Element "entry" Map.empty $ map NodeElement
|
||||||
[ Element "id" [] [NodeContent $ render feedEntryLink]
|
[ Element "id" Map.empty [NodeContent $ render feedEntryLink]
|
||||||
, Element "link" [("href", render feedEntryLink)] []
|
, Element "link" (Map.singleton "href" $ render feedEntryLink) []
|
||||||
, Element "updated" [] [NodeContent $ formatW3 feedEntryUpdated]
|
, Element "updated" Map.empty [NodeContent $ formatW3 feedEntryUpdated]
|
||||||
, Element "title" [] [NodeContent feedEntryTitle]
|
, Element "title" Map.empty [NodeContent feedEntryTitle]
|
||||||
, Element "content" [("type", "html")] [NodeContent $ toStrict $ renderHtml feedEntryContent]
|
, Element "content" (Map.singleton "type" "html") [NodeContent $ toStrict $ renderHtml feedEntryContent]
|
||||||
]
|
]
|
||||||
|
|
||||||
-- | Generates a link tag in the head of a widget.
|
-- | Generates a link tag in the head of a widget.
|
||||||
@ -72,5 +75,6 @@ atomLink :: Route m
|
|||||||
-> Text -- ^ title
|
-> Text -- ^ title
|
||||||
-> GWidget s m ()
|
-> GWidget s m ()
|
||||||
atomLink r title = toWidgetHead [hamlet|
|
atomLink r title = toWidgetHead [hamlet|
|
||||||
|
$newline never
|
||||||
<link href=@{r} type=#{S8.unpack typeAtom} rel="alternate" title=#{title}>
|
<link href=@{r} type=#{S8.unpack typeAtom} rel="alternate" title=#{title}>
|
||||||
|]
|
|]
|
||||||
|
|||||||
@ -7,11 +7,12 @@ import Text.Hamlet (Html)
|
|||||||
import Data.Time.Clock (UTCTime)
|
import Data.Time.Clock (UTCTime)
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
|
|
||||||
-- | The overal feed
|
-- | The overall feed
|
||||||
data Feed url = Feed
|
data Feed url = Feed
|
||||||
{ feedTitle :: Text
|
{ feedTitle :: Text
|
||||||
, feedLinkSelf :: url
|
, feedLinkSelf :: url
|
||||||
, feedLinkHome :: url
|
, feedLinkHome :: url
|
||||||
|
, feedAuthor :: Text
|
||||||
|
|
||||||
|
|
||||||
-- | note: currently only used for Rss
|
-- | note: currently only used for Rss
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
{-# LANGUAGE QuasiQuotes #-}
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
--
|
--
|
||||||
-- Module : Yesod.RssFeed
|
-- Module : Yesod.RssFeed
|
||||||
@ -26,7 +27,8 @@ import qualified Data.ByteString.Char8 as S8
|
|||||||
import Data.Text (Text, pack)
|
import Data.Text (Text, pack)
|
||||||
import Data.Text.Lazy (toStrict)
|
import Data.Text.Lazy (toStrict)
|
||||||
import Text.XML
|
import Text.XML
|
||||||
import Text.Blaze.Renderer.Text (renderHtml)
|
import Text.Blaze.Html.Renderer.Text (renderHtml)
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
newtype RepRss = RepRss Content
|
newtype RepRss = RepRss Content
|
||||||
instance HasReps RepRss where
|
instance HasReps RepRss where
|
||||||
@ -42,26 +44,26 @@ template :: Feed url -> (url -> Text) -> Document
|
|||||||
template Feed {..} render =
|
template Feed {..} render =
|
||||||
Document (Prologue [] Nothing []) root []
|
Document (Prologue [] Nothing []) root []
|
||||||
where
|
where
|
||||||
root = Element "rss" [("version", "2.0")] $ return $ NodeElement $ Element "channel" [] $ map NodeElement
|
root = Element "rss" (Map.singleton "version" "2.0") $ return $ NodeElement $ Element "channel" Map.empty $ map NodeElement
|
||||||
$ Element "{http://www.w3.org/2005/Atom}link"
|
$ Element "{http://www.w3.org/2005/Atom}link" (Map.fromList
|
||||||
[ ("href", render feedLinkSelf)
|
[ ("href", render feedLinkSelf)
|
||||||
, ("rel", "self")
|
, ("rel", "self")
|
||||||
, ("type", pack $ S8.unpack typeRss)
|
, ("type", pack $ S8.unpack typeRss)
|
||||||
] []
|
]) []
|
||||||
: Element "title" [] [NodeContent feedTitle]
|
: Element "title" Map.empty [NodeContent feedTitle]
|
||||||
: Element "link" [] [NodeContent $ render feedLinkHome]
|
: Element "link" Map.empty [NodeContent $ render feedLinkHome]
|
||||||
: Element "description" [] [NodeContent $ toStrict $ renderHtml feedDescription]
|
: Element "description" Map.empty [NodeContent $ toStrict $ renderHtml feedDescription]
|
||||||
: Element "lastBuildDate" [] [NodeContent $ formatRFC822 feedUpdated]
|
: Element "lastBuildDate" Map.empty [NodeContent $ formatRFC822 feedUpdated]
|
||||||
: Element "language" [] [NodeContent feedLanguage]
|
: Element "language" Map.empty [NodeContent feedLanguage]
|
||||||
: map (flip entryTemplate render) feedEntries
|
: map (flip entryTemplate render) feedEntries
|
||||||
|
|
||||||
entryTemplate :: FeedEntry url -> (url -> Text) -> Element
|
entryTemplate :: FeedEntry url -> (url -> Text) -> Element
|
||||||
entryTemplate FeedEntry {..} render = Element "item" [] $ map NodeElement
|
entryTemplate FeedEntry {..} render = Element "item" Map.empty $ map NodeElement
|
||||||
[ Element "title" [] [NodeContent feedEntryTitle]
|
[ Element "title" Map.empty [NodeContent feedEntryTitle]
|
||||||
, Element "link" [] [NodeContent $ render feedEntryLink]
|
, Element "link" Map.empty [NodeContent $ render feedEntryLink]
|
||||||
, Element "guid" [] [NodeContent $ render feedEntryLink]
|
, Element "guid" Map.empty [NodeContent $ render feedEntryLink]
|
||||||
, Element "pubDate" [] [NodeContent $ formatRFC822 feedEntryUpdated]
|
, Element "pubDate" Map.empty [NodeContent $ formatRFC822 feedEntryUpdated]
|
||||||
, Element "description" [] [NodeContent $ toStrict $ renderHtml feedEntryContent]
|
, Element "description" Map.empty [NodeContent $ toStrict $ renderHtml feedEntryContent]
|
||||||
]
|
]
|
||||||
|
|
||||||
-- | Generates a link tag in the head of a widget.
|
-- | Generates a link tag in the head of a widget.
|
||||||
@ -69,5 +71,6 @@ rssLink :: Route m
|
|||||||
-> Text -- ^ title
|
-> Text -- ^ title
|
||||||
-> GWidget s m ()
|
-> GWidget s m ()
|
||||||
rssLink r title = toWidgetHead [hamlet|
|
rssLink r title = toWidgetHead [hamlet|
|
||||||
|
$newline never
|
||||||
<link href=@{r} type=#{S8.unpack typeRss} rel="alternate" title=#{title}>
|
<link href=@{r} type=#{S8.unpack typeRss} rel="alternate" title=#{title}>
|
||||||
|]
|
|]
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-newsfeed
|
name: yesod-newsfeed
|
||||||
version: 1.0.0
|
version: 1.1.0
|
||||||
license: MIT
|
license: MIT
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Michael Snoyman, Patrick Brisbin
|
author: Michael Snoyman, Patrick Brisbin
|
||||||
@ -14,13 +14,16 @@ description: Helper functions and data types for producing News feeds.
|
|||||||
|
|
||||||
library
|
library
|
||||||
build-depends: base >= 4 && < 5
|
build-depends: base >= 4 && < 5
|
||||||
, yesod-core >= 1.0 && < 1.1
|
, yesod-core >= 1.1 && < 1.2
|
||||||
, time >= 1.1.4
|
, time >= 1.1.4
|
||||||
, hamlet >= 1.0 && < 1.1
|
, hamlet >= 1.1 && < 1.2
|
||||||
, bytestring >= 0.9.1.4 && < 0.10
|
, bytestring >= 0.9.1.4
|
||||||
, text >= 0.9 && < 0.12
|
, text >= 0.9 && < 0.12
|
||||||
, xml-conduit >= 0.7 && < 0.8
|
, xml-conduit >= 1.0 && < 1.1
|
||||||
, blaze-html >= 0.4 && < 0.5
|
, blaze-html >= 0.5 && < 0.6
|
||||||
|
, blaze-markup >= 0.5.1 && < 0.6
|
||||||
|
, containers
|
||||||
|
|
||||||
exposed-modules: Yesod.AtomFeed
|
exposed-modules: Yesod.AtomFeed
|
||||||
, Yesod.RssFeed
|
, Yesod.RssFeed
|
||||||
, Yesod.Feed
|
, Yesod.Feed
|
||||||
|
|||||||
@ -1,25 +1,20 @@
|
|||||||
The following license covers this documentation, and the source code, except
|
Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
|
||||||
where otherwise indicated.
|
|
||||||
|
|
||||||
Copyright 2010, Michael Snoyman. All rights reserved.
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
The above copyright notice and this permission notice shall be
|
||||||
modification, are permitted provided that the following conditions are met:
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
list of conditions and the following disclaimer.
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
this list of conditions and the following disclaimer in the documentation
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
and/or other materials provided with the distribution.
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
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.
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-persistent
|
name: yesod-persistent
|
||||||
version: 1.0.0
|
version: 1.1.0
|
||||||
license: MIT
|
license: MIT
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Michael Snoyman <michael@snoyman.com>
|
author: Michael Snoyman <michael@snoyman.com>
|
||||||
@ -14,9 +14,9 @@ description: Some helpers for using Persistent from Yesod.
|
|||||||
|
|
||||||
library
|
library
|
||||||
build-depends: base >= 4 && < 5
|
build-depends: base >= 4 && < 5
|
||||||
, yesod-core >= 1.0 && < 1.1
|
, yesod-core >= 1.1 && < 1.2
|
||||||
, persistent >= 0.9 && < 0.10
|
, persistent >= 1.0 && < 1.1
|
||||||
, persistent-template >= 0.9 && < 0.10
|
, persistent-template >= 1.0 && < 1.1
|
||||||
, transformers >= 0.2.2 && < 0.4
|
, transformers >= 0.2.2 && < 0.4
|
||||||
exposed-modules: Yesod.Persist
|
exposed-modules: Yesod.Persist
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
|
|||||||
@ -1,25 +1,20 @@
|
|||||||
The following license covers this documentation, and the source code, except
|
Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
|
||||||
where otherwise indicated.
|
|
||||||
|
|
||||||
Copyright 2010, Michael Snoyman. All rights reserved.
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
The above copyright notice and this permission notice shall be
|
||||||
modification, are permitted provided that the following conditions are met:
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
list of conditions and the following disclaimer.
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
this list of conditions and the following disclaimer in the documentation
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
and/or other materials provided with the distribution.
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
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,4 +7,4 @@ then
|
|||||||
cabal install cabal-nirvana -fgenerate
|
cabal install cabal-nirvana -fgenerate
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cabal-nirvana-generate yesod | runghc to-cabal.hs > yesod-platform.cabal
|
cabal-nirvana-generate yesod yesod-static yesod-default hjsmin blaze-html yesod-test | runghc to-cabal.hs > yesod-platform.cabal
|
||||||
|
|||||||
@ -3,14 +3,14 @@ import Control.Applicative ((<$>))
|
|||||||
|
|
||||||
main = do
|
main = do
|
||||||
pkgs <- map (intercalate " == ")
|
pkgs <- map (intercalate " == ")
|
||||||
. filter (\xs -> not $ ["parsec"] `isPrefixOf` xs)
|
. filter (\xs -> not $ any (`isPrefixOf` xs) $ map return ["parsec", "text", "transformers", "mtl", "HUnit", "QuickCheck", "binary", "zlib", "stm", "regex-compat"])
|
||||||
. map words
|
. map words
|
||||||
. filter (not . null)
|
. filter (not . null)
|
||||||
. lines
|
. lines
|
||||||
<$> getContents
|
<$> getContents
|
||||||
putStrLn "name: yesod-platform"
|
putStrLn "name: yesod-platform"
|
||||||
putStrLn "version: FIXME"
|
putStrLn "version: FIXME"
|
||||||
putStrLn "license: BSD3"
|
putStrLn "license: MIT"
|
||||||
putStrLn "license-file: LICENSE"
|
putStrLn "license-file: LICENSE"
|
||||||
putStrLn "author: Michael Snoyman <michael@snoyman.com>"
|
putStrLn "author: Michael Snoyman <michael@snoyman.com>"
|
||||||
putStrLn "maintainer: Michael Snoyman <michael@snoyman.com>"
|
putStrLn "maintainer: Michael Snoyman <michael@snoyman.com>"
|
||||||
|
|||||||
137
yesod-platform/yesod-platform.cabal
Normal file
137
yesod-platform/yesod-platform.cabal
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
name: yesod-platform
|
||||||
|
version: 1.1.3
|
||||||
|
license: MIT
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Michael Snoyman <michael@snoyman.com>
|
||||||
|
maintainer: Michael Snoyman <michael@snoyman.com>
|
||||||
|
synopsis: Meta package for Yesod
|
||||||
|
description: Instead of allowing version ranges of dependencies, this package requires specific versions to avoid dependency hell
|
||||||
|
category: Web, Yesod
|
||||||
|
stability: Stable
|
||||||
|
cabal-version: >= 1.6
|
||||||
|
build-type: Simple
|
||||||
|
homepage: http://www.yesodweb.com/
|
||||||
|
|
||||||
|
library
|
||||||
|
build-depends: base >= 4 && < 5
|
||||||
|
, SHA == 1.5.1
|
||||||
|
, aeson == 0.6.0.2
|
||||||
|
, ansi-terminal == 0.5.5
|
||||||
|
, asn1-data == 0.7.1
|
||||||
|
, attoparsec == 0.10.2.0
|
||||||
|
, attoparsec-conduit == 0.5.0.2
|
||||||
|
, authenticate == 1.3.1.1
|
||||||
|
, base-unicode-symbols == 0.2.2.4
|
||||||
|
, base64-bytestring == 1.0.0.0
|
||||||
|
, blaze-builder == 0.3.1.0
|
||||||
|
, blaze-builder-conduit == 0.5.0.1
|
||||||
|
, blaze-html == 0.5.1.0
|
||||||
|
, blaze-markup == 0.5.1.1
|
||||||
|
, byteorder == 1.0.3
|
||||||
|
, case-insensitive == 0.4.0.3
|
||||||
|
, cereal == 0.3.5.2
|
||||||
|
, certificate == 1.2.8
|
||||||
|
, clientsession == 0.8.0.1
|
||||||
|
, conduit == 0.5.2.4
|
||||||
|
, cookie == 0.4.0.1
|
||||||
|
, cprng-aes == 0.2.4
|
||||||
|
, cpu == 0.1.1
|
||||||
|
, crypto-api == 0.10.2
|
||||||
|
, crypto-conduit == 0.4.0.1
|
||||||
|
, crypto-pubkey-types == 0.1.1
|
||||||
|
, cryptocipher == 0.3.5
|
||||||
|
, cryptohash == 0.7.5
|
||||||
|
, css-text == 0.1.1
|
||||||
|
, data-default == 0.5.0
|
||||||
|
, date-cache == 0.3.0
|
||||||
|
, dlist == 0.5
|
||||||
|
, email-validate == 0.2.8
|
||||||
|
, entropy == 0.2.1
|
||||||
|
, failure == 0.2.0.1
|
||||||
|
, fast-logger == 0.3.1
|
||||||
|
, file-embed == 0.0.4.5
|
||||||
|
, filesystem-conduit == 0.5.0.1
|
||||||
|
, hamlet == 1.1.1
|
||||||
|
, hashable == 1.1.2.5
|
||||||
|
, hjsmin == 0.1.2
|
||||||
|
, hspec == 1.3.0
|
||||||
|
, hspec-expectations == 0.3.0.2
|
||||||
|
, html-conduit == 0.1.0.2
|
||||||
|
, http-conduit == 1.6.1
|
||||||
|
, http-date == 0.0.2
|
||||||
|
, http-types == 0.7.3.0.1
|
||||||
|
, language-javascript == 0.5.4
|
||||||
|
, largeword == 1.0.3
|
||||||
|
, lifted-base == 0.1.2
|
||||||
|
, mime-mail == 0.4.1.2
|
||||||
|
, mime-types == 0.1.0.0
|
||||||
|
, monad-control == 0.3.1.4
|
||||||
|
, monad-logger == 0.2.1
|
||||||
|
, network-conduit == 0.5.0.2
|
||||||
|
, path-pieces == 0.1.2
|
||||||
|
, pem == 0.1.1
|
||||||
|
, persistent == 1.0.1.2
|
||||||
|
, persistent-template == 1.0.0.2
|
||||||
|
, pool-conduit == 0.1.0.3
|
||||||
|
, primitive == 0.4.1
|
||||||
|
, pureMD5 == 2.1.2.1
|
||||||
|
, pwstore-fast == 2.3
|
||||||
|
, ranges == 0.2.4
|
||||||
|
, resource-pool == 0.2.1.1
|
||||||
|
, resourcet == 0.4.0.1
|
||||||
|
, safe == 0.3.3
|
||||||
|
, semigroups == 0.8.4.1
|
||||||
|
, shakespeare == 1.0.1.4
|
||||||
|
, shakespeare-css == 1.0.1.5
|
||||||
|
, shakespeare-i18n == 1.0.0.2
|
||||||
|
, shakespeare-js == 1.0.0.6
|
||||||
|
, shakespeare-text == 1.0.0.5
|
||||||
|
, silently == 1.2.0.2
|
||||||
|
, simple-sendfile == 0.2.7
|
||||||
|
, skein == 0.1.0.9
|
||||||
|
, socks == 0.4.2
|
||||||
|
, stringsearch == 0.3.6.3
|
||||||
|
, system-fileio == 0.3.10
|
||||||
|
, system-filepath == 0.4.7
|
||||||
|
, tagged == 0.4.4
|
||||||
|
, tagsoup == 0.12.8
|
||||||
|
, tagstream-conduit == 0.5.3
|
||||||
|
, tar == 0.4.0.0
|
||||||
|
, tls == 0.9.11
|
||||||
|
, tls-extra == 0.4.6
|
||||||
|
, transformers-base == 0.4.1
|
||||||
|
, unix-compat == 0.3.0.2
|
||||||
|
, unordered-containers == 0.2.2.1
|
||||||
|
, utf8-light == 0.4.0.1
|
||||||
|
, utf8-string == 0.3.7
|
||||||
|
, vault == 0.2.0.1
|
||||||
|
, vector == 0.9.1
|
||||||
|
, void == 0.5.8
|
||||||
|
, wai == 1.3.0.1
|
||||||
|
, wai-app-static == 1.3.0.2
|
||||||
|
, wai-extra == 1.3.0.2
|
||||||
|
, wai-logger == 0.3.0
|
||||||
|
, wai-test == 1.3.0
|
||||||
|
, warp == 1.3.2
|
||||||
|
, xml-conduit == 1.0.3.1
|
||||||
|
, xml-types == 0.3.3
|
||||||
|
, xss-sanitize == 0.3.2
|
||||||
|
, yaml == 0.8.0.2
|
||||||
|
, yesod == 1.1.1
|
||||||
|
, yesod-auth == 1.1.1.1
|
||||||
|
, yesod-core == 1.1.2
|
||||||
|
, yesod-default == 1.1.0
|
||||||
|
, yesod-form == 1.1.3
|
||||||
|
, yesod-json == 1.1.0
|
||||||
|
, yesod-persistent == 1.1.0
|
||||||
|
, yesod-routes == 1.1.0
|
||||||
|
, yesod-static == 1.1.0.1
|
||||||
|
, yesod-test == 0.3.0.1
|
||||||
|
, zlib-bindings == 0.1.1.1
|
||||||
|
, zlib-conduit == 0.5.0.1
|
||||||
|
|
||||||
|
exposed-modules: Yesod.Platform
|
||||||
|
|
||||||
|
source-repository head
|
||||||
|
type: git
|
||||||
|
location: https://github.com/yesodweb/yesod
|
||||||
@ -1,25 +1,20 @@
|
|||||||
The following license covers this documentation, and the source code, except
|
Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
|
||||||
where otherwise indicated.
|
|
||||||
|
|
||||||
Copyright 2010, Michael Snoyman. All rights reserved.
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
The above copyright notice and this permission notice shall be
|
||||||
modification, are permitted provided that the following conditions are met:
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
list of conditions and the following disclaimer.
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
this list of conditions and the following disclaimer in the documentation
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
and/or other materials provided with the distribution.
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
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.
|
|
||||||
|
|||||||
@ -2,27 +2,41 @@
|
|||||||
module Yesod.Routes.Overlap
|
module Yesod.Routes.Overlap
|
||||||
( findOverlaps
|
( findOverlaps
|
||||||
, findOverlapNames
|
, findOverlapNames
|
||||||
|
, Overlap (..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Yesod.Routes.TH.Types
|
import Yesod.Routes.TH.Types
|
||||||
import Control.Arrow ((***))
|
import Data.List (intercalate)
|
||||||
import Data.Maybe (mapMaybe)
|
|
||||||
|
|
||||||
findOverlaps :: [Resource t] -> [(Resource t, Resource t)]
|
data Overlap t = Overlap
|
||||||
findOverlaps [] = []
|
{ overlapParents :: [String] -> [String] -- ^ parent resource trees
|
||||||
findOverlaps (x:xs) = mapMaybe (findOverlap x) xs ++ findOverlaps xs
|
, overlap1 :: ResourceTree t
|
||||||
|
, overlap2 :: ResourceTree t
|
||||||
|
}
|
||||||
|
|
||||||
findOverlap :: Resource t -> Resource t -> Maybe (Resource t, Resource t)
|
findOverlaps :: ([String] -> [String]) -> [ResourceTree t] -> [Overlap t]
|
||||||
findOverlap x y
|
findOverlaps _ [] = []
|
||||||
| overlaps (resourcePieces x) (resourcePieces y) (hasSuffix x) (hasSuffix y) = Just (x, y)
|
findOverlaps front (x:xs) = concatMap (findOverlap front x) xs ++ findOverlaps front xs
|
||||||
| otherwise = Nothing
|
|
||||||
|
|
||||||
hasSuffix :: Resource t -> Bool
|
findOverlap :: ([String] -> [String]) -> ResourceTree t -> ResourceTree t -> [Overlap t]
|
||||||
hasSuffix r =
|
findOverlap front x y =
|
||||||
|
here rest
|
||||||
|
where
|
||||||
|
here
|
||||||
|
| overlaps (resourceTreePieces x) (resourceTreePieces y) (hasSuffix x) (hasSuffix y) = (Overlap front x y:)
|
||||||
|
| otherwise = id
|
||||||
|
rest =
|
||||||
|
case x of
|
||||||
|
ResourceParent name _ children -> findOverlaps (front . (name:)) children
|
||||||
|
ResourceLeaf{} -> []
|
||||||
|
|
||||||
|
hasSuffix :: ResourceTree t -> Bool
|
||||||
|
hasSuffix (ResourceLeaf r) =
|
||||||
case resourceDispatch r of
|
case resourceDispatch r of
|
||||||
Subsite{} -> True
|
Subsite{} -> True
|
||||||
Methods Just{} _ -> True
|
Methods Just{} _ -> True
|
||||||
Methods Nothing _ -> False
|
Methods Nothing _ -> False
|
||||||
|
hasSuffix ResourceParent{} = True
|
||||||
|
|
||||||
overlaps :: [(CheckOverlap, Piece t)] -> [(CheckOverlap, Piece t)] -> Bool -> Bool -> Bool
|
overlaps :: [(CheckOverlap, Piece t)] -> [(CheckOverlap, Piece t)] -> Bool -> Bool -> Bool
|
||||||
|
|
||||||
@ -50,9 +64,14 @@ piecesOverlap :: Piece t -> Piece t -> Bool
|
|||||||
piecesOverlap (Static x) (Static y) = x == y
|
piecesOverlap (Static x) (Static y) = x == y
|
||||||
piecesOverlap _ _ = True
|
piecesOverlap _ _ = True
|
||||||
|
|
||||||
findOverlapNames :: [Resource t] -> [(String, String)]
|
findOverlapNames :: [ResourceTree t] -> [(String, String)]
|
||||||
findOverlapNames = map (resourceName *** resourceName) . findOverlaps
|
findOverlapNames =
|
||||||
|
map go . findOverlaps id
|
||||||
|
where
|
||||||
|
go (Overlap front x y) =
|
||||||
|
(go' $ resourceTreeName x, go' $ resourceTreeName y)
|
||||||
|
where
|
||||||
|
go' = intercalate "/" . front . return
|
||||||
{-
|
{-
|
||||||
-- n^2, should be a way to speed it up
|
-- n^2, should be a way to speed it up
|
||||||
findOverlaps :: [Resource a] -> [[Resource a]]
|
findOverlaps :: [Resource a] -> [[Resource a]]
|
||||||
|
|||||||
@ -10,7 +10,6 @@ module Yesod.Routes.Parse
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import Language.Haskell.TH.Syntax
|
import Language.Haskell.TH.Syntax
|
||||||
import Data.Maybe
|
|
||||||
import Data.Char (isUpper)
|
import Data.Char (isUpper)
|
||||||
import Language.Haskell.TH.Quote
|
import Language.Haskell.TH.Quote
|
||||||
import qualified System.IO as SIO
|
import qualified System.IO as SIO
|
||||||
@ -55,18 +54,29 @@ parseRoutesNoCheck = QuasiQuoter
|
|||||||
-- | Convert a multi-line string to a set of resources. See documentation for
|
-- | Convert a multi-line string to a set of resources. See documentation for
|
||||||
-- the format of this string. This is a partial function which calls 'error' on
|
-- the format of this string. This is a partial function which calls 'error' on
|
||||||
-- invalid input.
|
-- invalid input.
|
||||||
resourcesFromString :: String -> [Resource String]
|
resourcesFromString :: String -> [ResourceTree String]
|
||||||
resourcesFromString =
|
resourcesFromString =
|
||||||
mapMaybe go . lines
|
fst . parse 0 . lines
|
||||||
where
|
where
|
||||||
go s =
|
parse _ [] = ([], [])
|
||||||
case takeWhile (/= "--") $ words s of
|
parse indent (thisLine:otherLines)
|
||||||
(pattern:constr:rest) ->
|
| length spaces < indent = ([], thisLine : otherLines)
|
||||||
let (pieces, mmulti) = piecesFromString $ drop1Slash pattern
|
| otherwise = (this others, remainder)
|
||||||
disp = dispatchFromString rest mmulti
|
where
|
||||||
in Just $ Resource constr pieces disp
|
spaces = takeWhile (== ' ') thisLine
|
||||||
[] -> Nothing
|
(others, remainder) = parse indent otherLines'
|
||||||
_ -> error $ "Invalid resource line: " ++ s
|
(this, otherLines') =
|
||||||
|
case takeWhile (/= "--") $ words thisLine of
|
||||||
|
[pattern, constr] | last constr == ':' ->
|
||||||
|
let (children, otherLines'') = parse (length spaces + 1) otherLines
|
||||||
|
(pieces, Nothing) = piecesFromString $ drop1Slash pattern
|
||||||
|
in ((ResourceParent (init constr) pieces children :), otherLines'')
|
||||||
|
(pattern:constr:rest) ->
|
||||||
|
let (pieces, mmulti) = piecesFromString $ drop1Slash pattern
|
||||||
|
disp = dispatchFromString rest mmulti
|
||||||
|
in ((ResourceLeaf (Resource constr pieces disp):), otherLines)
|
||||||
|
[] -> (id, otherLines)
|
||||||
|
_ -> error $ "Invalid resource line: " ++ thisLine
|
||||||
|
|
||||||
dispatchFromString :: [String] -> Maybe String -> Dispatch String
|
dispatchFromString :: [String] -> Maybe String -> Dispatch String
|
||||||
dispatchFromString rest mmulti
|
dispatchFromString rest mmulti
|
||||||
|
|||||||
@ -17,6 +17,16 @@ import Web.PathPieces (PathPiece (..), PathMultiPiece (..))
|
|||||||
import Control.Applicative ((<$>))
|
import Control.Applicative ((<$>))
|
||||||
import Data.List (foldl')
|
import Data.List (foldl')
|
||||||
|
|
||||||
|
data FlatResource a = FlatResource [(String, [(CheckOverlap, Piece a)])] String [(CheckOverlap, Piece a)] (Dispatch a)
|
||||||
|
|
||||||
|
flatten :: [ResourceTree a] -> [FlatResource a]
|
||||||
|
flatten =
|
||||||
|
concatMap (go id)
|
||||||
|
where
|
||||||
|
go front (ResourceLeaf (Resource a b c)) = [FlatResource (front []) a b c]
|
||||||
|
go front (ResourceParent name pieces children) =
|
||||||
|
concatMap (go (front . ((name, pieces):))) children
|
||||||
|
|
||||||
-- |
|
-- |
|
||||||
--
|
--
|
||||||
-- This function will generate a single clause that will address all
|
-- This function will generate a single clause that will address all
|
||||||
@ -83,9 +93,9 @@ import Data.List (foldl')
|
|||||||
mkDispatchClause :: Q Exp -- ^ runHandler function
|
mkDispatchClause :: Q Exp -- ^ runHandler function
|
||||||
-> Q Exp -- ^ dispatcher function
|
-> Q Exp -- ^ dispatcher function
|
||||||
-> Q Exp -- ^ fixHandler function
|
-> Q Exp -- ^ fixHandler function
|
||||||
-> [Resource a]
|
-> [ResourceTree a]
|
||||||
-> Q Clause
|
-> Q Clause
|
||||||
mkDispatchClause runHandler dispatcher fixHandler ress = do
|
mkDispatchClause runHandler dispatcher fixHandler ress' = do
|
||||||
-- Allocate the names to be used. Start off with the names passed to the
|
-- Allocate the names to be used. Start off with the names passed to the
|
||||||
-- function itself (with a 0 suffix).
|
-- function itself (with a 0 suffix).
|
||||||
--
|
--
|
||||||
@ -130,22 +140,25 @@ mkDispatchClause runHandler dispatcher fixHandler ress = do
|
|||||||
Nothing -> $(return $ VarE app4040)
|
Nothing -> $(return $ VarE app4040)
|
||||||
|]
|
|]
|
||||||
return $ Clause pats (NormalB u) $ dispatchFun : methodMaps
|
return $ Clause pats (NormalB u) $ dispatchFun : methodMaps
|
||||||
|
where
|
||||||
|
ress = flatten ress'
|
||||||
|
|
||||||
-- | Determine the name of the method map for a given resource name.
|
-- | Determine the name of the method map for a given resource name.
|
||||||
methodMapName :: String -> Name
|
methodMapName :: String -> Name
|
||||||
methodMapName s = mkName $ "methods" ++ s
|
methodMapName s = mkName $ "methods" ++ s
|
||||||
|
|
||||||
buildMethodMap :: Q Exp -- ^ fixHandler
|
buildMethodMap :: Q Exp -- ^ fixHandler
|
||||||
-> Resource a
|
-> FlatResource a
|
||||||
-> Q (Maybe Dec)
|
-> Q (Maybe Dec)
|
||||||
buildMethodMap _ (Resource _ _ (Methods _ [])) = return Nothing -- single handle function
|
buildMethodMap _ (FlatResource _ _ _ (Methods _ [])) = return Nothing -- single handle function
|
||||||
buildMethodMap fixHandler (Resource name pieces (Methods mmulti methods)) = do
|
buildMethodMap fixHandler (FlatResource parents name pieces' (Methods mmulti methods)) = do
|
||||||
fromList <- [|Map.fromList|]
|
fromList <- [|Map.fromList|]
|
||||||
methods' <- mapM go methods
|
methods' <- mapM go methods
|
||||||
let exp = fromList `AppE` ListE methods'
|
let exp = fromList `AppE` ListE methods'
|
||||||
let fun = FunD (methodMapName name) [Clause [] (NormalB exp) []]
|
let fun = FunD (methodMapName name) [Clause [] (NormalB exp) []]
|
||||||
return $ Just fun
|
return $ Just fun
|
||||||
where
|
where
|
||||||
|
pieces = concat $ map snd parents ++ [pieces']
|
||||||
go method = do
|
go method = do
|
||||||
fh <- fixHandler
|
fh <- fixHandler
|
||||||
let func = VarE $ mkName $ map toLower method ++ name
|
let func = VarE $ mkName $ map toLower method ++ name
|
||||||
@ -156,28 +169,31 @@ buildMethodMap fixHandler (Resource name pieces (Methods mmulti methods)) = do
|
|||||||
xs <- replicateM argCount $ newName "arg"
|
xs <- replicateM argCount $ newName "arg"
|
||||||
let rhs = LamE (map VarP xs) $ fh `AppE` (foldl' AppE func $ map VarE xs)
|
let rhs = LamE (map VarP xs) $ fh `AppE` (foldl' AppE func $ map VarE xs)
|
||||||
return $ TupE [pack' `AppE` LitE (StringL method), rhs]
|
return $ TupE [pack' `AppE` LitE (StringL method), rhs]
|
||||||
buildMethodMap _ (Resource _ _ Subsite{}) = return Nothing
|
buildMethodMap _ (FlatResource _ _ _ Subsite{}) = return Nothing
|
||||||
|
|
||||||
-- | Build a single 'D.Route' expression.
|
-- | Build a single 'D.Route' expression.
|
||||||
buildRoute :: Q Exp -> Q Exp -> Q Exp -> Resource a -> Q Exp
|
buildRoute :: Q Exp -> Q Exp -> Q Exp -> FlatResource a -> Q Exp
|
||||||
buildRoute runHandler dispatcher fixHandler (Resource name resPieces resDisp) = do
|
buildRoute runHandler dispatcher fixHandler (FlatResource parents name resPieces resDisp) = do
|
||||||
-- First two arguments to D.Route
|
-- First two arguments to D.Route
|
||||||
routePieces <- ListE <$> mapM (convertPiece . snd) resPieces
|
routePieces <- ListE <$> mapM (convertPiece . snd) allPieces
|
||||||
isMulti <-
|
isMulti <-
|
||||||
case resDisp of
|
case resDisp of
|
||||||
Methods Nothing _ -> [|False|]
|
Methods Nothing _ -> [|False|]
|
||||||
_ -> [|True|]
|
_ -> [|True|]
|
||||||
|
|
||||||
[|D.Route $(return routePieces) $(return isMulti) $(routeArg3 runHandler dispatcher fixHandler name (map snd resPieces) resDisp)|]
|
[|D.Route $(return routePieces) $(return isMulti) $(routeArg3 runHandler dispatcher fixHandler parents name (map snd allPieces) resDisp)|]
|
||||||
|
where
|
||||||
|
allPieces = concat $ map snd parents ++ [resPieces]
|
||||||
|
|
||||||
routeArg3 :: Q Exp -- ^ runHandler
|
routeArg3 :: Q Exp -- ^ runHandler
|
||||||
-> Q Exp -- ^ dispatcher
|
-> Q Exp -- ^ dispatcher
|
||||||
-> Q Exp -- ^ fixHandler
|
-> Q Exp -- ^ fixHandler
|
||||||
|
-> [(String, [(CheckOverlap, Piece a)])]
|
||||||
-> String -- ^ name of resource
|
-> String -- ^ name of resource
|
||||||
-> [Piece a]
|
-> [Piece a]
|
||||||
-> Dispatch a
|
-> Dispatch a
|
||||||
-> Q Exp
|
-> Q Exp
|
||||||
routeArg3 runHandler dispatcher fixHandler name resPieces resDisp = do
|
routeArg3 runHandler dispatcher fixHandler parents name resPieces resDisp = do
|
||||||
pieces <- newName "pieces"
|
pieces <- newName "pieces"
|
||||||
|
|
||||||
-- Allocate input piece variables (xs) and variables that have been
|
-- Allocate input piece variables (xs) and variables that have been
|
||||||
@ -187,8 +203,11 @@ routeArg3 runHandler dispatcher fixHandler name resPieces resDisp = do
|
|||||||
Static _ -> return Nothing
|
Static _ -> return Nothing
|
||||||
Dynamic _ -> Just <$> newName "x"
|
Dynamic _ -> Just <$> newName "x"
|
||||||
|
|
||||||
ys <- forM (catMaybes xs) $ \x -> do
|
-- Note: the zipping with Ints is just a workaround for (apparently) a bug
|
||||||
y <- newName "y"
|
-- in GHC where the identifiers are considered to be overlapping. Using
|
||||||
|
-- newName should avoid the problem, but it doesn't.
|
||||||
|
ys <- forM (zip (catMaybes xs) [1..]) $ \(x, i) -> do
|
||||||
|
y <- newName $ "y" ++ show (i :: Int)
|
||||||
return (x, y)
|
return (x, y)
|
||||||
|
|
||||||
-- In case we have multi pieces at the end
|
-- In case we have multi pieces at the end
|
||||||
@ -216,7 +235,7 @@ routeArg3 runHandler dispatcher fixHandler name resPieces resDisp = do
|
|||||||
_ -> return ([], [])
|
_ -> return ([], [])
|
||||||
|
|
||||||
-- The final expression that actually uses the values we've computed
|
-- The final expression that actually uses the values we've computed
|
||||||
caller <- buildCaller runHandler dispatcher fixHandler xrest name resDisp $ map snd ys ++ yrest'
|
caller <- buildCaller runHandler dispatcher fixHandler xrest parents name resDisp $ map snd ys ++ yrest'
|
||||||
|
|
||||||
-- Put together all the statements
|
-- Put together all the statements
|
||||||
just <- [|Just|]
|
just <- [|Just|]
|
||||||
@ -239,11 +258,12 @@ buildCaller :: Q Exp -- ^ runHandler
|
|||||||
-> Q Exp -- ^ dispatcher
|
-> Q Exp -- ^ dispatcher
|
||||||
-> Q Exp -- ^ fixHandler
|
-> Q Exp -- ^ fixHandler
|
||||||
-> Name -- ^ xrest
|
-> Name -- ^ xrest
|
||||||
|
-> [(String, [(CheckOverlap, Piece a)])]
|
||||||
-> String -- ^ name of resource
|
-> String -- ^ name of resource
|
||||||
-> Dispatch a
|
-> Dispatch a
|
||||||
-> [Name] -- ^ ys
|
-> [Name] -- ^ ys
|
||||||
-> Q Exp
|
-> Q Exp
|
||||||
buildCaller runHandler dispatcher fixHandler xrest name resDisp ys = do
|
buildCaller runHandler dispatcher fixHandler xrest parents name resDisp ys = do
|
||||||
master <- newName "master"
|
master <- newName "master"
|
||||||
sub <- newName "sub"
|
sub <- newName "sub"
|
||||||
toMaster <- newName "toMaster"
|
toMaster <- newName "toMaster"
|
||||||
@ -254,7 +274,7 @@ buildCaller runHandler dispatcher fixHandler xrest name resDisp ys = do
|
|||||||
let pat = map VarP [master, sub, toMaster, app404, handler405, method]
|
let pat = map VarP [master, sub, toMaster, app404, handler405, method]
|
||||||
|
|
||||||
-- Create the route
|
-- Create the route
|
||||||
let route = foldl' (\a b -> a `AppE` VarE b) (ConE $ mkName name) ys
|
let route = routeFromDynamics parents name ys
|
||||||
|
|
||||||
exp <-
|
exp <-
|
||||||
case resDisp of
|
case resDisp of
|
||||||
@ -309,3 +329,16 @@ buildCaller runHandler dispatcher fixHandler xrest name resDisp ys = do
|
|||||||
convertPiece :: Piece a -> Q Exp
|
convertPiece :: Piece a -> Q Exp
|
||||||
convertPiece (Static s) = [|D.Static (pack $(lift s))|]
|
convertPiece (Static s) = [|D.Static (pack $(lift s))|]
|
||||||
convertPiece (Dynamic _) = [|D.Dynamic|]
|
convertPiece (Dynamic _) = [|D.Dynamic|]
|
||||||
|
|
||||||
|
routeFromDynamics :: [(String, [(CheckOverlap, Piece a)])] -- ^ parents
|
||||||
|
-> String -- ^ constructor name
|
||||||
|
-> [Name]
|
||||||
|
-> Exp
|
||||||
|
routeFromDynamics [] name ys = foldl' (\a b -> a `AppE` VarE b) (ConE $ mkName name) ys
|
||||||
|
routeFromDynamics ((parent, pieces):rest) name ys =
|
||||||
|
foldl' (\a b -> a `AppE` b) (ConE $ mkName parent) here
|
||||||
|
where
|
||||||
|
(here', ys') = splitAt (length $ filter (isDynamic . snd) pieces) ys
|
||||||
|
isDynamic Dynamic{} = True
|
||||||
|
isDynamic _ = False
|
||||||
|
here = map VarE here' ++ [routeFromDynamics rest name ys']
|
||||||
|
|||||||
@ -14,17 +14,19 @@ import Control.Monad (replicateM)
|
|||||||
import Data.Text (pack)
|
import Data.Text (pack)
|
||||||
import Web.PathPieces (PathPiece (..), PathMultiPiece (..))
|
import Web.PathPieces (PathPiece (..), PathMultiPiece (..))
|
||||||
import Yesod.Routes.Class
|
import Yesod.Routes.Class
|
||||||
|
import Data.Monoid (mconcat)
|
||||||
|
|
||||||
-- | Generate the constructors of a route data type.
|
-- | Generate the constructors of a route data type.
|
||||||
mkRouteCons :: [Resource Type] -> [Con]
|
mkRouteCons :: [ResourceTree Type] -> ([Con], [Dec])
|
||||||
mkRouteCons =
|
mkRouteCons =
|
||||||
map mkRouteCon
|
mconcat . map mkRouteCon
|
||||||
where
|
where
|
||||||
mkRouteCon res =
|
mkRouteCon (ResourceLeaf res) =
|
||||||
NormalC (mkName $ resourceName res)
|
([con], [])
|
||||||
|
where
|
||||||
|
con = NormalC (mkName $ resourceName res)
|
||||||
$ map (\x -> (NotStrict, x))
|
$ map (\x -> (NotStrict, x))
|
||||||
$ concat [singles, multi, sub]
|
$ concat [singles, multi, sub]
|
||||||
where
|
|
||||||
singles = concatMap (toSingle . snd) $ resourcePieces res
|
singles = concatMap (toSingle . snd) $ resourcePieces res
|
||||||
toSingle Static{} = []
|
toSingle Static{} = []
|
||||||
toSingle (Dynamic typ) = [typ]
|
toSingle (Dynamic typ) = [typ]
|
||||||
@ -35,16 +37,53 @@ mkRouteCons =
|
|||||||
case resourceDispatch res of
|
case resourceDispatch res of
|
||||||
Subsite { subsiteType = typ } -> [ConT ''Route `AppT` typ]
|
Subsite { subsiteType = typ } -> [ConT ''Route `AppT` typ]
|
||||||
_ -> []
|
_ -> []
|
||||||
|
mkRouteCon (ResourceParent name pieces children) =
|
||||||
|
([con], dec : decs)
|
||||||
|
where
|
||||||
|
(cons, decs) = mkRouteCons children
|
||||||
|
con = NormalC (mkName name)
|
||||||
|
$ map (\x -> (NotStrict, x))
|
||||||
|
$ concat [singles, [ConT $ mkName name]]
|
||||||
|
dec = DataD [] (mkName name) [] cons [''Show, ''Read, ''Eq]
|
||||||
|
|
||||||
|
singles = concatMap (toSingle . snd) pieces
|
||||||
|
toSingle Static{} = []
|
||||||
|
toSingle (Dynamic typ) = [typ]
|
||||||
|
|
||||||
-- | Clauses for the 'renderRoute' method.
|
-- | Clauses for the 'renderRoute' method.
|
||||||
mkRenderRouteClauses :: [Resource Type] -> Q [Clause]
|
mkRenderRouteClauses :: [ResourceTree Type] -> Q [Clause]
|
||||||
mkRenderRouteClauses =
|
mkRenderRouteClauses =
|
||||||
mapM go
|
mapM go
|
||||||
where
|
where
|
||||||
isDynamic Dynamic{} = True
|
isDynamic Dynamic{} = True
|
||||||
isDynamic _ = False
|
isDynamic _ = False
|
||||||
|
|
||||||
go res = do
|
go (ResourceParent name pieces children) = do
|
||||||
|
let cnt = length $ filter (isDynamic . snd) pieces
|
||||||
|
dyns <- replicateM cnt $ newName "dyn"
|
||||||
|
child <- newName "child"
|
||||||
|
let pat = ConP (mkName name) $ map VarP $ dyns ++ [child]
|
||||||
|
|
||||||
|
pack' <- [|pack|]
|
||||||
|
tsp <- [|toPathPiece|]
|
||||||
|
let piecesSingle = mkPieces (AppE pack' . LitE . StringL) tsp (map snd pieces) dyns
|
||||||
|
|
||||||
|
childRender <- newName "childRender"
|
||||||
|
let rr = VarE childRender
|
||||||
|
childClauses <- mkRenderRouteClauses children
|
||||||
|
|
||||||
|
a <- newName "a"
|
||||||
|
b <- newName "b"
|
||||||
|
|
||||||
|
colon <- [|(:)|]
|
||||||
|
let cons y ys = InfixE (Just y) colon (Just ys)
|
||||||
|
let pieces' = foldr cons (VarE a) piecesSingle
|
||||||
|
|
||||||
|
let body = LamE [TupP [VarP a, VarP b]] (TupE [pieces', VarE b]) `AppE` (rr `AppE` VarE child)
|
||||||
|
|
||||||
|
return $ Clause [pat] (NormalB body) [FunD childRender childClauses]
|
||||||
|
|
||||||
|
go (ResourceLeaf res) = do
|
||||||
let cnt = length (filter (isDynamic . snd) $ resourcePieces res) + maybe 0 (const 1) (resourceMulti res)
|
let cnt = length (filter (isDynamic . snd) $ resourcePieces res) + maybe 0 (const 1) (resourceMulti res)
|
||||||
dyns <- replicateM cnt $ newName "dyn"
|
dyns <- replicateM cnt $ newName "dyn"
|
||||||
sub <-
|
sub <-
|
||||||
@ -93,18 +132,19 @@ mkRenderRouteClauses =
|
|||||||
-- This includes both the 'Route' associated type and the
|
-- This includes both the 'Route' associated type and the
|
||||||
-- 'renderRoute' method. This function uses both 'mkRouteCons' and
|
-- 'renderRoute' method. This function uses both 'mkRouteCons' and
|
||||||
-- 'mkRenderRouteClasses'.
|
-- 'mkRenderRouteClasses'.
|
||||||
mkRenderRouteInstance :: Type -> [Resource Type] -> Q Dec
|
mkRenderRouteInstance :: Type -> [ResourceTree Type] -> Q [Dec]
|
||||||
mkRenderRouteInstance = mkRenderRouteInstance' []
|
mkRenderRouteInstance = mkRenderRouteInstance' []
|
||||||
|
|
||||||
-- | A more general version of 'mkRenderRouteInstance' which takes an
|
-- | A more general version of 'mkRenderRouteInstance' which takes an
|
||||||
-- additional context.
|
-- additional context.
|
||||||
|
|
||||||
mkRenderRouteInstance' :: Cxt -> Type -> [Resource Type] -> Q Dec
|
mkRenderRouteInstance' :: Cxt -> Type -> [ResourceTree Type] -> Q [Dec]
|
||||||
mkRenderRouteInstance' cxt typ ress = do
|
mkRenderRouteInstance' cxt typ ress = do
|
||||||
cls <- mkRenderRouteClauses ress
|
cls <- mkRenderRouteClauses ress
|
||||||
|
let (cons, decs) = mkRouteCons ress
|
||||||
return $ InstanceD cxt (ConT ''RenderRoute `AppT` typ)
|
return $ InstanceD cxt (ConT ''RenderRoute `AppT` typ)
|
||||||
[ DataInstD [] ''Route [typ] (mkRouteCons ress) clazzes
|
[ DataInstD [] ''Route [typ] cons clazzes
|
||||||
, FunD (mkName "renderRoute") cls
|
, FunD (mkName "renderRoute") cls
|
||||||
]
|
] : decs
|
||||||
where
|
where
|
||||||
clazzes = [''Show, ''Eq, ''Read]
|
clazzes = [''Show, ''Eq, ''Read]
|
||||||
|
|||||||
@ -2,16 +2,37 @@
|
|||||||
module Yesod.Routes.TH.Types
|
module Yesod.Routes.TH.Types
|
||||||
( -- * Data types
|
( -- * Data types
|
||||||
Resource (..)
|
Resource (..)
|
||||||
|
, ResourceTree (..)
|
||||||
, Piece (..)
|
, Piece (..)
|
||||||
, Dispatch (..)
|
, Dispatch (..)
|
||||||
, CheckOverlap
|
, CheckOverlap
|
||||||
-- ** Helper functions
|
-- ** Helper functions
|
||||||
, resourceMulti
|
, resourceMulti
|
||||||
|
, resourceTreePieces
|
||||||
|
, resourceTreeName
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Language.Haskell.TH.Syntax
|
import Language.Haskell.TH.Syntax
|
||||||
import Control.Arrow (second)
|
import Control.Arrow (second)
|
||||||
|
|
||||||
|
data ResourceTree typ = ResourceLeaf (Resource typ) | ResourceParent String [(CheckOverlap, Piece typ)] [ResourceTree typ]
|
||||||
|
|
||||||
|
resourceTreePieces :: ResourceTree typ -> [(CheckOverlap, Piece typ)]
|
||||||
|
resourceTreePieces (ResourceLeaf r) = resourcePieces r
|
||||||
|
resourceTreePieces (ResourceParent _ x _) = x
|
||||||
|
|
||||||
|
resourceTreeName :: ResourceTree typ -> String
|
||||||
|
resourceTreeName (ResourceLeaf r) = resourceName r
|
||||||
|
resourceTreeName (ResourceParent x _ _) = x
|
||||||
|
|
||||||
|
instance Functor ResourceTree where
|
||||||
|
fmap f (ResourceLeaf r) = ResourceLeaf (fmap f r)
|
||||||
|
fmap f (ResourceParent a b c) = ResourceParent a (map (second $ fmap f) b) $ map (fmap f) c
|
||||||
|
|
||||||
|
instance Lift t => Lift (ResourceTree t) where
|
||||||
|
lift (ResourceLeaf r) = [|ResourceLeaf $(lift r)|]
|
||||||
|
lift (ResourceParent a b c) = [|ResourceParent $(lift a) $(lift b) $(lift c)|]
|
||||||
|
|
||||||
data Resource typ = Resource
|
data Resource typ = Resource
|
||||||
{ resourceName :: String
|
{ resourceName :: String
|
||||||
, resourcePieces :: [(CheckOverlap, Piece typ)]
|
, resourcePieces :: [(CheckOverlap, Piece typ)]
|
||||||
|
|||||||
103
yesod-routes/test/Hierarchy.hs
Normal file
103
yesod-routes/test/Hierarchy.hs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE TypeSynonymInstances #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
module Hierarchy
|
||||||
|
( hierarchy
|
||||||
|
, Dispatcher (..)
|
||||||
|
, RunHandler (..)
|
||||||
|
, Handler
|
||||||
|
, App
|
||||||
|
, toText
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Test.Hspec.Monadic
|
||||||
|
import Test.Hspec.HUnit ()
|
||||||
|
import Test.HUnit
|
||||||
|
import Yesod.Routes.Parse
|
||||||
|
import Yesod.Routes.TH
|
||||||
|
import Yesod.Routes.Class
|
||||||
|
import Language.Haskell.TH.Syntax
|
||||||
|
import qualified Yesod.Routes.Class as YRC
|
||||||
|
import Data.Text (Text, pack, append)
|
||||||
|
|
||||||
|
class ToText a where
|
||||||
|
toText :: a -> Text
|
||||||
|
|
||||||
|
instance ToText Text where toText = id
|
||||||
|
instance ToText String where toText = pack
|
||||||
|
|
||||||
|
type Handler sub master = Text
|
||||||
|
type App sub master = (Text, Maybe (YRC.Route master))
|
||||||
|
|
||||||
|
class Dispatcher sub master where
|
||||||
|
dispatcher
|
||||||
|
:: master
|
||||||
|
-> sub
|
||||||
|
-> (YRC.Route sub -> YRC.Route master)
|
||||||
|
-> App sub master -- ^ 404 page
|
||||||
|
-> (YRC.Route sub -> App sub master) -- ^ 405 page
|
||||||
|
-> Text -- ^ method
|
||||||
|
-> [Text]
|
||||||
|
-> App sub master
|
||||||
|
|
||||||
|
class RunHandler sub master where
|
||||||
|
runHandler
|
||||||
|
:: Handler sub master
|
||||||
|
-> master
|
||||||
|
-> sub
|
||||||
|
-> Maybe (YRC.Route sub)
|
||||||
|
-> (YRC.Route sub -> YRC.Route master)
|
||||||
|
-> App sub master
|
||||||
|
|
||||||
|
data Hierarchy = Hierarchy
|
||||||
|
|
||||||
|
do
|
||||||
|
let resources = [parseRoutes|
|
||||||
|
/ HomeR GET
|
||||||
|
/admin/#Int AdminR:
|
||||||
|
/ AdminRootR GET
|
||||||
|
/login LoginR GET POST
|
||||||
|
/table/#Text TableR GET
|
||||||
|
|]
|
||||||
|
rrinst <- mkRenderRouteInstance (ConT ''Hierarchy) $ map (fmap parseType) resources
|
||||||
|
dispatch <- mkDispatchClause [|runHandler|] [|dispatcher|] [|toText|] resources
|
||||||
|
return
|
||||||
|
$ InstanceD
|
||||||
|
[]
|
||||||
|
(ConT ''Dispatcher
|
||||||
|
`AppT` ConT ''Hierarchy
|
||||||
|
`AppT` ConT ''Hierarchy)
|
||||||
|
[FunD (mkName "dispatcher") [dispatch]]
|
||||||
|
: rrinst
|
||||||
|
|
||||||
|
getHomeR :: Handler sub master
|
||||||
|
getHomeR = "home"
|
||||||
|
|
||||||
|
getAdminRootR :: Int -> Handler sub master
|
||||||
|
getAdminRootR i = pack $ "admin root: " ++ show i
|
||||||
|
|
||||||
|
getLoginR :: Int -> Handler sub master
|
||||||
|
getLoginR i = pack $ "login: " ++ show i
|
||||||
|
|
||||||
|
postLoginR :: Int -> Handler sub master
|
||||||
|
postLoginR i = pack $ "post login: " ++ show i
|
||||||
|
|
||||||
|
getTableR :: Int -> Text -> Handler sub master
|
||||||
|
getTableR _ t = append "TableR " t
|
||||||
|
|
||||||
|
instance RunHandler Hierarchy master where
|
||||||
|
runHandler h _ _ subRoute toMaster = (h, fmap toMaster subRoute)
|
||||||
|
|
||||||
|
hierarchy :: Specs
|
||||||
|
hierarchy = describe "hierarchy" $ do
|
||||||
|
it "renders root correctly" $
|
||||||
|
renderRoute (AdminR 5 AdminRootR) @?= (["admin", "5"], [])
|
||||||
|
it "renders table correctly" $
|
||||||
|
renderRoute (AdminR 6 $ TableR "foo") @?= (["admin", "6", "table", "foo"], [])
|
||||||
|
let disp m ps = dispatcher Hierarchy Hierarchy id (pack "404", Nothing) (\route -> (pack "405", Just route)) (pack m) (map pack ps)
|
||||||
|
it "dispatches root correctly" $ disp "GET" ["admin", "7"] @?= ("admin root: 7", Just $ AdminR 7 AdminRootR)
|
||||||
|
it "dispatches table correctly" $ disp "GET" ["admin", "8", "table", "bar"] @?= ("TableR bar", Just $ AdminR 8 $ TableR "bar")
|
||||||
@ -20,12 +20,7 @@ import Yesod.Routes.Parse (parseRoutesNoCheck)
|
|||||||
import Yesod.Routes.Overlap (findOverlapNames)
|
import Yesod.Routes.Overlap (findOverlapNames)
|
||||||
import Yesod.Routes.TH hiding (Dispatch)
|
import Yesod.Routes.TH hiding (Dispatch)
|
||||||
import Language.Haskell.TH.Syntax
|
import Language.Haskell.TH.Syntax
|
||||||
|
import Hierarchy
|
||||||
class ToText a where
|
|
||||||
toText :: a -> Text
|
|
||||||
|
|
||||||
instance ToText Text where toText = id
|
|
||||||
instance ToText String where toText = pack
|
|
||||||
|
|
||||||
result :: ([Text] -> Maybe Int) -> Dispatch Int
|
result :: ([Text] -> Maybe Int) -> Dispatch Int
|
||||||
result f ts = f ts
|
result f ts = f ts
|
||||||
@ -101,32 +96,9 @@ instance RenderRoute MySubParam where
|
|||||||
getMySubParam :: MyApp -> Int -> MySubParam
|
getMySubParam :: MyApp -> Int -> MySubParam
|
||||||
getMySubParam _ = MySubParam
|
getMySubParam _ = MySubParam
|
||||||
|
|
||||||
type Handler sub master = Text
|
|
||||||
type App sub master = (Text, Maybe (YRC.Route master))
|
|
||||||
|
|
||||||
class Dispatcher sub master where
|
|
||||||
dispatcher
|
|
||||||
:: master
|
|
||||||
-> sub
|
|
||||||
-> (YRC.Route sub -> YRC.Route master)
|
|
||||||
-> App sub master -- ^ 404 page
|
|
||||||
-> (YRC.Route sub -> App sub master) -- ^ 405 page
|
|
||||||
-> Text -- ^ method
|
|
||||||
-> [Text]
|
|
||||||
-> App sub master
|
|
||||||
|
|
||||||
class RunHandler sub master where
|
|
||||||
runHandler
|
|
||||||
:: Handler sub master
|
|
||||||
-> master
|
|
||||||
-> sub
|
|
||||||
-> Maybe (YRC.Route sub)
|
|
||||||
-> (YRC.Route sub -> YRC.Route master)
|
|
||||||
-> App sub master
|
|
||||||
|
|
||||||
do
|
do
|
||||||
texts <- [t|[Text]|]
|
texts <- [t|[Text]|]
|
||||||
let ress =
|
let ress = map ResourceLeaf
|
||||||
[ Resource "RootR" [] $ Methods Nothing ["GET"]
|
[ Resource "RootR" [] $ Methods Nothing ["GET"]
|
||||||
, Resource "BlogPostR" (addCheck [Static "blog", Dynamic $ ConT ''Text]) $ Methods Nothing ["GET", "POST"]
|
, Resource "BlogPostR" (addCheck [Static "blog", Dynamic $ ConT ''Text]) $ Methods Nothing ["GET", "POST"]
|
||||||
, Resource "WikiR" (addCheck [Static "wiki"]) $ Methods (Just texts) []
|
, Resource "WikiR" (addCheck [Static "wiki"]) $ Methods (Just texts) []
|
||||||
@ -137,14 +109,13 @@ do
|
|||||||
rrinst <- mkRenderRouteInstance (ConT ''MyApp) ress
|
rrinst <- mkRenderRouteInstance (ConT ''MyApp) ress
|
||||||
dispatch <- mkDispatchClause [|runHandler|] [|dispatcher|] [|toText|] ress
|
dispatch <- mkDispatchClause [|runHandler|] [|dispatcher|] [|toText|] ress
|
||||||
return
|
return
|
||||||
[ rrinst
|
$ InstanceD
|
||||||
, InstanceD
|
|
||||||
[]
|
[]
|
||||||
(ConT ''Dispatcher
|
(ConT ''Dispatcher
|
||||||
`AppT` ConT ''MyApp
|
`AppT` ConT ''MyApp
|
||||||
`AppT` ConT ''MyApp)
|
`AppT` ConT ''MyApp)
|
||||||
[FunD (mkName "dispatcher") [dispatch]]
|
[FunD (mkName "dispatcher") [dispatch]]
|
||||||
]
|
: rrinst
|
||||||
|
|
||||||
instance RunHandler MyApp master where
|
instance RunHandler MyApp master where
|
||||||
runHandler h _ _ subRoute toMaster = (h, fmap toMaster subRoute)
|
runHandler h _ _ subRoute toMaster = (h, fmap toMaster subRoute)
|
||||||
@ -328,6 +299,7 @@ main = hspecX $ do
|
|||||||
/bar/baz Foo3
|
/bar/baz Foo3
|
||||||
|]
|
|]
|
||||||
findOverlapNames routes @?= []
|
findOverlapNames routes @?= []
|
||||||
|
hierarchy
|
||||||
|
|
||||||
getRootR :: Text
|
getRootR :: Text
|
||||||
getRootR = pack "this is the root"
|
getRootR = pack "this is the root"
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-routes
|
name: yesod-routes
|
||||||
version: 1.0.0
|
version: 1.1.0.2
|
||||||
license: MIT
|
license: MIT
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Michael Snoyman <michael@snoyman.com>
|
author: Michael Snoyman <michael@snoyman.com>
|
||||||
@ -11,12 +11,14 @@ 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/main.hs
|
||||||
|
|
||||||
library
|
library
|
||||||
build-depends: base >= 4 && < 5
|
build-depends: base >= 4 && < 5
|
||||||
, text >= 0.5 && < 0.12
|
, text >= 0.5 && < 0.12
|
||||||
, vector >= 0.8 && < 0.10
|
, vector >= 0.8 && < 0.11
|
||||||
, containers >= 0.2 && < 0.5
|
, containers >= 0.2
|
||||||
, template-haskell
|
, template-haskell
|
||||||
, path-pieces >= 0.1 && < 0.2
|
, path-pieces >= 0.1 && < 0.2
|
||||||
|
|
||||||
@ -34,12 +36,13 @@ test-suite runtests
|
|||||||
type: exitcode-stdio-1.0
|
type: exitcode-stdio-1.0
|
||||||
main-is: main.hs
|
main-is: main.hs
|
||||||
hs-source-dirs: test
|
hs-source-dirs: test
|
||||||
|
other-modules: Hierarchy
|
||||||
|
|
||||||
build-depends: base >= 4.3 && < 5
|
build-depends: base >= 4.3 && < 5
|
||||||
, yesod-routes
|
, yesod-routes
|
||||||
, text >= 0.5 && < 0.12
|
, text >= 0.5 && < 0.12
|
||||||
, HUnit >= 1.2 && < 1.3
|
, HUnit >= 1.2 && < 1.3
|
||||||
, hspec >= 0.6 && < 0.10
|
, hspec >= 1.3 && < 1.4
|
||||||
, containers
|
, containers
|
||||||
, template-haskell
|
, template-haskell
|
||||||
, path-pieces
|
, path-pieces
|
||||||
|
|||||||
@ -1,25 +1,20 @@
|
|||||||
The following license covers this documentation, and the source code, except
|
Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
|
||||||
where otherwise indicated.
|
|
||||||
|
|
||||||
Copyright 2010, Michael Snoyman. All rights reserved.
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
The above copyright notice and this permission notice shall be
|
||||||
modification, are permitted provided that the following conditions are met:
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
list of conditions and the following disclaimer.
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
this list of conditions and the following disclaimer in the documentation
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
and/or other materials provided with the distribution.
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
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.
|
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import Data.Time (UTCTime)
|
|||||||
import Data.Monoid (mappend)
|
import Data.Monoid (mappend)
|
||||||
import Text.XML
|
import Text.XML
|
||||||
import Data.Text (Text, pack)
|
import Data.Text (Text, pack)
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
data SitemapChangeFreq = Always
|
data SitemapChangeFreq = Always
|
||||||
| Hourly
|
| Hourly
|
||||||
@ -66,13 +67,13 @@ template urls render =
|
|||||||
addNS' n = n
|
addNS' n = n
|
||||||
namespace = "http://www.sitemaps.org/schemas/sitemap/0.9"
|
namespace = "http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||||
|
|
||||||
root = Element "urlset" [] $ map go urls
|
root = Element "urlset" Map.empty $ map go urls
|
||||||
|
|
||||||
go SitemapUrl {..} = NodeElement $ Element "url" [] $ map NodeElement
|
go SitemapUrl {..} = NodeElement $ Element "url" Map.empty $ map NodeElement
|
||||||
[ Element "loc" [] [NodeContent $ render sitemapLoc]
|
[ Element "loc" Map.empty [NodeContent $ render sitemapLoc]
|
||||||
, Element "lastmod" [] [NodeContent $ formatW3 sitemapLastMod]
|
, Element "lastmod" Map.empty [NodeContent $ formatW3 sitemapLastMod]
|
||||||
, Element "changefreq" [] [NodeContent $ showFreq sitemapChangeFreq]
|
, Element "changefreq" Map.empty [NodeContent $ showFreq sitemapChangeFreq]
|
||||||
, Element "priority" [] [NodeContent $ pack $ show sitemapPriority]
|
, Element "priority" Map.empty [NodeContent $ pack $ show sitemapPriority]
|
||||||
]
|
]
|
||||||
|
|
||||||
sitemap :: [SitemapUrl (Route master)] -> GHandler sub master RepXml
|
sitemap :: [SitemapUrl (Route master)] -> GHandler sub master RepXml
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: yesod-sitemap
|
name: yesod-sitemap
|
||||||
version: 1.0.0
|
version: 1.1.0
|
||||||
license: MIT
|
license: MIT
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Michael Snoyman <michael@snoyman.com>
|
author: Michael Snoyman <michael@snoyman.com>
|
||||||
@ -14,10 +14,11 @@ description: Generate XML sitemaps.
|
|||||||
|
|
||||||
library
|
library
|
||||||
build-depends: base >= 4 && < 5
|
build-depends: base >= 4 && < 5
|
||||||
, yesod-core >= 1.0 && < 1.1
|
, yesod-core >= 1.1 && < 1.2
|
||||||
, time >= 1.1.4
|
, time >= 1.1.4
|
||||||
, xml-conduit >= 0.7 && < 0.8
|
, xml-conduit >= 1.0 && < 1.1
|
||||||
, text
|
, text
|
||||||
|
, containers
|
||||||
exposed-modules: Yesod.Sitemap
|
exposed-modules: Yesod.Sitemap
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
|
|
||||||
|
|||||||
@ -1,25 +1,20 @@
|
|||||||
The following license covers this documentation, and the source code, except
|
Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
|
||||||
where otherwise indicated.
|
|
||||||
|
|
||||||
Copyright 2010, Michael Snoyman. All rights reserved.
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
The above copyright notice and this permission notice shall be
|
||||||
modification, are permitted provided that the following conditions are met:
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
list of conditions and the following disclaimer.
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
this list of conditions and the following disclaimer in the documentation
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
and/or other materials provided with the distribution.
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
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.
|
|
||||||
|
|||||||
@ -66,7 +66,6 @@ import qualified Data.ByteString.Char8 as S8
|
|||||||
import qualified Data.ByteString.Lazy as L
|
import qualified Data.ByteString.Lazy as L
|
||||||
import qualified Data.Serialize
|
import qualified Data.Serialize
|
||||||
import Data.Text (Text, pack)
|
import Data.Text (Text, pack)
|
||||||
import Data.Monoid (mempty)
|
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import Data.IORef (readIORef, newIORef, writeIORef)
|
import Data.IORef (readIORef, newIORef, writeIORef)
|
||||||
import Network.Wai (pathInfo, rawPathInfo, responseLBS)
|
import Network.Wai (pathInfo, rawPathInfo, responseLBS)
|
||||||
@ -79,19 +78,15 @@ import System.Posix.Types (EpochTime)
|
|||||||
import Data.Conduit (($$))
|
import Data.Conduit (($$))
|
||||||
import Data.Conduit.List (sourceList)
|
import Data.Conduit.List (sourceList)
|
||||||
import Data.Functor.Identity (runIdentity)
|
import Data.Functor.Identity (runIdentity)
|
||||||
|
import qualified Filesystem.Path.CurrentOS as F
|
||||||
|
|
||||||
import Network.Wai.Application.Static
|
import Network.Wai.Application.Static
|
||||||
( StaticSettings (..)
|
( StaticSettings (..)
|
||||||
, defaultWebAppSettings
|
|
||||||
, staticApp
|
, staticApp
|
||||||
, embeddedLookup
|
|
||||||
, toEmbedded
|
|
||||||
, toFilePath
|
|
||||||
, fromFilePath
|
|
||||||
, FilePath
|
|
||||||
, ETagLookup
|
|
||||||
, webAppSettingsWithLookup
|
, webAppSettingsWithLookup
|
||||||
|
, embeddedSettings
|
||||||
)
|
)
|
||||||
|
import WaiAppStatic.Storage.Filesystem (ETagLookup)
|
||||||
|
|
||||||
-- | Type used for the subsite with static contents.
|
-- | Type used for the subsite with static contents.
|
||||||
newtype Static = Static StaticSettings
|
newtype Static = Static StaticSettings
|
||||||
@ -107,7 +102,7 @@ type StaticRoute = Route Static
|
|||||||
static :: Prelude.FilePath -> IO Static
|
static :: Prelude.FilePath -> IO Static
|
||||||
static dir = do
|
static dir = do
|
||||||
hashLookup <- cachedETagLookup dir
|
hashLookup <- cachedETagLookup dir
|
||||||
return $ Static $ webAppSettingsWithLookup (toFilePath dir) hashLookup
|
return $ Static $ webAppSettingsWithLookup (F.decodeString dir) hashLookup
|
||||||
|
|
||||||
-- | Same as 'static', but does not assumes that the files do not
|
-- | Same as 'static', but does not assumes that the files do not
|
||||||
-- change and checks their modification time whenever a request
|
-- change and checks their modification time whenever a request
|
||||||
@ -115,15 +110,19 @@ static dir = do
|
|||||||
staticDevel :: Prelude.FilePath -> IO Static
|
staticDevel :: Prelude.FilePath -> IO Static
|
||||||
staticDevel dir = do
|
staticDevel dir = do
|
||||||
hashLookup <- cachedETagLookupDevel dir
|
hashLookup <- cachedETagLookupDevel dir
|
||||||
return $ Static $ webAppSettingsWithLookup (toFilePath dir) hashLookup
|
return $ Static $ webAppSettingsWithLookup (F.decodeString dir) hashLookup
|
||||||
|
|
||||||
-- | Produce a 'Static' based on embedding all of the static
|
-- | Produce a 'Static' based on embedding all of the static
|
||||||
-- files' contents in the executable at compile time.
|
-- files' contents in the executable at compile time.
|
||||||
|
-- Nota Bene: if you replace the scaffolded 'static' call in Settings/StaticFiles.hs
|
||||||
|
-- you will need to change the scaffolded addStaticContent. Otherwise, some of your
|
||||||
|
-- assets will be 404'ed. This is because by default yesod will generate compile those
|
||||||
|
-- assets to @static/tmp@ which for 'static' is fine since they are served out of the
|
||||||
|
-- directory itself. With embedded static, that will not work.
|
||||||
|
-- You can easily change @addStaticContent@ to @\_ _ _ -> return Nothing@ as a workaround.
|
||||||
|
-- This will cause yesod to embed those assets into the generated HTML file itself.
|
||||||
embed :: Prelude.FilePath -> Q Exp
|
embed :: Prelude.FilePath -> Q Exp
|
||||||
embed fp =
|
embed fp = [|Static (embeddedSettings $(embedDir fp))|]
|
||||||
[|Static (defaultWebAppSettings
|
|
||||||
{ ssFolder = embeddedLookup (toEmbedded $(embedDir fp))
|
|
||||||
})|]
|
|
||||||
|
|
||||||
instance RenderRoute Static where
|
instance RenderRoute Static where
|
||||||
-- | A route on the static subsite (see also 'staticFiles').
|
-- | A route on the static subsite (see also 'staticFiles').
|
||||||
@ -146,10 +145,10 @@ instance RenderRoute Static where
|
|||||||
|
|
||||||
instance Yesod master => YesodDispatch Static master where
|
instance Yesod master => YesodDispatch Static master where
|
||||||
-- Need to append trailing slash to make relative links work
|
-- Need to append trailing slash to make relative links work
|
||||||
yesodDispatch _ _ _ _ _ _ [] _ req =
|
yesodDispatch _ _ _ _ _ _ _ [] _ req =
|
||||||
return $ responseLBS status301 [("Location", rawPathInfo req `S.append` "/")] ""
|
return $ responseLBS status301 [("Location", rawPathInfo req `S.append` "/")] ""
|
||||||
|
|
||||||
yesodDispatch _ (Static set) _ _ _ _ textPieces _ req =
|
yesodDispatch _ _ (Static set) _ _ _ _ textPieces _ req =
|
||||||
staticApp set req { pathInfo = textPieces }
|
staticApp set req { pathInfo = textPieces }
|
||||||
|
|
||||||
notHidden :: Prelude.FilePath -> Bool
|
notHidden :: Prelude.FilePath -> Bool
|
||||||
@ -227,18 +226,18 @@ publicFiles :: Prelude.FilePath -> Q [Dec]
|
|||||||
publicFiles dir = mkStaticFiles' dir "StaticRoute" False
|
publicFiles dir = mkStaticFiles' dir "StaticRoute" False
|
||||||
|
|
||||||
|
|
||||||
mkHashMap :: Prelude.FilePath -> IO (M.Map FilePath S8.ByteString)
|
mkHashMap :: Prelude.FilePath -> IO (M.Map F.FilePath S8.ByteString)
|
||||||
mkHashMap dir = do
|
mkHashMap dir = do
|
||||||
fs <- getFileListPieces dir
|
fs <- getFileListPieces dir
|
||||||
hashAlist fs >>= return . M.fromList
|
hashAlist fs >>= return . M.fromList
|
||||||
where
|
where
|
||||||
hashAlist :: [[String]] -> IO [(FilePath, S8.ByteString)]
|
hashAlist :: [[String]] -> IO [(F.FilePath, S8.ByteString)]
|
||||||
hashAlist fs = mapM hashPair fs
|
hashAlist fs = mapM hashPair fs
|
||||||
where
|
where
|
||||||
hashPair :: [String] -> IO (FilePath, S8.ByteString)
|
hashPair :: [String] -> IO (F.FilePath, S8.ByteString)
|
||||||
hashPair pieces = do let file = pathFromRawPieces dir pieces
|
hashPair pieces = do let file = pathFromRawPieces dir pieces
|
||||||
h <- base64md5File file
|
h <- base64md5File file
|
||||||
return (toFilePath file, S8.pack h)
|
return (F.decodeString file, S8.pack h)
|
||||||
|
|
||||||
pathFromRawPieces :: Prelude.FilePath -> [String] -> Prelude.FilePath
|
pathFromRawPieces :: Prelude.FilePath -> [String] -> Prelude.FilePath
|
||||||
pathFromRawPieces =
|
pathFromRawPieces =
|
||||||
@ -249,12 +248,12 @@ pathFromRawPieces =
|
|||||||
cachedETagLookupDevel :: Prelude.FilePath -> IO ETagLookup
|
cachedETagLookupDevel :: Prelude.FilePath -> IO ETagLookup
|
||||||
cachedETagLookupDevel dir = do
|
cachedETagLookupDevel dir = do
|
||||||
etags <- mkHashMap dir
|
etags <- mkHashMap dir
|
||||||
mtimeVar <- newIORef (M.empty :: M.Map FilePath EpochTime)
|
mtimeVar <- newIORef (M.empty :: M.Map F.FilePath EpochTime)
|
||||||
return $ \f ->
|
return $ \f ->
|
||||||
case M.lookup f etags of
|
case M.lookup f etags of
|
||||||
Nothing -> return Nothing
|
Nothing -> return Nothing
|
||||||
Just checksum -> do
|
Just checksum -> do
|
||||||
fs <- getFileStatus $ fromFilePath f
|
fs <- getFileStatus $ F.encodeString f
|
||||||
let newt = modificationTime fs
|
let newt = modificationTime fs
|
||||||
mtimes <- readIORef mtimeVar
|
mtimes <- readIORef mtimeVar
|
||||||
oldt <- case M.lookup f mtimes of
|
oldt <- case M.lookup f mtimes of
|
||||||
@ -307,7 +306,7 @@ mkStaticFilesList fp fs 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
|
||||||
[|[(pack $(lift hash), mempty)]|]
|
[|[("etag" :: Text, pack $(lift hash))]|]
|
||||||
else return $ ListE []
|
else return $ ListE []
|
||||||
return
|
return
|
||||||
[ SigD routeName $ ConT route
|
[ SigD routeName $ ConT route
|
||||||
|
|||||||
@ -6,11 +6,9 @@ import Test.Hspec.HUnit ( )
|
|||||||
|
|
||||||
import Yesod.Static (getFileListPieces)
|
import Yesod.Static (getFileListPieces)
|
||||||
|
|
||||||
specs :: [Specs]
|
specs :: Spec
|
||||||
specs = [
|
specs = do
|
||||||
describe "get file list" [
|
describe "get file list" $ do
|
||||||
it "pieces" $ do
|
it "pieces" $ do
|
||||||
x <- getFileListPieces "test/fs"
|
x <- getFileListPieces "test/fs"
|
||||||
x @?= [["foo"], ["bar", "baz"]]
|
x @?= [["foo"], ["bar", "baz"]]
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|||||||
@ -4,4 +4,4 @@ import Test.Hspec
|
|||||||
import YesodStaticTest (specs)
|
import YesodStaticTest (specs)
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = hspecX $ descriptions specs
|
main = hspec specs
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user