on master help sign in
Merge pull request #15 from tazjin/master
2abbbe5Evan Czaplicki
Examples/elm-happstack/Main.hs+14−11
@@ -1,18 +1,21 @@
1−{-# LANGUAGE QuasiQuotes #-}
1+{-# LANGUAGE QuasiQuotes, TemplateHaskell #-}
2
2
3import Control.Monad (msum)
3import Control.Monad (msum)
4import Happstack.Server
4import Happstack.Server
5−import Language.Elm (elmFile, elm)
5+import Language.Elm
6−
7−import qualified Language.Elm as Elm
6+import Language.Elm.Quasi
8
7
9elmLoc :: String
8elmLoc :: String
10elmLoc = "http://f.cl.ly/items/2e3Z3r3v29263U393c3x/elm-min.js"
9elmLoc = "http://f.cl.ly/items/2e3Z3r3v29263U393c3x/elm-min.js"
11
10
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
12−elmResponse :: String -- ^ Page title
15+ => String -- ^ Page title
13− -> String -- ^ elm source
16+ -> a -- ^ elm source
14 -> Response
17 -> Response
15−elmResponse title = toResponse . Elm.toHtml elmLoc title
18+elmResponse title = toResponse . toHtml elmLoc title
16
19
17-- embedding variables (in this case URLs)
20-- embedding variables (in this case URLs)
18rootHandler :: ServerPart Response
21rootHandler :: ServerPart Response
@@ -21,16 +24,16 @@ rootHandler = ok $ elmResponse "Welcome!" $ elmIndex
21 mouse = "/mouse"
24 mouse = "/mouse"
22 clock = "/clock"
25 clock = "/clock"
23 shapes = "/shapes"
26 shapes = "/shapes"
24− elmIndex = [elmFile|elm_source/index.elm|]
27+ elmIndex = $(elmFile "elm_source/index.elm")
25
28
26-- loading elm source from file
29-- loading elm source from file
27
30
28mouseHandler :: ServerPart Response
31mouseHandler :: ServerPart Response
29−mouseHandler = ok $ elmResponse "Mouse position demo" $
32+mouseHandler = ok $ elmResponse "Mouse position demo"
30− [elmFile|elm_source/mouse.elm|]
33+ $(elmFile "elm_source/mouse.elm")
31
34
32clockHandler :: ServerPart Response
35clockHandler :: ServerPart Response
33−clockHandler = ok $ elmResponse "A clock" $ [elmFile|elm_source/clock.elm|]
36+clockHandler = ok $ elmResponse "A clock" $(elmFile "elm_source/clock.elm")
34
37
35-- embedding elm code inside Haskell using the QuasiQuoter:
38-- embedding elm code inside Haskell using the QuasiQuoter:
36shapesPage = [elm|
39shapesPage = [elm|
Examples/elm-happstack/elm_source/index.elm+3−3
@@ -25,9 +25,9 @@ info w = List.map (\f -> f ()) . List.intersperse (\x -> plainText " ") . L
25 toText ". Since you are looking at this page it is safe to assume that you already have the example code. "
25 toText ". Since you are looking at this page it is safe to assume that you already have the example code. "
26 , text $ toText "A second Happstack example using web-routes will be added later on."
26 , text $ toText "A second Happstack example using web-routes will be added later on."
27 , section "More examples:"
27 , section "More examples:"
28− , text $ toText "- " ++ link ^{mouse} (toText "A simple mouse input example")
28+ , text $ toText "- " ++ link "#{mouse}" (toText "A simple mouse input example")
29− , text $ toText "- " ++ link ^{clock} (toText "An animated analog clock")
29+ , text $ toText "- " ++ link "#{clock}" (toText "An animated analog clock")
30− , text $ toText "- " ++ link ^{shapes} (toText "Some simple rendered shapes")
30+ , text $ toText "- " ++ link "#{shapes}" (toText "Some simple rendered shapes")
31 ]
31 ]
32
32
33body outer inner = width outer . box 2 . flow down . (:) (plainText " ") $ info inner
33body outer inner = width outer . box 2 . flow down . (:) (plainText " ") $ info inner
Examples/elm-yesod/Main.hs+24−24
@@ -1,19 +1,17 @@
1{-# LANGUAGE QuasiQuotes, TemplateHaskell, OverloadedStrings, TypeFamilies, MultiParamTypeClasses #-}
1{-# LANGUAGE QuasiQuotes, TemplateHaskell, OverloadedStrings, TypeFamilies, MultiParamTypeClasses #-}
2
2
3import Language.Elm
3import Language.Elm
4+import Language.Elm.Quasi
4import Language.Elm.Yesod
5import Language.Elm.Yesod
5import Yesod
6import Yesod
6import Text.Hamlet
7import Text.Hamlet
7
8
8data ElmTest = ElmTest
9data ElmTest = ElmTest
9
10
10−-- embedding an external elm file (note: no spaces!)
11+-- loading external elm code
11−rootPage :: Show a => a -> a -> a -> String
12−rootPage mouse clock shapes = [elmFile|elm_source/index.elm|]
12+mousePage = $(elmFile "elm_source/mouse.elm")
13
13
14−mousePage = [elmFile|elm_source/mouse.elm|]
15−
16−clockPage = [elmFile|elm_source/clock.elm|]
14+clockPage = $(elmFile "elm_source/clock.elm")
17
15
18-- embedding elm code inside Haskell using the QuasiQuoter:
16-- embedding elm code inside Haskell using the QuasiQuoter:
19shapesPage = [elm|
17shapesPage = [elm|
@@ -36,34 +34,36 @@ mkYesod "ElmTest" [parseRoutes|
36/shapes ShapesR GET
34/shapes ShapesR GET
37|]
35|]
38
36
39−-- generateWidget is called with the result of an Elm QuasiQuoter (which is just a
37+-- elmWidget takes some elm source code and returns the finished elm widget
40−-- string containing some Elm code, with proper newline escaping)
38+-- inside the GHandler monad. URL interpolation is done automatically, all
39+-- interpolated variables have to be in scope when the elmWidget call happens.
41getMouseR :: Handler RepHtml
40getMouseR :: Handler RepHtml
41+getMouseR = do
42+ widget <- elmWidget mousePage
42−getMouseR = defaultLayout $ do
43+ defaultLayout $ do
43− setTitle "Mouse position demo"
44+ setTitle "Mouse position demo"
44− elmWidget mousePage
45+ widget
45
46
46getClockR :: Handler RepHtml
47getClockR :: Handler RepHtml
48+getClockR = do
49+ widget <- elmWidget clockPage
47−getClockR = defaultLayout $ do
50+ defaultLayout $ do
48− setTitle "A clock"
51+ setTitle "A clock"
49− elmWidget clockPage
52+ widget
50
53
51getShapesR :: Handler RepHtml
54getShapesR :: Handler RepHtml
55+getShapesR = do
56+ widget <- elmWidget shapesPage
52−getShapesR = defaultLayout $ do
57+ defaultLayout $ do
53− setTitle "Simple shapes"
58+ setTitle "Simple shapes"
54− elmWidget shapesPage
59+ widget
55
60
56−-- URLs are rendered manually and then passed on to the function containing
57−-- the elm QuasiQuoter.
58getRootR :: Handler RepHtml
61getRootR :: Handler RepHtml
59getRootR = do
62getRootR = do
60− render <- getUrlRender
63+ widget <- elmWidget $(elmFile "elm_source/index.elm")
61 defaultLayout $ do
64 defaultLayout $ do
62− let mouse = render MouseR
63− clock = render ClockR
64− shapes = render ShapesR
65 setTitle "Welcome!"
65 setTitle "Welcome!"
66− elmWidget $ rootPage mouse clock shapes
66+ widget
67
67
68
68
69-- Our Yesod instance contains the default layout, which inserts the elm-min.js
69-- 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
25 toText ". Since you are looking at this page it is safe to assume that you already have the example code. "
25 toText ". Since you are looking at this page it is safe to assume that you already have the example code. "
26 , text $ toText "Type-safe URLs are rendered using simple QuasiQuoter variable interpolation."
26 , text $ toText "Type-safe URLs are rendered using simple QuasiQuoter variable interpolation."
27 , section "More examples:"
27 , section "More examples:"
28− , text $ toText "- " ++ link ^{mouse} (toText "A simple mouse input example")
28+ , text $ toText "- " ++ link "@{MouseR}" (toText "A simple mouse input example")
29− , text $ toText "- " ++ link ^{clock} (toText "An animated analog clock")
29+ , text $ toText "- " ++ link "@{ClockR}" (toText "An animated analog clock")
30− , text $ toText "- " ++ link ^{shapes} (toText "Some simple rendered shapes")
30+ , text $ toText "- " ++ link "@{ShapesR}" (toText "Some simple rendered shapes")
31 ]
31 ]
32
32
33body outer inner = width outer . box 2 . flow down . (:) (plainText "&nbsp;") $ info inner
33body 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 #-}
2{- | This module provides a function for compiling Elm source code into a Yesod widget.
2{- | This module provides a function for compiling Elm source code into a Yesod widget.
3 In order to use this with your Yesod app, you need to define a defaultLayout like
3 In order to use this with your Yesod app, you need to define a defaultLayout like
4 function that embeds the elm-min.js file /in the <head> tag/.
4 function that embeds the elm-min.js file /in the <head> tag/.
@@ -15,20 +15,43 @@
15
15
16 A full example implementation is provided in the examples folder of the Elm github repository.
16 A full example implementation is provided in the examples folder of the Elm github repository.
17-}
17-}
18−module Language.Elm.Yesod (elmWidget) where
18+module Language.Elm.Yesod (
19+ elmWidget
20+ , ElmUrl) where
19
21
22+import Control.Monad (liftM)
20import Text.Blaze (preEscapedToMarkup)
23import Text.Blaze (preEscapedToMarkup)
21import Text.Julius
24import Text.Julius
22−import Yesod.Widget (toWidgetHead, toWidgetBody, GWidget)
25+import Yesod.Core (Route (..))
26+import Yesod.Handler (getUrlRenderParams, GHandler (..))
27+import Yesod.Widget
23import Language.Elm
28import Language.Elm
29+import Language.Elm.Quasi
24
30
25−-- |toWidget takes some Elm code in String format and produces a widget. Usage example:
31+import qualified Data.Text as TS
26−--
32+import qualified Data.Text.Lazy as TL
27−-- > toWidget [elmFile|elm-source/somePage.elm|]
33+
34+-- |elmWidget returns a Yesod widget from some Elm source code
35+-- with URL interpolation.
28−elmWidget :: String -- ^ The Elm source code
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
29− -> GWidget sub master ()
45+ -> GWidget sub master ()
30−elmWidget source =
46+mkElmWidget source urlFn =
31− let (html, css, js) = toParts source in
47+ let (html, css, js) = toParts (urlFn, source) in
32 do toWidgetHead css
48 do toWidgetHead css
33 toWidgetHead [julius| #{js} |]
49 toWidgetHead [julius| #{js} |]
34 toWidgetBody html
50 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
32 yesod-core >= 1,
32 yesod-core >= 1,
33 hamlet,
33 hamlet,
34 shakespeare-css,
34 shakespeare-css,
35− shakespeare-js
35+ shakespeare-js,
36+ text
elm/Elm.cabal+1−2
@@ -63,9 +63,8 @@ Library
63 blaze-markup == 0.5.1.*,
63 blaze-markup == 0.5.1.*,
64 deepseq,
64 deepseq,
65 text,
65 text,
66− attoparsec >= 0.10,
67 template-haskell,
66 template-haskell,
68− haskell-src-meta
67+ shakespeare
69
68
70Executable elm
69Executable elm
71 Main-is: Compiler.hs
70 Main-is: Compiler.hs
elm/src/Language/Elm.hs+55−27
@@ -1,16 +1,16 @@
1+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
1{- | This module exports the functions necessary for compiling Elm code into the
2{- | This module exports the functions necessary for compiling Elm code into the
2 respective HTML, JS and CSS code.
3 respective HTML, JS and CSS code.
3
4
4− It also provides a predefined compileToHtml function for use with the Blaze markup library
5+ The type class @'ElmSource'@ requires an instance for all types that the Elm compiler
5− as well as a simple QuasiQuoter for embedding literal elm code in a Haskell file.
6+ understands. The provided instances for String, Text and QuasiQuoted Elm source code
7+ should be sufficient.
6
8
7 The documentation for the Elm language is available at <http://elm-lang.org/Documentation.elm>
9 The documentation for the Elm language is available at <http://elm-lang.org/Documentation.elm>
8-}
10-}
9module Language.Elm (
11module Language.Elm (
10− toHtml,
12+ ElmSource (..)
11− toParts,
12− elm,
13− elmFile) where
13+ ) where
14
14
15import CompileToJS
15import CompileToJS
16import ExtractNoscript
16import ExtractNoscript
@@ -19,25 +19,53 @@ import Initialize
19import Text.Blaze.Html (Html)
19import Text.Blaze.Html (Html)
20import Language.Elm.Quasi
20import Language.Elm.Quasi
21
21
22+import qualified Data.Text as TS
23+import qualified Data.Text.Lazy as TL
22
24
23−-- |This function compiles Elm code into a full HTML page.
25+class ElmSource a where
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,
26+ -- |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
27+ -- and JavaScript. The HTML is only the contents of the body, so the three
36−-- parts must be combined in a basic HTML skeleton.
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
37−toParts :: String -- ^ The Elm source code.
34+ -> a -- ^ The elm source code
38− -> (Html, Html, String) -- ^ HTML, CSS, and JavaScript in that order.
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)
39−toParts source = (html, css, js)
68+toPartsHelper source = (html, css, js)
40− where expr = initialize source
69+ where expr = initialize source
41− js = compileToJS expr
70+ js = compileToJS expr
42− html = body $ either id extract expr
71+ html = body $ either id extract expr
43−
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 #-}
2{-# OPTIONS_GHC -fno-warn-missing-fields #-}
5{-# OPTIONS_GHC -fno-warn-missing-fields #-}
3
6
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>
4−module Language.Elm.Quasi (elm, elmFile) where
14+module Language.Elm.Quasi
5−
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+
6−import Language.Haskell.TH.Quote
37+import Language.Haskell.TH.Quote (QuasiQuoter (..))
7−import Language.Haskell.TH
38+import Language.Haskell.TH.Syntax
8−import Language.Haskell.Meta (parseExp)
39+import Data.Text.Lazy.Builder (Builder, fromText, toLazyText, fromLazyText)
9−
10−import Data.Attoparsec.Text
40+import Data.Monoid
41+import qualified Data.Text as TS
11−import Data.Text as T (Text, pack, unpack, strip)
42+import qualified Data.Text.Lazy as TL
12−
43+import Text.Shakespeare
13−{- | This QuasiQuoter allows for literal embedding of elm source code within
44+
14− Haskell files. It is not responsible for the actual compilation Process.
45+-- | Render Elm to lazy Text.
15− Usage:
46+renderElm :: Elm -> TL.Text
16−
47+renderElm (Elm b) = toLazyText b
17− @
48+
18− elmPage = [elm|
49+-- | Newtype wrapper of 'Builder'.
19− main = image \"someImage.jpg\"
50+newtype Elm = Elm { unElm :: Builder }
20− |]
51+ deriving Monoid
21− @
52+
22−
53+-- | A typeclass for types that can be interpolated in Elm templates.
23− It also allows for simple variable interpolation using the ^{...} syntax.
54+class ToElm a where
24− This is intended for e.g. embedding pre-rendered type-safe URLs.
55+ toElm :: a -> Builder
25− All interpolated variables must be a member of the Show typeclass. This
56+instance ToElm [Char] where toElm = fromLazyText . TL.pack
26− is not tested with more complicated expressions, so it is recommended to
57+instance ToElm TS.Text where toElm = fromText
27− restrict usage of interpolation to pre-calculated variables.
58+instance ToElm TL.Text where toElm = fromLazyText
28−
59+
29− Example:
60+elmSettings :: Q ShakespeareSettings
30−
61+elmSettings = do
31− > let google = "http://google.com"
62+ toJExp <- [|toElm|]
32− > in [elm|
63+ wrapExp <- [|Elm|]
33− > main = text $ link ^{google} (toText "Google")
64+ unWrapExp <- [|unElm|]
34− > |]
65+ return $ defaultShakespeareSettings { toBuilder = toJExp
35−
66+ , wrap = wrapExp
36−
67+ , unwrap = unWrapExp
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)
49 }
68 }
50
69
51−{- | elmFile is a quoteFile wrapper around the 'elm' QuasiQuoter and allows for
70+-- |QuasiQuoter for embedding Elm code inside of Haskell code.
52− external elm source files to be embedded.
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+--
53− Usage:
83+-- Usage:
54−
55− @
56− [elmFile|elm-source/page.elm|]
84+-- @$(elmFile "elm_source/index.elm")@
57− @
85+elmFile :: FilePath -> Q Exp
58−
86+elmFile fp = do
59− Please note that no spaces should be added before and after the filename.
87+ rs <- elmSettings
60−-}
88+ shakespeareFile rs fp
61−elmFile :: QuasiQuoter
89+
62−elmFile = quoteFile elm
90+elmFileReload :: FilePath -> Q Exp
63−
91+elmFileReload fp = do
64−data QChunk =
92+ rs <- elmSettings
65− S String -- ^ A literal string
93+ shakespeareFileReload rs fp
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