mirror of https://github.com/gogits/gogs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
282 lines
9.2 KiB
282 lines
9.2 KiB
<!doctype html> |
|
|
|
<title>CodeMirror: Haskell-literate mode</title> |
|
<meta charset="utf-8"/> |
|
<link rel=stylesheet href="../../doc/docs.css"> |
|
|
|
<link rel="stylesheet" href="../../lib/codemirror.css"> |
|
<script src="../../lib/codemirror.js"></script> |
|
<script src="haskell-literate.js"></script> |
|
<script src="../haskell/haskell.js"></script> |
|
<style>.CodeMirror { |
|
border-top : 1px solid #DDDDDD; |
|
border-bottom : 1px solid #DDDDDD; |
|
}</style> |
|
<div id=nav> |
|
<a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo |
|
src="../../doc/logo.png"></a> |
|
|
|
<ul> |
|
<li><a href="../../index.html">Home</a> |
|
<li><a href="../../doc/manual.html">Manual</a> |
|
<li><a href="https://github.com/codemirror/codemirror">Code</a> |
|
</ul> |
|
<ul> |
|
<li><a href="../index.html">Language modes</a> |
|
<li><a class=active href="#">Haskell-literate</a> |
|
</ul> |
|
</div> |
|
|
|
<article> |
|
<h2>Haskell literate mode</h2> |
|
<form> |
|
<textarea id="code" name="code"> |
|
> {-# LANGUAGE OverloadedStrings #-} |
|
> {-# OPTIONS_GHC -fno-warn-unused-do-bind #-} |
|
> import Control.Applicative ((<$>), (<*>)) |
|
> import Data.Maybe (isJust) |
|
|
|
> import Data.Text (Text) |
|
> import Text.Blaze ((!)) |
|
> import qualified Data.Text as T |
|
> import qualified Happstack.Server as Happstack |
|
> import qualified Text.Blaze.Html5 as H |
|
> import qualified Text.Blaze.Html5.Attributes as A |
|
|
|
> import Text.Digestive |
|
> import Text.Digestive.Blaze.Html5 |
|
> import Text.Digestive.Happstack |
|
> import Text.Digestive.Util |
|
|
|
Simple forms and validation |
|
--------------------------- |
|
|
|
Let's start by creating a very simple datatype to represent a user: |
|
|
|
> data User = User |
|
> { userName :: Text |
|
> , userMail :: Text |
|
> } deriving (Show) |
|
|
|
And dive in immediately to create a `Form` for a user. The `Form v m a` type |
|
has three parameters: |
|
|
|
- `v`: the type for messages and errors (usually a `String`-like type, `Text` in |
|
this case); |
|
- `m`: the monad we are operating in, not specified here; |
|
- `a`: the return type of the `Form`, in this case, this is obviously `User`. |
|
|
|
> userForm :: Monad m => Form Text m User |
|
|
|
We create forms by using the `Applicative` interface. A few form types are |
|
provided in the `Text.Digestive.Form` module, such as `text`, `string`, |
|
`bool`... |
|
|
|
In the `digestive-functors` library, the developer is required to label each |
|
field using the `.:` operator. This might look like a bit of a burden, but it |
|
allows you to do some really useful stuff, like separating the `Form` from the |
|
actual HTML layout. |
|
|
|
> userForm = User |
|
> <$> "name" .: text Nothing |
|
> <*> "mail" .: check "Not a valid email address" checkEmail (text Nothing) |
|
|
|
The `check` function enables you to validate the result of a form. For example, |
|
we can validate the email address with a really naive `checkEmail` function. |
|
|
|
> checkEmail :: Text -> Bool |
|
> checkEmail = isJust . T.find (== '@') |
|
|
|
More validation |
|
--------------- |
|
|
|
For our example, we also want descriptions of Haskell libraries, and in order to |
|
do that, we need package versions... |
|
|
|
> type Version = [Int] |
|
|
|
We want to let the user input a version number such as `0.1.0.0`. This means we |
|
need to validate if the input `Text` is of this form, and then we need to parse |
|
it to a `Version` type. Fortunately, we can do this in a single function: |
|
`validate` allows conversion between values, which can optionally fail. |
|
|
|
`readMaybe :: Read a => String -> Maybe a` is a utility function imported from |
|
`Text.Digestive.Util`. |
|
|
|
> validateVersion :: Text -> Result Text Version |
|
> validateVersion = maybe (Error "Cannot parse version") Success . |
|
> mapM (readMaybe . T.unpack) . T.split (== '.') |
|
|
|
A quick test in GHCi: |
|
|
|
ghci> validateVersion (T.pack "0.3.2.1") |
|
Success [0,3,2,1] |
|
ghci> validateVersion (T.pack "0.oops") |
|
Error "Cannot parse version" |
|
|
|
It works! This means we can now easily add a `Package` type and a `Form` for it: |
|
|
|
> data Category = Web | Text | Math |
|
> deriving (Bounded, Enum, Eq, Show) |
|
|
|
> data Package = Package Text Version Category |
|
> deriving (Show) |
|
|
|
> packageForm :: Monad m => Form Text m Package |
|
> packageForm = Package |
|
> <$> "name" .: text Nothing |
|
> <*> "version" .: validate validateVersion (text (Just "0.0.0.1")) |
|
> <*> "category" .: choice categories Nothing |
|
> where |
|
> categories = [(x, T.pack (show x)) | x <- [minBound .. maxBound]] |
|
|
|
Composing forms |
|
--------------- |
|
|
|
A release has an author and a package. Let's use this to illustrate the |
|
composability of the digestive-functors library: we can reuse the forms we have |
|
written earlier on. |
|
|
|
> data Release = Release User Package |
|
> deriving (Show) |
|
|
|
> releaseForm :: Monad m => Form Text m Release |
|
> releaseForm = Release |
|
> <$> "author" .: userForm |
|
> <*> "package" .: packageForm |
|
|
|
Views |
|
----- |
|
|
|
As mentioned before, one of the advantages of using digestive-functors is |
|
separation of forms and their actual HTML layout. In order to do this, we have |
|
another type, `View`. |
|
|
|
We can get a `View` from a `Form` by supplying input. A `View` contains more |
|
information than a `Form`, it has: |
|
|
|
- the original form; |
|
- the input given by the user; |
|
- any errors that have occurred. |
|
|
|
It is this view that we convert to HTML. For this tutorial, we use the |
|
[blaze-html] library, and some helpers from the `digestive-functors-blaze` |
|
library. |
|
|
|
[blaze-html]: http://jaspervdj.be/blaze/ |
|
|
|
Let's write a view for the `User` form. As you can see, we here refer to the |
|
different fields in the `userForm`. The `errorList` will generate a list of |
|
errors for the `"mail"` field. |
|
|
|
> userView :: View H.Html -> H.Html |
|
> userView view = do |
|
> label "name" view "Name: " |
|
> inputText "name" view |
|
> H.br |
|
> |
|
> errorList "mail" view |
|
> label "mail" view "Email address: " |
|
> inputText "mail" view |
|
> H.br |
|
|
|
Like forms, views are also composable: let's illustrate that by adding a view |
|
for the `releaseForm`, in which we reuse `userView`. In order to do this, we |
|
take only the parts relevant to the author from the view by using `subView`. We |
|
can then pass the resulting view to our own `userView`. |
|
We have no special view code for `Package`, so we can just add that to |
|
`releaseView` as well. `childErrorList` will generate a list of errors for each |
|
child of the specified form. In this case, this means a list of errors from |
|
`"package.name"` and `"package.version"`. Note how we use `foo.bar` to refer to |
|
nested forms. |
|
|
|
> releaseView :: View H.Html -> H.Html |
|
> releaseView view = do |
|
> H.h2 "Author" |
|
> userView $ subView "author" view |
|
> |
|
> H.h2 "Package" |
|
> childErrorList "package" view |
|
> |
|
> label "package.name" view "Name: " |
|
> inputText "package.name" view |
|
> H.br |
|
> |
|
> label "package.version" view "Version: " |
|
> inputText "package.version" view |
|
> H.br |
|
> |
|
> label "package.category" view "Category: " |
|
> inputSelect "package.category" view |
|
> H.br |
|
|
|
The attentive reader might have wondered what the type parameter for `View` is: |
|
it is the `String`-like type used for e.g. error messages. |
|
But wait! We have |
|
releaseForm :: Monad m => Form Text m Release |
|
releaseView :: View H.Html -> H.Html |
|
... doesn't this mean that we need a `View Text` rather than a `View Html`? The |
|
answer is yes -- but having `View Html` allows us to write these views more |
|
easily with the `digestive-functors-blaze` library. Fortunately, we will be able |
|
to fix this using the `Functor` instance of `View`. |
|
fmap :: Monad m => (v -> w) -> View v -> View w |
|
A backend |
|
--------- |
|
To finish this tutorial, we need to be able to actually run this code. We need |
|
an HTTP server for that, and we use [Happstack] for this tutorial. The |
|
`digestive-functors-happstack` library gives about everything we need for this. |
|
[Happstack]: http://happstack.com/ |
|
|
|
> site :: Happstack.ServerPart Happstack.Response |
|
> site = do |
|
> Happstack.decodeBody $ Happstack.defaultBodyPolicy "/tmp" 4096 4096 4096 |
|
> r <- runForm "test" releaseForm |
|
> case r of |
|
> (view, Nothing) -> do |
|
> let view' = fmap H.toHtml view |
|
> Happstack.ok $ Happstack.toResponse $ |
|
> template $ |
|
> form view' "/" $ do |
|
> releaseView view' |
|
> H.br |
|
> inputSubmit "Submit" |
|
> (_, Just release) -> Happstack.ok $ Happstack.toResponse $ |
|
> template $ do |
|
> css |
|
> H.h1 "Release received" |
|
> H.p $ H.toHtml $ show release |
|
> |
|
> main :: IO () |
|
> main = Happstack.simpleHTTP Happstack.nullConf site |
|
|
|
Utilities |
|
--------- |
|
|
|
> template :: H.Html -> H.Html |
|
> template body = H.docTypeHtml $ do |
|
> H.head $ do |
|
> H.title "digestive-functors tutorial" |
|
> css |
|
> H.body body |
|
> css :: H.Html |
|
> css = H.style ! A.type_ "text/css" $ do |
|
> "label {width: 130px; float: left; clear: both}" |
|
> "ul.digestive-functors-error-list {" |
|
> " color: red;" |
|
> " list-style-type: none;" |
|
> " padding-left: 0px;" |
|
> "}" |
|
</textarea> |
|
</form> |
|
|
|
<p><strong>MIME types |
|
defined:</strong> <code>text/x-literate-haskell</code>.</p> |
|
|
|
<p>Parser configuration parameters recognized: <code>base</code> to |
|
set the base mode (defaults to <code>"haskell"</code>).</p> |
|
|
|
<script> |
|
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {mode: "haskell-literate"}); |
|
</script> |
|
|
|
</article>
|
|
|