reflex-jsx (empty) → 0.1.0.0
raw patch · 6 files changed
+308/−0 lines, 6 filesdep +basedep +containersdep +haskell-src-metasetup-changed
Dependencies added: base, containers, haskell-src-meta, parsec, reflex, reflex-dom, template-haskell, text
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- reflex-jsx.cabal +50/−0
- src/ReflexJsx.hs +7/−0
- src/ReflexJsx/Parser.hs +147/−0
- src/ReflexJsx/QQ.hs +72/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright David Ackerman (c) 2016++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Author name here nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ reflex-jsx.cabal view
@@ -0,0 +1,50 @@+name: reflex-jsx+version: 0.1.0.0+stability: Experimental+synopsis: Use jsx-like syntax in Reflex+description: `reflex-jsx` is a library for writing jsx-like syntax in+ reflex code. This can be useful for situations where you+ have a large block of HTML with some styles, and it would+ be easier to read as actual HTML than various reflex+ functions.++ It's implemented as a "QuasiQuoter", so you just import the+ `jsx` function from ReflexJsx and generate the equivalent+ functions that would run inside of a "MonadWidget t m".++ Not only can you generate a block of static HTML/CSS, but you+ can also splice in your own nodes that implement+ "MonadWidget t m", string attribute values, or even entire+ dynamic attribute maps.++homepage: https://github.com/dackerman/reflex-jsx+bug-reports: https://github.com/dackerman/reflex-jsx/issues+license: BSD3+license-file: LICENSE+author: David Ackerman+maintainer: david.w.ackerman@gmail.com+copyright: 2016 David Ackerman+category: FRP, Web, GUI, JSX, Language+build-type: Simple+cabal-version: >=1.10++library+ hs-source-dirs: src+ exposed-modules: ReflexJsx+ , ReflexJsx.Parser+ , ReflexJsx.QQ+ ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -ferror-spans+ build-depends: base >= 4.8 && < 5+ , parsec >= 3.1 && < 3.2+ , reflex+ , reflex-dom+ , template-haskell+ , haskell-src-meta+ , containers+ , text+ default-language: Haskell2010+++source-repository head+ type: git+ location: https://github.com/dackerman/reflex-jsx
+ src/ReflexJsx.hs view
@@ -0,0 +1,7 @@+module ReflexJsx+ ( module ReflexJsx.Parser+ , module ReflexJsx.QQ+ ) where++import ReflexJsx.Parser+import ReflexJsx.QQ
+ src/ReflexJsx/Parser.hs view
@@ -0,0 +1,147 @@+{-| The Parser for the reflex-jsx language++ Given a "String", @parseJsx@ outputs the AST for the language. Note that at+ this point, we capture spliced expressions from the meta-language as+ Strings, and parse them during the quasiquoting phase in @ReflexJsx.QQ@+-}+module ReflexJsx.Parser+ ( parseJsx+ , Node(..)+ , Attrs(..)+ , AttrValue(..)+ ) where++import Text.Parsec (runParser, Parsec, try, eof, many, many1, between)+import Text.Parsec.Char (char, letter, noneOf, string, alphaNum, spaces)++import Control.Applicative ((<|>))+++data AttrValue = TextVal String+ | ExprVal String+++data Attrs = SplicedAttrs String+ | StaticAttrs [(String, AttrValue)]+++data Node = Node String Attrs [Node]+ | Text String+ | SplicedNode String+++parseJsx :: Monad m => String -> m Node+parseJsx s =+ case runParser p () "" s of+ Left err -> fail $ show err+ Right e -> return e+ where p = do+ spaces+ node <- jsxElement+ spaces+ eof+ return node+++jsxElement :: Parsec String u Node+jsxElement = do+ try jsxSelfClosingElement <|> jsxNormalElement+++jsxSelfClosingElement :: Parsec String u Node+jsxSelfClosingElement = do+ _ <- char '<'+ name <- jsxElementName+ attrs <- jsxNodeAttrs+ _ <- string "/>"+ return (Node name attrs [])+++jsxNormalElement :: Parsec String u Node+jsxNormalElement = do+ (name, attrs) <- jsxOpeningElement+ children <- many jsxChild+ jsxClosingElement name+ return (Node name attrs children)+++jsxOpeningElement :: Parsec String u (String, Attrs)+jsxOpeningElement = do+ _ <- char '<'+ name <- jsxElementName+ attrs <- jsxNodeAttrs+ _ <- char '>'+ return (name, attrs)+++jsxNodeAttrs :: Parsec String u Attrs+jsxNodeAttrs = do+ try jsxSplicedAttrMap <|> (StaticAttrs <$> many jsxNodeAttr)+++jsxSplicedAttrMap :: Parsec String u Attrs+jsxSplicedAttrMap = do+ name <- between (string "{...") (string "}") $ many (noneOf "}")+ return $ SplicedAttrs name+++jsxNodeAttr :: Parsec String u (String, AttrValue)+jsxNodeAttr = do+ key <- jsxAttributeName+ spaces+ _ <- char '='+ spaces+ value <- jsxQuotedValue <|> jsxSplicedValue+ spaces+ return (key, value)+++jsxAttributeName :: Parsec String u String+jsxAttributeName = do+ many $ letter <|> char '-'+++jsxQuotedValue :: Parsec String u AttrValue+jsxQuotedValue = do+ contents <- between (char '"') (char '"') $ many (noneOf "\"")+ return $ TextVal contents+++jsxSplicedValue :: Parsec String u AttrValue+jsxSplicedValue = do+ name <- between (char '{') (char '}') $ many (noneOf "}")+ return $ ExprVal name+++jsxClosingElement :: String -> Parsec String u ()+jsxClosingElement ele = do+ _ <- string "</" *> string ele *> char '>'+ return ()+++jsxChild :: Parsec String u Node+jsxChild = do+ try jsxText <|> try jsxSplicedNode <|> try jsxElement+++jsxText :: Parsec String u Node+jsxText = do+ contents <- many1 $ noneOf "{<>}"+ return $ Text contents+++jsxSplicedNode :: Parsec String u Node+jsxSplicedNode = do+ exprString <- between (char '{') (char '}') $ many (noneOf "}")+ return $ SplicedNode exprString+++jsxElementName :: Parsec String u String+jsxElementName = jsxIdentifier+++jsxIdentifier :: Parsec String u String+jsxIdentifier = do+ name <- many1 alphaNum+ spaces+ return name
+ src/ReflexJsx/QQ.hs view
@@ -0,0 +1,72 @@+{-| The Quasiquoter for the reflex-jsx language++ The only import you need is "jsx", which is the quasiquoter. See the README+ for more information.+-}++{-# LANGUAGE TemplateHaskell #-}++module ReflexJsx.QQ+ ( jsx+ ) where++import qualified Language.Haskell.TH as TH+import Language.Haskell.TH.Quote+import Language.Haskell.Meta (parseExp)++import qualified Data.List as List+import qualified Data.Map as Map++import qualified Reflex.Dom as Dom++import ReflexJsx.Parser++import Prelude hiding (exp)++{-| Quasiquoter for jsx-like expressions++ Used like "[jsx| <div /> |]"+-}+jsx :: QuasiQuoter+jsx = QuasiQuoter+ { quoteExp = quoteJsxExpression+ , quotePat = undefined+ , quoteDec = undefined+ , quoteType = undefined+ }+++quoteJsxExpression :: String -> TH.ExpQ+quoteJsxExpression str = do+ exp <- parseJsx str+ outputWidgetCode exp+++outputWidgetCode :: Node -> TH.ExpQ+outputWidgetCode node =+ case node of+ Node tag attrs children -> outputNode tag attrs children+ Text content -> [| Dom.text content |]+ SplicedNode varName -> do+ let Right exp = parseExp varName+ return exp+++outputNode :: String -> Attrs -> [Node] -> TH.ExpQ+outputNode tag attrs children =+ let renderedChildren = TH.listE $ List.map outputWidgetCode children+ in case attrs of+ StaticAttrs staticAttrs ->+ let stringAttrs = TH.listE $ List.map toStringAttr staticAttrs+ in [| Dom.elAttr tag (Map.fromList $(stringAttrs)) $ sequence_ $(renderedChildren) |]+ SplicedAttrs attrExpr -> do+ let Right exp = parseExp attrExpr+ [| Dom.elDynAttr tag $(return exp) $ sequence_ $(renderedChildren) |]+++toStringAttr :: (String, AttrValue) -> TH.ExpQ+toStringAttr (key, value) = case value of+ TextVal content -> [| (key, content) |]+ ExprVal exprString -> do+ let Right exp = parseExp exprString+ [| (key, $(return exp)) |]