on master help sign in
Merge pull request #15 from tazjin/master

* new Shakespearean Elm templates

commit2abbbe5f0b14465a1853ee93df96d34a4ee000ad authorEvan Czaplicki <info@elm-lang.org> dateWed 30 May 2012, 13:06 (14 years ago) parent0c3960958a6eb6
9 files changed+224−170
Examples/elm-happstack/Main.hs+14−11 Examples/elm-happstack/elm_source/index.elm+3−3 Examples/elm-yesod/Main.hs+24−24 Examples/elm-yesod/elm_source/index.elm+3−3 elm-yesod/Language/Elm/Yesod.hs+33−10 elm-yesod/elm-yesod.cabal+2−1 elm/Elm.cabal+1−2 elm/src/Language/Elm.hs+55−27 elm/src/Language/Elm/Quasi.hs+89−89
Examples/elm-happstack/Main.hs+14−11
@@ -1,18 +1,21 @@
1−{-# LANGUAGE QuasiQuotes #-}
1+{-# LANGUAGE QuasiQuotes, TemplateHaskell #-}
22
33import Control.Monad (msum)
44import Happstack.Server
5−import Language.Elm (elmFile, elm)
6−
7−import qualified Language.Elm as Elm
5+import Language.Elm
6+import Language.Elm.Quasi
87
98elmLoc :: String
109elmLoc = "http://f.cl.ly/items/2e3Z3r3v29263U393c3x/elm-min.js"
1110
12−elmResponse :: String -- ^ Page title
13− -> String -- ^ elm source
11+-- elmResponse is a "nice to have" helper function for compiling
12+-- Elm code when using Elm with Happstack. At some point this might
13+-- be moved to an elm-happstack package.
14+elmResponse :: ElmSource a
15+ => String -- ^ Page title
16+ -> a -- ^ elm source
1417 -> Response
15−elmResponse title = toResponse . Elm.toHtml elmLoc title
18+elmResponse title = toResponse . toHtml elmLoc title
1619
1720-- embedding variables (in this case URLs)
1821rootHandler :: ServerPart Response
@@ -21,16 +24,16 @@ rootHandler = ok $ elmResponse "Welcome!" $ elmIndex
2124 mouse = "/mouse"
2225 clock = "/clock"
2326 shapes = "/shapes"
24− elmIndex = [elmFile|elm_source/index.elm|]
27+ elmIndex = $(elmFile "elm_source/index.elm")
2528
2629-- loading elm source from file
2730
2831mouseHandler :: ServerPart Response
29−mouseHandler = ok $ elmResponse "Mouse position demo" $
30− [elmFile|elm_source/mouse.elm|]
32+mouseHandler = ok $ elmResponse "Mouse position demo"
33+ $(elmFile "elm_source/mouse.elm")
3134
3235clockHandler :: ServerPart Response
33−clockHandler = ok $ elmResponse "A clock" $ [elmFile|elm_source/clock.elm|]
36+clockHandler = ok $ elmResponse "A clock" $(elmFile "elm_source/clock.elm")
3437
3538-- embedding elm code inside Haskell using the QuasiQuoter:
3639shapesPage = [elm|
Examples/elm-happstack/elm_source/index.elm+3−3
@@ -25,9 +25,9 @@ info w = List.map (\f -> f ()) . List.intersperse (\x -> plainText "&nbsp;") . L
2525 toText ". Since you are looking at this page it is safe to assume that you already have the example code. "
2626 , text $ toText "A second Happstack example using web-routes will be added later on."
2727 , section "More examples:"
28− , text $ toText "- " ++ link ^{mouse} (toText "A simple mouse input example")
29− , text $ toText "- " ++ link ^{clock} (toText "An animated analog clock")
30− , text $ toText "- " ++ link ^{shapes} (toText "Some simple rendered shapes")
28+ , text $ toText "- " ++ link "#{mouse}" (toText "A simple mouse input example")
29+ , text $ toText "- " ++ link "#{clock}" (toText "An animated analog clock")
30+ , text $ toText "- " ++ link "#{shapes}" (toText "Some simple rendered shapes")
3131 ]
3232
3333body outer inner = width outer . box 2 . flow down . (:) (plainText "&nbsp;") $ info inner
Examples/elm-yesod/Main.hs+24−24
@@ -1,19 +1,17 @@
11{-# LANGUAGE QuasiQuotes, TemplateHaskell, OverloadedStrings, TypeFamilies, MultiParamTypeClasses #-}
22
33import Language.Elm
4+import Language.Elm.Quasi
45import Language.Elm.Yesod
56import Yesod
67import Text.Hamlet
78
89data ElmTest = ElmTest
910
10−-- embedding an external elm file (note: no spaces!)
11−rootPage :: Show a => a -> a -> a -> String
12−rootPage mouse clock shapes = [elmFile|elm_source/index.elm|]
11+-- loading external elm code
12+mousePage = $(elmFile "elm_source/mouse.elm")
1313
14−mousePage = [elmFile|elm_source/mouse.elm|]
15−
16−clockPage = [elmFile|elm_source/clock.elm|]
14+clockPage = $(elmFile "elm_source/clock.elm")
1715
1816-- embedding elm code inside Haskell using the QuasiQuoter:
1917shapesPage = [elm|
@@ -36,34 +34,36 @@ mkYesod "ElmTest" [parseRoutes|
3634/shapes ShapesR GET
3735|]
3836
39−-- generateWidget is called with the result of an Elm QuasiQuoter (which is just a
40−-- string containing some Elm code, with proper newline escaping)
37+-- elmWidget takes some elm source code and returns the finished elm widget
38+-- inside the GHandler monad. URL interpolation is done automatically, all
39+-- interpolated variables have to be in scope when the elmWidget call happens.
4140getMouseR :: Handler RepHtml
42−getMouseR = defaultLayout $ do
43− setTitle "Mouse position demo"
44− elmWidget mousePage
41+getMouseR = do
42+ widget <- elmWidget mousePage
43+ defaultLayout $ do
44+ setTitle "Mouse position demo"
45+ widget
4546
4647getClockR :: Handler RepHtml
47−getClockR = defaultLayout $ do
48− setTitle "A clock"
49− elmWidget clockPage
48+getClockR = do
49+ widget <- elmWidget clockPage
50+ defaultLayout $ do
51+ setTitle "A clock"
52+ widget
5053
5154getShapesR :: Handler RepHtml
52−getShapesR = defaultLayout $ do
53− setTitle "Simple shapes"
54− elmWidget shapesPage
55+getShapesR = do
56+ widget <- elmWidget shapesPage
57+ defaultLayout $ do
58+ setTitle "Simple shapes"
59+ widget
5560
56−-- URLs are rendered manually and then passed on to the function containing
57−-- the elm QuasiQuoter.
5861getRootR :: Handler RepHtml
5962getRootR = do
60− render <- getUrlRender
63+ widget <- elmWidget $(elmFile "elm_source/index.elm")
6164 defaultLayout $ do
62− let mouse = render MouseR
63− clock = render ClockR
64− shapes = render ShapesR
6565 setTitle "Welcome!"
66− elmWidget $ rootPage mouse clock shapes
66+ widget
6767
6868
6969-- Our Yesod instance contains the default layout, which inserts the elm-min.js
Examples/elm-yesod/elm_source/index.elm+3−3
@@ -25,9 +25,9 @@ info w = List.map (\f -> f ()) . List.intersperse (\x -> plainText "&nbsp;") . L
2525 toText ". Since you are looking at this page it is safe to assume that you already have the example code. "
2626 , text $ toText "Type-safe URLs are rendered using simple QuasiQuoter variable interpolation."
2727 , section "More examples:"
28− , text $ toText "- " ++ link ^{mouse} (toText "A simple mouse input example")
29− , text $ toText "- " ++ link ^{clock} (toText "An animated analog clock")
30− , text $ toText "- " ++ link ^{shapes} (toText "Some simple rendered shapes")
28+ , text $ toText "- " ++ link "@{MouseR}" (toText "A simple mouse input example")
29+ , text $ toText "- " ++ link "@{ClockR}" (toText "An animated analog clock")
30+ , text $ toText "- " ++ link "@{ShapesR}" (toText "Some simple rendered shapes")
3131 ]
3232
3333body outer inner = width outer . box 2 . flow down . (:) (plainText "&nbsp;") $ info inner
elm-yesod/Language/Elm/Yesod.hs+33−10
@@ -1,4 +1,4 @@
1−{-# LANGUAGE QuasiQuotes #-}
1+{-# LANGUAGE QuasiQuotes, TypeSynonymInstances #-}
22{- | This module provides a function for compiling Elm source code into a Yesod widget.
33 In order to use this with your Yesod app, you need to define a defaultLayout like
44 function that embeds the elm-min.js file /in the <head> tag/.
@@ -15,20 +15,43 @@
1515
1616 A full example implementation is provided in the examples folder of the Elm github repository.
1717-}
18−module Language.Elm.Yesod (elmWidget) where
18+module Language.Elm.Yesod (
19+ elmWidget
20+ , ElmUrl) where
1921
22+import Control.Monad (liftM)
2023import Text.Blaze (preEscapedToMarkup)
2124import Text.Julius
22−import Yesod.Widget (toWidgetHead, toWidgetBody, GWidget)
25+import Yesod.Core (Route (..))
26+import Yesod.Handler (getUrlRenderParams, GHandler (..))
27+import Yesod.Widget
2328import Language.Elm
29+import Language.Elm.Quasi
2430
25−-- |toWidget takes some Elm code in String format and produces a widget. Usage example:
26−--
27−-- > toWidget [elmFile|elm-source/somePage.elm|]
28−elmWidget :: String -- ^ The Elm source code
29− -> GWidget sub master ()
30−elmWidget source =
31− let (html, css, js) = toParts source in
31+import qualified Data.Text as TS
32+import qualified Data.Text.Lazy as TL
33+
34+-- |elmWidget returns a Yesod widget from some Elm source code
35+-- with URL interpolation.
36+elmWidget :: ElmUrl (Route master) -- ^ Elm source code
37+ -> GHandler sub master (GWidget sub master())
38+elmWidget source = do
39+ urlF <- getUrlRenderParams
40+ return $ mkElmWidget source urlF
41+
42+
43+mkElmWidget :: ElmUrl (Route master) -- ^ Elm source code
44+ -> RenderFn (Route master) -- ^ URL rendering function
45+ -> GWidget sub master ()
46+mkElmWidget source urlFn =
47+ let (html, css, js) = toParts (urlFn, source) in
3248 do toWidgetHead css
3349 toWidgetHead [julius| #{js} |]
3450 toWidgetBody html
51+
52+
53+-- | Return type of template-reading functions.
54+type ElmUrl url = (url -> [(TS.Text, TS.Text)] -> TS.Text) -> Elm
55+
56+-- | Readability synonym for render function
57+type RenderFn url = (url -> [(TS.Text, TS.Text)] -> TS.Text)
elm-yesod/elm-yesod.cabal+2−1
@@ -32,4 +32,5 @@ Library
3232 yesod-core >= 1,
3333 hamlet,
3434 shakespeare-css,
35− shakespeare-js
35+ shakespeare-js,
36+ text
elm/Elm.cabal+1−2
@@ -63,9 +63,8 @@ Library
6363 blaze-markup == 0.5.1.*,
6464 deepseq,
6565 text,
66− attoparsec >= 0.10,
6766 template-haskell,
68− haskell-src-meta
67+ shakespeare
6968
7069Executable elm
7170 Main-is: Compiler.hs
elm/src/Language/Elm.hs+55−27
@@ -1,16 +1,16 @@
1+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
12{- | This module exports the functions necessary for compiling Elm code into the
23 respective HTML, JS and CSS code.
34
4− It also provides a predefined compileToHtml function for use with the Blaze markup library
5− as well as a simple QuasiQuoter for embedding literal elm code in a Haskell file.
5+ The type class @'ElmSource'@ requires an instance for all types that the Elm compiler
6+ understands. The provided instances for String, Text and QuasiQuoted Elm source code
7+ should be sufficient.
68
79 The documentation for the Elm language is available at <http://elm-lang.org/Documentation.elm>
810-}
911module Language.Elm (
10− toHtml,
11− toParts,
12− elm,
13− elmFile) where
12+ ElmSource (..)
13+ ) where
1414
1515import CompileToJS
1616import ExtractNoscript
@@ -19,25 +19,53 @@ import Initialize
1919import Text.Blaze.Html (Html)
2020import Language.Elm.Quasi
2121
22+import qualified Data.Text as TS
23+import qualified Data.Text.Lazy as TL
2224
23−-- |This function compiles Elm code into a full HTML page.
24−--
25−-- Usage example:
26−--
27−-- > toHtml "/elm-min.js" "Some title" [elmFile|elm-source/somePage.elm|]
28−toHtml :: String -- ^ Location of elm-min.js as expected by the browser
29− -> String -- ^ The page title
30− -> String -- ^ The elm source code.
31− -> Html
32−toHtml = generateHtml
33−
34−-- |This function compiles Elm code to three separate parts: HTML, CSS,
35−-- and JavaScript. The HTML is only the contents of the body, so the three
36−-- parts must be combined in a basic HTML skeleton.
37−toParts :: String -- ^ The Elm source code.
38− -> (Html, Html, String) -- ^ HTML, CSS, and JavaScript in that order.
39−toParts source = (html, css, js)
40− where expr = initialize source
41− js = compileToJS expr
42− html = body $ either id extract expr
43−
25+class ElmSource a where
26+ -- |This function compiles Elm code to three separate parts: HTML, CSS,
27+ -- and JavaScript. The HTML is only the contents of the body, so the three
28+ -- parts must be combined in a basic HTML skeleton.
29+ toParts :: a -> (Html, Html, String)
30+
31+ -- |This function compiles Elm code into a full HTML page.
32+ toHtml :: String -- ^ Location of elm-min.js as expected by the browser
33+ -> String -- ^ The page title
34+ -> a -- ^ The elm source code
35+ -> Html
36+
37+instance ElmSource String where
38+ toParts = toPartsHelper
39+ toHtml = generateHtml
40+
41+instance ElmSource Elm where
42+ toParts = toPartsHelper . TL.unpack . renderElm
43+ toHtml elmL title = generateHtml elmL title . TL.unpack . renderElm
44+
45+-- |Strict text
46+instance ElmSource TS.Text where
47+ toParts = toPartsHelper . TS.unpack
48+ toHtml elmL title = generateHtml elmL title . TS.unpack
49+
50+-- |Lazy text
51+instance ElmSource TL.Text where
52+ toParts = toPartsHelper . TL.unpack
53+ toHtml elmL title = generateHtml elmL title . TL.unpack
54+
55+-- | (urlRenderFn, urlRenderFn -> Elm)
56+instance ElmSource (t, t -> Elm) where
57+ toParts (f, s) = toPartsHelper $ TL.unpack $ renderElm $ s f
58+ toHtml elmL title (f, s) = generateHtml elmL title $ TL.unpack $ renderElm $ s f
59+
60+-- | to be used without URL interpolation
61+instance ElmSource (t -> Elm) where
62+ toParts s = toPartsHelper $ TL.unpack $ renderElm $ s undefined
63+ toHtml l t s = generateHtml l t $ TL.unpack $ renderElm $ s undefined
64+
65+
66+-- build helper to avoid boilerplate repetition
67+toPartsHelper :: String -> (Html, Html, String)
68+toPartsHelper source = (html, css, js)
69+ where expr = initialize source
70+ js = compileToJS expr
71+ html = body $ either id extract expr
elm/src/Language/Elm/Quasi.hs+89−89
@@ -1,93 +1,93 @@
1−{-# LANGUAGE TemplateHaskell, OverloadedStrings #-}
1+{-# LANGUAGE TemplateHaskell #-}
2+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3+{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE CPP #-}
25{-# OPTIONS_GHC -fno-warn-missing-fields #-}
36
4−module Language.Elm.Quasi (elm, elmFile) where
5−
6−import Language.Haskell.TH.Quote
7−import Language.Haskell.TH
8−import Language.Haskell.Meta (parseExp)
9−
10−import Data.Attoparsec.Text
11−import Data.Text as T (Text, pack, unpack, strip)
12−
13−{- | This QuasiQuoter allows for literal embedding of elm source code within
14− Haskell files. It is not responsible for the actual compilation Process.
15− Usage:
16−
17− @
18− elmPage = [elm|
19− main = image \"someImage.jpg\"
20− |]
21− @
22−
23− It also allows for simple variable interpolation using the ^{...} syntax.
24− This is intended for e.g. embedding pre-rendered type-safe URLs.
25− All interpolated variables must be a member of the Show typeclass. This
26− is not tested with more complicated expressions, so it is recommended to
27− restrict usage of interpolation to pre-calculated variables.
28−
29− Example:
30−
31− > let google = "http://google.com"
32− > in [elm|
33− > main = text $ link ^{google} (toText "Google")
34− > |]
35−
36−
37− How the elm code is compiled depends on how you intend to use it.
38− Example uses are the included 'generateHtml' function as well as the
39− "Language.Elm.Yesod" module.
40−-}
41−elm :: QuasiQuoter
42−elm = QuasiQuoter
43− { quoteExp = \s ->
44− let chunks = flip map (getQChunks s) $ \c ->
45− case c of
46− S t -> [| t |]
47− E t -> let Right e = parseExp t in appE [| show |] (return e)
48− in appE [| concat |] (listE chunks)
7+-- | This module contains Shakespearean (see "Text.Shakespeare") templates for Elm.
8+-- It introduces type-safe compile-time variable and URL interpolation. A typeclass
9+-- @'ToElm'@ is provided for interpolated variables.
10+--
11+-- Further reading on Shakespearean templates: <http://www.yesodweb.com/book/templates>
12+--
13+-- Further reading on Elm: <http://elm-lang.org>
14+module Language.Elm.Quasi
15+ ( -- * Functions
16+ -- ** Template-Reading Functions
17+ -- |These QuasiQuoters return functions of the type @(t -> 'Elm')@
18+ -- where @t@ is the URL rendering function if type-safe URLs are used.
19+ --
20+ -- A usage example for both type-safe (Yesod) and standard path segment (Happstack)
21+ -- URLs is provided in the Examples folder in the Git repository.
22+ elm
23+ , elmFile
24+ , elmFileReload
25+
26+ -- * Datatypes
27+ , Elm (..)
28+
29+ -- * Typeclass for interpolated variables
30+ , ToElm (..)
31+
32+ -- ** Rendering Functions
33+ , renderElm
34+
35+ ) where
36+
37+import Language.Haskell.TH.Quote (QuasiQuoter (..))
38+import Language.Haskell.TH.Syntax
39+import Data.Text.Lazy.Builder (Builder, fromText, toLazyText, fromLazyText)
40+import Data.Monoid
41+import qualified Data.Text as TS
42+import qualified Data.Text.Lazy as TL
43+import Text.Shakespeare
44+
45+-- | Render Elm to lazy Text.
46+renderElm :: Elm -> TL.Text
47+renderElm (Elm b) = toLazyText b
48+
49+-- | Newtype wrapper of 'Builder'.
50+newtype Elm = Elm { unElm :: Builder }
51+ deriving Monoid
52+
53+-- | A typeclass for types that can be interpolated in Elm templates.
54+class ToElm a where
55+ toElm :: a -> Builder
56+instance ToElm [Char] where toElm = fromLazyText . TL.pack
57+instance ToElm TS.Text where toElm = fromText
58+instance ToElm TL.Text where toElm = fromLazyText
59+
60+elmSettings :: Q ShakespeareSettings
61+elmSettings = do
62+ toJExp <- [|toElm|]
63+ wrapExp <- [|Elm|]
64+ unWrapExp <- [|unElm|]
65+ return $ defaultShakespeareSettings { toBuilder = toJExp
66+ , wrap = wrapExp
67+ , unwrap = unWrapExp
4968 }
5069
51−{- | elmFile is a quoteFile wrapper around the 'elm' QuasiQuoter and allows for
52− external elm source files to be embedded.
53− Usage:
54−
55− @
56− [elmFile|elm-source/page.elm|]
57− @
58−
59− Please note that no spaces should be added before and after the filename.
60−-}
61−elmFile :: QuasiQuoter
62−elmFile = quoteFile elm
63−
64−data QChunk =
65− S String -- ^ A literal string
66− | E String -- ^ An expression (must be of the Show typeclass)
67− deriving (Show, Eq)
68−
69−
70−{- thanks to mikeplus64 for providing an example QuasiQuoter that I could look at while writing this :) -}
71−getQChunks :: String -> [QChunk]
72−getQChunks si = let i = T.pack si
73− Right m = parseOnly parser (strip i) in m
74− where
75− parser = go []
76−
77− go s = do
78− txt <- takeTill (== '^')
79− evt <- choice [expression, fmap (S . T.unpack) takeText]
80− end <- atEnd
81− if end
82− then return $ filter (not . blank) $ reverse (evt:(S . T.unpack) txt:s)
83− else go (evt:(S . T.unpack) txt:s)
84−
85− blank (S "") = True
86− blank (E "") = True
87− blank _ = False
88−
89− expression = do
90− string "^{"
91− expr <- takeTill (== '}')
92− char '}'
93− return $ (E . T.unpack) expr
70+-- |QuasiQuoter for embedding Elm code inside of Haskell code.
71+--
72+-- Usage:
73+-- @[elm|main = plaintext "Some elm code"|]@
74+elm :: QuasiQuoter
75+elm = QuasiQuoter { quoteExp = \s -> do
76+ rs <- elmSettings
77+ quoteExp (shakespeare rs) s
78+ }
79+
80+-- |A Template Haskell function for embedding Elm code from external
81+-- .elm files.
82+--
83+-- Usage:
84+-- @$(elmFile "elm_source/index.elm")@
85+elmFile :: FilePath -> Q Exp
86+elmFile fp = do
87+ rs <- elmSettings
88+ shakespeareFile rs fp
89+
90+elmFileReload :: FilePath -> Q Exp
91+elmFileReload fp = do
92+ rs <- elmSettings
93+ shakespeareFileReload rs fp