File modules/editor/codemirror/mode/haskell-literate/index.html
Last commit: Sun Dec 17 01:14:09 2017 +0100 Jan Dankert Integration eines weiteren Code-Editors: Codemirror. Demnächst müssen wir hier mal aufräumen und andere Editoren rauswerfen.
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="http://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 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>
Downloadmodules/editor/codemirror/mode/haskell-literate/index.html
History Sun, 17 Dec 2017 01:14:09 +0100 Jan Dankert Integration eines weiteren Code-Editors: Codemirror. Demnächst müssen wir hier mal aufräumen und andere Editoren rauswerfen.