scripts/CodeMirror/mode/haskell-literate/index.html (view raw)
1<!doctype html>
2
3<title>CodeMirror: Haskell-literate mode</title>
4<meta charset="utf-8"/>
5<link rel=stylesheet href="../../doc/docs.css">
6
7<link rel="stylesheet" href="../../lib/codemirror.css">
8<script src="../../lib/codemirror.js"></script>
9<script src="haskell-literate.js"></script>
10<script src="../haskell/haskell.js"></script>
11<style>.CodeMirror {
12 border-top : 1px solid #DDDDDD;
13 border-bottom : 1px solid #DDDDDD;
14}</style>
15<div id=nav>
16 <a href="https://codemirror.net"><h1>CodeMirror</h1><img id=logo
17 src="../../doc/logo.png"></a>
18
19 <ul>
20 <li><a href="../../index.html">Home</a>
21 <li><a href="../../doc/manual.html">Manual</a>
22 <li><a href="https://github.com/codemirror/codemirror">Code</a>
23 </ul>
24 <ul>
25 <li><a href="../index.html">Language modes</a>
26 <li><a class=active href="#">Haskell-literate</a>
27 </ul>
28</div>
29
30<article>
31 <h2>Haskell literate mode</h2>
32 <form>
33 <textarea id="code" name="code">
34> {-# LANGUAGE OverloadedStrings #-}
35> {-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
36> import Control.Applicative ((<$>), (<*>))
37> import Data.Maybe (isJust)
38
39> import Data.Text (Text)
40> import Text.Blaze ((!))
41> import qualified Data.Text as T
42> import qualified Happstack.Server as Happstack
43> import qualified Text.Blaze.Html5 as H
44> import qualified Text.Blaze.Html5.Attributes as A
45
46> import Text.Digestive
47> import Text.Digestive.Blaze.Html5
48> import Text.Digestive.Happstack
49> import Text.Digestive.Util
50
51Simple forms and validation
52---------------------------
53
54Let's start by creating a very simple datatype to represent a user:
55
56> data User = User
57> { userName :: Text
58> , userMail :: Text
59> } deriving (Show)
60
61And dive in immediately to create a `Form` for a user. The `Form v m a` type
62has three parameters:
63
64- `v`: the type for messages and errors (usually a `String`-like type, `Text` in
65 this case);
66- `m`: the monad we are operating in, not specified here;
67- `a`: the return type of the `Form`, in this case, this is obviously `User`.
68
69> userForm :: Monad m => Form Text m User
70
71We create forms by using the `Applicative` interface. A few form types are
72provided in the `Text.Digestive.Form` module, such as `text`, `string`,
73`bool`...
74
75In the `digestive-functors` library, the developer is required to label each
76field using the `.:` operator. This might look like a bit of a burden, but it
77allows you to do some really useful stuff, like separating the `Form` from the
78actual HTML layout.
79
80> userForm = User
81> <$> "name" .: text Nothing
82> <*> "mail" .: check "Not a valid email address" checkEmail (text Nothing)
83
84The `check` function enables you to validate the result of a form. For example,
85we can validate the email address with a really naive `checkEmail` function.
86
87> checkEmail :: Text -> Bool
88> checkEmail = isJust . T.find (== '@')
89
90More validation
91---------------
92
93For our example, we also want descriptions of Haskell libraries, and in order to
94do that, we need package versions...
95
96> type Version = [Int]
97
98We want to let the user input a version number such as `0.1.0.0`. This means we
99need to validate if the input `Text` is of this form, and then we need to parse
100it to a `Version` type. Fortunately, we can do this in a single function:
101`validate` allows conversion between values, which can optionally fail.
102
103`readMaybe :: Read a => String -> Maybe a` is a utility function imported from
104`Text.Digestive.Util`.
105
106> validateVersion :: Text -> Result Text Version
107> validateVersion = maybe (Error "Cannot parse version") Success .
108> mapM (readMaybe . T.unpack) . T.split (== '.')
109
110A quick test in GHCi:
111
112 ghci> validateVersion (T.pack "0.3.2.1")
113 Success [0,3,2,1]
114 ghci> validateVersion (T.pack "0.oops")
115 Error "Cannot parse version"
116
117It works! This means we can now easily add a `Package` type and a `Form` for it:
118
119> data Category = Web | Text | Math
120> deriving (Bounded, Enum, Eq, Show)
121
122> data Package = Package Text Version Category
123> deriving (Show)
124
125> packageForm :: Monad m => Form Text m Package
126> packageForm = Package
127> <$> "name" .: text Nothing
128> <*> "version" .: validate validateVersion (text (Just "0.0.0.1"))
129> <*> "category" .: choice categories Nothing
130> where
131> categories = [(x, T.pack (show x)) | x <- [minBound .. maxBound]]
132
133Composing forms
134---------------
135
136A release has an author and a package. Let's use this to illustrate the
137composability of the digestive-functors library: we can reuse the forms we have
138written earlier on.
139
140> data Release = Release User Package
141> deriving (Show)
142
143> releaseForm :: Monad m => Form Text m Release
144> releaseForm = Release
145> <$> "author" .: userForm
146> <*> "package" .: packageForm
147
148Views
149-----
150
151As mentioned before, one of the advantages of using digestive-functors is
152separation of forms and their actual HTML layout. In order to do this, we have
153another type, `View`.
154
155We can get a `View` from a `Form` by supplying input. A `View` contains more
156information than a `Form`, it has:
157
158- the original form;
159- the input given by the user;
160- any errors that have occurred.
161
162It is this view that we convert to HTML. For this tutorial, we use the
163[blaze-html] library, and some helpers from the `digestive-functors-blaze`
164library.
165
166[blaze-html]: http://jaspervdj.be/blaze/
167
168Let's write a view for the `User` form. As you can see, we here refer to the
169different fields in the `userForm`. The `errorList` will generate a list of
170errors for the `"mail"` field.
171
172> userView :: View H.Html -> H.Html
173> userView view = do
174> label "name" view "Name: "
175> inputText "name" view
176> H.br
177>
178> errorList "mail" view
179> label "mail" view "Email address: "
180> inputText "mail" view
181> H.br
182
183Like forms, views are also composable: let's illustrate that by adding a view
184for the `releaseForm`, in which we reuse `userView`. In order to do this, we
185take only the parts relevant to the author from the view by using `subView`. We
186can then pass the resulting view to our own `userView`.
187We have no special view code for `Package`, so we can just add that to
188`releaseView` as well. `childErrorList` will generate a list of errors for each
189child of the specified form. In this case, this means a list of errors from
190`"package.name"` and `"package.version"`. Note how we use `foo.bar` to refer to
191nested forms.
192
193> releaseView :: View H.Html -> H.Html
194> releaseView view = do
195> H.h2 "Author"
196> userView $ subView "author" view
197>
198> H.h2 "Package"
199> childErrorList "package" view
200>
201> label "package.name" view "Name: "
202> inputText "package.name" view
203> H.br
204>
205> label "package.version" view "Version: "
206> inputText "package.version" view
207> H.br
208>
209> label "package.category" view "Category: "
210> inputSelect "package.category" view
211> H.br
212
213The attentive reader might have wondered what the type parameter for `View` is:
214it is the `String`-like type used for e.g. error messages.
215But wait! We have
216 releaseForm :: Monad m => Form Text m Release
217 releaseView :: View H.Html -> H.Html
218... doesn't this mean that we need a `View Text` rather than a `View Html`? The
219answer is yes -- but having `View Html` allows us to write these views more
220easily with the `digestive-functors-blaze` library. Fortunately, we will be able
221to fix this using the `Functor` instance of `View`.
222 fmap :: Monad m => (v -> w) -> View v -> View w
223A backend
224---------
225To finish this tutorial, we need to be able to actually run this code. We need
226an HTTP server for that, and we use [Happstack] for this tutorial. The
227`digestive-functors-happstack` library gives about everything we need for this.
228[Happstack]: http://happstack.com/
229
230> site :: Happstack.ServerPart Happstack.Response
231> site = do
232> Happstack.decodeBody $ Happstack.defaultBodyPolicy "/tmp" 4096 4096 4096
233> r <- runForm "test" releaseForm
234> case r of
235> (view, Nothing) -> do
236> let view' = fmap H.toHtml view
237> Happstack.ok $ Happstack.toResponse $
238> template $
239> form view' "/" $ do
240> releaseView view'
241> H.br
242> inputSubmit "Submit"
243> (_, Just release) -> Happstack.ok $ Happstack.toResponse $
244> template $ do
245> css
246> H.h1 "Release received"
247> H.p $ H.toHtml $ show release
248>
249> main :: IO ()
250> main = Happstack.simpleHTTP Happstack.nullConf site
251
252Utilities
253---------
254
255> template :: H.Html -> H.Html
256> template body = H.docTypeHtml $ do
257> H.head $ do
258> H.title "digestive-functors tutorial"
259> css
260> H.body body
261> css :: H.Html
262> css = H.style ! A.type_ "text/css" $ do
263> "label {width: 130px; float: left; clear: both}"
264> "ul.digestive-functors-error-list {"
265> " color: red;"
266> " list-style-type: none;"
267> " padding-left: 0px;"
268> "}"
269 </textarea>
270 </form>
271
272 <p><strong>MIME types
273 defined:</strong> <code>text/x-literate-haskell</code>.</p>
274
275 <p>Parser configuration parameters recognized: <code>base</code> to
276 set the base mode (defaults to <code>"haskell"</code>).</p>
277
278 <script>
279 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {mode: "haskell-literate"});
280 </script>
281
282</article>