Mercurial
comparison .cms/lib/codemirror/mode/haskell-literate/index.html @ 0:78edf6b517a0 draft
24.10
author | Coffee CMS <info@coffee-cms.ru> |
---|---|
date | Fri, 11 Oct 2024 22:40:23 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:78edf6b517a0 |
---|---|
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/5"><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/codemirror5">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 | |
51 Simple forms and validation | |
52 --------------------------- | |
53 | |
54 Let'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 | |
61 And dive in immediately to create a `Form` for a user. The `Form v m a` type | |
62 has 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 | |
71 We create forms by using the `Applicative` interface. A few form types are | |
72 provided in the `Text.Digestive.Form` module, such as `text`, `string`, | |
73 `bool`... | |
74 | |
75 In the `digestive-functors` library, the developer is required to label each | |
76 field using the `.:` operator. This might look like a bit of a burden, but it | |
77 allows you to do some really useful stuff, like separating the `Form` from the | |
78 actual HTML layout. | |
79 | |
80 > userForm = User | |
81 > <$> "name" .: text Nothing | |
82 > <*> "mail" .: check "Not a valid email address" checkEmail (text Nothing) | |
83 | |
84 The `check` function enables you to validate the result of a form. For example, | |
85 we can validate the email address with a really naive `checkEmail` function. | |
86 | |
87 > checkEmail :: Text -> Bool | |
88 > checkEmail = isJust . T.find (== '@') | |
89 | |
90 More validation | |
91 --------------- | |
92 | |
93 For our example, we also want descriptions of Haskell libraries, and in order to | |
94 do that, we need package versions... | |
95 | |
96 > type Version = [Int] | |
97 | |
98 We want to let the user input a version number such as `0.1.0.0`. This means we | |
99 need to validate if the input `Text` is of this form, and then we need to parse | |
100 it 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 | |
110 A 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 | |
117 It 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 | |
133 Composing forms | |
134 --------------- | |
135 | |
136 A release has an author and a package. Let's use this to illustrate the | |
137 composability of the digestive-functors library: we can reuse the forms we have | |
138 written 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 | |
148 Views | |
149 ----- | |
150 | |
151 As mentioned before, one of the advantages of using digestive-functors is | |
152 separation of forms and their actual HTML layout. In order to do this, we have | |
153 another type, `View`. | |
154 | |
155 We can get a `View` from a `Form` by supplying input. A `View` contains more | |
156 information than a `Form`, it has: | |
157 | |
158 - the original form; | |
159 - the input given by the user; | |
160 - any errors that have occurred. | |
161 | |
162 It 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` | |
164 library. | |
165 | |
166 [blaze-html]: http://jaspervdj.be/blaze/ | |
167 | |
168 Let's write a view for the `User` form. As you can see, we here refer to the | |
169 different fields in the `userForm`. The `errorList` will generate a list of | |
170 errors 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 | |
183 Like forms, views are also composable: let's illustrate that by adding a view | |
184 for the `releaseForm`, in which we reuse `userView`. In order to do this, we | |
185 take only the parts relevant to the author from the view by using `subView`. We | |
186 can then pass the resulting view to our own `userView`. | |
187 We 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 | |
189 child 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 | |
191 nested 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 | |
213 The attentive reader might have wondered what the type parameter for `View` is: | |
214 it is the `String`-like type used for e.g. error messages. | |
215 But 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 | |
219 answer is yes -- but having `View Html` allows us to write these views more | |
220 easily with the `digestive-functors-blaze` library. Fortunately, we will be able | |
221 to fix this using the `Functor` instance of `View`. | |
222 fmap :: Monad m => (v -> w) -> View v -> View w | |
223 A backend | |
224 --------- | |
225 To finish this tutorial, we need to be able to actually run this code. We need | |
226 an 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 | |
252 Utilities | |
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> |