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

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